Skip to content

withAsyncTaskCleanupHandler calls its cleanup handler twice if body throws #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jakepetroules
Copy link
Contributor

If the body closure given to withAsyncTaskCleanupHandler throws, its cleanup handler is called twice: once in the catch block where body is called, and then again in the task group task once the Task.sleep throws due to cancellation, which swallows the error and then continues to call the handler as well.

This results in the teardown sequence being invoked twice.

Closes #80

@jakepetroules jakepetroules force-pushed the eng/PR-withAsyncTaskCleanupHandler branch from b443d52 to f9c5c22 Compare June 14, 2025 23:01
…hrows

If the body closure given to withAsyncTaskCleanupHandler throws, its cleanup handler is called twice: once in the catch block where body is called, and then again in the task group task once the Task.sleep throws due to cancellation, which swallows the error and then continues to call the handler as well. That results in the teardown sequence being invoked twice, as well as the teardown sequence being invoked for non-error cases.

This patch ensures the cleanup handler is invoked in failure cases only, and only once.

Closes #80
@jakepetroules jakepetroules force-pushed the eng/PR-withAsyncTaskCleanupHandler branch from f9c5c22 to 326b01c Compare June 14, 2025 23:05
return result
} catch {
await handler()
runCancellationHandlerPromise.fulfill(with: true)
throw error
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like the issue is simply we don't have to call handler() here and let the one above run. Why do we need Promise?

On a related note, we are really trying very very hard to move away from LockedState like types like this at least on Darwin (this is why we decided to move .standardOutput and .standardError out from the Execution type, so we don't have to hold a LockedState).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I cannot see any other way to ensure the cancellation handler runs only once and only on cancellation or failure. The group.addTask is ALWAYS going to run to completion no matter what happens, but we specifically don't want to run the teardown sequence if no cancellation was invoked or error was thrown. Otherwise you may incorrectly attempt to terminate a still-running process after the body closure runs.

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.

withAsyncTaskCleanupHandler calls its cleanup handler twice if body throws
2 participants