Skip to content

Draft: Switch the select() I/O manager to use MVars for thread delays

Duncan Coutts requested to merge dcoutts/ghc:dcoutts/schedule-io-mvars into master

Previously the scheduler and select()-based I/O manager cooperated to manage a queue of threads blocked in delay#, using a special BlockedOnDelay thread status.

This arrangement was unpleasent for a number of reasons.

  1. The sleeping_queue blonged to the scheduler but was only used by the select() I/O manager. It ought really to belong to the select() I/O manager. On Windows where none of the I/O managager impls use the sleeping_queue, the scheduler nevertheless checks that the sleeping_queue is empty as part of thread deadlock detection. This may be a contributing factor to the complexity of deadlock detection for the Windows I/O managers.
  2. It means there are special thread blocking states used only for one I/O manager. The move in recent years has been towards I/O managers that work with "ordinary" scheduler primitives like MVars.
  3. We have an increasing proliferation of I/O manager styles. The one that is most different from the others is the legacy select() one. Its greatest divergence from the other I/O managers is its use of special thread states and being overly intimate with the scheduler.

With this patch we change the scheduler and the select() I/O manager to use MVars as the blocking mechanism for thread delays. We stop using the delay# primop and introduce a registerDelay# primop which is given an MVar which will be written into when the timer expires.

Now instead of a queue of sleeping threads shared with the scheduler, the select() I/O manager just manages a sorted queue of timers. The timeout queue enties record the wake time and an MVar to write into. The queue enties are heap allocated. It uses performTryPutMVar when the timers expire.

Note that this does use MVars and not IOPorts, because there appears to be no need to avoid MVars. There is no problem with deadlock detection, and we do not need to constrain to a single reader/writer. We use performTryPutMVar for the wakeups, so if the MVar is not empty at the time then the wakeup is lost. This is plausibly useful: allowing waiting on the a timeout or some other event that write into the same MVar.

The sorted queue of timers is still grossly inefficient, so no change there. That could obviously be done better, but that would be for separate patches.

The same transformation to use MVars can and should be done for the I/O blocking operations for the select() I/O manager, but that's for later patches.

The delay# primop remains for now, but is now only used by the Windows non-threaded I/O managers. They could be similarly transformed to use the MVar/IOPort scheme which would restore a degree of uniformity.

Edited by Andreas Klebinger

Merge request reports