HeinrichApfelmus / threepenny-gui

GUI framework that uses the web browser as a display.
https://heinrichapfelmus.github.io/threepenny-gui/
Other
439 stars 77 forks source link

Foreign.JavaScript: Hs callbacks don't always execute #108

Closed hanshoglund closed 8 years ago

hanshoglund commented 9 years ago

I'm using Foreign.JavaScript to communicate between some custom JS GUI stuff and a Haskell backend. The JS GUI need to send values to the backend in response to certain events, so I am using exportHandler to pass a HS handler to the JS side. However this callback failed to execute until I came up with the idea of letting HS call into JS occasionally in a separate thread as per below – note that I'm not doing anything with the callback in the dummy function.

Is this expected, and if so, what is the conditions in which JS may call back into HS?

// gui.html
window.h.dummy = function(cb) { /*Nothing*/ }
window.h.start = function(cb) {
  // Create lots of stuff that may use cb later on to send values to Haskell
}
-- main.hs
import Foreign.JavaScript
import Foreign.RemotePtr
import Control.Concurrent (forkIO, threadDelay)

dummyPoll = True -- If changed to False, callback doesn't work

main = serve (defaultConfig {jsCustomHTML=Just "gui.html"}) 
  $ \w -> do
    handler <- exportHandler w $ \data -> do
      print (data::Value)
    withRemotePtr rectHandler $ \_ _ -> do
      runFunction w $ (ffi "window.h.start(%1)" handler)
      when $ dummyPoll $ forkIO $ forever $ do
        threadDelay 1000000
        runFunction w $ (ffi "window.h.dummy(%1)" handler)
    return ()
HeinrichApfelmus commented 9 years ago

This looks like an issue with garbage collection to me. (Also, your code does not compile as rectHandler is not in scope). As the documentation for exportHandler says:

WARNING: The event handler will be garbage collected unless you keep a reference to it on the Haskell side! Registering it with a JavaScript function will generally not keep it alive.

You need to use addReachable to connect the result of exportHandler to the root of the window if you want to keep the RemotePtr alive after the argument to serve has finished.

hanshoglund commented 9 years ago

Thanks Heinrich, I'll give it a go (rectHandler should have been handler btw – bad refactoring before posting).