Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parallel composition of FixedStep clocks evaluates in unexpected order #365

Open
DISTEL100 opened this issue Oct 21, 2024 · 13 comments · May be fixed by #376
Open

Parallel composition of FixedStep clocks evaluates in unexpected order #365

DISTEL100 opened this issue Oct 21, 2024 · 13 comments · May be fixed by #376
Assignees

Comments

@DISTEL100
Copy link

There seems to be a problem with the parallel clock when composing two pure clocks in ScheduleT. The merged output is not ordered by the timestamps.

The following is a minimal example to reproduce the issue:

f300 :: (MonadIO m) => Rhine (ScheduleT Integer m) (FixedStep 300) a Integer
f300 =  absoluteS @@ FixedStep

f500 :: (MonadIO m) => Rhine (ScheduleT Integer m) (FixedStep 500) a Integer
f500 =  absoluteS @@ FixedStep

f :: IO ()
f = runScheduleIO $ flow $ (f300 +@+ f500) @>-^ arrM (liftIO . print)

The output is:

ghci> f
Left 300
Left 600
Right 500
Left 900
Left 1200
Right 1000
Left 1500
Left 1800
Right 1500
Left 2100
Left 2400
Right 2000
Left 2700
Right 2500
Left 3000
Left 3300
^CInterrupted.

I would expect it to be

ghci> f
Left 300
Right 500
Left 600
Left 900
Right 1000
Left 1200
Left 1500
Right 1500
Left 1800
Right 2000
Left 2100
Left 2400
Right 2500
Left 2700
Left 3000
Left 3300
^CInterrupted.
@turion turion self-assigned this Oct 21, 2024
@turion
Copy link
Owner

turion commented Oct 21, 2024

Thanks for finding this! I can sort of reproduce, but I'm getting a different (yet still incorrect) scheduling:

ghci> runScheduleIO $ flow $ (f300 +@+ f500) @>-^ arrM (liftIO . print)
Left 300
Right 500
Left 600
Left 900
Left 1200
Right 1000
Left 1500
Left 1800
Left 2100
Right 1500
Left 2400
Right 2000
Left 2700
Left 3000
^CInterrupted.

@turion
Copy link
Owner

turion commented Oct 21, 2024

I have an inkling that this might be an upstream issue in https://github.com/turion/monad-schedule. Let me investigate.

@turion
Copy link
Owner

turion commented Oct 21, 2024

The problem is not related to the clock structures, since it can be reproduced with automata:

ghci> a300 = Data.Automaton.constM (wait 300 >> pure 300) >>> accumulateWith (+) 0
ghci> a500 = Data.Automaton.constM (wait 500 >> pure 500) >>> accumulateWith (+) 0
ghci> runScheduleIO $ Data.Automaton.reactimate $ schedulePair a300 a500 >>> arrM (liftIO . print)
300
500
600
900
1200
1000
1500
1800
1500
2100
2400
2000
2700
3000
^CInterrupted.

@turion
Copy link
Owner

turion commented Oct 21, 2024

Which versions of rhine and monad-schedule are you using? (I'm using rhine-1.4.0.1 and monad-schedule-0.2)

@DISTEL100
Copy link
Author

i have rhine ==1.4.0.1 and monad-schedule 0.2.0.1 and i just ran it again and had the same output like you

Thanks for finding this! I can sort of reproduce, but I'm getting a different (yet still incorrect) scheduling:

ghci> runScheduleIO $ flow $ (f300 +@+ f500) @>-^ arrM (liftIO . print)
Left 300
Right 500
Left 600
Left 900
Left 1200
Right 1000
Left 1500
Left 1800
Left 2100
Right 1500
Left 2400
Right 2000
Left 2700
Left 3000
^CInterrupted.

@turion
Copy link
Owner

turion commented Oct 21, 2024

Yes, it unfortunately depends on how fast the GHC IO machine is running. Basically, if there is an IO delay that is too big then an action may fail to even start before the other finishes. In that case, the second one cannot judge whether it should have waited for the first one.

@turion
Copy link
Owner

turion commented Oct 21, 2024

I don't have a good solution for this other than deprecating the MonadSchedule IO instance. I'll have to think about this. For the time being, your workaround should be replacing runScheduleIO by runFreeAsync . runScheduleIO everywhere.

@DISTEL100
Copy link
Author

ok, thanks for the fast replies!

@turion
Copy link
Owner

turion commented Oct 22, 2024

I'm afraid this is a known and poorly documented issue in monad-schedule. A workaround would be using https://hackage.haskell.org/package/monad-schedule-0.2.0.1/docs/Control-Monad-Schedule-FreeAsync.html.

For example:

ghci> runFreeAsync $ runScheduleIO $ Data.Automaton.reactimate $ scheduleList (a300 :| [a500]) >>> arrM (liftIO . print)
300 :| []
500 :| []
600 :| []
900 :| []
1000 :| []
1200 :| []
1500 :| [1500]

The issue is that the MonadSchedule IO instance is not very well behaved.

@turion
Copy link
Owner

turion commented Oct 23, 2024

Hah I was mistaken luckily. There is a rare race condition in schedulePair when two clocks are supposed to tick at exactly the same time. I'll publish a fix later.

turion added a commit that referenced this issue Oct 27, 2024
turion added a commit that referenced this issue Oct 27, 2024
@turion
Copy link
Owner

turion commented Oct 27, 2024

I had hoped that #343 had fixed this. @DISTEL100 What ghc version are you on? Can you clone the rhine repository and run cabal test all?

@DISTEL100
Copy link
Author

DISTEL100 commented Oct 28, 2024

i am using ghc 9.4.8 and the tests are all passing

@turion
Copy link
Owner

turion commented Nov 29, 2024

See #377 for an in-depth discussion of the probable underlying issue.

@turion turion linked a pull request Nov 29, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants