Skip to content

Reconsider the behavior of trying to fork a new thread in a closing scope #23

@mitchellwrosen

Description

@mitchellwrosen

Some background: conceptually, there are three states a scope can be in:

  • "open", allowing new threads to be created within it
  • "closing", because we reached the end of a scoped block naturally, or because we got hit by an exception, and are going to proceed to killing all living children and waiting for them to terminate
  • "closed", because we're outside the callback in which the scope was valid, like any regular resource acquired in bracket-style

Clearly, we do want to disallow this bogus program, either with the type system (meh) or via a runtime exception:

scope <- Ki.scoped pure
Ki.fork scope whatever -- using a scope outside its callback

On to the implementation. Each scope keeps an int count of the threads that are about to start, with the sentinel value -1 meaning closed/closing. When we go to fork a thread, if this counter is not -1, we bump the counter, then spawn the thread, then decrement the counter. If the counter is -1, we throw a runtime exception (error "ki: scope closed").

This design makes closing a scope pretty simple: wait until there are 0 children about to start, then prevent new children from starting by writing -1, then kill all of the living children.

The problem (potentially) is that there's not actually a "closing" state that's distinguishable from "closed". So while we do prevent bogus programs like the above from spawning a thread in a closed scope, it seems wrong to punish code that attempts to spawn a thread into a closing scope in the same way.

Some options:

  1. (straw man) Make fork have type fork :: Scope -> IO a -> IO (Maybe (Thread a)), and return Nothing if we try to fork a thread in a closing scope. I don't think this API is good, but it's conceptually what we are after. The current behavior (to reiterate/summarize the above) is to throw a lazy runtime exception with error "ki: scope closing" rather than return Nothing.
  2. Make Thread a two-variant sum type, with a DidntActuallyMakeTheThreadBecauseTheScopeWasClosing variant. We'll have to decide what to do if you await such a thing.
  3. Tweak the teardown dance to actually continue to allow threads to be created in a closing scope, if only to throw a ScopeClosing exception to them soon after (which is how we kill children). This doesn't seem meaningfully different to (2).
  4. Something else, or nothing?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions