Node: Worker_threads SharedArrayBuffer in multiple object properties cross-referenced randomly (object corruption?)

Created on 5 Jul 2019  路  2Comments  路  Source: nodejs/node

  • Version: 12.6.0
  • Platform: Linux dc 4.4.62-1.el7.elrepo.x86_64 #1 SMP Tue Apr 18 11:02:11 EDT 2017 x86_64 x86_64 x86_64 GNU/Linux and 64-bit Windows
  • Subsystem: worker_threads

MainThread

const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

function allocateSharedMemory(objSharedBuffer, strBufferType, nBytesCount)
{
    objSharedBuffer[strBufferType] = new SharedArrayBuffer(nBytesCount);
    const arrSharedMemoryView = new Uint32Array(objSharedBuffer[strBufferType]);
    arrSharedMemoryView[0] = nBytesCount;
}

const arrFruit = [
    "apples",
    "bananas",
    "oranges",
];

const objToShare = {};

for(let strFruit of arrFruit)
{   
    objToShare[strFruit] = {};
    objToShare[strFruit]["type"] = {};

    objToShare[strFruit]["type"]["sunlight"] = {};

    allocateSharedMemory(objToShare[strFruit]["type"]["sunlight"], "lastHour", 100);
    allocateSharedMemory(objToShare[strFruit]["type"]["sunlight"], "lastDay", 104);
    allocateSharedMemory(objToShare[strFruit]["type"]["sunlight"], "lastMonth", 108);
    allocateSharedMemory(objToShare[strFruit]["type"]["sunlight"], "lastYear", 112);

    objToShare[strFruit]["type"]["water"] = {};

    allocateSharedMemory(objToShare[strFruit]["type"]["water"], "lastHour", 116);
    allocateSharedMemory(objToShare[strFruit]["type"]["water"], "lastDay", 120);
    allocateSharedMemory(objToShare[strFruit]["type"]["water"], "lastMonth", 124);
    allocateSharedMemory(objToShare[strFruit]["type"]["water"], "lastYear", 128);
}   

for(let i = 0; i < 4; i++)
{
    const w = new Worker("./WorkerThread.js");

    w.postMessage(objToShare);

    /*w.on('message', (msg) => {
        const arrDataPoints = new Uint32Array(objToShare["bananas"]["type"]["sunlight"]["lastHour"]);
    });*/   
}

WorkerThread

const {  parentPort, workerData } = require('worker_threads');

parentPort.once('message', (value) => {

    const objGranularities = {
        "lastHour": 100,
        "lastDay": 104,
        "lastMonth": 108,
        "lastYear": 112
    }

    for(let strGranularity in objGranularities)
    {
        const sharedBuffer = value["bananas"]["type"]["sunlight"][strGranularity];
        const sharedBufferView = new Uint32Array(sharedBuffer);
        const nByteLengthReceived = sharedBuffer.byteLength;
        const nByteLengthExpected = objGranularities[strGranularity]; 

        if(nByteLengthExpected === nByteLengthReceived)
        {
            console.log("[Pass]", "receivedLength =", nByteLengthReceived, "expectedLength =", nByteLengthExpected, "firstValue =", sharedBufferView[0], "type =", strGranularity);

        }
        else
        {
            console.log("[Fail]", "receivedLength =", nByteLengthReceived, "expectedLength =", nByteLengthExpected, "firstValue =", sharedBufferView[0],  "type =", strGranularity);
        }

    }

    parentPort.postMessage("finished");

});

Output

[Fail] receivedLength = 104 expectedLength = 100 firstValue = 104 type = lastHour
[Fail] receivedLength = 108 expectedLength = 104 firstValue = 108 type = lastDay
[Fail] receivedLength = 104 expectedLength = 100 firstValue = 104 type = lastHour
[Fail] receivedLength = 112 expectedLength = 108 firstValue = 112 type = lastMonth
[Fail] receivedLength = 108 expectedLength = 104 firstValue = 108 type = lastDay
[Fail] receivedLength = 128 expectedLength = 112 firstValue = 128 type = lastYear
[Fail] receivedLength = 112 expectedLength = 108 firstValue = 112 type = lastMonth
[Fail] receivedLength = 128 expectedLength = 112 firstValue = 128 type = lastYear
[Fail] receivedLength = 104 expectedLength = 100 firstValue = 104 type = lastHour
[Fail] receivedLength = 108 expectedLength = 104 firstValue = 108 type = lastDay
[Fail] receivedLength = 112 expectedLength = 108 firstValue = 112 type = lastMonth
[Fail] receivedLength = 128 expectedLength = 112 firstValue = 128 type = lastYear
[Fail] receivedLength = 104 expectedLength = 100 firstValue = 104 type = lastHour
[Fail] receivedLength = 108 expectedLength = 104 firstValue = 108 type = lastDay
[Fail] receivedLength = 112 expectedLength = 108 firstValue = 112 type = lastMonth
[Fail] receivedLength = 128 expectedLength = 112 firstValue = 128 type = lastYear

