Skip to content

WIP: Ensure restarts cancel outstanding shake threads - #5021

Closed
crtschin wants to merge 3 commits into
haskell:masterfrom
crtschin:crtschin/thread-registry
Closed

WIP: Ensure restarts cancel outstanding shake threads#5021
crtschin wants to merge 3 commits into
haskell:masterfrom
crtschin:crtschin/thread-registry

Conversation

@crtschin

Copy link
Copy Markdown
Collaborator

Reattempt and fixes #4985

The previous approach used a guard added to every shake thread to check whether the rule evaluated an outdated value. That approach was incomplete as the core issue is that the thread shouldn't have survived to execute.

This PR takes a different approach tracks every spawned thread and does a cancellation when a restart occurs.

@fendor
fendor requested a review from soulomoon July 21, 2026 08:19
@soulomoon

soulomoon commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

The approach is to introduce a centralized management on the asyncs for the database if I understand correctly.
In this branch, we have both the decentralized AIO and the centrol cancelTrackedAsyncs. I think we should choose one instead. And we have to consider how it interacts with databaseValues.
Also trackedAsync might happens during cancelTrackedAsyncs, we might have to introduce a global lock.

Take a look at the work replacing the decentralized AIO entirely at #4927 @crtschin
Would you like to work with me on replacing the AIO entirly ?

@soulomoon soulomoon added performance Issues about memory consumption, responsiveness, etc. bindists Anything to do with binary distributions of HLS and removed bindists Anything to do with binary distributions of HLS labels Jul 21, 2026
@crtschin

Copy link
Copy Markdown
Collaborator Author

The approach is to introduce a centralized management on the asyncs for the database if I understand correctly. In this branch, we have both the decentralized AIO and the centrol cancelTrackedAsyncs.

This does introduce management of asyncs, which previously only existed in pure thunks. Though I wouldn't call AIO decentralized, AIO itself tracks the threads it spawned. The piece that this PR adds is a layer on top that ensures that any outstanding threads have all settled when doing a shake restart.

Also trackedAsync might happens during cancelTrackedAsyncs, we might have to introduce a global lock.

That should be fine as the function is recursive until it settles. So even if threads were spawned in the timespan before they died, cancelTrackedAsyncs just continues killing those children. Though this is the most contentious bit of the PR, I'm definitely open to improvements.

Take a look at the work replacing the decentralized AIO entirely at #4927

I tried looking at the PR, but there are a lot of moving bits, and is a bit hard to understand what was going on 😅. I want to avoid large-scale refactors if possible. I'll give it a second pass and try to post some probing questions.

Would you like to work with me on replacing the AIO entirly?

I don't know what you have in mind, so maybe!

@soulomoon

soulomoon commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

I tried looking at the PR, but there are a lot of moving bits,

Yes, it contains a lot of exprimental improvements.
We should focus on replacing the AIO parts first. The idea is simple, we do not use the AIO to track the threads and kills them, we only track them in database and use things like cancelTrackedAsyncs.

That should be fine as the function is recursive until it settles. So even if threads were spawned in the timespan before they died, cancelTrackedAsyncs just continues killing those children.

As we discuss in #4988, leaking AIO thread is the problem, so a lingering thread won't be killed by any parent. So it is still possible to escape from interleaving trackedAsync and cancelTrackedAsyncs .

cancelTrackedAsyncs Database{..} =
let go = do
as <- atomically $ IntMap.elems . snd <$> readTVar databaseAsyncs
unless (null as) $ do

@soulomoon soulomoon Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Perhaps we should guard the databaseAsyncs here, so no new async would be created in between readTVar databaseAsyncs and killing them. Then we can ensure no one leak ?

@soulomoon

soulomoon commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

I got an idea to fix the AIO leaking bug @crtschin, add a switch for AIO, when the thread that have the scope exit, we flip the switch that would halt any future registration. Then we won't have lingering threads that won't be killed by a session restart.

@crtschin

Copy link
Copy Markdown
Collaborator Author

add a switch for AIO, when the thread that have the scope exit, we flip the switch that would halt any future registration.

Trying to understand what you mean here. So adding a guard to trackedAsync so an atomic increment of the database step in STM and thread being spawned can't do so? I think that works and avoids the need for the loop in cancelTrackedAsyncs.

@soulomoon

soulomoon commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

I mean attacking the bug directly #4988 (comment), then we do not need the extra bookeeping of threads.

more presicely, close the possiblility to register a new async in asyncWithCleanUp once the corresponding cleanupAsync is invoked.
e.g. change AIO to newtype AIO a = AIO { unAIO :: ReaderT (IORef (Maybe [Async ()])) IO a }, cleanupAsync change the IORef content to Nothing, then the corresponding asyncWithCleanUp won't be able to register.

@crtschin

Copy link
Copy Markdown
Collaborator Author

I see, so avoiding threads from being spawned in the first place when a restart occurs, instead of trying to track/find and kill them.

@crtschin

crtschin commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Superceded by #5027

@crtschin crtschin closed this Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Issues about memory consumption, responsiveness, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Diagnostics don't always refresh on edits.

2 participants