Comparing version 0.0.3 to 0.0.4
{ | ||
"name": "worker_map", | ||
"description": "Tread-safe map structure for worker_threads.", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"main": "./src/worker_hash.js", | ||
"scripts": { | ||
"test": "node --test ./test" | ||
"test": "node --test ./test", | ||
"lint": "eslint ./**/*.js" | ||
}, | ||
"devDependencies": { | ||
"eslint": "8.49.0", | ||
"eslint-config-airbnb-base": "15.0.0", | ||
"eslint-config-prettier": "9.0.0", | ||
"eslint-plugin-prettier": "5.0.0", | ||
"eslint-plugin-import": "2.28.1", | ||
"prettier": "3.0.3" | ||
}, | ||
"homepage": "https://github.com/nairihar/worker_map", | ||
@@ -10,0 +19,0 @@ "keywords": [ |
![](https://img.shields.io/badge/dependencies-none-brightgreen.svg) | ||
![](https://img.shields.io/npm/l/worker_map.svg) | ||
![](https://img.shields.io/npm/dt/worker_map.svg) | ||
[![Known Vulnerabilities](https://snyk.io/test/github/nairihar/worker_map/badge.svg)](https://snyk.io/test/github/nairihar/funthreads) | ||
![](https://img.shields.io/npm/l/worker_map.svg) | ||
@@ -59,9 +58,11 @@ # worker_map | ||
### `map.get(key):` | ||
Worker_map is much like JavaScript's regular Map. | ||
### `map.set(key, value)` | ||
``` | ||
const name = map.get('name'); | ||
map.set('name', 'John'); // true | ||
``` | ||
### `map.set(key, value)` | ||
### `map.get(key):` | ||
``` | ||
map.set('name', 'John'); | ||
const name = map.get('name'); // 'John' | ||
``` | ||
@@ -71,3 +72,3 @@ ### `map.delete(key):` | ||
map.delete('name'); // true | ||
map.delete('something'); // false because doesn't exist | ||
map.delete('something'); // false because it doesn't exist | ||
``` | ||
@@ -94,12 +95,16 @@ ### `map.has(key)` | ||
``` | ||
const mapObject = map.toObject(); | ||
const mapObject = map.toObject(); // { ... } | ||
mapObject.name; // 'John' | ||
``` | ||
#### TODO | ||
## Contributing | ||
See the [contributing guide](https://github.com/nairihar/worker_map/blob/main/CONTRIBUTING.md) for detailed instructions on how to get started with our project. | ||
**TODO** | ||
- `map.clear()` | ||
- `map.entries()` | ||
- `map.forEach()` | ||
- Currently, when performing an action on the map, it temporarily locks the entire map, loads the necessary data, and then unlocks the map, allowing other threads to access it. However, this approach is suboptimal. It would be more efficient if we could lock only the specific portion of memory required for the particular operation. | ||
## Limitations | ||
@@ -110,2 +115,1 @@ | ||
2. **NaN Values:** NaN values are not supported. | ||
@@ -5,21 +5,25 @@ const LOCKED = 1; | ||
function lock(buffer) { | ||
for(;;) { | ||
if (Atomics.compareExchange(buffer, 0, UNLCOKED, LOCKED) == UNLCOKED) { | ||
return; | ||
} | ||
Atomics.wait(buffer, 0, LOCKED); | ||
for (;;) { | ||
if (Atomics.compareExchange(buffer, 0, UNLCOKED, LOCKED) === UNLCOKED) { | ||
return; | ||
} | ||
Atomics.wait(buffer, 0, LOCKED); | ||
} | ||
} | ||
function unlock(buffer) { | ||
if (Atomics.compareExchange(buffer, 0, LOCKED, UNLCOKED) != LOCKED) { | ||
throw new Error("Mutex is in inconsistent state: unlock on unlocked Mutex."); | ||
} | ||
if (Atomics.compareExchange(buffer, 0, LOCKED, UNLCOKED) !== LOCKED) { | ||
throw new Error( | ||
"Mutex is in inconsistent state: unlock on unlocked Mutex.", | ||
); | ||
} | ||
Atomics.notify(buffer, 0, 1); | ||
Atomics.notify(buffer, 0, 1); | ||
} | ||
module.exports = { | ||
lock, unlock, | ||
LOCKED, UNLCOKED, | ||
}; | ||
lock, | ||
unlock, | ||
LOCKED, | ||
UNLCOKED, | ||
}; |
@@ -1,5 +0,5 @@ | ||
const decoder = new TextDecoder('utf8'); | ||
const encoder = new TextEncoder('utf8'); | ||
const decoder = new TextDecoder("utf8"); | ||
const encoder = new TextEncoder("utf8"); | ||
const { lock, unlock, UNLCOKED } = require('./mutex'); | ||
const { lock, unlock, UNLCOKED } = require("./mutex"); | ||
@@ -9,209 +9,210 @@ const DEFAULT_OBJECT_BYTE_LENGTH = 4096; // total characters of stringified object (2^12) | ||
const MAP_SIZE = Symbol('MAP_SIZE'); | ||
const PLAIN_OBJECT = Symbol('PLAIN_OBJECT'); | ||
const VALUE_BUFFER = Symbol('VALUE_BUFFER'); | ||
const SHARED_BUFFER = Symbol('SHARED_BUFFER'); | ||
const MAP_SIZE = Symbol("MAP_SIZE"); | ||
const PLAIN_OBJECT = Symbol("PLAIN_OBJECT"); | ||
const VALUE_BUFFER = Symbol("VALUE_BUFFER"); | ||
const SHARED_BUFFER = Symbol("SHARED_BUFFER"); | ||
function getSharedMemoryBuffer(sharedBuffer) { | ||
return new Int32Array(sharedBuffer); | ||
return new Int32Array(sharedBuffer); | ||
} | ||
function calculateUsedBufferLength(valueBuffer) { | ||
// exclude first item | ||
return valueBuffer.reduce((acc, value) => ( | ||
acc + (value ? 1 : 0) | ||
), -1); | ||
// exclude first item | ||
return valueBuffer.reduce((acc, value) => acc + (value ? 1 : 0), -1); | ||
} | ||
function loadPlainSharedObject(valueBuffer) { | ||
const usedBufferLength = calculateUsedBufferLength(valueBuffer); | ||
const decodeBuffer = new Uint8Array(usedBufferLength); | ||
const usedBufferLength = calculateUsedBufferLength(valueBuffer); | ||
const decodeBuffer = new Uint8Array(usedBufferLength); | ||
for (let i = 1; i <= usedBufferLength; i++) { | ||
if (valueBuffer[i]) { | ||
decodeBuffer[i - 1] = valueBuffer[i]; | ||
} | ||
for (let i = 1; i <= usedBufferLength; i++) { | ||
if (valueBuffer[i]) { | ||
decodeBuffer[i - 1] = valueBuffer[i]; | ||
} | ||
} | ||
return JSON.parse( | ||
decoder.decode(decodeBuffer) | ||
); | ||
return JSON.parse(decoder.decode(decodeBuffer)); | ||
} | ||
function safeLoadSharedObject(valueBuffer) { | ||
lock(valueBuffer); | ||
lock(valueBuffer); | ||
const sharedObject = loadPlainSharedObject(valueBuffer) | ||
const sharedObject = loadPlainSharedObject(valueBuffer); | ||
unlock(valueBuffer); | ||
unlock(valueBuffer); | ||
return sharedObject; | ||
return sharedObject; | ||
} | ||
function calculateStringByteLength(string) { | ||
return encoder.encode(string).length; | ||
return encoder.encode(string).length; | ||
} | ||
function saveObjectInBuffer(sharedObject, valueBuffer, objString) { | ||
objString ??= JSON.stringify(sharedObject); | ||
objString ??= JSON.stringify(sharedObject); | ||
const stringByteLength = calculateStringByteLength(objString); | ||
const encodeBuffer = new Uint8Array(stringByteLength); | ||
encoder.encodeInto(objString, encodeBuffer) | ||
const stringByteLength = calculateStringByteLength(objString); | ||
const encodeBuffer = new Uint8Array(stringByteLength); | ||
encoder.encodeInto(objString, encodeBuffer); | ||
for (let i = 0; i < stringByteLength; i++) { | ||
valueBuffer[i + 1] = encodeBuffer[i]; | ||
} | ||
for (let i = 0; i < stringByteLength; i++) { | ||
valueBuffer[i + 1] = encodeBuffer[i]; | ||
} | ||
for (let j = stringByteLength + 1; j < valueBuffer.length; j++) { | ||
valueBuffer[j] = 0; | ||
} | ||
for (let j = stringByteLength + 1; j < valueBuffer.length; j++) { | ||
valueBuffer[j] = 0; | ||
} | ||
} | ||
function adjustSharedBufferGrow(sharedBuffer, sharedObject) { | ||
const objectByteLength = calculateStringByteLength(JSON.stringify(sharedObject)); | ||
const diff = (sharedBuffer.byteLength / 4) - objectByteLength; | ||
const objectByteLength = calculateStringByteLength( | ||
JSON.stringify(sharedObject), | ||
); | ||
const diff = sharedBuffer.byteLength / 4 - objectByteLength; | ||
if (diff <= 0) { | ||
if (!sharedBuffer.grow) { | ||
throw new Error( | ||
"No more space, create a new bigger shared object or use Node >= v20 to support auto grow.", | ||
); | ||
} | ||
if (diff <= 0) { | ||
if (!sharedBuffer.grow) { | ||
throw new Error('No more space, create a new bigger shared object or use Node >= v20 to support auto grow.'); | ||
} | ||
// byte length of Int32Array should be a multiple of 4 | ||
const growedLength = sharedBuffer.byteLength + 4 * Math.abs(diff) + 64; | ||
// byte length of Int32Array should be a multiple of 4 | ||
let growedLength = sharedBuffer.byteLength + (4 * Math.abs(diff)) + 64; | ||
sharedBuffer.grow(growedLength); | ||
} | ||
sharedBuffer.grow(growedLength); | ||
} | ||
} | ||
function WorkerMap(providedObject, length=DEFAULT_OBJECT_BYTE_LENGTH) { | ||
if (providedObject instanceof SharedArrayBuffer) { | ||
const sharedBuffer = providedObject; | ||
function WorkerMap(providedObject, length = DEFAULT_OBJECT_BYTE_LENGTH) { | ||
if (providedObject instanceof SharedArrayBuffer) { | ||
const sharedBuffer = providedObject; | ||
const valueBuffer = getSharedMemoryBuffer(sharedBuffer); | ||
this[SHARED_BUFFER] = sharedBuffer; | ||
this[VALUE_BUFFER] = valueBuffer; | ||
this[MAP_SIZE] = this.keys().length; | ||
const valueBuffer = getSharedMemoryBuffer(sharedBuffer); | ||
return this; | ||
} | ||
this[SHARED_BUFFER] = sharedBuffer; | ||
this[VALUE_BUFFER] = valueBuffer; | ||
this[MAP_SIZE] = this.keys().length; | ||
const initialObject = providedObject || {}; | ||
const stringifiedObject = JSON.stringify(initialObject); | ||
return this; | ||
} | ||
length += length % 4; // should be a multiple of 4 | ||
const initialObject = providedObject || {}; | ||
const stringifiedObject = JSON.stringify(initialObject); | ||
if (stringifiedObject.length >= length) { | ||
length = 4 * stringifiedObject.length; | ||
} | ||
length += length % 4; // should be a multiple of 4 | ||
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * length, { | ||
maxByteLength: MAX_OBJECT_BYTE_LENGTH, | ||
}); | ||
const valueBuffer = getSharedMemoryBuffer(sharedBuffer); | ||
if (stringifiedObject.length >= length) { | ||
length = 4 * stringifiedObject.length; | ||
} | ||
valueBuffer[0] = UNLCOKED; // first value used for locking and unlocking | ||
const sharedBuffer = new SharedArrayBuffer( | ||
Int32Array.BYTES_PER_ELEMENT * length, | ||
{ | ||
maxByteLength: MAX_OBJECT_BYTE_LENGTH, | ||
}, | ||
); | ||
const valueBuffer = getSharedMemoryBuffer(sharedBuffer); | ||
saveObjectInBuffer(null, valueBuffer, stringifiedObject); | ||
valueBuffer[0] = UNLCOKED; // first value used for locking and unlocking | ||
this[SHARED_BUFFER] = sharedBuffer; | ||
this[VALUE_BUFFER] = valueBuffer; | ||
this[MAP_SIZE] = 0; | ||
saveObjectInBuffer(null, valueBuffer, stringifiedObject); | ||
return this; | ||
this[SHARED_BUFFER] = sharedBuffer; | ||
this[VALUE_BUFFER] = valueBuffer; | ||
this[MAP_SIZE] = 0; | ||
return this; | ||
} | ||
WorkerMap.prototype.set = function (key, value) { | ||
if (typeof key === 'function' || typeof value === 'function') { | ||
return false; | ||
} | ||
if (typeof key === "function" || typeof value === "function") { | ||
return false; | ||
} | ||
const valueBuffer = this[VALUE_BUFFER]; | ||
const sharedBuffer = this[SHARED_BUFFER]; | ||
const valueBuffer = this[VALUE_BUFFER]; | ||
const sharedBuffer = this[SHARED_BUFFER]; | ||
if (Number.isNaN(value) || value === undefined) { | ||
value = null; | ||
} | ||
if (Number.isNaN(value) || value === undefined) { | ||
value = null; | ||
} | ||
lock(valueBuffer); | ||
lock(valueBuffer); | ||
const sharedObject = loadPlainSharedObject(valueBuffer) | ||
sharedObject[key] = value; | ||
const sharedObject = loadPlainSharedObject(valueBuffer); | ||
adjustSharedBufferGrow(sharedBuffer, sharedObject); | ||
sharedObject[key] = value; | ||
saveObjectInBuffer(sharedObject, valueBuffer); | ||
adjustSharedBufferGrow(sharedBuffer, sharedObject); | ||
this[MAP_SIZE] = Object.keys(sharedObject).length; | ||
saveObjectInBuffer(sharedObject, valueBuffer); | ||
unlock(valueBuffer); | ||
this[MAP_SIZE] = Object.keys(sharedObject).length; | ||
return true; | ||
unlock(valueBuffer); | ||
return true; | ||
}; | ||
WorkerMap.prototype.get = function (key) { | ||
const valueBuffer = this[VALUE_BUFFER]; | ||
const sharedObject = safeLoadSharedObject(valueBuffer) | ||
const valueBuffer = this[VALUE_BUFFER]; | ||
const sharedObject = safeLoadSharedObject(valueBuffer); | ||
if (key === PLAIN_OBJECT) { | ||
return sharedObject; | ||
} | ||
if (key === PLAIN_OBJECT) { | ||
return sharedObject; | ||
} | ||
return sharedObject[key]; | ||
return sharedObject[key]; | ||
}; | ||
WorkerMap.prototype.delete = function (key) { | ||
const valueBuffer = this[VALUE_BUFFER]; | ||
const valueBuffer = this[VALUE_BUFFER]; | ||
lock(valueBuffer); | ||
lock(valueBuffer); | ||
const sharedObject = loadPlainSharedObject(valueBuffer) | ||
if (sharedObject[key] === undefined) { | ||
unlock(valueBuffer); | ||
const sharedObject = loadPlainSharedObject(valueBuffer); | ||
return false; | ||
} | ||
delete sharedObject[key]; | ||
if (sharedObject[key] === undefined) { | ||
unlock(valueBuffer); | ||
saveObjectInBuffer(sharedObject, valueBuffer); | ||
return false; | ||
} | ||
unlock(valueBuffer); | ||
delete sharedObject[key]; | ||
this[MAP_SIZE] = Object.keys(sharedObject).length; | ||
saveObjectInBuffer(sharedObject, valueBuffer); | ||
return true; | ||
unlock(valueBuffer); | ||
this[MAP_SIZE] = Object.keys(sharedObject).length; | ||
return true; | ||
}; | ||
WorkerMap.prototype.size = function (key) { | ||
return this[MAP_SIZE]; | ||
WorkerMap.prototype.size = function () { | ||
return this[MAP_SIZE]; | ||
}; | ||
WorkerMap.prototype.keys = function () { | ||
const valueBuffer = this[VALUE_BUFFER]; | ||
const sharedObject = safeLoadSharedObject(valueBuffer) | ||
return Object.keys(sharedObject); | ||
const valueBuffer = this[VALUE_BUFFER]; | ||
const sharedObject = safeLoadSharedObject(valueBuffer); | ||
return Object.keys(sharedObject); | ||
}; | ||
WorkerMap.prototype.has = function (key) { | ||
const valueBuffer = this[VALUE_BUFFER]; | ||
const sharedObject = safeLoadSharedObject(valueBuffer) | ||
const valueBuffer = this[VALUE_BUFFER]; | ||
const sharedObject = safeLoadSharedObject(valueBuffer); | ||
return sharedObject[key] !== undefined; | ||
return sharedObject[key] !== undefined; | ||
}; | ||
WorkerMap.prototype.toSharedBuffer = function () { | ||
return this[SHARED_BUFFER]; | ||
return this[SHARED_BUFFER]; | ||
}; | ||
WorkerMap.prototype.toObject = function() { | ||
return this.get(PLAIN_OBJECT); | ||
WorkerMap.prototype.toObject = function () { | ||
return this.get(PLAIN_OBJECT); | ||
}; | ||
module.exports = { WorkerMap }; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18857
15
287
112
6