
2) Calls to the CGA can be made at any time, from any thread. The implementation is responsible for assuring that the serialization requirements of the backend toolkit are met.
Unfortunately, you are not clear about what kind of thread you mean: OS thread or Haskell thread?
The CGA will be called from Haskell code, so I'm talking of Haskell threads. However, an implementation may choose to use any OS thread it likes to execute the Haskell code, so I'm also talking of OS threads.
I therefore propose another rule:
2d) All haskell code is run in a single OS thread (the "GUI thread"). Calls to the CGA can be made at any time from any Haskell thread (within the GUI thread).
My vote is a big NO: This means restricting what a Haskell runtime system is allowed to do.
Note that: - We can run many haskell threads within one OS thread. Furthermore, the interaction between Haskell threads is well understood and light weight.
But there is no standard way to manage the correspondence between haskell threads and OS threads. Relying on the fact that most current implementations use one OS thread for all haskell threads is just asking for trouble.
- The serialization requirements of the backend toolkit are automatically met since all these haskell threads run in the same OS thread.
... unless the toolkit uses some global state variable (the "current graphics port" in carbon; drawing to one window from multiple _haskell_ threads simultanously would probably be a problem for Win32, too, as Drawing Context state may be modified in the process).
- All current Haskell systems can easily support this model -- in contrast, no haskell system (except the LVM in Helium :-) supports multiple OS threads to my knowledge.
The "threaded RTS" is disabled by default in GHC 5.04, because it wasn't well tested and contained several bugs. I fixed a whole lot of bugs for the GHC 5.05 CVS version, and therefore I hope htat GHC 5.06 will fully support using multiple OS threads.
- We can implement all advanced event models on top of this model using concurrency.
- We most surely do *not* want foreign C calls to be run in a different OS thread and don't want the "threadsafe" keyword here. (I am opposed to such extension -- complexity without reason - run your OS threads from C yourself!)
We (I have to admit, that means: I) most surely *do* want the "threadsafe" keyword here. After all, it is a feature that I have been taking for granted before I switched from C++ to Hasell. Having to think about an implementation detail of Haskell where things work "just right" when you use C++ is scary and definitely the wrong way to go. That's why threadsafe calls are the future. (On the other hand,the behaviour of "safe" calls has never been specified accurately, is difficult to implement in implementations that properly support multiple OS threads, and will probably have to go, unless somebody comes up with a meaningful and implementable specification on ffi@haskell.org).
As you explained, a callback can use forkIO to achieve concurrent callbacks for example. However a more "real world" reason for having concurrency would be that a callback needs to do a lot of processing. As callbacks are run sequentially, the entire application will not react to events while the callback does its work. Therefore, a callback that needs to do a lot of processing should spawn a Haskell thread to do the processing (a worker thread) and return as soon as possible in order to stay reactive.
Agreed.
Now, the big issue here is how to keep those Haskell threads running! [...]
With threadsafe calls, that problem disappears. There'll just be normal synchronization issues like in every other language.
When a callback returns that has just forked another Haskell thread, the eventloop will make an OS call to wait for the next event. Since we are running in a single OS thread, this will "disable" the Haskell scheduler and the worker thread will not run at all until another event happens!
The question is how to solve this. Alastair Reid used the "yield" primitive to wait until all Haskell threads were done (or called yield themselves), but that wouldn't solve our particular problem as we want to stay reactive *and* have a lot of processing to do.
Now, I think that we should use "idle time" events to solve this. It would stay nicely in our simple concurrency model and also keep the whole GUI desktop reactive (do we remember the old Winhugs bug :-). Basically, when an "idle" time event happens we call a callback that just sleeps for, say, 0.1 seconds, giving the Haskell scheduler time to run other haskell threads. It would be good of course when we have some hooks to determine whether there are any haskell threads waiting to be scheduled so we can avoid calling that handler when not necessary. We don't necessarily have to use real "idle" events: if there are Haskell treads waiting to be scheduled, we can also peek at the event queue, and when no new events have arrived, call the idle callback. When all haskell threads are done, we wait again. Note that this schedule also give more priority to new events, keeping the whole application highly reactive.
I don't like that. Having those "hooks" into the runtime system sounds like much more unnecessary (and implementation-specific) complexity than the "threadsafe" extension. Not having those hooks would waste some CPU time. It should of course be possible to implement the CGA on single-OS-thread haskell implementations (using the "idle time" scheme you proposed above). But we should write the specification in a way that allows taking advantage of "threadsafe". It would be a big mistake to write the specification in a way that is incompatible with a planned feature of the next version of GHC. Cheers, Wolfgang