Native Threads in the RTS

Wolfgang Thaller
Wed, 27 Nov 2002 00:23:58 +0100

Nice design, Alastair. I've stolen lots of ideas and some text for the 
complete rewrite of the proposal. The concept of "associating" haskell 
threads to native threads proved to be a good way of explaining my 
original idea in a different way --- and then I found out that 
forkNativeThread needn't be a primitive, but can be implemented on top 
of the FFI. After that I found out what the bound/free exports 
buisiness was all about and why we might need it.

As for the questions regarding my previous proposal, I think they are 
answered in the new one. If they aren't, let me know. [It's no use 
explaining what I meant to say if I already want to say something 
different ;-) ]



Threads Proposal, version 3


Since foreign libraries sometimes exploit thread local state, it is
necessary to provide some control over which thread is used to execute
foreign code.  In particular, it is important that it should be
possible for Haskell code to arrange that a sequence of calls to a
given library are performed by the same native thread and that if an
external library calls into Haskell, then any outgoing calls from
Haskell are performed by the same native thread.

This specification is intended to be implementable both by
multithreaded Haskell implementations and by single-threaded
implementations and so it does not comment on which particular OS
thread is used to execute Haskell code.


Haskell threads may be associated at thread creation time with either
zero or one native threads. Each Native thread is associated with zero 
or one Haskell threads (no native thread may be associated with two 
Haskell threads at a time).

An associated pair of a Haskell thread and a native thread can only 
execute either foreign code or haskell code at any one time.

The thread that main runs in, threads created using forkIO and threads 
created for running finalizers or signal handlers are not associated 
with a native thread [Actually, we might make this an implementation 
detail: These Haskell threads are not guaranteed to be associated with 
a native thread. If it makes sense for some implementation, all threads 
might be associated with native threads].

There are now two kinds of foreign exported [and foreign import 
wrapped] functions: bound and free [I'm not happy with these names, for 
the same reasons as given by Seth before].

When a "bound" foreign exported function is invoked [by foreign code], 
the implementation checks whether a Haskell thread is associated with 
the current OS thread.
If there is one, this Haskell thread is used to execute the callback.
If there is none, a new Haskell thread is created and associated with 
the native thread. This is the only situation where a Haskell thread is 
associated with a native thread. The new associated Haskell thread is 
then used to execute the callback. When the callback finishes, the 
Haskell thread is terminated, the association is dissolved, but the OS 
thread continues to run.

When a "free" foreign exported function is invoked, the implementation 
may freely choose what Haskell thread the function is executed in. It 
is not specified whether this thread is associated with a particular OS 
thread or not.

When a foreign imported function is invoked [by Haskell code], the 
foreign code is executed in the native thread associated with the 
current Haskell thread, if an association exists. If the current 
Haskell thread is not associated to a native thread, the implementation 
may freely decide which thread to run the foreign function in.
This must be done for all foreign imports, including  "unsafe".
An unsafe call must not call back to Haskell or otherwise cause a 
garbage collection. Other Haskell threads may be blocked while the 
unsafe call executes.
A safe call allows callbacks to Haskell. Other Haskell threads may be 
blocked while the unsafe call executes.
A threadsafe call additionally guarantees other Haskell threads not to 

forkNativeThread :: IO () -> IO ThreadID may be implemented using the 
FFI and an OS-specific thread creation routine.


Finalizers and signal handlers cannot be associated with a particular 
native thread. If they have to trigger an action in a particular native 
thread, a message has to be sent manually (via MVars and friends) to 
the Haskell thread associated with the native thread in question. This 
might be tedious, but if we want to avoid the nastiness of multiple 
Haskell threads associated with one OS thread (lots of unpredictable 
blocking), it looks like our only choice.