profile
viewpoint

marjakh/ecma262 0

Status, process, and documents for ECMA-262

marjakh/jquery 0

jQuery JavaScript Library

marjakh/node 0

Node.js JavaScript runtime :sparkles::turtle::rocket::sparkles:

marjakh/notes 0

TC39 meeting notes

marjakh/proposal-promise-any 0

ECMAScript proposal: Promise.any

marjakh/SillyLittleCompiler 0

A silly little hobby project for learning more about compilers.

marjakh/test262 0

Official ECMAScript Conformance Test Suite

issue closedtc39/proposal-record-tuple

Use cases where we need fast equality comparison?

One of the discussion points of the proposal has been making equality comparison fast. But which use cases need it?

The thought process goes like this:

  1. We can achieve fast equality comparisons by internalizing.

  2. Maybe we shouldn't internalize upfront on creation, since not all records / tuples will be equality-compared.

  3. So let's internalize when we do the first equality comparison.

  4. But do we ever do equality comparisons where both objects are "old"?

E.g., if we have a big state object rec_a, and we compare it against rec_b (with structural sharing), to figure out whether the state has changed. Then we throw one of them away. Say we keep rec_b. Then later we compare it against a new one rec_c, etc.

By having internalized any of them, we can't make the equality comparison any faster. Either we traverse (*) the new one to internalize it eagerly, or we traverse (*) it to internalize it lazily, or we don't internalize at all but traverse (*) it when doing the equality comparison.

(*) We only need to traverse until we hit the structurally shared part of the record / tuple. This holds for all the options.

  1. Are there some other use cases which would benefit from fast equality? Alternatively, what are we missing?

(Kudos to @camillobruni and @LeszekSwirski for coming up with this thought process.)

closed time in 3 days

marjakh

issue commenttc39/proposal-record-tuple

Use cases where we need fast equality comparison?

@jridgewell thanks for pointing out the wrong assumption (of maximal structural sharing) (sorry for the delay)

marjakh

comment created time in 4 days

pull request commentv8/v8.dev

Code style nits

lgtm, thanks!

mathiasbynens

comment created time in a month

push eventv8/v8.dev

Marja Hölttä

commit sha 0322fcfc4f12aaf1efad1680fce686371eda2577

review

view details

push time in a month

PullRequestReviewEvent

Pull request review commentv8/v8.dev

Add a feature explainer about Atomics

+---+title: 'Atomics.wait, Atomics.notify, Atomics.waitAsync'+author: '[Marja Hölttä](https://twitter.com/marjakh), a non-blocking blogger'+avatars:+  - marja-holtta+date: 2020-09-14+tags:+  - ECMAScript+  - ES2020+description: 'Using Atomics to implement a mutex with synchronous and asynchronous modes.'

ok, changed that, but where is this text even visible?

marjakh

comment created time in a month

Pull request review commentv8/v8.dev

Add a feature explainer about Atomics

