turion/rhine

Unexpected behavior, with effects on stdin/out not running

freckletonj opened this issue ยท 2 comments

I've spent some time boiling this bug (?) down to a simple example:

  1. readLn
  2. (*100)
  3. print
input :: ClSF IO StdinClock () Int
input = arrMCl (const $ fromMaybe 0 . readMaybe <$> getLine)

stepFn :: Monad m => ClSF m (Millisecond 300) Int Int
stepFn = arr (*100)

output :: Show a => ClSF IO (Millisecond 500) a ()
output = arrMCl print

main :: IO ()
main = flow $
  (input @@ StdinClock)
  >-- (keepLast 1 -@- concurrently) -->
  (stepFn @@ waitClock)
  >-- (collect -@- concurrently) -->
  (output @@ waitClock)

When I run this, it starts printing as expected.

Then I enter a number at the repl, and printing stops.

If I enter numbers 4 times printing continues, but the interim 3 numbers fall into a black hole, never getting processed.

Enter a 5th number, and printing stops again, until you've entered 4 more numbers.

Each time you get printing to resume, it catch-up-prints dozens of numbers, but they'll all be the most recently input number, many times all instantaneously.

EDIT: I thought it was a flushing issue, but nope, that number never gets processed, as evident in the "catchup" prints. I've even tried passing stdin/out handles throughout and manually flushing, and... no go.

EDIT: Doesn't help to print to stderr, nor to change the buffering status of the handle (hSetBuffering stdin NoBuffering). Probably just spinning my wheels at this point ๐Ÿ˜…

EDIT: Let me know if you struggle to reproduce it, I can screencast a video.

This is to be expected, in fact. I ought to document this better. Maybe a FAQ section in the README.md, or what do you think?

The reason is that you have a blocking ClSF, namely input, in the signal network. All purely data-related components should be non-blocking, only clocks are allowed to block. There is no obvious way to prevent blocking IO.

We can simplify your program to make it work. The standard approach to deal with blocking IO is to put it in a clock. In the case of stdin, this has already been done in form of the StdinClock, as you've found out. Since the clock already does the blocking IO, you don't need to do it again with getLine. Instead, you can simply use tagS to retrieve the input line. It should look something like:

input :: ClSF IO StdinClock () Int
input = tagS >-> arr (readMaybe >>> fromMaybe 0)

(You can use >>> instead of >-> as well, matter of taste)

put blocking code in a clock

ohhh, that makes so much more sense and works perfectly. I'll add a couple of my recent questions and your answers to a FAQ sometime today :D