Skip to content

Fix #23: throw ScopeClosing if trying to fork in a closed scope#25

Merged
mitchellwrosen merged 2 commits intomainfrom
fork-in-closing-scope-fix
Mar 22, 2023
Merged

Fix #23: throw ScopeClosing if trying to fork in a closed scope#25
mitchellwrosen merged 2 commits intomainfrom
fork-in-closing-scope-fix

Conversation

@mitchellwrosen
Copy link
Copy Markdown
Member

@mitchellwrosen mitchellwrosen commented Mar 21, 2023

Fixes #23

Comment on lines +94 to +95
-- Sentinel value: -1 means the scope is closing.
-- Sentinel value: -2 means the scope is closed.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you use sentinel values instead of a proper sum type?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to benchmark but my answer at the moment is efficiency. 🤷

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I think it'd be good to verify that! One sentinel value is liveable, but two starts getting very hairy! But then again, it's not my library 😆

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hehe, good point. Well, I'd like to think of it not as "my" library either! I'll add benching that to the todo list.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to know what is the overhead of using a sum type here. Out of curiosity I wrote this playground https://play.haskell.org/saved/r85araK7 , and it looks like doing n >= 0 produces more core than a pattern match. Though I can't tell if that's better or not :)

Comment on lines +219 to +223
assert (n >= -2) do
if
| n >= 0 -> writeTVar startingVar $! n + 1
| n == -1 -> throwSTM ScopeClosing
| otherwise -> throwSTM (ErrorCall "ki: scope closed")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you had a sum type this would be a much cleaner case imo.

@mitchellwrosen
Copy link
Copy Markdown
Member Author

mitchellwrosen commented Mar 22, 2023

Ok, @tstat and I looked into the cmm and asm a bit here. We discovered that listing the Open data constructor last resulted in the most efficient assembly (requiring one branch for the happy path, not two), with the following data type:

data ScopeStatus
  = Closing
  | Closed
  | Open {-# UNPACK #-} !Int

(It's kind of lame that GHC doesn't take the user's match order into account). All else seemed equal, as far as just the price of pattern matching and reassembling one of these things goes.

However, the sum type is less desirable when closing a scope because, while waiting for the number of starting threads to drop to 0, know that the constructor is Open, yet still need to pay for the pattern match (and additionally once per unlikely retry). We could work around that with an additional TVar to hold just the status enum independently of the starting thread count, but that's one extra TVar per Scope, and then awaitAll's performance would suffer slightly.

So, I think a fair compromise is to just introduce a pattern synonym for readability. It's not as safe as a proper sum type of course. If anyone wants to put together an benchmark that actually measures the (probably negligible) performance hit of a few extra comparison instructions here and there, please go ahead. Because the little integer state machine isn't that hard to grok (0+ to -1 to -2), and looks decently comprehensible with the pattern synonyms, I'm declaring this good enough for now.

@mitchellwrosen mitchellwrosen merged commit 2441ab5 into main Mar 22, 2023
@mitchellwrosen mitchellwrosen deleted the fork-in-closing-scope-fix branch March 22, 2023 18:31
-- Record the child as having started. Not allowed to retry.
atomically do
n <- readTVar startingVar
writeTVar startingVar $! n - 1 -- it's actually ok to go from e.g. -1 to -2 here (very unlikely)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment was inaccurate; we know n is 1 or more here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

4 participants