2015-06-17 10 views
7

Próbowałem usunąć problem przy korzystaniu z wielu MVarów, ale bez powodzenia.Wątek zablokowany na czas nieokreślony w operacji MVar

Mój kod używa dwóch MVars: jednego do przechowywania stanu bieżącego serwerów, a drugiego do przekazywania zdarzeń sieciowych do i od wątków klienta. Jednak po kilkakrotnym ponownym podłączeniu i odłączeniu serwer przestaje wysyłać dane po nawiązaniu połączenia z nowymi klientami (prawdopodobnie dlatego, że zdarzenia sieciowe MVar są opróżniane z dowolnego powodu) i ostatecznie pojawia się wraz z błędem: *** Exception: thread blocked indefinitely in an MVar operation

Podjęłam następujące próby debugować ten problem w ciągu ostatnich kilku dni:

  1. funkcje wykorzystywane do modyfikowania MVar (y) przyzwyczajenie generują wyjątki
  2. problem nie występuje aż do klienta albo łączy lub łączy następnie rozłącza
  3. Wydaje się, że problem wystąpił domly (czasem kilku klientów może połączyć następnie rozłączyć, innym razem to się dzieje od razu)

mam pojedyncze problem do trzech plików:

  1. https://github.com/Mattiemus/IMC-Server/blob/master/IMC.hs (wyjątek zostaje wrzucony w sense)
  2. https://github.com/Mattiemus/IMC-Server/blob/master/IMC/Networking/Server.hs (Modifined ciągu applicationhandleClient i cleanupClient)
  3. https://github.com/Mattiemus/IMC-Server/blob/master/IMC/Utilities/Concurrency.hs (funkcje, które pchają i pop do listy przechowywanej w MVar)

Nie mam pomysłów, ponieważ używam tylko modifyMVar iMVar (więc na pewno nigdy nie powinien być całkowicie pusty) - moim jedynym założeniem jest to, że być może Wyjątek jest generowany podczas modyfikowania MVar, jednak myślę, że jest to mało prawdopodobne.

Każda pomoc jest doceniana, problem ten od pewnego czasu mnie dręczy.

+1

Nie sądzę, że większość osób będzie chciała przeszukać tyle kodu, aby znaleźć ten błąd. Być może napisanie kilku testów pomoże ci go znaleźć. – user2407038

+0

Zmodyfikowałem to pytanie, aby jeszcze bardziej uściślić problem na 4 główne funkcje. Jakiego rodzaju testy będę musiał napisać, aby wyizolować ten problem, problem wydaje się w końcu występować niezależnie od pogody. Usuwam wszystkie modyfikatory przez serwer lub zdarzenia MVar, co prowadzi mnie do przekonania, że ​​w jakiś sposób używam ich niepoprawnie. Istnieje wiele producentów nad wydarzeniami MVar (jeden wątek dla każdego klienta), ale tylko jeden konsument - może to jest problem? – Mattiemus

+0

W "znaczeniu" używasz 'MVar' bardzo podobnego do kanału. Czemu zamiast tego użyć "Chan"? –

Odpowiedz

4

Trzy dni później i jego rozwiązanie: w rzeczywistości nie było związane ani z kodem sieciowym, ani ze współbieżnością, a także ze względu na moją niepoprawną ponowną implementację Yampasa dpSwitch w Netwire. Kod poprawione pisał poniżej dla każdego, kto chce realizować tę funkcję:

dpSwitch :: (Monoid e, Applicative m, Monad m, T.Traversable col) => (forall wire. a -> col wire -> col (b, wire)) 
    -> col (Wire s e m b c) 
    -> Wire s e m (a, col c) (Event d) 
    -> (col (Wire s e m b c) -> d -> Wire s e m a (col c)) 
    -> Wire s e m a (col c) 
dpSwitch route wireCol switchEvtGen continuation = WGen $ gen wireCol switchEvtGen 
where 
    gen wires switchEvtGenWire _ (Left x) = return (Left mempty, WGen $ gen wires switchEvtGenWire) 
    gen wires switchEvtGenWire ws (Right x) = do    
     let routings = route x wires 
     wireSteps <- T.sequenceA (fmap (\(wireInput, wire) -> stepWire wire ws (Right wireInput)) routings) 
     let wireOutputs = T.sequenceA (fmap fst wireSteps) 
      steppedWires = fmap snd wireSteps 
     case wireOutputs of 
      Left wireInhibitedOutput -> return (Left wireInhibitedOutput, WGen $ gen steppedWires switchEvtGenWire) 
      Right wireResultOutput -> do 
       (event, steppedSwitchEvtGenWire) <- stepWire switchEvtGenWire ws (Right (x, wireResultOutput)) 
       case event of 
        Left eventInhibited -> return (Left eventInhibited, WGen $ gen steppedWires steppedSwitchEvtGenWire) 
        Right NoEvent -> return (wireOutputs, WGen $ gen steppedWires steppedSwitchEvtGenWire) 
        Right (Event e) -> return (wireOutputs, continuation steppedWires e) 
2

Myślę, że widzę problem - jest w Server.hs. Masz operacje, które wykonują sieć IO wewnątrz połączenia withMVar. Teraz wyobraź sobie, że IO blokuje skutecznie na zawsze. Nie otrzymujesz wyjątku, który wymusza zmianę var, ani też operacja nie kończy się normalnie i nie zastąpisz var, a więc utkniesz.

Ogólnie rzecz biorąc, nie powinieneś wykonywać żadnych znaczących operacji w rozmowie withMVar, nawet jeśli można. A jeśli wykonasz jakąś taką operację, musisz być podwójnie pewny, że skutecznie strzeżesz ich z przekroczeniem limitu czasu, itd., Tak, że jesteś pewien, że zawsze kończą tak czy inaczej.

+1

" Bloki ... na zawsze "mogą wyzwolić tylko ten wyjątek, jeśli środowisko wykonawcze GHC może udowodnić, że jest blokowane na zawsze. Czy operatorzy sieci mogą do tego doprowadzić? –

+0

@sclv Jeśli wskazujesz na funkcje broadcast/sendTo/sendToExcept, wówczas przeniesienie operacji sieciowych poza funkcją withMVar nie rozwiązuje problemu: zakleszczenie trwa. – Mattiemus

3

Dla każdego, kto może potknąć się na tym, kilka dodatkowych informacji thread blocked indefinitely in an MVar operation nie jest aż tak silny. Dzieje się tak, gdy każdy wątek zawierający odniesienie do MVar próbuje odczytać (lub zapisać) do tego miejsca, umarł lub czeka na inną prymitywę, która jest blokowana na zawsze.np gwint 1 próbuje odczytać MVar a i czeka na gwincie 2, który jest albo martwy, także próbuje odczytać MVar a lub próbuje odczytać MVar b, które mogą być zapisywane tylko w wątku 1.

Poniższy kod szczęśliwie zawiesza się na zawsze:

do 
    a <- newEmptyMVar 
    forkIO (readMVar a >>= putStrLn) 
    putMVar a $ last $ repeat 0