admin管理员组

文章数量:1307503

Here's the full program¹

module Main where

import Control.Concurrent.Async
import Control.Concurrent.MVar
import System.Environment
import System.IO
import System.Process

main :: IO ()
main = do
  args <- getArgs
  (Just i, Just o, Nothing, p) <- createProcess (proc "socat" args)
                                                {std_in = CreatePipe, std_out = CreatePipe}
  sequence_ [hSetBuffering h NoBuffering | h <- [i, o, stdin, stdout]]
  hSetEcho stdin False
  mine <- newEmptyMVar
  res <- concurrently
    (do c <- getChar
        putMVar mine c
        hPutChar i c)
    (do other <- hGetChar o
        mine' <- takeMVar mine
        return (mine', other))
  print $ snd res
  terminateProcess p

I launch two instances of it in different terminals, like this:

$ cabal run myprogram -- TCP-LISTEN:12345,fork -       # in terminal 1
$ cabal run myprogram -- TCP-CONNECT:localhost:12345 - # in terminal 2

(in that order) then I hit one key in each terminal (doesn't matter the order), and they will both print those two keys in a pair (with sides swapped).

Sometimes, though if I hit the key in terminal 1 first, then the process in terminal 1 doesn't return (while the one in terminal 2 does).

Being not particularly experienced with concurrent programming, I wouldn't be surprised for a deadlock, but I don't see how this can be one! Here's my observations/reasoning:

  • Terminal 2 always returns (at least I've never seen it not return);
  • the behavior of terminal 1 seems to be "fixed" if I add the line putStrLn "hello" right before print $ snd res;
  • if I swap the lines print $ snd res and terminateProcess p, the "wrong" behavior is much more frequent;
  • the deadlock could happen between the two threads of each of the two processes I launch, but
    • since terminal 2 is always returning (and specifically it prints the pair snd res), surely those 2 threads haven't deadlocked, I think,
    • the other one is running an identical program, the only difference being the arguments passed to the spawned socat process, so I don't understand why the behavior should be asymmetrical, in the sense that if there was a deadlock, I'd expect it to happen regardless of what program received the keystroke first;
    • and how can deadlock happen between the 2 threads (of each process) if one thread is calling putMVar and the other is calling takeMVar on the same single MVar in that program run? Each of the two calls will block until the other catches up, no?

(¹) In this very stripped down example, the frequency with which this happens is relatively low, but not too much (a few tens of attempts seems to suffice). I do have a slightly more nosisy example that seems to be impacted a bit more, but I don't think there's a "structural" difference with respect to this one, so I haven't posted it to keep it simpler, in case the reason for the observed behavior is apparent to the experts, but I can post it if deemed useful.

Here's the full program¹

module Main where

import Control.Concurrent.Async
import Control.Concurrent.MVar
import System.Environment
import System.IO
import System.Process

main :: IO ()
main = do
  args <- getArgs
  (Just i, Just o, Nothing, p) <- createProcess (proc "socat" args)
                                                {std_in = CreatePipe, std_out = CreatePipe}
  sequence_ [hSetBuffering h NoBuffering | h <- [i, o, stdin, stdout]]
  hSetEcho stdin False
  mine <- newEmptyMVar
  res <- concurrently
    (do c <- getChar
        putMVar mine c
        hPutChar i c)
    (do other <- hGetChar o
        mine' <- takeMVar mine
        return (mine', other))
  print $ snd res
  terminateProcess p

I launch two instances of it in different terminals, like this:

$ cabal run myprogram -- TCP-LISTEN:12345,fork -       # in terminal 1
$ cabal run myprogram -- TCP-CONNECT:localhost:12345 - # in terminal 2

(in that order) then I hit one key in each terminal (doesn't matter the order), and they will both print those two keys in a pair (with sides swapped).

Sometimes, though if I hit the key in terminal 1 first, then the process in terminal 1 doesn't return (while the one in terminal 2 does).

Being not particularly experienced with concurrent programming, I wouldn't be surprised for a deadlock, but I don't see how this can be one! Here's my observations/reasoning:

  • Terminal 2 always returns (at least I've never seen it not return);
  • the behavior of terminal 1 seems to be "fixed" if I add the line putStrLn "hello" right before print $ snd res;
  • if I swap the lines print $ snd res and terminateProcess p, the "wrong" behavior is much more frequent;
  • the deadlock could happen between the two threads of each of the two processes I launch, but
    • since terminal 2 is always returning (and specifically it prints the pair snd res), surely those 2 threads haven't deadlocked, I think,
    • the other one is running an identical program, the only difference being the arguments passed to the spawned socat process, so I don't understand why the behavior should be asymmetrical, in the sense that if there was a deadlock, I'd expect it to happen regardless of what program received the keystroke first;
    • and how can deadlock happen between the 2 threads (of each process) if one thread is calling putMVar and the other is calling takeMVar on the same single MVar in that program run? Each of the two calls will block until the other catches up, no?

(¹) In this very stripped down example, the frequency with which this happens is relatively low, but not too much (a few tens of attempts seems to suffice). I do have a slightly more nosisy example that seems to be impacted a bit more, but I don't think there's a "structural" difference with respect to this one, so I haven't posted it to keep it simpler, in case the reason for the observed behavior is apparent to the experts, but I can post it if deemed useful.

Share Improve this question edited Feb 2 at 17:52 Enlico asked Feb 2 at 16:28 EnlicoEnlico 28.5k8 gold badges67 silver badges149 bronze badges 8
  • How do you determine this deadlocks? – cafce25 Commented Feb 2 at 17:12
  • @cafce25, I've clarified in the title that I don't think it's necessarily a deadlock, but it might be. – Enlico Commented Feb 2 at 17:22
  • @Enlico What happens if you close stdin immediately after hGetChar and stdout immediately after hPutChar (which presumably will make socat stop by itself) and then waitForProcess instead of terminateProcess? – danidiaz Commented Feb 2 at 18:00
  • @danidiaz, I get sasso-carta-forbici: <stdout>: hPutStr: illegal operation (handle is closed) in terminal 2, terminal 1 "hangs" presuambly in the same way as in my question. – Enlico Commented Feb 2 at 18:09
  • By the way, I've added one bullet point in the question about swapping 2 lines. – Enlico Commented Feb 2 at 18:10
 |  Show 3 more comments

1 Answer 1

Reset to default 4

Here is one sequence of events that lead to the observed behavior:

  • you type a char and it is transmitted: terminal1 → app1 → socat1 → socat2 → app2
  • you type a char in terminal2 → app2 → socat2

now both threads in app2 are done so:

  • it prints both chars and then
  • terminates socat2 without checking if socat2 is done sending and thus possibly before it had a chance to send any data to socat1

finally:

  • app1 forever waits for socat1 to send it a char which never happens because it never receives one

本文标签: multithreadingWhy doesn39t this program return Or is it a deadlockStack Overflow