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

Weird casuality loop detection #516

Open
bglgwyng opened this issue Jan 2, 2025 · 2 comments
Open

Weird casuality loop detection #516

bglgwyng opened this issue Jan 2, 2025 · 2 comments

Comments

@bglgwyng
Copy link

bglgwyng commented Jan 2, 2025

Here I made a reproducible.
https://github.com/bglgwyng/reflex-casuality-loop-report

The network description reads as follows.
You don't need to understand the following code. This is the result of the removal of domain-specific code from my project.

app = do
  eTick <- tickLossyFromPostBuildTime 0.1

  -- `mergeMapIncremental` cause the casuality loop
  e1 <- mergeMapIncremental <$> holdIncremental @_ @_ @(PatchMap Int (Event t ())) mempty never
  d1 <- holdDyn mempty e1

  dGate <- toggle False (eTick $> True)

  e2 <- mergeMapIncremental <$> holdIncremental @_ @_ @(PatchMap Int (Event t ())) mempty (eTick $> mempty)
  d2 <- holdDyn mempty e2

  let d3 = do
        d1
        gate <- dGate
        when gate (d2 $> ()) -- d2 is optionally evaluated by dGate
  performEvent_ $ updated d3 $> pure () -- subscribe `updated d3`
  pure never

I don't think there is any causality loop. The usage of mergeMapIncremental and toggling of evaluation of d2 in Dynamic monad is crucial to reproduce the causality loop.

Does anyone have an idea why this code is reported to include a loop?

@bglgwyng
Copy link
Author

bglgwyng commented Jan 4, 2025

Here is the profiling result.

Main.main (app/Main.hs:11:1-25)
|
`- Reflex.Host.Headless.runHeadlessApp (src/Reflex/Host/Headless.hs:(61,1)-(147,12))
   |
   `- Reflex.Spider.Internal.withSpiderTimeline (src/Reflex/Spider/Internal.hs:(2749,1)-(2751,49))
      |
      `- Reflex.PerformEvent.Base.hostPerformEventT (src/Reflex/PerformEvent/Base.hs:(135,1)-(155,15))
         |
         `- Reflex.Spider.Internal.runFrame (src/Reflex/Spider/Internal.hs:(2347,1)-(2416,15))
            |
            +- Reflex.Spider.Internal.subscribe (src/Reflex/Spider/Internal.hs:416:1-44)
            |  |
            |  `- Reflex.Requester.Base.Internal.runRequesterT (src/Reflex/Requester/Base/Internal.hs:(315,1)-(318,122))
            |     |
            |     `- Reflex.TriggerEvent.Base.runTriggerEventT (src/Reflex/TriggerEvent/Base.hs:52:1-47)
            |        |
            |        `- Main.app (app/Main.hs:(24,1)-(41,12))
            |           |
            |           `- Reflex.Spider.Internal.newJoinDyn (src/Reflex/Spider/Internal.hs:(2567,1)-(2573,56))
            |              |
            |              +- Reflex.Spider.Internal.coincidence (src/Reflex/Spider/Internal.hs:(1286,1)-(1291,5))
            |              |  |
            |              |  `- Reflex.Spider.Internal.eventCoincidence (src/Reflex/Spider/Internal.hs:441:1-90)
            |              |     |
            |              |     `- Reflex.Spider.Internal.eventSubscribedCoincidence (src/Reflex/Spider/Internal.hs:(648,1)-(660,3))
            |              |
            |              `- Reflex.Class.mergeInt (src/Reflex/Class.hs:355:1-74)
            |                 |
            |                 `- Reflex.Spider.Internal.mergeInt (src/Reflex/Spider/Internal.hs:2173:1-37)
            |                    |
            |                    `- Reflex.Spider.Internal.mergeIntCheap (src/Reflex/Spider/Internal.hs:(2177,1)-(2282,59))
            |
            +- Reflex.Requester.Base.Internal.runRequesterT (src/Reflex/Requester/Base/Internal.hs:(315,1)-(318,122))
            |  |
            |  `- Reflex.TriggerEvent.Base.runTriggerEventT (src/Reflex/TriggerEvent/Base.hs:52:1-47)
            |     |
            |     `- Main.app (app/Main.hs:(24,1)-(41,12))
            |        |
            |        `- Reflex.Spider.Internal.newJoinDyn (src/Reflex/Spider/Internal.hs:(2567,1)-(2573,56))
            |           |
            |           +- Reflex.Class.mergeInt (src/Reflex/Class.hs:355:1-74)
            |           |  |
            |           |  `- Reflex.Spider.Internal.mergeInt (src/Reflex/Spider/Internal.hs:2173:1-37)
            |           |     |
            |           |     `- Reflex.Spider.Internal.cacheEvent (src/Reflex/Spider/Internal.hs:(335,1)-(382,72))
            |           |
            |           `- Reflex.Spider.Internal.cacheEvent (src/Reflex/Spider/Internal.hs:(335,1)-(382,72))
            |
            `- Reflex.Spider.Internal.subscribe (src/Reflex/Spider/Internal.hs:416:1-44)
               |
               `- Reflex.Requester.Base.Internal.runRequesterT (src/Reflex/Requester/Base/Internal.hs:(315,1)-(318,122))
                  |
                  `- Reflex.Class.mergeInt (src/Reflex/Class.hs:355:1-74)
                     |
                     `- Reflex.Spider.Internal.mergeInt (src/Reflex/Spider/Internal.hs:2173:1-37)
                        |
                        `- Reflex.Spider.Internal.mergeIntCheap (src/Reflex/Spider/Internal.hs:(2177,1)-(2282,59))

@bglgwyng
Copy link
Author

bglgwyng commented Jan 4, 2025

I found that this issue is related to ApplicativeDo. When I disable the ApplicativeDo extension, the app works without the causality loop.

It seems that Applicative (Dynamic t) and Monad (Dynamic t) handle conditional reads differently. In the d3 definition, dGate makes the read of d2 conditional, and this conditional behavior appears to be treated differently under Applicative vs Monad operations when ApplicativeDo is enabled.

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

No branches or pull requests

1 participant