pkamenarsky / replica

A remote virtual DOM library for Haskell
BSD 3-Clause "New" or "Revised" License
139 stars 14 forks source link

Mis-dispatch to a unexisting element freezes the running loop #8

Closed kamoii closed 5 years ago

kamoii commented 5 years ago

This problem occurs when there is some lag(e.g. slow network, server being buzy, etc.).

Consider following example(using concur-replica):

main :: IO ()
main = do
  runDefault 8080 "test" $ do
    div []
      [ p [] [ text "hello" ]
      , button [ onClick ] [ text "go next" ]
      ]
    c <- div []
      [ p [] [ text "choose" ]
      , div []
        [ button [ "a" <$ onClick ] [ text "a" ]
        , button [ "b" <$ onClick ] [ text "b" ]
        ]
      ]
    p [] [ text $ "you choosed: " <> c ]

It first greets the user, next make user choose from "a" or "b" and finlly display the users choice.

screencast-2019-07-13T19:59:59

Under slow connection, when we double click "go next" the choose page becomes unresponsive. I'm simulating slow connection using throttle.

screencast-2019-07-13T20:05:47

This is what happening:

To fix this we need to skip some events. When arrived event has frame number from past and there is no element corresponding to evtPath, we need to skip this event.

pkamenarsky commented 5 years ago

Thanks for the report!

Unfortunately it's a little bit more complicated - if we skipped all events with client frames from the past typing into input boxes on slow connections would be really jarring, since we'd skip events and thus characters.

I'll need to think more about this.

kamoii commented 5 years ago

@pkamenarsky

Unfortunately it's a little bit more complicated - if we skipped all events with client frames from the past typing into input boxes on slow connections would be really jarring, since we'd skip events and thus characters.

Yes, I understand. Thats why we need to only skip events from the past which has no element corresponding to its evtPath. We can consume events from past if its evtPath is valid and thus dispatchable.

Unfortunately, in rare case "We can consume events from past if its evtPath is valid and thus dispatchable." is not true. Between two vdom, buttons that has different identity could have same Path. For example, a little modification of previous sample:

main :: IO ()
main = do
  runDefault 8080 "test" $ do
    div []
      [ p [] [ text "hello" ]
      , button [ onClick ] [ text "go next" ]
      ]
    c <- div []
      [ p [] [ text "choose" ]
      , button [ "a" <$ onClick ] [ text "a" ]
      , button [ "b" <$ onClick ] [ text "b" ]
      ]
    p [] [ text $ "you choosed: " <> c ]

Greeting frame's "go next" button and Choosing frame's "a" button has same Path. Double clicking "go next" button leads to auto-choosing the "a" which is troublesome if user wanted to choose "b". screencast-2019-07-14T02:12:37

There is also "Has same identity, but Path differs between two vdom" problem, but this is kinda rare and also even if the problem occur, we just lose one event, so it is not that critical.

pkamenarsky commented 5 years ago

Fixed in https://github.com/pkamenarsky/replica/commit/69249ec760b8b8272d302269b54ddc4531d508a9.

The "double event firing on the next VDOM" problem is harder to solve. We could force a hard sync between server and client (i.e. client events are only sent after a received DOM update) and implement exceptions for onChange events (like in the case of inputs), but I imagine that'd get hairy.

I'd rather leave it for now and gather some feedback -- if it turns out to be a bigger problem then at least we'll have a bit more data to base a solution on.

Feel free to reopen if you've got other ideas.