The order of creating the keys in the object matters.
It appears there's some optimization in V8 which is disabled partially (object keys reordering).

When ordered this way, everything works fine:

const arrFruit = [
    "bananas",
    "oranges",
    "apples",
];

{
  bananas: { type: { sunlight: [Object], water: [Object] } },
  oranges: { type: { sunlight: [Object], water: [Object] } },
  apples: { type: { sunlight: [Object], water: [Object] } }
}

I get the following output:

[Pass] receivedLength = 100 expectedLength = 100 firstValue = 100 type = lastHour
[Pass] receivedLength = 104 expectedLength = 104 firstValue = 104 type = lastDay
[Pass] receivedLength = 108 expectedLength = 108 firstValue = 108 type = lastMonth
[Pass] receivedLength = 112 expectedLength = 112 firstValue = 112 type = lastYear
[Pass] receivedLength = 100 expectedLength = 100 firstValue = 100 type = lastHour
[Pass] receivedLength = 104 expectedLength = 104 firstValue = 104 type = lastDay
[Pass] receivedLength = 100 expectedLength = 100 firstValue = 100 type = lastHour
[Pass] receivedLength = 108 expectedLength = 108 firstValue = 108 type = lastMonth
[Pass] receivedLength = 104 expectedLength = 104 firstValue = 104 type = lastDay
[Pass] receivedLength = 112 expectedLength = 112 firstValue = 112 type = lastYear
[Pass] receivedLength = 108 expectedLength = 108 firstValue = 108 type = lastMonth
[Pass] receivedLength = 112 expectedLength = 112 firstValue = 112 type = lastYear
[Pass] receivedLength = 100 expectedLength = 100 firstValue = 100 type = lastHour
[Pass] receivedLength = 104 expectedLength = 104 firstValue = 104 type = lastDay
[Pass] receivedLength = 108 expectedLength = 108 firstValue = 108 type = lastMonth
[Pass] receivedLength = 112 expectedLength = 112 firstValue = 112 type = lastYear
confirmed-bug worker

Most helpful comment

@eduardnegru Thanks for opening an issue about this (and thanks @oxygen for the simplified reproduction), https://github.com/nodejs/node/pull/28582 should address the bug :)

All 2 comments

I simplified this a bit.

MasterThread.js

const WorkerThread = require("worker_threads").Worker;

// Bug: the second object key (apples) 
// always gets the wrong SharedArrayBuffer instances in the wrong keys.
const objFruits_A = {
    oranges: {
        x212: new SharedArrayBuffer(212),
        x216: new SharedArrayBuffer(216),
        x222: new SharedArrayBuffer(222)
    },
    apples: {
        x100: new SharedArrayBuffer(100),
        x104: new SharedArrayBuffer(104),
        x108: new SharedArrayBuffer(108)
    }
};

// Bug: the second object key (oranges) 
// always gets the wrong SharedArrayBuffer instances in the wrong keys.
const objFruits_B = {
    apples: {
        x100: new SharedArrayBuffer(100),
        x104: new SharedArrayBuffer(104),
        x108: new SharedArrayBuffer(108)
    },
    oranges: {
        x212: new SharedArrayBuffer(212),
        x216: new SharedArrayBuffer(216),
        x222: new SharedArrayBuffer(222)
    }
};

const workerThread = new WorkerThread("./WorkerThread.js");
workerThread.postMessage(objFruits_A);
workerThread.postMessage(objFruits_B);

// The second key in the sent object seems to get the SharedArrayBuffer instances 
// shuffled somehow for both objFruits_A and objFruits_B.
setTimeout(process.exit, 3000);

WorkerThread.js:

const parentPort = require("worker_threads").parentPort;

parentPort.on("message", console.log);

Output:

C:\Users\oxygen\Desktop\SharedArrayBuffer>node --experimental-worker MainThread.js
{ oranges:
   { x212: SharedArrayBuffer { byteLength: 212 },
     x216: SharedArrayBuffer { byteLength: 216 },
     x222: SharedArrayBuffer { byteLength: 222 } },
  apples:
   { x100: SharedArrayBuffer { byteLength: 216 },
     x104: SharedArrayBuffer { byteLength: 222 },
     x108: SharedArrayBuffer { byteLength: 108 } } }
{ apples:
   { x100: SharedArrayBuffer { byteLength: 100 },
     x104: SharedArrayBuffer { byteLength: 104 },
     x108: SharedArrayBuffer { byteLength: 108 } },
  oranges:
   { x212: SharedArrayBuffer { byteLength: 104 },
     x216: SharedArrayBuffer { byteLength: 108 },
     x222: SharedArrayBuffer { byteLength: 222 } } }

@eduardnegru Thanks for opening an issue about this (and thanks @oxygen for the simplified reproduction), https://github.com/nodejs/node/pull/28582 should address the bug :)

Was this page helpful?
0 / 5 - 0 ratings