Aligning token semantics across Web Locks, TC39 Mutex, and Concurrency Control proposals

Hi all,

I’d like to ask for some design guidance and cross-proposal alignment regarding “token” semantics for locks and concurrency primitives in JavaScript and the Web Platform.

This question sits at the intersection of:


Background: Web Locks proposal

On the Web Platform side, I’ve been working on a proposal to allow navigator.locks.request() to return a disposable lock object (a “token” representing ownership), instead of only supporting the callback-based form:

The basic idea is to allow patterns like:

// Hypothetical Web Locks token-returning API
const token = await navigator.locks.request("resource", { ifAvailable: true });

if (token) {
  await using token;
  // critical section
} else {
  // lock not available
}

In this model:

  • The returned object is a token representing lock ownership
  • Disposal ([Symbol.asyncDispose]) or release() ends that ownership
  • null indicates that the lock could not be acquired (e.g., ifAvailable: true)

Related TC39 designs: Mutex / Concurrency Control

On the TC39 side, several proposals already introduce similar “ownership token” concepts:

  • Structs / Mutex & Condition
    mutex.lock() returns something like a guard/token which represents ownership of the mutex.
  • Concurrency Control proposal
    introduces primitives for mutual exclusion, conditions, atomic execution, etc., with token-like capabilities.
  • Explicit Resource Management
    introduces using / await using and Symbol.asyncDispose for structured lifetime management.

All of these share a common flavor:

Acquiring a resource returns a token object;
the lifetime of that token represents ownership;
releasing/disposing the token relinquishes the resource.

This is also exactly the model I would like Web Locks to move toward, instead of the purely callback-based style.


Questions about token semantics & alignment

Given all of the above, I’m wondering about cross-spec alignment and would really appreciate guidance from folks involved in these proposals.

Concretely:

  1. Should we be aiming for a common conceptual “lock token” pattern?
    For example, a shape like:

    interface LockToken {
      release(): void | Promise<void>;
      [Symbol.asyncDispose](): Promise<void>;
    }
    

    Is it helpful to think of Web Locks tokens, Mutex tokens, and other concurrency tokens as instances of the same conceptual pattern? Or should they remain intentionally separate?

  2. How should release() vs @@asyncDispose be split?
    For lock tokens, should:

    • release() be sync and [Symbol.asyncDispose]() do the async waiting?
    • or should release() itself be async?
    • or is this entirely up to each API, with no expected consistency?
  3. What about failure / nullability?
    In Web Locks, ifAvailable: true can fail to acquire the lock. In that case, returning null works well with await using (per TC39 feedback in another issue).
    Is there any emerging guideline for “failed acquisition” in token-returning APIs that want to integrate with await using?

  4. Naming & conceptual expectations
    Is “token” the right word? Or should Web Locks prefer LockToken, Guard, ReleasableLock, etc.?
    Are there expectations (implicit or explicit) that TC39 proposals will use “token” with a particular semantic (e.g., non-transferable, single-owner, non-cloneable)?


Why I’m asking here (and not only in individual repos)

This question spans multiple in-progress designs:

  • Web Locks: W3C API, but strongly influenced by JS concurrency patterns
  • Mutex / Condition: low-level language primitives for mutual exclusion
  • Concurrency Control: higher-level concurrency constructs
  • Explicit Resource Management: lifetime / disposal semantics (using)

Rather than opening narrowly scoped issues in each repo, I wanted to first ask at the design level:

Is there an intended shared mental model for “lock tokens” across these proposals (language + Web APIs)?
Or should each proposal define its own “token” semantics independently?

Any thoughts, pointers, or prior discussions on this would be very appreciated.

Links again for convenience:

2 Likes

Thank you for opening this issue! I really appreciate you considering the similarities between these different proposals/features. I see two parts to your question: 1) should there be some kind of shared concept of a token, and 2) should there be similar ways to acquire/release such a thing.

You’re right that these tokens do have a commonality, but that commonality is entirely the fact that they implement the (existing) disposable interface(s) introduced as part of the explicit resource management proposal. In addition, they do have semantic similarities in that they represent that the bearer has some kind of special authority until the token is disposed. But functionally, they provide nothing beyond the disposable interface(s). So I don’t really see a need to give them a shared prototype or anything.

On the second part, they should definitely all take advantage of the disposable interface(s) for disposal. I think the most interesting question here is whether we can align on acquisition. Here’s some links to some discussions on that topic:

  1. `[Symbol.enter]()` Follow-on proposal · Issue #195 · tc39/proposal-explicit-resource-management · GitHub
  2. Should acquire accept an optional argument? · Issue #15 · tc39/proposal-concurrency-control · GitHub
  3. should we have a Symbol-based acquisition protocol · Issue #18 · tc39/proposal-concurrency-control · GitHub

Aligning on acquisition across these proposals/features could help them integrate better into built-in facilities.