+---+title: 'Atomics.wait, Atomics.notify, Atomics.waitAsync'+author: '[Marja Hölttä](https://twitter.com/marjakh), a non-blocking blogger'+avatars:+  - marja-holtta+date: 2020-09-14+tags:+  - ECMAScript+  - ES2020+description: 'Using Atomics to implement a mutex with synchronous and asynchronous modes.'+tweet: ''+---++[`Atomics.wait`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait) and [`Atomics.notify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/notify) are low-level synchronization primitives useful for implementing mutexes and other means of synchronization. However, since `Atomics.wait` is blocking, it’s not possible to call it on the main thread (trying to do so will throw a TypeError).++Starting from Chrome 87, V8 supports a non-blocking version, [`Atomics.waitAsync`](https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md), which is also usable on the main thread.++In this post, we explain how to use these low-level APIs to implement a mutex that works both synchronously (for worker threads) and asynchronously (for worker threads or the main thread).++`Atomics.wait` and `Atomics.waitAsync` take the following parameters:++- `buffer`: an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- `index`: a valid index within the array+- `expectedValue`: a value we expect to be present in the memory location described by `(buffer, index)`+- `timeout`: a timeout in milliseconds (optional, defaults to `Infinity`)++The return value of `Atomics.wait` is a string. If the memory location doesn’t contain the expected value, `Atomics.wait` returns immediately with the value `"not-equal"`. Otherwise, the thread is blocked until another thread calls `Atomics.notify` with the same memory location or the timeout is reached. In the former case, `Atomics.wait` returns the value `"ok"`, in the latter case, `Atomics.wait` returns the value `"timed-out"`.++`Atomics.notify` takes the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- an index (valid within the array)+- how many waiters to notify (optional, defaults to `Infinity`)++It notifies the given amount of waiters, in FIFO order, waiting on the memory location described by `(buffer, index)`. If there are several pending `Atomics.wait` calls or `Atomics.waitAsync` calls related to the same location, they are all in the same FIFO queue.++In contrast to `Atomics.wait`, `Atomics.waitAsync` always returns immediately. The return value is one of the following:++- `{ async: false, value: "not-equal" }` (if the memory location didn’t contain the expected value)+- `{ async: false, value: "timed-out" }` (only for immediate timeout 0)+- `{ async: true, value: promise }`++The promise may later be resolved with a string value `"ok"` (if `Atomics.notify` was called with the same memory location) or `"timed-out"` (if the timeout was reached). The promise is never rejected.++The following example demonstrates the basic usage of `Atomics.waitAsync`:++```js+let sab = new SharedArrayBuffer(16);+let i32a = new Int32Array(sab);+let result = Atomics.waitAsync(i32a, 0, 0, 1000);+//                                   |  |  ^ timeout (opt)+//                                   |  ^ expected value+//                                   ^ index++if (result.value === "not-equal") {+  // The value in the SharedArrayBuffer was not the expected one.+} else {+  result.value instanceof Promise; // true+  result.value.then(+    (value) => {+      if (value == "ok") { /* notified */ }+      else { /* value is "timed-out" */ }+    });+}++// In this thread, or in another thread:+Atomics.notify(i32a, 0);+```++Next, we'll show how to implement a mutex which can be used both synchronously and asynchronously. Implementing the synchronous version of the mutex has been previously discussed e.g., [in this blog post](https://blogtitle.github.io/using-javascript-sharedarraybuffers-and-atomics/).++In the example, we don’t use the timeout parameter in `Atomics.wait` and `Atomics.waitAsync`. The parameter can be used for implementing condition variables with a timeout.++Our mutex class, `AsyncLock`, operates on a `SharedArrayBuffer` and implements the following methods:++- `lock` - blocks the thread until we're able to lock the mutex (usable only on a worker thread)+- `unlock` - unlocks the mutex (counterpart of `lock`)+- `executeLocked(callback)` - non-blocking lock, can be used by the main thread; schedules `callback` to be executed once we manage to get the lock++Let’s see how each of those can be implemented. The class definition includes constants and a constructor which takes the `SharedArrayBuffer` as a parameter.++```js+class AsyncLock {+    static INDEX = 0;+    static UNLOCKED = 0;+    static LOCKED = 1;++    constructor(sab) {+      this.sab = sab;+      this.i32a = new Int32Array(sab);+    }++    lock() {+      ...+    }++    unlock() {+      ...+    }++    executeLocked(f) {+      ...+    }+}+```++Here `i32a[0]` contains either the value `LOCKED` or `UNLOCKED`. It's also the wait location for `Atomics.wait`and `Atomics.waitAsync`. The `AsyncLock` class ensures the following invariants:++1. If `i32a[0] == LOCKED`, and a thread starts to wait (either via `Atomics.wait` or `Atomics.waitAsync`) on `i32a[0]`, it will eventually be notified.+2. After getting notified, the thread tries to grab the lock. If it gets the lock, it will notify again when releasing it.++## Sync lock and unlock++Next we show the blocking `lock` method which can only be called from a worker thread:++```js+lock() {+  while (true) {+    const oldValue = Atomics.compareExchange(self.i32a, AsyncLock.INDEX,+                       /* old value >>> */  AsyncLock.UNLOCKED,+                       /* new value >>> */  AsyncLock.LOCKED);+    if (oldValue == AsyncLock.UNLOCKED) {+      return;+    }+    Atomics.wait(this.i32a, AsyncLock.INDEX,+                 AsyncLock.LOCKED); // <<< expected value at start+  }+}+```++When a thread calls `lock()`, first it tries to get the lock by using `Atomics.compareExchange` to change the lock state from `UNLOCKED` to `LOCKED`. `Atomics.compareExchange` tries to do the state change atomically, and it returns the original value of the memory location. If the original value was `UNLOCKED`, we know the state change succeeded, and the thread acquired the lock. Nothing more is needed.++If `Atomics.compareExchange` doesn’t manage to change the lock state, another thread must be holding the lock. Thus, this thread tries `Atomics.wait` in order to wait for the other thread to release the lock. If the memory location still holds the expected value (in this case, `AsyncLock.LOCKED`), calling `Atomics.wait` will block the thread and the `Atomics.wait` call will return only when another thread calls `Atomics.notify`.++The `unlock` is method sets the lock to the `UNLOCKED` state and calls `Atomics.notify` to wake up one waiter which was waiting for the lock. The state change is always expected to succeed, since this thread is holding the lock, and nobody else should call `unlock()` meanwhile.++```js+unlock() {+  const oldValue = Atomics.compareExchange(this.i32a, AsyncLock.INDEX,+                      /* old value >>> */  AsyncLock.LOCKED,+                      /* new value >>> */  AsyncLock.UNLOCKED);+  if (oldValue != AsyncLock.LOCKED) {+    throw new Error("Tried to unlock while not holding the mutex");+  }+  Atomics.notify(this.i32a, AsyncLock.INDEX, 1);+}+```++The straightforward case goes as follows: the lock is free and thread T1 acquires it by changing the lock state with `Atomics.compareExchange`. Thread T2 tries to acquire the lock by calling `Atomics.compareExchange`, but it doesn’t succeed in changing the lock state. T2 then calls `Atomics.wait`, which blocks the thread. At some point T1 releases the lock and calls `Atomics.notify`. That makes the `Atomics.wait` call in T2 return `"ok"`, waking up T2. T2 then tries to acquire the lock again, and this time succeeds.++There are also 2 possible corner cases - these demonstrate the reason for `Atomics.wait` and `Atomics.waitAsync` checking for a specific value at the index:++- T1 is holding the lock and T2 tries to get it. First, T2 tries to change the lock state with `Atomics.compareExchange`, but doesn't succeed. But then T1 releases the lock before T2 manages to call `Atomics.wait`. When T2 calls `Atomics.wait`, it will return immediately with the return value `"not-equal"`. In that case, T2 will continue with the next loop iteration, trying to acquire the lock again.+- T1 is holding the lock and T2 is waiting for it with `Atomics.wait`. T1 releases the lock - T2 wakes up (the `Atomics.wait` call returns) and tries to do `Atomics.compareExchange` to acquire the lock, but another thread T3 was faster and got the lock already. So the call to `Atomics.compareExchange` fails to get the lock, and T2 calls `Atomics.wait` again, blocking until T3 releases the lock.++Because of the latter corner case, the mutex isn’t "fair". It’s possible that T2 has been waiting for the lock to be released, but T3 comes and gets it immediately. A more realistic lock implementation may use several states to differentiate between "locked" and "locked with contention".++## Async lock++The non-blocking `executeLocked` method is callable from the main thread, unlike the blocking `lock` method. It gets a callback function as its only parameter and schedules the callback to be executed once it has successfully acquired the lock.++```js+executeLocked(f) {+  const self = this;

Yea I think this is clearer.

marjakh

comment created time in a month

PullRequestReviewEvent

push eventv8/v8.dev

Marja Hölttä

commit sha 87cf6d73024023bb672b04abbea80f3c732c663e

Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com>

view details

push time in a month

push eventv8/v8.dev

Marja Hölttä

commit sha c481b01d10991202308e040ea86be16f7cff09c9

Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com>

view details

push time in a month

push eventv8/v8.dev

Marja Hölttä

commit sha 3b60bd0789610fa3e54bf7f67d60b2bf065c7480

Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com>

view details

push time in a month

push eventv8/v8.dev

Marja Hölttä

commit sha 4ffdecb7f57851d3692556cb391dca01a7482d64

Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com>

view details

push time in a month

issue commenttc39/proposal-record-tuple

Use cases where we need fast equality comparison?

But the same argument holds for rec_a[key] and rec_b[key]. Either they are the same (because of structural sharing), or one of them is "old" and another one is "new".

I'm assuming that whenever the new states are created, they're created with maximal structural sharing with the "current state", i.e., these records don't just get recreated out of nowhere, without structural sharing with the existing records.

This argument formulated in another way is: looks like we can achieve the same by just structural sharing + using normal JS objects in an immutable way.

marjakh

comment created time in a month

issue openedtc39/proposal-record-tuple

Use cases where we need fast equality comparison?

One of the discussion points of the proposal has been making equality comparison fast. But which use cases need it?

The thought process goes like this:

  1. We can achieve fast equality comparisons by internalizing.

  2. Maybe we shouldn't internalize upfront on creation, since not all records / tuples will be equality-compared.

  3. So let's internalize when we do the first equality comparison.

  4. But do we ever do equality comparisons where both objects are "old"?

E.g., if we have a big state object rec_a, and we compare it against rec_b (with structural sharing), to figure out whether the state has changed. Then we throw one of them away. Say we keep rec_b. Then later we compare it against a new one rec_c, etc.

By having internalized any of them, we can't make the equality comparison any faster. Either we traverse () the new one to internalize it eagerly, or we traverse () it to internalize it lazily, or we don't internalize at all but traverse (*) it when doing the equality comparison.

(*) We only need to traverse until we hit the structurally shared part of the record / tuple. This holds for all the options.

  1. Are there some other use cases which would benefit from fast equality? Alternatively, what are we missing?

(Kudos to @camillobruni and leszeks@chromium.org (somehow tagging doesn't work...) for coming up with this thought process.)

created time in a month

issue openedv8/v8.dev

Followed default instructions (incl npm start) -> the server doesn't print error messages, but doesn't handle requests either

To reproduce:

Take a clean checkout npm run build npm run watch

(In another terminal): npm start

Output from npm start:

v8.dev$ npm start

> @ start /home/marja/code/v8.dev2/v8.dev
> superstatic --port=9001


Superstatic started.
Visit http://localhost:9001 to view your app.

But nothing is served on that port (when navigating with a non-incognito browser, it'll show a cached version).

$ wget http://localhost:9001
--2020-09-14 09:02:54--  http://localhost:9001/
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:9001... failed: Connection refused.
Connecting to localhost (localhost)|127.0.0.1|:9001... connected.
HTTP request sent, awaiting response... ^C

Workaround: npx serve dist

v8.dev$ npx serve dist
npx: installed 78 in 4.18s

   ┌────────────────────────────────────────────────────┐
   │                                                    │
   │   Serving!                                         │
   │                                                    │
   │   - Local:            http://localhost:5000        │
   │   - On Your Network:  http://192.168.178.24:5000   │
   │                                                    │
   │   Copied local address to clipboard!               │
   │                                                    │
   └────────────────────────────────────────────────────┘
$ wget http://localhost:5000
--2020-09-14 09:04:26--  http://localhost:5000/
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:5000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6267 (6.1K) [text/html]
Saving to: ‘index.html’

index.html          100%[===================>]   6.12K  --.-KB/s    in 0s      

2020-09-14 09:04:26 (341 MB/s) - ‘index.html’ saved [6267/6267]

If it's a problem with "the server fails to get the port" or something, it should at least print an error message!

created time in a month

push eventv8/v8.dev

Marja Hölttä

commit sha 21c546cd7166073c4280180352aebe90a9a8fb06

Add a feature explaimer about Atomics Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> foo

view details

push time in a month

Pull request review commentv8/v8.dev

Add a feature explainer about Atomics

+---+title: 'Atomics.wait, Atomics.notify, Atomics.waitAsync'+author: '[Marja Hölttä](https://twitter.com/marjakh), a non-blocking blogger'+avatars:+  - marja-holtta+date: 2020-09-14+tags:+  - ECMAScript+  - ES2020+description: 'The JavaScript nullish coalescing operator enables safer default expressions.'+tweet: ''+---++# Atomics.wait, Atomics.notify, Atomics.waitAsync++[`Atomics.wait`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait) and [`Atomics.notify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/notify) are low-level synchronization primitives useful for implementing mutexes and other means of synchronization. However, since `Atomics.wait` is blocking, it’s not possible to call it on the main thread (trying to do so will throw a TypeError).++Starting from Chrome 87, V8 supports a non-blocking version, [Atomics.waitAsync](https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md), which is also usable on the main thread.++In this post, we explain how to use these low-level APIs to implement a mutex that works both synchronously (for worker threads) and asynchronously (for worker threads or the main thread).++`Atomics.wait` and `Atomics.waitAsync` take the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- a value we expect to be present in the memory location pointed to by buffer and index+- timeout in milliseconds (optional, defaults to `Infinity`)++The return value of `Atomics.wait` is a string. If the memory location doesn’t contain the expected value, `Atomics.wait` returns immediately with the value `"not-equal"`. Otherwise, the thread is blocked until another thread calls `Atomics.notify` with the same memory location or the timeout is reached. In the former case, `Atomics.wait` returns the value `"ok"`, in the latter case, `Atomics.wait` returns the value `"timed-out"`.++`Atomics.notify` takes the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- how many waiters to notify (optional, defaults to `Infinity`)++It notifies the given amout of waiters, in FIFO order, waiting on the memory location described by (buffer, index). If there are several pending `Atomics.wait` calls or `Atomics.waitAsync` calls related to the same location, they are all in the same FIFO queue.

done

marjakh

comment created time in a month

PullRequestReviewEvent

push eventv8/v8.dev

Marja Hölttä

commit sha 4ac4c83bafd6a809257e757f98a7dbeec14c1019

Add a feature explaimer about Atomics Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> foo

view details

push time in a month

Pull request review commentv8/v8.dev

Add a feature explainer about Atomics

+---+title: 'Atomics.wait, Atomics.notify, Atomics.waitAsync'+author: '[Marja Hölttä](https://twitter.com/marjakh), a non-blocking blogger'+avatars:+  - marja-holtta+date: 2020-09-14+tags:+  - ECMAScript+  - ES2020+description: 'The JavaScript nullish coalescing operator enables safer default expressions.'+tweet: ''+---++# Atomics.wait, Atomics.notify, Atomics.waitAsync++[`Atomics.wait`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait) and [`Atomics.notify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/notify) are low-level synchronization primitives useful for implementing mutexes and other means of synchronization. However, since `Atomics.wait` is blocking, it’s not possible to call it on the main thread (trying to do so will throw a TypeError).++Starting from Chrome 87, V8 supports a non-blocking version, [Atomics.waitAsync](https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md), which is also usable on the main thread.++In this post, we explain how to use these low-level APIs to implement a mutex that works both synchronously (for worker threads) and asynchronously (for worker threads or the main thread).++`Atomics.wait` and `Atomics.waitAsync` take the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- a value we expect to be present in the memory location pointed to by buffer and index+- timeout in milliseconds (optional, defaults to `Infinity`)++The return value of `Atomics.wait` is a string. If the memory location doesn’t contain the expected value, `Atomics.wait` returns immediately with the value `"not-equal"`. Otherwise, the thread is blocked until another thread calls `Atomics.notify` with the same memory location or the timeout is reached. In the former case, `Atomics.wait` returns the value `"ok"`, in the latter case, `Atomics.wait` returns the value `"timed-out"`.++`Atomics.notify` takes the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- how many waiters to notify (optional, defaults to `Infinity`)++It notifies the given amout of waiters, in FIFO order, waiting on the memory location described by (buffer, index). If there are several pending `Atomics.wait` calls or `Atomics.waitAsync` calls related to the same location, they are all in the same FIFO queue.++In contrast to `Atomics.wait`, `Atomics.waitAsync` always returns immediately. The return value is one of the following:++- `{ async: false, value: "not-equal" }` (if the memory location didn’t contain the expected value)+- `{ async: false, value: "timed-out" }` (only for immediate timeout 0)+- `{ async: true, value: promise }`++The promise may later be resolved with a string value `"ok"` (if `Atomics.notify` was called with the same memory location) or `"timed-out"` (if the timeout was reached). The promise is never rejected.++The following example demonstrates the basic usage of `Atomics.waitAsync`:++```js+let sab = new SharedArrayBuffer(16);+let i32a = new Int32Array(sab);+let result = Atomics.waitAsync(i32a, 0, 0, 1000);+//                                         ^ timeout (opt)

done (in a separate commit)

marjakh

comment created time in a month

PullRequestReviewEvent

Pull request review commentv8/v8.dev

Add a feature explainer about Atomics

+---+title: 'Atomics.wait, Atomics.notify, Atomics.waitAsync'+author: '[Marja Hölttä](https://twitter.com/marjakh), a non-blocking blogger'+avatars:+  - marja-holtta+date: 2020-09-14+tags:+  - ECMAScript+  - ES2020+description: 'The JavaScript nullish coalescing operator enables safer default expressions.'+tweet: ''+---++# Atomics.wait, Atomics.notify, Atomics.waitAsync++[`Atomics.wait`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait) and [`Atomics.notify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/notify) are low-level synchronization primitives useful for implementing mutexes and other means of synchronization. However, since `Atomics.wait` is blocking, it’s not possible to call it on the main thread (trying to do so will throw a TypeError).++Starting from Chrome 87, V8 supports a non-blocking version, [Atomics.waitAsync](https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md), which is also usable on the main thread.++In this post, we explain how to use these low-level APIs to implement a mutex that works both synchronously (for worker threads) and asynchronously (for worker threads or the main thread).++`Atomics.wait` and `Atomics.waitAsync` take the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- a value we expect to be present in the memory location pointed to by buffer and index+- timeout in milliseconds (optional, defaults to `Infinity`)++The return value of `Atomics.wait` is a string. If the memory location doesn’t contain the expected value, `Atomics.wait` returns immediately with the value `"not-equal"`. Otherwise, the thread is blocked until another thread calls `Atomics.notify` with the same memory location or the timeout is reached. In the former case, `Atomics.wait` returns the value `"ok"`, in the latter case, `Atomics.wait` returns the value `"timed-out"`.++`Atomics.notify` takes the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- how many waiters to notify (optional, defaults to `Infinity`)++It notifies the given amout of waiters, in FIFO order, waiting on the memory location described by (buffer, index). If there are several pending `Atomics.wait` calls or `Atomics.waitAsync` calls related to the same location, they are all in the same FIFO queue.++In contrast to `Atomics.wait`, `Atomics.waitAsync` always returns immediately. The return value is one of the following:++- `{ async: false, value: "not-equal" }` (if the memory location didn’t contain the expected value)+- `{ async: false, value: "timed-out" }` (only for immediate timeout 0)+- `{ async: true, value: promise }`++The promise may later be resolved with a string value `"ok"` (if `Atomics.notify` was called with the same memory location) or `"timed-out"` (if the timeout was reached). The promise is never rejected.++The following example demonstrates the basic usage of `Atomics.waitAsync`:++```js+let sab = new SharedArrayBuffer(16);+let i32a = new Int32Array(sab);+let result = Atomics.waitAsync(i32a, 0, 0, 1000);+//                                         ^ timeout (opt)+//                                      ^ expected value

done (in a separate commit)

marjakh

comment created time in a month

PullRequestReviewEvent

Pull request review commentv8/v8.dev

Add a feature explainer about Atomics

+---+title: 'Atomics.wait, Atomics.notify, Atomics.waitAsync'+author: '[Marja Hölttä](https://twitter.com/marjakh), a non-blocking blogger'+avatars:+  - marja-holtta+date: 2020-09-14+tags:+  - ECMAScript+  - ES2020+description: 'The JavaScript nullish coalescing operator enables safer default expressions.'+tweet: ''+---++# Atomics.wait, Atomics.notify, Atomics.waitAsync++[`Atomics.wait`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait) and [`Atomics.notify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/notify) are low-level synchronization primitives useful for implementing mutexes and other means of synchronization. However, since `Atomics.wait` is blocking, it’s not possible to call it on the main thread (trying to do so will throw a TypeError).++Starting from Chrome 87, V8 supports a non-blocking version, [Atomics.waitAsync](https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md), which is also usable on the main thread.++In this post, we explain how to use these low-level APIs to implement a mutex that works both synchronously (for worker threads) and asynchronously (for worker threads or the main thread).++`Atomics.wait` and `Atomics.waitAsync` take the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- a value we expect to be present in the memory location pointed to by buffer and index+- timeout in milliseconds (optional, defaults to `Infinity`)++The return value of `Atomics.wait` is a string. If the memory location doesn’t contain the expected value, `Atomics.wait` returns immediately with the value `"not-equal"`. Otherwise, the thread is blocked until another thread calls `Atomics.notify` with the same memory location or the timeout is reached. In the former case, `Atomics.wait` returns the value `"ok"`, in the latter case, `Atomics.wait` returns the value `"timed-out"`.++`Atomics.notify` takes the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- how many waiters to notify (optional, defaults to `Infinity`)++It notifies the given amout of waiters, in FIFO order, waiting on the memory location described by (buffer, index). If there are several pending `Atomics.wait` calls or `Atomics.waitAsync` calls related to the same location, they are all in the same FIFO queue.++In contrast to `Atomics.wait`, `Atomics.waitAsync` always returns immediately. The return value is one of the following:++- `{ async: false, value: "not-equal" }` (if the memory location didn’t contain the expected value)+- `{ async: false, value: "timed-out" }` (only for immediate timeout 0)+- `{ async: true, value: promise }`++The promise may later be resolved with a string value `"ok"` (if `Atomics.notify` was called with the same memory location) or `"timed-out"` (if the timeout was reached). The promise is never rejected.++The following example demonstrates the basic usage of `Atomics.waitAsync`:++```js+let sab = new SharedArrayBuffer(16);+let i32a = new Int32Array(sab);+let result = Atomics.waitAsync(i32a, 0, 0, 1000);+//                                         ^ timeout (opt)+//                                      ^ expected value+//                                   ^ index++if (result.value === "not-equal") {+  // The value in the SharedArrayBuffer was not the expected one.+} else {+  result.value instanceof Promise; // true+  result.value.then(+    (value) => { if (value == "ok") { /* notified */ }+                 else { /* value is 'timed-out' */ }+               });+}++// In this thread, or another thread:+Atomics.notify(i32a, 0);+```++Next, we'll show how to implement a mutex which can be used both synchronously and asynchronously. Implementing the synchronous version of the mutex has been previously discussed e.g., [in this blog post](https://blogtitle.github.io/using-javascript-sharedarraybuffers-and-atomics/).++In the example, we don’t use the timeout parameter in `Atomics.wait` and `Atomics.waitAsync`. The parameter can be used for implementing condition variables with a timeout.++Our mutex class, `AsyncLock`, operates on a `SharedArrayBuffer` and implements the following methods:++- `lock` - blocks the thread until we're able to lock the mutex (usable only on a worker thread)+- `unlock` - unlocks the mutex (counterpart of `lock`)+- `executeLocked(callback)` - non-blocking lock, can be used by the main thread; schedules `callback` to be executed once we manage to get the lock++Let’s see how each of those can be implemented. The class definition includes constants and a trivial constructor which takes the `SharedArrayBuffer` as a parameter.++```js+class AsyncLock {+    static INDEX = 0;+    static UNLOCKED = 0;+    static LOCKED = 1;++    constructor(sab) {+      this.sab = sab;+      this.i32a = new Int32Array(sab);+    }++    lock() {+      ...+    }++    unlock() {+      ...+    }++    executeLocked(f) {+      ...+    }+}+```++Here `i32a[0]` contains either the value `LOCKED` or `UNLOCKED`. It's also the wait location for `Atomics.wait`and `Atomics.waitAsync`. The `AsyncLock` class ensures the following invariants:++1. If `i32a[0] == LOCKED`, and a thread starts to wait (either via `Atomics.wait` or `Atomics.waitAsync`) on `i32a[0]`, it will eventually be notified.+2. After getting notified, the thread tries to grab the lock. If it gets the lock, it will notify again when releasing it.++## Sync lock and unlock++Next comes the blocking `lock` method, which can only be called from a worker thread:++```js+lock() {+  while (true) {+    const oldValue = Atomics.compareExchange(self.i32a, AsyncLock.INDEX,+                       /* old value >>> */  AsyncLock.UNLOCKED,+                       /* new value >>> */  AsyncLock.LOCKED);+    if (oldValue == AsyncLock.UNLOCKED) {+      return;+    }+    Atomics.wait(this.i32a, AsyncLock.INDEX,+                 AsyncLock.LOCKED /* expected value at start */);+  }+}+```++When a thread calls `lock()`, first it tries to get the lock by using `Atomics.compareExchange` to change the lock state from `UNLOCKED` to `LOCKED`. `Atomics.compareExchange` tries to do the state change atomically, and it returns the original value of the memory location. If the original value was `UNLOCKED`, we know the state change succeeded, and the thread acquired the lock. Nothing more is needed.++If `Atomics.compareExchange` doesn’t manage to change the lock state, another thread must be holding the lock. Thus, this thread tries `Atomics.wait` in order to wait for the other thread to release the lock. If the memory location still holds the expected value (in this case, `AsyncLock.LOCKED`), calling `Atomics.wait` will block the thread and the `Atomics.wait` call will return only when another thread calls `Atomics.notify`.++The `unlock` is method sets the lock to the `UNLOCKED` state and calls `Atomics.notify` to wake up one waiter which was waiting for the lock. The state change is always expected to succeed, since this thread is holding the lock, and nobody else should call `unlock()` meanwhile.++```js+unlock() {+  const oldValue = Atomics.compareExchange(this.i32a, AsyncLock.INDEX,+                      /* old value >>> */  AsyncLock.LOCKED,+                      /* new value >>> */  AsyncLock.UNLOCKED);+  if (oldValue != AsyncLock.LOCKED) {+    throw new Error("Tried to unlock while not holding the mutex");+  }+  Atomics.notify(this.i32a, AsyncLock.INDEX, 1);+}+```++The straightforward case goes as follows: the lock is free and thread T1 acquires it by changing the lock state with `Atomics.compareExchange`. Thread T2 tries to acquire the lock by calling `Atomics.compareExchange`, but it doesn’t succeed in changing the lock state. T2 then calls `Atomics.wait`, which blocks the thread. At some point T1 releases the lock and calls `Atomics.notify`. That makes the `Atomics.wait` call in T2 return `"ok"`, waking up T2. T2 then tries to acquire the lock again, and this time succeeds.++There are also 2 possible corner cases - these demonstrate the reason for `Atomics.wait` and `Atomics.waitAsync` checking for a specific value at the index:++- T1 is holding the lock and T2 tries to get it. First, T2 tries to change the lock state with `Atomics.compareExchange`, but doesn't succeed. But then T1 releases the lock before T2 manages to call `Atomics.wait`. When T2 calls `Atomics.wait`, it will return immediately with the return value `"not-equal"`. In that case, T2 will continue with the next loop iteration, trying to acquire the lock again.+- T1 is holding the lock and T2 is waiting for it with `Atomics.wait`. T1 releases the lock - T2 wakes up (the `Atomics.wait` call returns) and tries to do `Atomics.compareExchange` to acquire the lock, but another thread T3 was faster and got the lock already. So the call to `Atomics.compareExchange` fails to get the lock, and T2 calls `Atomics.wait` again, blocking until T3 releases the lock.++Because of the latter corner case, the mutex isn’t "fair". It’s possible that T2 has been waiting for the lock to be released, but T3 comes and gets it immediately. A more realistic lock implementation may use several states to differentiate between "locked" and "locked with contention".++## Async lock++The non-blocking `executeLocked` method is callable from the main thread, unlike the blocking `lock` method. It gets a callback function as its only parameter and schedules the callback to be executed once it has successfully acquired the lock.++```js+executeLocked(f) {+  const self = this;++  function tryGetLock() {+    while (true) {+      const oldValue = Atomics.compareExchange(self.i32a, AsyncLock.INDEX,+                          /* old value >>> */  AsyncLock.UNLOCKED,+                          /* new value >>> */  AsyncLock.LOCKED);+      if (oldValue == AsyncLock.UNLOCKED) {+        f();+        self.unlock();+        return;+      }+      const result = Atomics.waitAsync(self.i32a, AsyncLock.INDEX,+                                       AsyncLock.LOCKED+                                   /*  ^ expected value at start */);+      if (result.value == "not-equal") {+        continue;+      }+      result.value.then(tryGetLock);

reworked the example into an async func

marjakh

comment created time in a month

PullRequestReviewEvent

push eventv8/v8.dev

Marja Hölttä

commit sha 54a7ae1c56d712b39357e8e297f24e19b2071087

Add a feature explaimer about Atomics Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> foo

view details

push time in a month

Pull request review commentv8/v8.dev

Add a feature explainer about Atomics

+---+title: 'Atomics.wait, Atomics.notify, Atomics.waitAsync'+author: '[Marja Hölttä](https://twitter.com/marjakh), a non-blocking blogger'+avatars:+  - marja-holtta+date: 2020-09-14+tags:+  - ECMAScript+  - ES2020+description: 'The JavaScript nullish coalescing operator enables safer default expressions.'+tweet: ''+---++# Atomics.wait, Atomics.notify, Atomics.waitAsync++[`Atomics.wait`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait) and [`Atomics.notify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/notify) are low-level synchronization primitives useful for implementing mutexes and other means of synchronization. However, since `Atomics.wait` is blocking, it’s not possible to call it on the main thread (trying to do so will throw a TypeError).++Starting from Chrome 87, V8 supports a non-blocking version, [Atomics.waitAsync](https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md), which is also usable on the main thread.++In this post, we explain how to use these low-level APIs to implement a mutex that works both synchronously (for worker threads) and asynchronously (for worker threads or the main thread).++`Atomics.wait` and `Atomics.waitAsync` take the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- a value we expect to be present in the memory location pointed to by buffer and index+- timeout in milliseconds (optional, defaults to `Infinity`)++The return value of `Atomics.wait` is a string. If the memory location doesn’t contain the expected value, `Atomics.wait` returns immediately with the value `"not-equal"`. Otherwise, the thread is blocked until another thread calls `Atomics.notify` with the same memory location or the timeout is reached. In the former case, `Atomics.wait` returns the value `"ok"`, in the latter case, `Atomics.wait` returns the value `"timed-out"`.++`Atomics.notify` takes the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- how many waiters to notify (optional, defaults to `Infinity`)++It notifies the given amout of waiters, in FIFO order, waiting on the memory location described by (buffer, index). If there are several pending `Atomics.wait` calls or `Atomics.waitAsync` calls related to the same location, they are all in the same FIFO queue.++In contrast to `Atomics.wait`, `Atomics.waitAsync` always returns immediately. The return value is one of the following:++- `{ async: false, value: "not-equal" }` (if the memory location didn’t contain the expected value)+- `{ async: false, value: "timed-out" }` (only for immediate timeout 0)+- `{ async: true, value: promise }`++The promise may later be resolved with a string value `"ok"` (if `Atomics.notify` was called with the same memory location) or `"timed-out"` (if the timeout was reached). The promise is never rejected.++The following example demonstrates the basic usage of `Atomics.waitAsync`:++```js+let sab = new SharedArrayBuffer(16);+let i32a = new Int32Array(sab);+let result = Atomics.waitAsync(i32a, 0, 0, 1000);+//                                         ^ timeout (opt)+//                                      ^ expected value+//                                   ^ index++if (result.value === "not-equal") {+  // The value in the SharedArrayBuffer was not the expected one.+} else {+  result.value instanceof Promise; // true+  result.value.then(+    (value) => { if (value == "ok") { /* notified */ }+                 else { /* value is 'timed-out' */ }+               });+}++// In this thread, or another thread:+Atomics.notify(i32a, 0);+```++Next, we'll show how to implement a mutex which can be used both synchronously and asynchronously. Implementing the synchronous version of the mutex has been previously discussed e.g., [in this blog post](https://blogtitle.github.io/using-javascript-sharedarraybuffers-and-atomics/).++In the example, we don’t use the timeout parameter in `Atomics.wait` and `Atomics.waitAsync`. The parameter can be used for implementing condition variables with a timeout.++Our mutex class, `AsyncLock`, operates on a `SharedArrayBuffer` and implements the following methods:++- `lock` - blocks the thread until we're able to lock the mutex (usable only on a worker thread)+- `unlock` - unlocks the mutex (counterpart of `lock`)+- `executeLocked(callback)` - non-blocking lock, can be used by the main thread; schedules `callback` to be executed once we manage to get the lock++Let’s see how each of those can be implemented. The class definition includes constants and a trivial constructor which takes the `SharedArrayBuffer` as a parameter.++```js+class AsyncLock {+    static INDEX = 0;+    static UNLOCKED = 0;+    static LOCKED = 1;++    constructor(sab) {+      this.sab = sab;+      this.i32a = new Int32Array(sab);+    }++    lock() {+      ...+    }++    unlock() {+      ...+    }++    executeLocked(f) {+      ...+    }+}+```++Here `i32a[0]` contains either the value `LOCKED` or `UNLOCKED`. It's also the wait location for `Atomics.wait`and `Atomics.waitAsync`. The `AsyncLock` class ensures the following invariants:++1. If `i32a[0] == LOCKED`, and a thread starts to wait (either via `Atomics.wait` or `Atomics.waitAsync`) on `i32a[0]`, it will eventually be notified.+2. After getting notified, the thread tries to grab the lock. If it gets the lock, it will notify again when releasing it.++## Sync lock and unlock++Next comes the blocking `lock` method, which can only be called from a worker thread:++```js+lock() {+  while (true) {+    const oldValue = Atomics.compareExchange(self.i32a, AsyncLock.INDEX,+                       /* old value >>> */  AsyncLock.UNLOCKED,+                       /* new value >>> */  AsyncLock.LOCKED);+    if (oldValue == AsyncLock.UNLOCKED) {+      return;+    }+    Atomics.wait(this.i32a, AsyncLock.INDEX,+                 AsyncLock.LOCKED /* expected value at start */);+  }+}+```++When a thread calls `lock()`, first it tries to get the lock by using `Atomics.compareExchange` to change the lock state from `UNLOCKED` to `LOCKED`. `Atomics.compareExchange` tries to do the state change atomically, and it returns the original value of the memory location. If the original value was `UNLOCKED`, we know the state change succeeded, and the thread acquired the lock. Nothing more is needed.++If `Atomics.compareExchange` doesn’t manage to change the lock state, another thread must be holding the lock. Thus, this thread tries `Atomics.wait` in order to wait for the other thread to release the lock. If the memory location still holds the expected value (in this case, `AsyncLock.LOCKED`), calling `Atomics.wait` will block the thread and the `Atomics.wait` call will return only when another thread calls `Atomics.notify`.++The `unlock` is method sets the lock to the `UNLOCKED` state and calls `Atomics.notify` to wake up one waiter which was waiting for the lock. The state change is always expected to succeed, since this thread is holding the lock, and nobody else should call `unlock()` meanwhile.++```js+unlock() {+  const oldValue = Atomics.compareExchange(this.i32a, AsyncLock.INDEX,+                      /* old value >>> */  AsyncLock.LOCKED,+                      /* new value >>> */  AsyncLock.UNLOCKED);+  if (oldValue != AsyncLock.LOCKED) {+    throw new Error("Tried to unlock while not holding the mutex");+  }+  Atomics.notify(this.i32a, AsyncLock.INDEX, 1);+}+```++The straightforward case goes as follows: the lock is free and thread T1 acquires it by changing the lock state with `Atomics.compareExchange`. Thread T2 tries to acquire the lock by calling `Atomics.compareExchange`, but it doesn’t succeed in changing the lock state. T2 then calls `Atomics.wait`, which blocks the thread. At some point T1 releases the lock and calls `Atomics.notify`. That makes the `Atomics.wait` call in T2 return `"ok"`, waking up T2. T2 then tries to acquire the lock again, and this time succeeds.++There are also 2 possible corner cases - these demonstrate the reason for `Atomics.wait` and `Atomics.waitAsync` checking for a specific value at the index:++- T1 is holding the lock and T2 tries to get it. First, T2 tries to change the lock state with `Atomics.compareExchange`, but doesn't succeed. But then T1 releases the lock before T2 manages to call `Atomics.wait`. When T2 calls `Atomics.wait`, it will return immediately with the return value `"not-equal"`. In that case, T2 will continue with the next loop iteration, trying to acquire the lock again.+- T1 is holding the lock and T2 is waiting for it with `Atomics.wait`. T1 releases the lock - T2 wakes up (the `Atomics.wait` call returns) and tries to do `Atomics.compareExchange` to acquire the lock, but another thread T3 was faster and got the lock already. So the call to `Atomics.compareExchange` fails to get the lock, and T2 calls `Atomics.wait` again, blocking until T3 releases the lock.++Because of the latter corner case, the mutex isn’t "fair". It’s possible that T2 has been waiting for the lock to be released, but T3 comes and gets it immediately. A more realistic lock implementation may use several states to differentiate between "locked" and "locked with contention".++## Async lock++The non-blocking `executeLocked` method is callable from the main thread, unlike the blocking `lock` method. It gets a callback function as its only parameter and schedules the callback to be executed once it has successfully acquired the lock.++```js+executeLocked(f) {+  const self = this;++  function tryGetLock() {+    while (true) {+      const oldValue = Atomics.compareExchange(self.i32a, AsyncLock.INDEX,+                          /* old value >>> */  AsyncLock.UNLOCKED,+                          /* new value >>> */  AsyncLock.LOCKED);+      if (oldValue == AsyncLock.UNLOCKED) {+        f();+        self.unlock();+        return;+      }+      const result = Atomics.waitAsync(self.i32a, AsyncLock.INDEX,+                                       AsyncLock.LOCKED+                                   /*  ^ expected value at start */);+      if (result.value == "not-equal") {+        continue;+      }+      result.value.then(tryGetLock);+      return;+    }+  }++  tryGetLock();+}+```++The inner function `tryGetLock` tries to first get the lock with `Atomics.compareExchange`, as before. If that successfully changes the lock state, it can execute the callback, unlock the lock, and return.++If `Atomics.compareExchange` fails to get the lock, we need to try again when the lock is probably free. We can’t block and wait for the lock to become free - instead, we schedule the new try using `Atomics.waitAsync` and the Promise it returns.++If we successfully started `Atomics.waitAsync`, the returned Promise will resolve when the lock-holding thread does `Atomics.notify`. Then the thread that was waiting for the lock will try to get the lock again, like before.++The same corner cases (the lock getting released between the `Atomics.compareExchange` call and the `Atomics.waitAsync` call, as well as the lock getting acquired again between the Promise resolving and the `Atomics.compareExchange` call) are possible in the asynchronous version too, so the code has to handle them in a robust way.++## Conclusion

done

marjakh

comment created time in a month

PullRequestReviewEvent

push eventv8/v8.dev

Marja Hölttä

commit sha 68dc41c45ed99bf7eeab39c9f2fd337dafcf858a

Add a feature explaimer about Atomics Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> foo

view details

push time in a month

push eventv8/v8.dev

Marja Hölttä

commit sha c4df9b5e01063d14f4f9d79824b28bb9127bd8f6

Add a feature explaimer about Atomics Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> foo

view details

push time in a month

Pull request review commentv8/v8.dev

Add a feature explainer about Atomics

+---+title: 'Atomics.wait, Atomics.notify, Atomics.waitAsync'+author: '[Marja Hölttä](https://twitter.com/marjakh), a non-blocking blogger'+avatars:+  - marja-holtta+date: 2020-09-14+tags:+  - ECMAScript+  - ES2020+description: 'The JavaScript nullish coalescing operator enables safer default expressions.'+tweet: ''+---++# Atomics.wait, Atomics.notify, Atomics.waitAsync++[`Atomics.wait`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait) and [`Atomics.notify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/notify) are low-level synchronization primitives useful for implementing mutexes and other means of synchronization. However, since `Atomics.wait` is blocking, it’s not possible to call it on the main thread (trying to do so will throw a TypeError).++Starting from Chrome 87, V8 supports a non-blocking version, [Atomics.waitAsync](https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md), which is also usable on the main thread.++In this post, we explain how to use these low-level APIs to implement a mutex that works both synchronously (for worker threads) and asynchronously (for worker threads or the main thread).++`Atomics.wait` and `Atomics.waitAsync` take the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- a value we expect to be present in the memory location pointed to by buffer and index+- timeout in milliseconds (optional, defaults to `Infinity`)++The return value of `Atomics.wait` is a string. If the memory location doesn’t contain the expected value, `Atomics.wait` returns immediately with the value `"not-equal"`. Otherwise, the thread is blocked until another thread calls `Atomics.notify` with the same memory location or the timeout is reached. In the former case, `Atomics.wait` returns the value `"ok"`, in the latter case, `Atomics.wait` returns the value `"timed-out"`.++`Atomics.notify` takes the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- how many waiters to notify (optional, defaults to `Infinity`)++It notifies the given amout of waiters, in FIFO order, waiting on the memory location described by (buffer, index). If there are several pending `Atomics.wait` calls or `Atomics.waitAsync` calls related to the same location, they are all in the same FIFO queue.++In contrast to `Atomics.wait`, `Atomics.waitAsync` always returns immediately. The return value is one of the following:++- `{ async: false, value: "not-equal" }` (if the memory location didn’t contain the expected value)+- `{ async: false, value: "timed-out" }` (only for immediate timeout 0)+- `{ async: true, value: promise }`++The promise may later be resolved with a string value `"ok"` (if `Atomics.notify` was called with the same memory location) or `"timed-out"` (if the timeout was reached). The promise is never rejected.++The following example demonstrates the basic usage of `Atomics.waitAsync`:++```js+let sab = new SharedArrayBuffer(16);+let i32a = new Int32Array(sab);+let result = Atomics.waitAsync(i32a, 0, 0, 1000);+//                                         ^ timeout (opt)+//                                      ^ expected value+//                                   ^ index++if (result.value === "not-equal") {+  // The value in the SharedArrayBuffer was not the expected one.+} else {+  result.value instanceof Promise; // true+  result.value.then(+    (value) => { if (value == "ok") { /* notified */ }+                 else { /* value is 'timed-out' */ }+               });+}++// In this thread, or another thread:

(discarding, afaik incorrect)

marjakh

comment created time in a month

PullRequestReviewEvent

Pull request review commentv8/v8.dev

Add a feature explainer about Atomics

+---+title: 'Atomics.wait, Atomics.notify, Atomics.waitAsync'+author: '[Marja Hölttä](https://twitter.com/marjakh), a non-blocking blogger'+avatars:+  - marja-holtta+date: 2020-09-14+tags:+  - ECMAScript+  - ES2020+description: 'The JavaScript nullish coalescing operator enables safer default expressions.'+tweet: ''+---++# Atomics.wait, Atomics.notify, Atomics.waitAsync++[`Atomics.wait`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait) and [`Atomics.notify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/notify) are low-level synchronization primitives useful for implementing mutexes and other means of synchronization. However, since `Atomics.wait` is blocking, it’s not possible to call it on the main thread (trying to do so will throw a TypeError).++Starting from Chrome 87, V8 supports a non-blocking version, [Atomics.waitAsync](https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md), which is also usable on the main thread.++In this post, we explain how to use these low-level APIs to implement a mutex that works both synchronously (for worker threads) and asynchronously (for worker threads or the main thread).++`Atomics.wait` and `Atomics.waitAsync` take the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- a value we expect to be present in the memory location pointed to by buffer and index+- timeout in milliseconds (optional, defaults to `Infinity`)++The return value of `Atomics.wait` is a string. If the memory location doesn’t contain the expected value, `Atomics.wait` returns immediately with the value `"not-equal"`. Otherwise, the thread is blocked until another thread calls `Atomics.notify` with the same memory location or the timeout is reached. In the former case, `Atomics.wait` returns the value `"ok"`, in the latter case, `Atomics.wait` returns the value `"timed-out"`.++`Atomics.notify` takes the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- how many waiters to notify (optional, defaults to `Infinity`)++It notifies the given amout of waiters, in FIFO order, waiting on the memory location described by (buffer, index). If there are several pending `Atomics.wait` calls or `Atomics.waitAsync` calls related to the same location, they are all in the same FIFO queue.++In contrast to `Atomics.wait`, `Atomics.waitAsync` always returns immediately. The return value is one of the following:++- `{ async: false, value: "not-equal" }` (if the memory location didn’t contain the expected value)+- `{ async: false, value: "timed-out" }` (only for immediate timeout 0)+- `{ async: true, value: promise }`++The promise may later be resolved with a string value `"ok"` (if `Atomics.notify` was called with the same memory location) or `"timed-out"` (if the timeout was reached). The promise is never rejected.++The following example demonstrates the basic usage of `Atomics.waitAsync`:++```js+let sab = new SharedArrayBuffer(16);+let i32a = new Int32Array(sab);+let result = Atomics.waitAsync(i32a, 0, 0, 1000);+//                                         ^ timeout (opt)+//                                      ^ expected value+//                                   ^ index++if (result.value === "not-equal") {+  // The value in the SharedArrayBuffer was not the expected one.+} else {+  result.value instanceof Promise; // true+  result.value.then(+    (value) => { if (value == "ok") { /* notified */ }

done

marjakh

comment created time in a month

PullRequestReviewEvent

push eventv8/v8.dev

Marja Hölttä

commit sha bf730096bb3bc52385eee9ffea3d0a93b288f8a5

Add a feature explaimer about Atomics Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com> foo

view details

push time in a month

Pull request review commentv8/v8.dev

Add a feature explainer about Atomics

+---+title: 'Atomics.wait, Atomics.notify, Atomics.waitAsync'+author: '[Marja Hölttä](https://twitter.com/marjakh), a non-blocking blogger'+avatars:+  - marja-holtta+date: 2020-09-14+tags:+  - ECMAScript+  - ES2020+description: 'The JavaScript nullish coalescing operator enables safer default expressions.'+tweet: ''+---++# Atomics.wait, Atomics.notify, Atomics.waitAsync++[`Atomics.wait`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait) and [`Atomics.notify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/notify) are low-level synchronization primitives useful for implementing mutexes and other means of synchronization. However, since `Atomics.wait` is blocking, it’s not possible to call it on the main thread (trying to do so will throw a TypeError).++Starting from Chrome 87, V8 supports a non-blocking version, [Atomics.waitAsync](https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md), which is also usable on the main thread.++In this post, we explain how to use these low-level APIs to implement a mutex that works both synchronously (for worker threads) and asynchronously (for worker threads or the main thread).++`Atomics.wait` and `Atomics.waitAsync` take the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- a value we expect to be present in the memory location pointed to by buffer and index+- timeout in milliseconds (optional, defaults to `Infinity`)++The return value of `Atomics.wait` is a string. If the memory location doesn’t contain the expected value, `Atomics.wait` returns immediately with the value `"not-equal"`. Otherwise, the thread is blocked until another thread calls `Atomics.notify` with the same memory location or the timeout is reached. In the former case, `Atomics.wait` returns the value `"ok"`, in the latter case, `Atomics.wait` returns the value `"timed-out"`.++`Atomics.notify` takes the following parameters:++- an `Int32Array` or `BigInt64Array` backed by a `SharedArrayBuffer`+- index (valid within the array)+- how many waiters to notify (optional, defaults to `Infinity`)++It notifies the given amout of waiters, in FIFO order, waiting on the memory location described by (buffer, index). If there are several pending `Atomics.wait` calls or `Atomics.waitAsync` calls related to the same location, they are all in the same FIFO queue.++In contrast to `Atomics.wait`, `Atomics.waitAsync` always returns immediately. The return value is one of the following:++- `{ async: false, value: "not-equal" }` (if the memory location didn’t contain the expected value)+- `{ async: false, value: "timed-out" }` (only for immediate timeout 0)+- `{ async: true, value: promise }`++The promise may later be resolved with a string value `"ok"` (if `Atomics.notify` was called with the same memory location) or `"timed-out"` (if the timeout was reached). The promise is never rejected.++The following example demonstrates the basic usage of `Atomics.waitAsync`:++```js+let sab = new SharedArrayBuffer(16);+let i32a = new Int32Array(sab);+let result = Atomics.waitAsync(i32a, 0, 0, 1000);+//                                         ^ timeout (opt)+//                                      ^ expected value+//                                   ^ index++if (result.value === "not-equal") {+  // The value in the SharedArrayBuffer was not the expected one.+} else {+  result.value instanceof Promise; // true+  result.value.then(+    (value) => { if (value == "ok") { /* notified */ }+                 else { /* value is 'timed-out' */ }+               });+}++// In this thread, or another thread:+Atomics.notify(i32a, 0);+```++Next, we'll show how to implement a mutex which can be used both synchronously and asynchronously. Implementing the synchronous version of the mutex has been previously discussed e.g., [in this blog post](https://blogtitle.github.io/using-javascript-sharedarraybuffers-and-atomics/).++In the example, we don’t use the timeout parameter in `Atomics.wait` and `Atomics.waitAsync`. The parameter can be used for implementing condition variables with a timeout.++Our mutex class, `AsyncLock`, operates on a `SharedArrayBuffer` and implements the following methods:++- `lock` - blocks the thread until we're able to lock the mutex (usable only on a worker thread)+- `unlock` - unlocks the mutex (counterpart of `lock`)+- `executeLocked(callback)` - non-blocking lock, can be used by the main thread; schedules `callback` to be executed once we manage to get the lock++Let’s see how each of those can be implemented. The class definition includes constants and a trivial constructor which takes the `SharedArrayBuffer` as a parameter.++```js+class AsyncLock {+    static INDEX = 0;+    static UNLOCKED = 0;+    static LOCKED = 1;++    constructor(sab) {+      this.sab = sab;+      this.i32a = new Int32Array(sab);+    }++    lock() {+      ...+    }++    unlock() {+      ...+    }++    executeLocked(f) {+      ...+    }+}+```++Here `i32a[0]` contains either the value `LOCKED` or `UNLOCKED`. It's also the wait location for `Atomics.wait`and `Atomics.waitAsync`. The `AsyncLock` class ensures the following invariants:++1. If `i32a[0] == LOCKED`, and a thread starts to wait (either via `Atomics.wait` or `Atomics.waitAsync`) on `i32a[0]`, it will eventually be notified.+2. After getting notified, the thread tries to grab the lock. If it gets the lock, it will notify again when releasing it.++## Sync lock and unlock++Next comes the blocking `lock` method, which can only be called from a worker thread:++```js+lock() {+  while (true) {+    const oldValue = Atomics.compareExchange(self.i32a, AsyncLock.INDEX,+                       /* old value >>> */  AsyncLock.UNLOCKED,+                       /* new value >>> */  AsyncLock.LOCKED);+    if (oldValue == AsyncLock.UNLOCKED) {+      return;+    }+    Atomics.wait(this.i32a, AsyncLock.INDEX,+                 AsyncLock.LOCKED /* expected value at start */);+  }+}+```++When a thread calls `lock()`, first it tries to get the lock by using `Atomics.compareExchange` to change the lock state from `UNLOCKED` to `LOCKED`. `Atomics.compareExchange` tries to do the state change atomically, and it returns the original value of the memory location. If the original value was `UNLOCKED`, we know the state change succeeded, and the thread acquired the lock. Nothing more is needed.++If `Atomics.compareExchange` doesn’t manage to change the lock state, another thread must be holding the lock. Thus, this thread tries `Atomics.wait` in order to wait for the other thread to release the lock. If the memory location still holds the expected value (in this case, `AsyncLock.LOCKED`), calling `Atomics.wait` will block the thread and the `Atomics.wait` call will return only when another thread calls `Atomics.notify`.++The `unlock` is method sets the lock to the `UNLOCKED` state and calls `Atomics.notify` to wake up one waiter which was waiting for the lock. The state change is always expected to succeed, since this thread is holding the lock, and nobody else should call `unlock()` meanwhile.++```js+unlock() {+  const oldValue = Atomics.compareExchange(this.i32a, AsyncLock.INDEX,+                      /* old value >>> */  AsyncLock.LOCKED,+                      /* new value >>> */  AsyncLock.UNLOCKED);+  if (oldValue != AsyncLock.LOCKED) {+    throw new Error("Tried to unlock while not holding the mutex");+  }+  Atomics.notify(this.i32a, AsyncLock.INDEX, 1);+}+```++The straightforward case goes as follows: the lock is free and thread T1 acquires it by changing the lock state with `Atomics.compareExchange`. Thread T2 tries to acquire the lock by calling `Atomics.compareExchange`, but it doesn’t succeed in changing the lock state. T2 then calls `Atomics.wait`, which blocks the thread. At some point T1 releases the lock and calls `Atomics.notify`. That makes the `Atomics.wait` call in T2 return `"ok"`, waking up T2. T2 then tries to acquire the lock again, and this time succeeds.++There are also 2 possible corner cases - these demonstrate the reason for `Atomics.wait` and `Atomics.waitAsync` checking for a specific value at the index:++- T1 is holding the lock and T2 tries to get it. First, T2 tries to change the lock state with `Atomics.compareExchange`, but doesn't succeed. But then T1 releases the lock before T2 manages to call `Atomics.wait`. When T2 calls `Atomics.wait`, it will return immediately with the return value `"not-equal"`. In that case, T2 will continue with the next loop iteration, trying to acquire the lock again.+- T1 is holding the lock and T2 is waiting for it with `Atomics.wait`. T1 releases the lock - T2 wakes up (the `Atomics.wait` call returns) and tries to do `Atomics.compareExchange` to acquire the lock, but another thread T3 was faster and got the lock already. So the call to `Atomics.compareExchange` fails to get the lock, and T2 calls `Atomics.wait` again, blocking until T3 releases the lock.++Because of the latter corner case, the mutex isn’t "fair". It’s possible that T2 has been waiting for the lock to be released, but T3 comes and gets it immediately. A more realistic lock implementation may use several states to differentiate between "locked" and "locked with contention".++## Async lock++The non-blocking `executeLocked` method is callable from the main thread, unlike the blocking `lock` method. It gets a callback function as its only parameter and schedules the callback to be executed once it has successfully acquired the lock.++```js+executeLocked(f) {+  const self = this;++  function tryGetLock() {+    while (true) {+      const oldValue = Atomics.compareExchange(self.i32a, AsyncLock.INDEX,+                          /* old value >>> */  AsyncLock.UNLOCKED,+                          /* new value >>> */  AsyncLock.LOCKED);+      if (oldValue == AsyncLock.UNLOCKED) {+        f();+        self.unlock();+        return;+      }+      const result = Atomics.waitAsync(self.i32a, AsyncLock.INDEX,+                                       AsyncLock.LOCKED+                                   /*  ^ expected value at start */);+      if (result.value == "not-equal") {+        continue;+      }+      result.value.then(tryGetLock);

There's no point using Atomics.waitAsync if you're going to await for the result. Could just use Atomics.wait instead.

marjakh

comment created time in a month

PullRequestReviewEvent

push eventv8/v8.dev

Marja Hölttä

commit sha c54cb2454bc261467e64daf38566ab955b5d0aff

Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com>

view details

push time in a month

push eventv8/v8.dev

Marja Hölttä

commit sha 1b2d0e35d4bfa400e91fb595b47890add7b332c0

Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com>

view details

push time in a month

push eventv8/v8.dev

Marja Hölttä

commit sha d9ca58b786d34e3031ca124035c1713130642e0a

Update src/features/atomics.md Co-authored-by: Ingvar Stepanyan <rreverser@google.com>

view details

push time in a month

push eventv8/v8.dev

Marja Hölttä

commit sha b8a13fb77286c04d79b5e464dd1542b6d0bc7fb1

Add a feature explaimer about Atomics

view details

push time in a month

PR opened v8/v8.dev

Add a feature explaimer about Atomics
+198 -0

0 comment

1 changed file

pr created time in a month

create barnchv8/v8.dev

branch : atomics-explainer

created branch time in a month

fork marjakh/v8.dev

The source code of v8.dev, the official website of the V8 project.

https://v8.dev/

fork in a month

issue openedmozilla/standards-positions

Atomics.waitAsync

Request for Mozilla Position on an Emerging Web Specification

  • Specification Title: Atomics.waitAsync
  • Specification or proposal URL: https://tc39.es/proposal-atomics-wait-async/
  • Caniuse.com URL (optional):
  • Bugzilla URL (optional): https://bugzilla.mozilla.org/show_bug.cgi?id=1467846
  • Mozillians who can provide input (optional):

Other information

created time in 2 months

Pull request review commentv8/v8.dev

Add tags to the Understanding ECMAScript posts

 avatars: date: 2020-02-03 13:33:37 tags:   - ECMAScript+  - Understanding ECMAScript

Ok this comment was inaccurate. So, they render as UnderstandingECMAScript and the link is /blog/tags/understanding-ecmascript

So maybe it's fine? I'd like the link to be understanding-ecmascript and not understandingecmascript.

marjakh

comment created time in 2 months

Pull request review commentv8/v8.dev

Add tags to the Understanding ECMAScript posts

 avatars: date: 2020-02-03 13:33:37 tags:   - ECMAScript+  - Understanding ECMAScript

They seem to "work" as in they will get transformed to the space-free version. I can also use that explicitly if you'd prefer.

marjakh

comment created time in 2 months

PR opened v8/v8.dev

Reviewers
Add tags to the Understanding ECMAScript posts

Also unify the posts a bit (add "Summary" to the ones where it was missing etc).

+18 -17

0 comment

4 changed files

pr created time in 3 months

push eventv8/v8.dev

Marja Hölttä

commit sha 4c8185e6628da0f2b6d1edc1945bf86357d1f751

Add tags to the Understanding ECMAScript posts .

view details

push time in 3 months

create barnchv8/v8.dev

branch : add-tags-to-understanding-ecmascript

created branch time in 3 months

more