Question

Arrière-plan

En réponse à une question, J'ai construit et téléchargé bornée-tchan (n'aurait pas été bon pour moi de télécharger jnb version).Si le nom n'est pas assez, bornée-tchan (BTChan) est un STM canal qui a une capacité maximum (écrit bloc si le canal est à pleine capacité).

Récemment, j'ai reçu une demande d'ajout d'une dup caractéristique, comme dans le régulière du TChan.Et ainsi commence le problème.

Comment le BTChan semble

Simplifié (et en fait non-fonctionnelle) vue de BTChan est ci-dessous.

data BTChan a = BTChan
    { max :: Int
    , count :: TVar Int
    , channel :: TVar [(Int, a)]
    , nrDups  :: TVar Int
    }

Chaque fois que vous écrivez pour le canal que vous incluent le nombre de dup (nrDups) dans le n-uplet - c'est un 'élément de compteur" qui indique combien de lecteurs ont eu cet élément.

Chaque lecteur se décrémente le compteur pour l'élément qu'il lit ensuite le déplacer de la lecture du pointeur de puis de l'élément suivant dans la liste.Si le lecteur décrémente le compteur à zéro, alors la valeur de count est décrémenté à refléter correctement la capacité disponible sur le canal.

Pour être clair sur la sémantique:D'une capacité de canal indique le nombre maximal d'éléments en file d'attente dans le canal.Tout élément est mis en attente jusqu'à ce qu'un lecteur de chaque dup a reçu de l'élément.Pas d'éléments restent en file d'attente pour un GCed dup (c'est le principal problème).

Par exemple, qu'il y ait trois dup d'un canal (c1, c2, c3) avec une capacité de 2, où 2 articles ont été écrits dans le canal, puis tous les articles ont été lus de c1 et c2.Le canal est encore plein (0 capacité restante) parce que c3 n'a pas consommé de ses copies.À n'importe quel point dans le temps si toutes les références àc3 sont supprimés (donc c3 est GCed) alors la capacité doit être libéré (restauré à 2 dans ce cas).

Voici la question: disons que j'ai le code suivant

c <- newBTChan 1
_ <- dupBTChan c  -- This represents what would probably be a pathological bug or terminated reader
writeBTChan c "hello"
_ <- readBTChan c

Origine du BTChan à ressembler à:

BTChan 1 (TVar 0) (TVar []) (TVar 1)             -->   -- newBTChan
BTChan 1 (TVar 0) (TVar []) (TVar 2)             -->   -- dupBTChan
BTChan 1 (TVar 1) (TVar [(2, "hello")]) (TVar 2) -->   -- readBTChan c
BTChan 1 (TVar 1) (TVar [(1, "hello")]) (TVar 2)       -- OH NO!

Avis à la fin de la lecture de comte de "hello" est encore 1?Cela signifie que le message n'est pas considéré comme disparu (même si elle va obtenir GCed dans la mise en œuvre réelle) et notre count ne sera jamais la décrémenter.Parce que le canal est à pleine capacité (1 élément maximum) les auteurs seront toujours bloquer.

Je veux un finaliseur créé à chaque fois dupBTChan est appelé.Lorsqu'un dupped (ou d'origine) du canal est recueilli tous les éléments restant à lire sur ce canal obtiendrez la par-élément compter décrémenté, aussi la nrDups variable sera décrémenté.En conséquence, les futurs écrit sera la bonne count (un count cela ne veut pas réserver de l'espace pour les variables non-lu par GCed canaux).

Solution 1 - Manuel de Gestion des Ressources (ce que je veux éviter)

JNB est délimité-tchan a effectivement un manuel de gestion des ressources pour cette raison.Voir la cancelBTChan.Je suis aller pour quelque chose de plus difficile pour l'utilisateur de se tromper (pas que la gestion manuelle n'est pas la bonne façon d'aller dans de nombreux cas).

Solution 2 - Utiliser les exceptions en bloquant sur TVars (GHC ne peut pas le faire comme je veux)

MODIFIER cette solution, et la solution 3 qui est juste un spin-off, ne fonctionne pas!En raison de bug 5055 (WONTFIX) le compilateur GHC envoie des exceptions aux deux threads bloqués, même si l'on est suffisante (ce qui est théoriquement déterminable, mais pas pratique avec le GHC GC).

Si toutes les manières d'obtenir un BTChan sont IO, nous pouvons forkIO un thread qui lit/tentatives sur un supplément (dummy) TVar champ unique de la BTChan.Le nouveau thread va attraper une exception lorsque toutes les autres références à la TVar sont supprimés, de sorte qu'il saura quand pour décrémenter le nrDups et des éléments individuels des compteurs.Cela devrait fonctionner, mais les forces de tous mes utilisateurs à utiliser IO pour obtenir leur BTChans:

data BTChan = BTChan { ... as before ..., dummyTV :: TVar () }

dupBTChan :: BTChan a -> IO (BTChan a)
dupBTChan c = do
       ... as before ...
       d <- newTVarIO ()
       let chan = BTChan ... d
       forkIO $ watchChan chan
       return chan

watchBTChan :: BTChan a -> IO ()
watchBTChan b = do
    catch (atomically (readTVar (dummyTV b) >> retry)) $ \e -> do
    case fromException e of
        BlockedIndefinitelyOnSTM -> atomically $ do -- the BTChan must have gotten collected
            ls <- readTVar (channel b)
            writeTVar (channel b) (map (\(a,b) -> (a-1,b)) ls)
            readTVar (nrDup b) >>= writeTVar (nrDup b) . (-1)
        _ -> watchBTChan b

EDIT:Oui, c'est un pauvre mans finaliseur et je n'ai pas de raison particulière pour éviter l'utilisation de addFinalizer.Que serait la même solution, encore de forcer l'utilisation de IO afaict.

Solution 3:Une API plus propre que la solution 2, mais GHC encore ne le supporte pas

Aux utilisateurs de démarrer un thread de gestionnaire en appelant initBTChanCollector, qui sera chargé de surveiller un ensemble de ces factice TVars (à partir de la solution 2) et de faire le nécessaire de nettoyage.Fondamentalement, il enfonce le IO dans un autre thread qui sait quoi faire par l'intermédiaire d'un mondial (unsafePerformIOed) TVar.Les choses fonctionnent essentiellement comme la solution 2, mais la création de BTChan peut encore être de la STM.Incapacité à exécuter initBTChanCollector entraînerait une liste toujours croissante (espace de fuite) des tâches en tant que le processus s'exécute.

Solution 4:Ne laissez jamais les jeter BTChans

Cela revient à ignorer le problème.Si l'utilisateur ne descend jamais en un dupped BTChan ensuite, le problème disparaît.

Solution 5 Je vois ezyang réponse (totalement valide et apprécié), mais voudrais vraiment garder l'API actuelle, avec juste un " dup " la fonction.

** Solution 6** S'il vous plaît dites-moi, il ya une meilleure option.

EDIT:J' mise en œuvre de la solution 3 (non éprouvées en version alpha) et géré l'espace potentiel de fuite en faisant le mondial lui-même un BTChan - que chan devrait probablement avoir une capacité de 1 donc oublier pour exécuter init montre très rapide, mais c'est un changement mineur.Cela fonctionne dans GHCi (7.0.3), mais qui semble être accessoire.GHC lève des exceptions pour les deux threads bloqués (la validité d'une lecture de la BTChan et le regarder fil) afin que mes si vous êtes bloqué la lecture d'un BTChan quand un autre thread, il se défait de référence puis de mourir.

Était-ce utile?

La solution

Voici une autre solution:exiger que tous les accès à la délimitée canal double pour être encadré par une fonction qui libère ses ressources à la sortie (par une exception ou normalement).Vous pouvez utiliser une monade avec un rang-2 coureur à éviter le double des canaux de fuite.C'est encore manuel, mais le type de système, il est beaucoup plus dur de faire des choses coquines.

Vous ne voulez vraiment pas à s'appuyer sur une véritable IO finaliseurs, parce que GHC donne aucune garantie à propos de quand un finaliseur peut être exécuté:pour tous vous le savez peut attendre jusqu'à la fin du programme avant l'exécution de l'outil de finalisation, ce qui signifie que vous êtes dans l'impasse jusqu'alors.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top