Skip to content

Commit c7c19aa

Browse files
Brooooooklynclaudetoyobayashi
authored
fix: refresh stale shared memory buffer before atomic access (v1.x backport) (#218)
* fix: refresh stale shared memory buffer before atomic access (1.11.0 backport) Backport of fix/stale-shared-memory-buffer onto v1.11.0 for drop-in dist replacement of published @emnapi/core 1.11.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: update DataView creation to include `wasmMemory.grow(0)` * Revert "fix: update DataView creation to include `wasmMemory.grow(0)`" This reverts commit ad16d18. * more ensure buffer --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: toyobayashi <lifenglin314@outlook.com>
1 parent e4a7825 commit c7c19aa

2 files changed

Lines changed: 109 additions & 60 deletions

File tree

packages/emnapi/src/core/async-work.ts

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,21 @@ var emnapiAWMT = {
4141
/* napi_async_complete_callback */ complete: 6 * POINTER_SIZE + 24,
4242
end: 7 * POINTER_SIZE + 24
4343
},
44+
/**
45+
* When another thread grows the shared WebAssembly.Memory, this agent's
46+
* cached `wasmMemory.buffer` may still have the old shorter length
47+
* (V8 refreshes it lazily). If a pointer derived from shared memory lies
48+
* beyond the cached length, `wasmMemory.grow(0)` forces the agent to
49+
* observe the current memory size and refreshes the buffer.
50+
*/
51+
ensureBufferFor (end: number): ArrayBufferLike {
52+
let buffer = wasmMemory.buffer
53+
if (end > buffer.byteLength) {
54+
wasmMemory.grow(0)
55+
buffer = wasmMemory.buffer
56+
}
57+
return buffer
58+
},
4459
init () {
4560
emnapiAWMT.pool = []
4661
emnapiAWMT.workerReady = null
@@ -94,7 +109,7 @@ var emnapiAWMT = {
94109
from64('emnapiAWMT.globalAddress')
95110
const size = emnapiAWMT.globalOffset.end
96111
const addr = emnapiAWMT.globalAddress
97-
new Uint8Array(wasmMemory.buffer, addr, size).fill(0)
112+
new Uint8Array(emnapiAWMT.ensureBufferFor(addr + size), addr, size).fill(0)
98113
emnapiAWMT.queueInit(emnapiAWMT.globalAddress + emnapiAWMT.globalOffset.q)
99114
emnapiAWMT.queueInit(emnapiAWMT.globalAddress + emnapiAWMT.globalOffset.exit_message)
100115
}
@@ -133,8 +148,8 @@ var emnapiAWMT = {
133148
}
134149
from64('index')
135150

136-
const view = new DataView(wasmMemory.buffer)
137151
const tidOffset = 20
152+
const view = new DataView(emnapiAWMT.ensureBufferFor((index as number) + tidOffset + 4))
138153
const tid = view.getInt32(index + tidOffset, true)
139154
worker = PThread.pthreads[tid]
140155
return worker.whenLoaded!
@@ -143,26 +158,31 @@ var emnapiAWMT = {
143158
return emnapiAWMT.workerReady as Promise<any>
144159
},
145160
getResource (work: number): number {
161+
emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.resource + 4)
146162
return makeGetValue('work', 'emnapiAWMT.offset.resource', '*')
147163
},
148164
getExecute (work: number): number {
165+
emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.execute + 4)
149166
return makeGetValue('work', 'emnapiAWMT.offset.execute', '*')
150167
},
151168
getComplete (work: number): number {
169+
emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.complete + 4)
152170
return makeGetValue('work', 'emnapiAWMT.offset.complete', '*')
153171
},
154172
getEnv (work: number): number {
173+
emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.env + 4)
155174
return makeGetValue('work', 'emnapiAWMT.offset.env', '*')
156175
},
157176
getData (work: number): number {
177+
emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.data + 4)
158178
return makeGetValue('work', 'emnapiAWMT.offset.data', '*')
159179
},
160180
getMutex () {
161181
const index = emnapiAWMT.globalAddress + emnapiAWMT.globalOffset.mutex
162182
const mutex = {
163183
lock () {
164184
const isBrowserMain = typeof window !== 'undefined' && typeof document !== 'undefined' && !ENVIRONMENT_IS_NODE
165-
const i32a = new Int32Array(wasmMemory.buffer, index, 1)
185+
const i32a = new Int32Array(emnapiAWMT.ensureBufferFor(index + 4), index, 1)
166186
if (isBrowserMain) {
167187
while (true) {
168188
const oldValue = Atomics.compareExchange(i32a, 0, 0, 10)
@@ -181,7 +201,7 @@ var emnapiAWMT = {
181201
}
182202
},
183203
unlock () {
184-
const i32a = new Int32Array(wasmMemory.buffer, index, 1)
204+
const i32a = new Int32Array(emnapiAWMT.ensureBufferFor(index + 4), index, 1)
185205
const oldValue = Atomics.compareExchange(i32a, 0, 10, 0)
186206
if (oldValue !== 10) {
187207
throw new Error('Tried to unlock while not holding the mutex')
@@ -205,25 +225,28 @@ var emnapiAWMT = {
205225
const mutex = emnapiAWMT.getMutex()
206226
const cond = {
207227
wait () {
208-
const i32a = new Int32Array(wasmMemory.buffer, index, 1)
228+
const i32a = new Int32Array(emnapiAWMT.ensureBufferFor(index + 4), index, 1)
209229
const value = Atomics.load(i32a, 0)
210230
mutex.unlock()
211231
Atomics.wait(i32a, 0, value)
212232
mutex.lock()
213233
},
214234
signal () {
215-
const i32a = new Int32Array(wasmMemory.buffer, index, 1)
235+
const i32a = new Int32Array(emnapiAWMT.ensureBufferFor(index + 4), index, 1)
216236
Atomics.add(i32a, 0, 1)
217237
Atomics.notify(i32a, 0, 1)
218238
}
219239
}
220240
return cond
221241
},
222242
queueInit (q: number) {
243+
emnapiAWMT.ensureBufferFor(q + POINTER_SIZE + 4)
223244
makeSetValue('q', 0, 'q', '*')
224245
makeSetValue('q', POINTER_SIZE, 'q', '*')
225246
},
226247
queueInsertTail (h: number, q: number): void {
248+
emnapiAWMT.ensureBufferFor(h + POINTER_SIZE + 4)
249+
emnapiAWMT.ensureBufferFor(q + POINTER_SIZE + 4)
227250
makeSetValue('q', 0, 'h', '*')
228251
// eslint-disable-next-line @typescript-eslint/no-unused-vars
229252
const tempValue = makeGetValue('h', POINTER_SIZE, '*')
@@ -234,6 +257,7 @@ var emnapiAWMT = {
234257
makeSetValue('h', POINTER_SIZE, 'q', '*')
235258
},
236259
queueRemove (q: number): void {
260+
emnapiAWMT.ensureBufferFor(q + POINTER_SIZE + 4)
237261
// eslint-disable-next-line @typescript-eslint/no-unused-vars
238262
const qprev = makeGetValue('q', POINTER_SIZE, '*')
239263
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -242,6 +266,7 @@ var emnapiAWMT = {
242266
makeSetValue('qnext', POINTER_SIZE, 'qprev', '*')
243267
},
244268
queueEmpty (q: number): boolean {
269+
emnapiAWMT.ensureBufferFor(q + 4)
245270
// eslint-disable-next-line eqeqeq
246271
return q == makeGetValue('q', 0, '*')
247272
},
@@ -257,7 +282,7 @@ var emnapiAWMT = {
257282

258283
_emnapi_runtime_keepalive_push()
259284
emnapiCtx.increaseWaitingRequestCounter()
260-
const statusBuffer = new Int32Array(wasmMemory.buffer, work + emnapiAWMT.offset.status, 1)
285+
const statusBuffer = new Int32Array(emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.status + 4), work + emnapiAWMT.offset.status, 1)
261286
Atomics.store(statusBuffer, 0, AsyncWorkStatus.Pending)
262287

263288
const mutex = emnapiAWMT.getMutex()
@@ -272,6 +297,7 @@ var emnapiAWMT = {
272297
throw err
273298
}
274299

300+
emnapiAWMT.ensureBufferFor(emnapiAWMT.globalAddress + emnapiAWMT.globalOffset.idle_threads + 4)
275301
if (makeGetValue('emnapiAWMT.globalAddress', 'emnapiAWMT.globalOffset.idle_threads', 'u32') > 0) {
276302
cond.signal()
277303
}
@@ -280,6 +306,7 @@ var emnapiAWMT = {
280306
cancelWork (work: number) {
281307
let cancelled = false
282308
emnapiAWMT.getMutex().execute(() => {
309+
emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.status + 4)
283310
cancelled = !emnapiAWMT.queueEmpty(work + emnapiAWMT.offset.queue) && makeGetValue('work', 'emnapiAWMT.offset.status', 'i32') !== AsyncWorkStatus.Completed
284311
if (cancelled) {
285312
emnapiAWMT.queueRemove(work + emnapiAWMT.offset.queue)
@@ -288,7 +315,7 @@ var emnapiAWMT = {
288315
if (!cancelled) {
289316
return napi_status.napi_generic_failure
290317
}
291-
if (Atomics.compareExchange(new Int32Array(wasmMemory.buffer, work + emnapiAWMT.offset.status, 1), 0, AsyncWorkStatus.Pending, AsyncWorkStatus.Cancelled) !== AsyncWorkStatus.Pending) {
318+
if (Atomics.compareExchange(new Int32Array(emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.status + 4), work + emnapiAWMT.offset.status, 1), 0, AsyncWorkStatus.Pending, AsyncWorkStatus.Cancelled) !== AsyncWorkStatus.Pending) {
292319
return napi_status.napi_generic_failure
293320
}
294321
emnapiCtx.feature.setImmediate(() => {
@@ -316,7 +343,7 @@ var emnapiAWMT = {
316343
const resource = emnapiAWMT.getResource(work)
317344
const resource_value = emnapiCtx.refStore.get(resource)!.get()
318345
const resourceObject = emnapiCtx.handleStore.get(resource_value)!.value
319-
const view = new DataView(wasmMemory.buffer)
346+
const view = new DataView(emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.trigger_async_id + 8))
320347
const asyncId = view.getFloat64(work + emnapiAWMT.offset.async_id, true)
321348
const triggerAsyncId = view.getFloat64(work + emnapiAWMT.offset.trigger_async_id, true)
322349
emnapiNodeBinding.node.makeCallback(resourceObject, callback, [], {
@@ -375,7 +402,7 @@ export var napi_create_async_work = singleThreadAsyncWork
375402
const aw = _malloc(to64('sizeofAW'))
376403
if (!aw) return envObject.setLastError(napi_status.napi_generic_failure)
377404
from64('aw')
378-
new Uint8Array(wasmMemory.buffer).subarray(aw, aw + sizeofAW).fill(0)
405+
new Uint8Array(emnapiAWMT.ensureBufferFor(aw + sizeofAW)).subarray(aw, aw + sizeofAW).fill(0)
379406
const s = envObject.ensureHandleId(resourceObject)
380407
const resourceRef = emnapiCtx.createReference(envObject, s, 1, ReferenceOwnership.kUserland as any)
381408
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -409,7 +436,7 @@ export var napi_delete_async_work = singleThreadAsyncWork
409436
emnapiCtx.refStore.get(resource)!.dispose()
410437

411438
if (emnapiNodeBinding) {
412-
const view = new DataView(wasmMemory.buffer)
439+
const view = new DataView(emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.trigger_async_id + 8))
413440
const asyncId = view.getFloat64(work + emnapiAWMT.offset.async_id, true)
414441
const triggerAsyncId = view.getFloat64(work + emnapiAWMT.offset.trigger_async_id, true)
415442
_emnapi_node_emit_async_destroy(asyncId, triggerAsyncId)
@@ -470,10 +497,11 @@ export function _emnapi_async_worker (globalAddress: number): number {
470497
const idleThreadsAddr = globalAddress + emnapiAWMT.globalOffset.idle_threads
471498
const workerQueueAddr = globalAddress + emnapiAWMT.globalOffset.q
472499
for (;;) {
500+
emnapiAWMT.ensureBufferFor(workerQueueAddr + 4)
473501
while (emnapiAWMT.queueEmpty(workerQueueAddr)) {
474-
Atomics.add(new Int32Array(wasmMemory.buffer, idleThreadsAddr, 1), 0, 1)
502+
Atomics.add(new Int32Array(emnapiAWMT.ensureBufferFor(idleThreadsAddr + 4), idleThreadsAddr, 1), 0, 1)
475503
cond.wait()
476-
Atomics.sub(new Int32Array(wasmMemory.buffer, idleThreadsAddr, 1), 0, 1)
504+
Atomics.sub(new Int32Array(emnapiAWMT.ensureBufferFor(idleThreadsAddr + 4), idleThreadsAddr, 1), 0, 1)
477505
}
478506
const q = makeGetValue('workerQueueAddr', 0, '*')
479507
if (q === exitMessageAddr) {
@@ -487,7 +515,7 @@ export function _emnapi_async_worker (globalAddress: number): number {
487515

488516
mutex.unlock()
489517

490-
const statusBuffer = new Int32Array(wasmMemory.buffer, work + emnapiAWMT.offset.status, 1)
518+
const statusBuffer = new Int32Array(emnapiAWMT.ensureBufferFor(work + emnapiAWMT.offset.status + 4), work + emnapiAWMT.offset.status, 1)
491519
if (Atomics.load(statusBuffer, 0) === AsyncWorkStatus.Cancelled) {
492520
abort('unreachable')
493521
}

0 commit comments

Comments
 (0)