Как эта, казалось бы, герметичная синхронная установка SharedArrayBuffer демонстрирует повреждение?Javascript

Форум по Javascript
Ответить
Anonymous
 Как эта, казалось бы, герметичная синхронная установка SharedArrayBuffer демонстрирует повреждение?

Сообщение Anonymous »

У меня есть настройка, в которой несколько десятков веб-работников отправляют сообщения postMessage в основной поток, а затем синхронно блокируют ответ:
Изображение

Поскольку это синхронно, веб-работник никогда не отправляет запрос postMessage до тех пор, пока не будет успешно извлечен ответ из буфера общего массива. Однако я наблюдаю, что очень редко (после тысяч сообщений) SharedArrayBuffer имеет поврежденный ответ. Мне удалось усугубить проблему, поэтому она случается довольно часто с помощью этого воспроизводимого примера из 80 строк:

Код: Выделить всё

//////////////////// WORKER
function workerFn() {
const sab = new SharedArrayBuffer(1024 * 1024) // [0:4 Atomics.wait signal][4:8 data length][8:8+data length JSON data]
const vi32 = new Int32Array(sab); // View needed for Atomics.wait
const vui8 = new Uint8Array(sab); // View needed for TextDecoder
const sbuf = { sab, vi32, vui8 };
postMessage({ type: "sab", sab });

let pl = 0;
while (true) {
postMessage({ type: "sync", payload: pl++ });

// This ostensibly synchronously blocks until the first int32 of the SharedArrayBuffer changes
// The main thread is responsible for changing this value and calling Atomics.notify()
Atomics.wait(sbuf.vi32, 0, 0); // Wait for expected value to change
Atomics.store(sbuf.vi32, 0, 0); // Reset expected value to 0 for next iteration

// The data is JSON as utf-8 encoded uint8
let data_length = sbuf.vi32[1];
let data = new TextDecoder().decode(sbuf.vui8.slice(8, 8 + data_length)); // 8 byte offset for header

let m;
try {
m = JSON.parse(data);
} catch (e) {
// This should never happen, yet it does
throw new Error("How is this possible? Bad JSON:" + data);
}

if (m.cooldown > 0) {
// Since this should never change until the next postMessage, we should be able to wait on it
Atomics.wait(sbuf.vi32, 0, 0, m.cooldown); // Sometimes this returns something other than "timed-out" which should be impossible!
}
}
}

//////////////////// MAIN THREAD
let processedMessages = 0;
function onWorkerMessage(workerName, data) {
if (data.type === 'sab') {
console.log('Received SAB from', workerName)
workers[workerName].sbuf = {
sab: data.sab,
vi32: new Int32Array(data.sab), // View needed for Atomics.store
vui8: new Uint8Array(data.sab), // View needed for TextEncoder
};
} else if (data.type === 'sync') {
processedMessages++;
if (processedMessages % 10000 === 0) {
console.log('Processed', processedMessages, 'messages')
}

// Do a little fake work
for (let i = 0; i < 100; i++)
Math.random();

// Send a message back to the worker
let m = { rv: data.payload % 2 === 0, cooldown: data.payload % 2 === 0 ? 0 : 0.5 };
const rui8 = new TextEncoder().encode(JSON.stringify(m));
const sbuf = workers[workerName].sbuf;

Atomics.store(sbuf.vi32, 1, rui8.length);
sbuf.vui8.set(rui8, 8);

// Signal the worker that the data is ready
Atomics.store(sbuf.vi32, 0, 1);
Atomics.notify(sbuf.vi32, 0);
}
}

//////////////////// INIT
let workers = {}
for (let i = 0; i < 20; i++) {
console.log('Starting worker', i)
let wf = workerFn.toString();
wf = wf.substring(wf.indexOf('{') + 1, wf.lastIndexOf('}'))
const blob = new Blob([wf], { type: 'application/javascript' })
const worker = new Worker(URL.createObjectURL(blob), { name: `worker${i}` })
worker.onmessage = e => onWorkerMessage(`worker${i}`, e.data)
workers[`worker${i}`] = worker
}
Чтобы запустить этот код, он должен обслуживаться веб-сервером, который устанавливает следующие заголовки ответа:

Код: Выделить всё

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Пример HTML: Если вы запустите это, вы увидите следующий вывод на консоли:
[img]https://i. sstatic.net/JpKYLj42.png[/img]

Такое поведение происходит как в Firefox, так и в Chrome.
Я просмотрел этот минимальный фрагмент для часов, и мне кажется, что он герметичен, что приводит меня к считаю, что это либо недоразумение с моей стороны, либо (менее вероятно) ошибка в самом JavaScript.
Еще одна подсказка: если я изменю код так, чтобы раздел восстановления имел следующее:
р>

Код: Выделить всё

        if (m.cooldown > 0) {
// Since this should never change until the next postMessage, we should be able to wait on it
let rv = Atomics.wait(sbuf.vi32, 0, 0, m.cooldown);
if (rv !== 'timed-out') {
// !!! This should never happen, yet it does
throw new Error("How is this possible? Atomics.wait returned: " + rv);
}
}
Затем я вижу множество ошибок консоли, что Atomics.wait в состоянии восстановления на самом деле не истекает по тайм-ауту, что опять же должно быть невозможно, поскольку ничто не должно иметь возможности изменять sbuf .vi32[0] до тех пор, пока не будет отправлено следующее сообщение postMessage.
Кроме того, удаление всего блока if (m.cooldown > 0) делает проблему невоспроизводимой, так что это явно суть, но я не понимаю, почему.

Подробнее здесь: https://stackoverflow.com/questions/793 ... ifesting-c
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Javascript»