Skip to content
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

Add raceAndCancel helper function for structured concurrency #11

Closed

Conversation

ondrej-stanek-ozobot
Copy link
Contributor

Motivation:

The core concept of Structured concurrency [1] requires that "all spawned threads have completed before exit".
The classic implementation of Promise.race doesn't meet this requirement, as the returned promise finishes sooner than any of the other promises and the other promises are kept running until completion. Therefore, the Promise.race violates the basic principle of structured concurrency when used as a building block for combining parallel tasks.

We propose a simple wrapper for the Promise.race that ensures all remaining promises are automatically cancelled upon completion of the first one. We name the wrapper raceAndCancel, however, we are open to any other naming suggestions.

Example of use:

	const result = await raceAndCancel([
		someLongOperation(),
		CancellablePromise.delay(timeout)
	])

[1] https://en.wikipedia.org/wiki/Structured_concurrency

Motivation:

The core concept of Structured concurrency [1] requires that
"all spawned threads have completed before exit".
The classic implementation of `Promise.race` doesn't meet this
requirement, as the returned promise finishes sooner than any
of the other promises and the other promises are kept running
until completion. Therefore, the `Promise.race` violates the
basic principle of structured concurrency when used as a building
block for combining parallel tasks.

We propose a simple wrapper for the `Promise.race` that ensures
all remaining promises are automatically cancelled upon completion
of the first one. We name the wrapper `raceAndCancel`, however,
we are open to any other naming suggestions.

Example of use:

```
	const result = await raceAndCancel([
		someLongOperation(),
		CancellablePromise.delay(timeout)
	])
```

[1] https://en.wikipedia.org/wiki/Structured_concurrency
@ondrej-stanek-ozobot
Copy link
Contributor Author

This PR is a draft, if the concept of raceAndCancel is welcomed by the maintainers, we are happy to bring it to production quality, add tests, etc..

@ondrej-stanek-ozobot
Copy link
Contributor Author

This is an example how a timeout for a (cancellable) promise can be implemented using raceAndCancel:

/**
 * Guards the duration of a cancellable promise resolution with a timeout.
 * If promise resolves before the timeout, its result is returned.
 * Otherwise, the promise is cancelled and timeout exception is thrown.
 * 
 * @param promise promise to be guarded with timeout, has to implement the CancellablePromise contract
 * @param timeout_ms
 * @returns value of the resolved promise
 */
export function timeoutGuard<T extends CancellablePromise<unknown>>(
  promise: T,
  timeout_ms: number
) {

  // promise that never resolves, but throws a timeout error after `timeout_ms` lapsed
  const timeout = CancellablePromise.delay(timeout_ms).then(
    () => { throw new Error("Timeout") }
  )

  return raceAndCancel([ promise, timeout ])
}

@srmagura
Copy link
Owner

Hey @ondrej-stanek-ozobot, nice work. My only concern is, I am not sure how frequently this new function would be used, and I would prefer to keep the API surface area of this library relatively small.

Is there a reason why raceAndCancel needs to be part of the real-cancellable-promise source code? It seems like this function is straightforward to implement in userland.

Some ideas for how we could proceed:

  • Create a GitHub issue called `Proposal: Add raceAndCancel helper function ...". If multiple people express interest in the feature, we can consider adding it to real-cancellable-promise.
  • Add a section to the README that demonstrates this pattern, and explains when you should consider using it.

Thanks 😀

to correctly work with `Awaited<>`
@ondrej-stanek-ozobot
Copy link
Contributor Author

I totally understand, no problem.

We are using this (and other) helper functions quite extensively in our codebase, and the real-cancellable-promise became a cornerstone on which we build the structured concurrency patterns. Thanks for this great project!

We are thinking about publishing the helpers for structured concurrency. If the intention is to keep real-cancellable-promise relatively small, perhaps we could publish the helpers as a separate package that tightly depends on real-cancellable-promise?

Because the real-cancellable-promise is such a great fit, I wanted to ask if you have interest in structured concurrency and related software patterns?

@srmagura
Copy link
Owner

Publishing your structured concurrency helper functions as a new npm package is a cool idea! Let me know if you end up doing that.

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.

2 participants