How to use lockHeld method in mountebank

Best JavaScript code snippet using mountebank

index.umd.js

Source:index.umd.js Github

copy

Full Screen

1(function (global, factory) {2 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :3 typeof define === 'function' && define.amd ? define(factory) :4 (global = global || self, global.default = factory());5}(this, (function () { 'use strict';6 const UINT32_MAX = 0xFFFFFFFF;7 const UINT32_UNDEFINED = 0xFFFFFFFF;8 /**9 * This is MurmurHash210 * @private11 * @param {string}12 * @return {number}13 */14 function _hash(str) {15 var16 l = str.length,17 h = 17 ^ l,18 i = 0,19 k;20 while (l >= 4) {21 k =22 ((str.charCodeAt(i) & 0xff)) |23 ((str.charCodeAt(++i) & 0xff) << 8) |24 ((str.charCodeAt(++i) & 0xff) << 16) |25 ((str.charCodeAt(++i) & 0xff) << 14);26 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));27 k ^= k >>> 14;28 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));29 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;30 l -= 4;31 ++i;32 }33 /* eslint-disable no-fallthrough */34 switch (l) {35 case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;36 case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;37 case 1: h ^= (str.charCodeAt(i) & 0xff);38 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));39 }40 /* eslint-enable no-fallthrough */41 h ^= h >>> 13;42 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));43 h ^= h >>> 15;44 h = h >>> 0;45 return (h != UINT32_UNDEFINED) ? h : 1;46 }47 function align32(v) {48 return (v & 0xFFFFFFFFFFFFC) + ((v & 0x3) ? 0x4 : 0);49 }50 const META = {51 maxSize: 0,52 keySize: 1,53 objSize: 2,54 length: 355 };56 const LOCK = {57 SHAREDREAD: 0,58 READLOCK: 1,59 READERS: 2,60 SHAREDWRITE: 3,61 WRITELOCK: 4,62 WRITERS: 563 };64 class Deadlock extends Error {65 constructor(...params) {66 super(...params);67 }68 }69 /**70 * SharedMap71 * 72 * zero-dependency73 * high-performance74 * unordered75 * Vanilla JS implementation of SharedMap,76 * a synchronous multi-threading capable,77 * fine-grain-locked with deadlock recovery,78 * static memory allocated,79 * coalesced-chaining HashMap,80 * backed by SharedArrayBuffer81 * that supports deleting82 * and is capable of auto-defragmenting itself on delete unless almost full83 * compatible with both Node.js and SharedArrayBuffer-enabled browsers84 * @author Momtchil Momtchev <momtchil@momtchev.com>85 * @see http://github.com/mmomtchev/SharedMap86 */87 class SharedMap {88 /**89 * Creates a new SharedMap90 * @param {number} maxSize - Maximum number of entries91 * @param {number} keySize - Maximum length of keys in UTF-16 codepoints92 * @param {number} objSize - Maximum length of values in UTF-16 codepoints93 * @return {SharedMap}94 */95 constructor(maxSize, keySize, objSize) {96 maxSize = align32(maxSize);97 keySize = align32(keySize);98 objSize = align32(objSize);99 if (!(maxSize > 0 && keySize > 0 && objSize > 0))100 throw new RangeError('maxSize, keySize and objSize must be positive numbers');101 this.storage = new SharedArrayBuffer(102 Object.keys(META).length * Uint32Array.BYTES_PER_ELEMENT103 + (keySize + objSize) * maxSize * Uint16Array.BYTES_PER_ELEMENT104 + maxSize * Uint32Array.BYTES_PER_ELEMENT105 + Math.ceil(maxSize / 32) * Int32Array.BYTES_PER_ELEMENT106 + Object.keys(LOCK).length * Int32Array.BYTES_PER_ELEMENT);107 let offset = 0;108 this.meta = new Uint32Array(this.storage, offset, Object.keys(META).length);109 offset += this.meta.byteLength;110 this.meta[META.maxSize] = maxSize;111 this.meta[META.keySize] = keySize;112 this.meta[META.objSize] = objSize;113 this.meta[META.length] = 0;114 this.keysData = new Uint16Array(this.storage, offset, this.meta[META.keySize] * this.meta[META.maxSize]);115 offset += this.keysData.byteLength;116 this.valuesData = new Uint16Array(this.storage, offset, this.meta[META.objSize] * this.meta[META.maxSize]);117 offset += this.valuesData.byteLength;118 this.chaining = new Uint32Array(this.storage, offset, this.meta[META.maxSize]);119 offset += this.chaining.byteLength;120 this.linelocks = new Int32Array(this.storage, offset, Math.ceil(maxSize / 32));121 offset += this.linelocks.byteLength;122 this.maplock = new Int32Array(this.storage, offset, Object.keys(LOCK).length);123 this.stats = { set: 0, delete: 0, collisions: 0, rechains: 0, get: 0, deadlock: 0 };124 }125 /**126 * Number of elements present127 * @return {number}128 */129 get length() {130 /* We do not hold a lock here */131 return Atomics.load(this.meta, META.length);132 }133 /**134 * Maximum number of elements allowed135 * @return {number}136 */137 get size() {138 return this.meta[META.maxSize];139 }140 /* eslint-disable no-constant-condition */141 /**142 * @private143 */144 _lock(l) {145 while (true) {146 let state;147 state = Atomics.exchange(this.maplock, l, 1);148 if (state == 0)149 return;150 Atomics.wait(this.maplock, l, state);151 }152 }153 /**154 * @private155 */156 _unlock(l) {157 const state = Atomics.exchange(this.maplock, l, 0);158 if (state == 0)159 throw new Error('maplock desync ' + l);160 Atomics.notify(this.maplock, l);161 }162 /**163 * @private164 */165 _lockLine(pos) {166 const bitmask = 1 << (pos % 32);167 const index = Math.floor(pos / 32);168 while (true) {169 const state = Atomics.or(this.linelocks, index, bitmask);170 if ((state & bitmask) == 0)171 return pos;172 Atomics.wait(this.linelocks, index, state);173 }174 }175 /* eslint-enable no-constant-condition */176 /**177 * @private178 */179 _unlockLine(pos) {180 const bitmask = 1 << (pos % 32);181 const notbitmask = (~bitmask) & UINT32_MAX;182 const index = Math.floor(pos / 32);183 const state = Atomics.and(this.linelocks, index, notbitmask);184 if ((state & bitmask) == 0)185 throw new Error('linelock desync ' + pos);186 Atomics.notify(this.linelocks, index);187 }188 /**189 * @private190 */191 _lockLineSliding(oldLock, newLock) {192 if (newLock <= oldLock)193 throw new Deadlock();194 this._lockLine(newLock);195 this._unlockLine(oldLock);196 return newLock;197 }198 /**199 * Acquire an exclusive lock,200 * All operations that need it, automatically acquire it,201 * Use only if you need to block all other threads from accessing the map;202 * The thread holding the lock can then call map.set(k, v, {lockHeld: true})203 * @return {void}204 */205 lockExclusive() {206 this._lock(LOCK.READLOCK);207 }208 /**209 * Release the exclusive lock210 * @return {void}211 */212 unlockExclusive() {213 this._unlock(LOCK.READLOCK);214 }215 /**216 * @private217 */218 _lockSharedRead() {219 this._lock(LOCK.SHAREDREAD);220 if (++this.maplock[LOCK.READERS] == 1)221 this._lock(LOCK.READLOCK);222 this._unlock(LOCK.SHAREDREAD);223 }224 /**225 * @private226 */227 _unlockSharedRead() {228 this._lock(LOCK.SHAREDREAD);229 if (--this.maplock[LOCK.READERS] == 0)230 this._unlock(LOCK.READLOCK);231 this._unlock(LOCK.SHAREDREAD);232 }233 /**234 * @private235 */236 _lockSharedWrite() {237 this._lockSharedRead();238 this._lock(LOCK.SHAREDWRITE);239 if (++this.maplock[LOCK.WRITERS] == 1)240 this._lock(LOCK.WRITELOCK);241 this._unlock(LOCK.SHAREDWRITE);242 }243 /**244 * @private245 */246 _unlockSharedWrite() {247 this._lock(LOCK.SHAREDWRITE);248 if (--this.maplock[LOCK.WRITERS] == 0)249 this._unlock(LOCK.WRITELOCK);250 this._unlock(LOCK.SHAREDWRITE);251 this._unlockSharedRead();252 }253 /**254 * Acquire a write lock,255 * All operations that need it, automatically acquire it,256 * Use only if you need to block all other threads from writing to the map,257 * The thread holding the lock can then call map.set(k, v, {lockHeld: true})258 * @example259 * myMap.lockWrite();260 * for (let k of myMap.keys({lockWrite: true}))261 * myMap.set(k,262 * myMap.get(k, {lockWrite: true}).toUpperCase(),263 * {lockWrite: true});264 * myMap.unlockWrite();265 * @return {void}266 */267 lockWrite() {268 this._lockSharedRead();269 this._lock(LOCK.WRITELOCK);270 }271 /**272 * Release the write lock273 * @return {void}274 */275 unlockWrite() {276 this._unlock(LOCK.WRITELOCK);277 this._unlockSharedRead();278 }279 /**280 * @private281 */282 _match(key, pos) {283 let i;284 for (i = 0; i < key.length; i++)285 if (this.keysData[pos * this.meta[META.keySize] + i] !== key.charCodeAt(i))286 break;287 return i === key.length && this.keysData[pos * this.meta[META.keySize] + i] === 0;288 }289 /**290 * @private291 */292 _decodeValue(pos) {293 const eos = this.valuesData.subarray(pos * this.meta[META.objSize], (pos + 1) * this.meta[META.objSize]).findIndex(x => x === 0);294 const end = eos < 0 ? (pos + 1) * this.meta[META.objSize] : pos * this.meta[META.objSize] + eos;295 return String.fromCharCode.apply(null, this.valuesData.subarray(pos * this.meta[META.objSize], end));296 }297 /**298 * @private299 */300 _decodeKey(pos) {301 const eos = this.keysData.subarray(pos * this.meta[META.keySize], (pos + 1) * this.meta[META.keySize]).findIndex(x => x === 0);302 const end = eos < 0 ? (pos + 1) * this.meta[META.keySize] : pos * this.meta[META.keySize] + eos;303 return String.fromCharCode.apply(null, this.keysData.subarray(pos * this.meta[META.keySize], end));304 }305 /**306 * These two are debugging aids307 * @private308 */309 /* c8 ignore next 8 */310 _decodeBucket(pos, n) {311 return `pos: ${pos}`312 + ` hash: ${this._hash(this._decodeKey(pos))}`313 + ` key: ${this._decodeKey(pos)}`314 + ` value: ${this._decodeValue(pos)}`315 + ` chain: ${this.chaining[pos]}`316 + ((n > 0 && this.chaining[pos] !== UINT32_UNDEFINED) ? '\n' + (this._decodeBucket(this.chaining[pos], n - 1)) : '');317 }318 /**319 * @private320 */321 /* c8 ignore next 5 */322 __printMap() {323 for (let i = 0; i < this.meta[META.maxSize]; i++)324 console.log(this._decodeBucket(i, 0));325 if (typeof process !== 'undefined') process.exit(1);326 }327 /**328 * @private329 */330 _write(pos, key, value) {331 let i;332 for (i = 0; i < key.length; i++)333 this.keysData[pos * this.meta[META.keySize] + i] = key.charCodeAt(i);334 this.keysData[pos * this.meta[META.keySize] + i] = 0;335 for (i = 0; i < value.length; i++)336 this.valuesData[pos * this.meta[META.objSize] + i] = value.charCodeAt(i);337 this.valuesData[pos * this.meta[META.objSize] + i] = 0;338 }339 /**340 * @private341 */342 _set(key, value, exclusive) {343 /* Hash */344 let pos = this._hash(key);345 /* Check for full table condition */346 if (Atomics.load(this.meta, META.length) === this.meta[META.maxSize])347 if (!this._find(key, exclusive))348 throw new RangeError('SharedMap is full');349 /* Find the first free bucket, remembering the last occupied one to chain it */350 let toChain;351 let slidingLock;352 exclusive || (slidingLock = this._lockLine(pos, exclusive));353 try {354 while (this.keysData[pos * this.meta[META.keySize]] !== 0) {355 this.stats.collisions++;356 /* Replacing existing key */357 if (this._match(key, pos)) {358 let i;359 for (i = 0; i < value.length; i++)360 this.valuesData[pos * this.meta[META.objSize] + i] = value.charCodeAt(i);361 this.valuesData[pos * this.meta[META.objSize] + i] = 0;362 exclusive || this._unlockLine(slidingLock);363 return;364 }365 if (this.chaining[pos] === UINT32_UNDEFINED || toChain !== undefined) {366 /* This is the last collision element, we will chain ourselves to it */367 if (toChain == undefined) {368 toChain = pos;369 pos = (pos + 1) % this.meta[META.maxSize];370 exclusive || (slidingLock = this._lockLine(pos));371 } else {372 /* Now lets find the first free position (or a match of a preexising key) */373 pos = (pos + 1) % this.meta[META.maxSize];374 exclusive || (slidingLock = this._lockLineSliding(slidingLock, pos));375 }376 } else {377 /* We are following the collision chain here */378 pos = this.chaining[pos];379 exclusive || (slidingLock = this._lockLineSliding(slidingLock, pos));380 }381 }382 /* Copy the element into place, chaining when needed */383 this._write(pos, key, value);384 this.chaining[pos] = UINT32_UNDEFINED;385 /* Use Atomics to increase the length, we do not hold an exclusive lock here */386 Atomics.add(this.meta, META.length, 1);387 if (toChain !== undefined) {388 this.chaining[toChain] = pos;389 exclusive || this._unlockLine(toChain);390 toChain = undefined;391 }392 exclusive || this._unlockLine(slidingLock);393 } catch (e) {394 if (!exclusive) {395 this._unlockLine(slidingLock);396 if (toChain !== undefined)397 this._unlockLine(toChain);398 }399 throw e;400 }401 }402 /**403 * @typedef SharedMapOptions404 * @type {object}405 * @property {boolean} lockWrite Already holding write lock, useful when manually locking with lockWrite406 * @property {boolean} lockExclusive Already holding exclusive lock, useful when manually locking with lockExclusive407 */408 /**409 * Add/replace an element, fully thread-safe, multiple get/set can execute in parallel410 * @param {string} key411 * @param {string|number} value412 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite413 * @throws {RangeError} when the map is full414 * @throws {RangeError} when the input values do not fit415 * @throws {TypeError} when the input values are of a wrong type416 * @return {void}417 */418 set(key, value, opt) {419 if (typeof key !== 'string' || key.length === 0)420 throw new TypeError(`SharedMap keys must be non-emptry strings, invalid key ${key}`);421 if (typeof value === 'number')422 value = value.toString();423 if (typeof value !== 'string')424 throw new TypeError('SharedMap can contain only strings and numbers which will be converted to strings');425 if (key.length > this.meta[META.keySize])426 throw new RangeError(`SharedMap key ${key} does not fit in ${this.meta[META.keySize] * Uint16Array.BYTES_PER_ELEMENT} bytes, ${this.meta[META.keySize]} UTF-16 code points`);427 if (value.length > this.meta[META.objSize])428 throw new RangeError(`SharedMap value ${value} does not fit in ${this.meta[META.objSize] * Uint16Array.BYTES_PER_ELEMENT} bytes, ${this.meta[META.objSize]} UTF-16 code points`);429 const lockHeld = opt && (opt.lockWrite || opt.lockExclusive);430 this.stats.set++;431 lockHeld || this._lockSharedWrite();432 try {433 this._set(key, value, lockHeld);434 lockHeld || this._unlockSharedWrite();435 } catch (e) {436 lockHeld || this._unlockSharedWrite();437 if (e instanceof Deadlock && !lockHeld) {438 this.lockExclusive();439 this.stats.deadlock++;440 try {441 this._set(key, value, true);442 this.unlockExclusive();443 } catch (e) {444 this.unlockExclusive();445 throw e;446 }447 } else448 throw e;449 }450 }451 /**452 * @private453 */454 _find(key, exclusive) {455 let slidingLock;456 try {457 /* Hash */458 let pos = this._hash(key);459 let previous = UINT32_UNDEFINED;460 this.stats.get++;461 exclusive || (slidingLock = this._lockLine(pos));462 /* Loop through the bucket chaining */463 while (pos !== UINT32_UNDEFINED && this.keysData[pos * this.meta[META.keySize]] !== 0) {464 if (this._match(key, pos)) {465 return { pos, previous };466 }467 previous = pos;468 pos = this.chaining[pos];469 if (pos !== UINT32_UNDEFINED && !exclusive)470 slidingLock = this._lockLineSliding(slidingLock, pos);471 }472 exclusive || this._unlockLine(slidingLock);473 return undefined;474 } catch (e) {475 exclusive || this._unlockLine(slidingLock);476 throw e;477 }478 }479 /**480 * Get an element, fully thread-safe, multiple get/set can execute in parallel481 * @param {string} key482 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite483 * @return {string|undefined}484 */485 get(key, opt) {486 let pos, val;487 const lockHeld = opt && (opt.lockWrite || opt.lockExclusive);488 lockHeld || this._lockSharedRead();489 try {490 pos = this._find(key, lockHeld);491 if (pos !== undefined) {492 val = this._decodeValue(pos.pos);493 lockHeld || this._unlockLine(pos.pos);494 }495 lockHeld || this._unlockSharedRead();496 } catch (e) {497 lockHeld || this._unlockSharedRead();498 if (e instanceof Deadlock && !lockHeld) {499 this.lockExclusive();500 this.stats.deadlock++;501 try {502 pos = this._find(key, true);503 if (pos !== undefined) {504 val = this._decodeValue(pos.pos);505 }506 this.unlockExclusive();507 } catch (e) {508 this.unlockExclusive();509 throw e;510 }511 } else512 throw e;513 }514 return val;515 }516 /**517 * Find an element, fully thread-safe, identical to get(key) !== undefined518 * @param {string} key519 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite520 * @return {boolean}521 */522 has(key, opt) {523 return this.get(key, opt) !== undefined;524 }525 /**526 * @private527 */528 _hash(s) {529 if (typeof s.hash === 'function')530 return s.hash(s) % this.meta[META.maxSize];531 if (typeof s.hash === 'number')532 return s.hash % this.meta[META.maxSize];533 else534 return _hash(s) % this.meta[META.maxSize];535 }536 /**537 * Delete an element, fully thread-safe, acquires an exlusive lock and it is very expensive538 * @param {string} key539 * @param {SharedMapOptions} [opt] options, { lockExclusive: true } if manually calling lockExlusive540 * @throws {RangeError} when the key does not exit541 * @throws {Error} when calling map.delete(key, value, { lockWrite: true, lockExclusive: false })542 * @return {void}543 */544 delete(key, opt) {545 /* delete is slow */546 const lockHeld = opt && opt.lockExclusive;547 if (opt && opt.lockWrite && !lockHeld) {548 throw new Error('delete requires an exclusive lock');549 }550 let find;551 try {552 lockHeld || this.lockExclusive();553 find = this._find(key, true);554 } catch (e) {555 lockHeld || this.unlockExclusive();556 throw e;557 }558 if (find === undefined) {559 lockHeld || this.unlockExclusive();560 throw new RangeError(`SharedMap does not contain key ${key}`);561 }562 this.stats.delete++;563 const { pos, previous } = find;564 const next = this.chaining[pos];565 this.keysData[pos * this.meta[META.keySize]] = 0;566 if (previous !== UINT32_UNDEFINED)567 this.chaining[previous] = UINT32_UNDEFINED;568 Atomics.sub(this.meta, META.length, 1);569 if (next === UINT32_UNDEFINED) {570 /* There was no further chaining, just delete this element */571 /* and unchain it from the previous */572 lockHeld || this.unlockExclusive();573 return;574 }575 /* Full rechaining */576 /* Some slight optimization avoiding copying some elements around577 * is possible, but the O(n) complexity is not578 */579 this.stats.rechains++;580 let el = next;581 let chain = [];582 while (el !== UINT32_UNDEFINED) {583 chain.push({ key: this._decodeKey(el), value: this._decodeValue(el) });584 this.keysData[el * this.meta[META.keySize]] = 0;585 Atomics.sub(this.meta, META.length, 1);586 el = this.chaining[el];587 }588 for (el of chain) {589 this._set(el.key, el.value, true);590 }591 lockHeld || this.unlockExclusive();592 }593 /**594 * @private595 */596 *_keys(exclusive) {597 for (let pos = 0; pos < this.meta[META.maxSize]; pos++) {598 exclusive || this._lockSharedRead();599 exclusive || this._lockLine(pos);600 if (this.keysData[pos * this.meta[META.keySize]] !== 0) {601 yield pos;602 } else {603 exclusive || this._unlockLine(pos);604 exclusive || this._unlockSharedRead();605 }606 }607 }608 /**609 * A generator that can be used to iterate over the keys, thread-safe but allows610 * additions and deletions during the iteration611 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite612 * @return {Iterable}613 */614 *keys(opt) {615 const lockHeld = opt && (opt.lockWrite || opt.lockExclusive);616 for (let pos of this._keys(lockHeld)) {617 const k = this._decodeKey(pos);618 lockHeld || this._unlockLine(pos);619 lockHeld || this._unlockSharedRead();620 yield k;621 }622 }623 /**624 * @callback mapCallback callback(currentValue[, key] )}625 * map.get(key)=currentValue is guaranteed while the callback runs,626 * You shall not manipulate the map in the callback, use an explicitly-locked627 * keys() in this case (look at the example for lockWrite)628 *629 * @param {string} currentValue630 * @param {string} [key]631 */632 /**633 * A thread-safe map(). Doesn't block additions or deletions634 * between two calls of the callback,635 * all map operations are guaranteed atomic,636 * map.get(index)=currentValue is guaranteed while the callback runs,637 * You shall not manipulate the map in the callback, use an explicitly-locked638 * keys() in this case (look at the example for lockWrite)639 *640 * @param {mapCallback} cb callback641 * @param {*} [thisArg] callback will have its this set to thisArg642 * @return {Array}643 */644 map(cb, thisArg) {645 const a = [];646 for (let pos of this._keys()) {647 const k = this._decodeKey(pos);648 const v = this._decodeValue(pos);649 try {650 a.push(cb.call(thisArg, v, k));651 this._unlockLine(pos);652 this._unlockSharedRead();653 } catch (e) {654 this._unlockLine(pos);655 this._unlockSharedRead();656 throw e;657 }658 }659 return a;660 }661 /**662 * @callback reduceCallback callback(accumulator, currentValue[, key] )}663 * all map operations are guaranteed atomic,664 * map.get(key)=currentValue is guaranteed while the callback runs,665 * You shall not manipulate the map in the callback, use an explicitly-locked666 * keys() in this case (look at the example for lockWrite)667 *668 * @param accumulator669 * @param {string} currentValue670 * @param {string} [key]671 */672 /**673 * A thread-safe reduce(). Doesn't block additions or deletions674 * between two calls of the callback,675 * map.get(key)=currentValue is guaranteed while the callback runs,676 * You shall not manipulate the map in the callback, use an explicitly-locked677 * keys() in this case (look at the example for lockWrite)678 *679 * @param {reduceCallback} cb callback680 * @param {*} initialValue initial value of the accumulator681 * @return {*}682 */683 reduce(cb, initialValue) {684 let a = initialValue;685 for (let pos of this._keys(false)) {686 const k = this._decodeKey(pos);687 const v = this._decodeValue(pos);688 try {689 a = cb(a, v, k);690 this._unlockLine(pos);691 this._unlockSharedRead();692 } catch (e) {693 this._unlockLine(pos);694 this._unlockSharedRead();695 throw e;696 }697 }698 return a;699 }700 /**701 * Clear the SharedMap702 * @return {void}703 */704 clear() {705 this.lockExclusive();706 this.keysData.fill(0);707 this.valuesData.fill(0);708 Atomics.store(this.meta, META.length, 0);709 this.unlockExclusive();710 }711 }712 return SharedMap;...

Full Screen

Full Screen

index.js

Source:index.js Github

copy

Full Screen

1'use strict';2const UINT32_MAX = 0xFFFFFFFF;3const UINT32_UNDEFINED = 0xFFFFFFFF;4/**5 * This is MurmurHash26 * @private7 * @param {string}8 * @return {number}9 */10function _hash(str) {11 var12 l = str.length,13 h = 17 ^ l,14 i = 0,15 k;16 while (l >= 4) {17 k =18 ((str.charCodeAt(i) & 0xff)) |19 ((str.charCodeAt(++i) & 0xff) << 8) |20 ((str.charCodeAt(++i) & 0xff) << 16) |21 ((str.charCodeAt(++i) & 0xff) << 14);22 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));23 k ^= k >>> 14;24 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));25 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;26 l -= 4;27 ++i;28 }29 /* eslint-disable no-fallthrough */30 switch (l) {31 case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;32 case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;33 case 1: h ^= (str.charCodeAt(i) & 0xff);34 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));35 }36 /* eslint-enable no-fallthrough */37 h ^= h >>> 13;38 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));39 h ^= h >>> 15;40 h = h >>> 0;41 return (h != UINT32_UNDEFINED) ? h : 1;42}43function align32(v) {44 return (v & 0xFFFFFFFFFFFFC) + ((v & 0x3) ? 0x4 : 0);45}46const META = {47 maxSize: 0,48 keySize: 1,49 objSize: 2,50 length: 351};52const LOCK = {53 SHAREDREAD: 0,54 READLOCK: 1,55 READERS: 2,56 SHAREDWRITE: 3,57 WRITELOCK: 4,58 WRITERS: 559};60class Deadlock extends Error {61 constructor(...params) {62 super(...params);63 }64}65/**66 * SharedMap67 * 68 * zero-dependency69 * high-performance70 * unordered71 * Vanilla JS implementation of SharedMap,72 * a synchronous multi-threading capable,73 * fine-grain-locked with deadlock recovery,74 * static memory allocated,75 * coalesced-chaining HashMap,76 * backed by SharedArrayBuffer77 * that supports deleting78 * and is capable of auto-defragmenting itself on delete unless almost full79 * compatible with both Node.js and SharedArrayBuffer-enabled browsers80 * @author Momtchil Momtchev <momtchil@momtchev.com>81 * @see http://github.com/mmomtchev/SharedMap82 */83export default class SharedMap {84 /**85 * Creates a new SharedMap86 * @param {number} maxSize - Maximum number of entries87 * @param {number} keySize - Maximum length of keys in UTF-16 codepoints88 * @param {number} objSize - Maximum length of values in UTF-16 codepoints89 * @return {SharedMap}90 */91 constructor(maxSize, keySize, objSize) {92 maxSize = align32(maxSize);93 keySize = align32(keySize);94 objSize = align32(objSize);95 if (!(maxSize > 0 && keySize > 0 && objSize > 0))96 throw new RangeError('maxSize, keySize and objSize must be positive numbers');97 this.storage = new SharedArrayBuffer(98 Object.keys(META).length * Uint32Array.BYTES_PER_ELEMENT99 + (keySize + objSize) * maxSize * Uint16Array.BYTES_PER_ELEMENT100 + maxSize * Uint32Array.BYTES_PER_ELEMENT101 + Math.ceil(maxSize / 32) * Int32Array.BYTES_PER_ELEMENT102 + Object.keys(LOCK).length * Int32Array.BYTES_PER_ELEMENT);103 let offset = 0;104 this.meta = new Uint32Array(this.storage, offset, Object.keys(META).length);105 offset += this.meta.byteLength;106 this.meta[META.maxSize] = maxSize;107 this.meta[META.keySize] = keySize;108 this.meta[META.objSize] = objSize;109 this.meta[META.length] = 0;110 this.keysData = new Uint16Array(this.storage, offset, this.meta[META.keySize] * this.meta[META.maxSize]);111 offset += this.keysData.byteLength;112 this.valuesData = new Uint16Array(this.storage, offset, this.meta[META.objSize] * this.meta[META.maxSize]);113 offset += this.valuesData.byteLength;114 this.chaining = new Uint32Array(this.storage, offset, this.meta[META.maxSize]);115 offset += this.chaining.byteLength;116 this.linelocks = new Int32Array(this.storage, offset, Math.ceil(maxSize / 32));117 offset += this.linelocks.byteLength;118 this.maplock = new Int32Array(this.storage, offset, Object.keys(LOCK).length);119 this.stats = { set: 0, delete: 0, collisions: 0, rechains: 0, get: 0, deadlock: 0 };120 }121 /**122 * Number of elements present123 * @return {number}124 */125 get length() {126 /* We do not hold a lock here */127 return Atomics.load(this.meta, META.length);128 }129 /**130 * Maximum number of elements allowed131 * @return {number}132 */133 get size() {134 return this.meta[META.maxSize];135 }136 /* eslint-disable no-constant-condition */137 /**138 * @private139 */140 _lock(l) {141 while (true) {142 let state;143 state = Atomics.exchange(this.maplock, l, 1);144 if (state == 0)145 return;146 Atomics.wait(this.maplock, l, state);147 }148 }149 /**150 * @private151 */152 _unlock(l) {153 const state = Atomics.exchange(this.maplock, l, 0);154 if (state == 0)155 throw new Error('maplock desync ' + l);156 Atomics.notify(this.maplock, l);157 }158 /**159 * @private160 */161 _lockLine(pos) {162 const bitmask = 1 << (pos % 32);163 const index = Math.floor(pos / 32);164 while (true) {165 const state = Atomics.or(this.linelocks, index, bitmask);166 if ((state & bitmask) == 0)167 return pos;168 Atomics.wait(this.linelocks, index, state);169 }170 }171 /* eslint-enable no-constant-condition */172 /**173 * @private174 */175 _unlockLine(pos) {176 const bitmask = 1 << (pos % 32);177 const notbitmask = (~bitmask) & UINT32_MAX;178 const index = Math.floor(pos / 32);179 const state = Atomics.and(this.linelocks, index, notbitmask);180 if ((state & bitmask) == 0)181 throw new Error('linelock desync ' + pos);182 Atomics.notify(this.linelocks, index);183 }184 /**185 * @private186 */187 _lockLineSliding(oldLock, newLock) {188 if (newLock <= oldLock)189 throw new Deadlock();190 this._lockLine(newLock);191 this._unlockLine(oldLock);192 return newLock;193 }194 /**195 * Acquire an exclusive lock,196 * All operations that need it, automatically acquire it,197 * Use only if you need to block all other threads from accessing the map;198 * The thread holding the lock can then call map.set(k, v, {lockHeld: true})199 * @return {void}200 */201 lockExclusive() {202 this._lock(LOCK.READLOCK);203 }204 /**205 * Release the exclusive lock206 * @return {void}207 */208 unlockExclusive() {209 this._unlock(LOCK.READLOCK);210 }211 /**212 * @private213 */214 _lockSharedRead() {215 this._lock(LOCK.SHAREDREAD);216 if (++this.maplock[LOCK.READERS] == 1)217 this._lock(LOCK.READLOCK);218 this._unlock(LOCK.SHAREDREAD);219 }220 /**221 * @private222 */223 _unlockSharedRead() {224 this._lock(LOCK.SHAREDREAD);225 if (--this.maplock[LOCK.READERS] == 0)226 this._unlock(LOCK.READLOCK);227 this._unlock(LOCK.SHAREDREAD);228 }229 /**230 * @private231 */232 _lockSharedWrite() {233 this._lockSharedRead();234 this._lock(LOCK.SHAREDWRITE);235 if (++this.maplock[LOCK.WRITERS] == 1)236 this._lock(LOCK.WRITELOCK);237 this._unlock(LOCK.SHAREDWRITE);238 }239 /**240 * @private241 */242 _unlockSharedWrite() {243 this._lock(LOCK.SHAREDWRITE);244 if (--this.maplock[LOCK.WRITERS] == 0)245 this._unlock(LOCK.WRITELOCK);246 this._unlock(LOCK.SHAREDWRITE);247 this._unlockSharedRead();248 }249 /**250 * Acquire a write lock,251 * All operations that need it, automatically acquire it,252 * Use only if you need to block all other threads from writing to the map,253 * The thread holding the lock can then call map.set(k, v, {lockHeld: true})254 * @example255 * myMap.lockWrite();256 * for (let k of myMap.keys({lockWrite: true}))257 * myMap.set(k,258 * myMap.get(k, {lockWrite: true}).toUpperCase(),259 * {lockWrite: true});260 * myMap.unlockWrite();261 * @return {void}262 */263 lockWrite() {264 this._lockSharedRead();265 this._lock(LOCK.WRITELOCK);266 }267 /**268 * Release the write lock269 * @return {void}270 */271 unlockWrite() {272 this._unlock(LOCK.WRITELOCK);273 this._unlockSharedRead();274 }275 /**276 * @private277 */278 _match(key, pos) {279 let i;280 for (i = 0; i < key.length; i++)281 if (this.keysData[pos * this.meta[META.keySize] + i] !== key.charCodeAt(i))282 break;283 return i === key.length && this.keysData[pos * this.meta[META.keySize] + i] === 0;284 }285 /**286 * @private287 */288 _decodeValue(pos) {289 const eos = this.valuesData.subarray(pos * this.meta[META.objSize], (pos + 1) * this.meta[META.objSize]).findIndex(x => x === 0);290 const end = eos < 0 ? (pos + 1) * this.meta[META.objSize] : pos * this.meta[META.objSize] + eos;291 return String.fromCharCode.apply(null, this.valuesData.subarray(pos * this.meta[META.objSize], end));292 }293 /**294 * @private295 */296 _decodeKey(pos) {297 const eos = this.keysData.subarray(pos * this.meta[META.keySize], (pos + 1) * this.meta[META.keySize]).findIndex(x => x === 0);298 const end = eos < 0 ? (pos + 1) * this.meta[META.keySize] : pos * this.meta[META.keySize] + eos;299 return String.fromCharCode.apply(null, this.keysData.subarray(pos * this.meta[META.keySize], end));300 }301 /**302 * These two are debugging aids303 * @private304 */305 /* c8 ignore next 8 */306 _decodeBucket(pos, n) {307 return `pos: ${pos}`308 + ` hash: ${this._hash(this._decodeKey(pos))}`309 + ` key: ${this._decodeKey(pos)}`310 + ` value: ${this._decodeValue(pos)}`311 + ` chain: ${this.chaining[pos]}`312 + ((n > 0 && this.chaining[pos] !== UINT32_UNDEFINED) ? '\n' + (this._decodeBucket(this.chaining[pos], n - 1)) : '');313 }314 /**315 * @private316 */317 /* c8 ignore next 5 */318 __printMap() {319 for (let i = 0; i < this.meta[META.maxSize]; i++)320 console.log(this._decodeBucket(i, 0));321 if (typeof process !== 'undefined') process.exit(1);322 }323 /**324 * @private325 */326 _write(pos, key, value) {327 let i;328 for (i = 0; i < key.length; i++)329 this.keysData[pos * this.meta[META.keySize] + i] = key.charCodeAt(i);330 this.keysData[pos * this.meta[META.keySize] + i] = 0;331 for (i = 0; i < value.length; i++)332 this.valuesData[pos * this.meta[META.objSize] + i] = value.charCodeAt(i);333 this.valuesData[pos * this.meta[META.objSize] + i] = 0;334 }335 /**336 * @private337 */338 _set(key, value, exclusive) {339 /* Hash */340 let pos = this._hash(key);341 /* Check for full table condition */342 if (Atomics.load(this.meta, META.length) === this.meta[META.maxSize])343 if (!this._find(key, exclusive))344 throw new RangeError('SharedMap is full');345 /* Find the first free bucket, remembering the last occupied one to chain it */346 let toChain;347 let slidingLock;348 exclusive || (slidingLock = this._lockLine(pos, exclusive));349 try {350 while (this.keysData[pos * this.meta[META.keySize]] !== 0) {351 this.stats.collisions++;352 /* Replacing existing key */353 if (this._match(key, pos)) {354 let i;355 for (i = 0; i < value.length; i++)356 this.valuesData[pos * this.meta[META.objSize] + i] = value.charCodeAt(i);357 this.valuesData[pos * this.meta[META.objSize] + i] = 0;358 exclusive || this._unlockLine(slidingLock);359 return;360 }361 if (this.chaining[pos] === UINT32_UNDEFINED || toChain !== undefined) {362 /* This is the last collision element, we will chain ourselves to it */363 if (toChain == undefined) {364 toChain = pos;365 pos = (pos + 1) % this.meta[META.maxSize];366 exclusive || (slidingLock = this._lockLine(pos));367 } else {368 /* Now lets find the first free position (or a match of a preexising key) */369 pos = (pos + 1) % this.meta[META.maxSize];370 exclusive || (slidingLock = this._lockLineSliding(slidingLock, pos));371 }372 } else {373 /* We are following the collision chain here */374 pos = this.chaining[pos];375 exclusive || (slidingLock = this._lockLineSliding(slidingLock, pos));376 }377 }378 /* Copy the element into place, chaining when needed */379 this._write(pos, key, value);380 this.chaining[pos] = UINT32_UNDEFINED;381 /* Use Atomics to increase the length, we do not hold an exclusive lock here */382 Atomics.add(this.meta, META.length, 1);383 if (toChain !== undefined) {384 this.chaining[toChain] = pos;385 exclusive || this._unlockLine(toChain);386 toChain = undefined;387 }388 exclusive || this._unlockLine(slidingLock);389 } catch (e) {390 if (!exclusive) {391 this._unlockLine(slidingLock);392 if (toChain !== undefined)393 this._unlockLine(toChain);394 }395 throw e;396 }397 }398 /**399 * @typedef SharedMapOptions400 * @type {object}401 * @property {boolean} lockWrite Already holding write lock, useful when manually locking with lockWrite402 * @property {boolean} lockExclusive Already holding exclusive lock, useful when manually locking with lockExclusive403 */404 /**405 * Add/replace an element, fully thread-safe, multiple get/set can execute in parallel406 * @param {string} key407 * @param {string|number} value408 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite409 * @throws {RangeError} when the map is full410 * @throws {RangeError} when the input values do not fit411 * @throws {TypeError} when the input values are of a wrong type412 * @return {void}413 */414 set(key, value, opt) {415 if (typeof key !== 'string' || key.length === 0)416 throw new TypeError(`SharedMap keys must be non-emptry strings, invalid key ${key}`);417 if (typeof value === 'number')418 value = value.toString();419 if (typeof value !== 'string')420 throw new TypeError('SharedMap can contain only strings and numbers which will be converted to strings');421 if (key.length > this.meta[META.keySize])422 throw new RangeError(`SharedMap key ${key} does not fit in ${this.meta[META.keySize] * Uint16Array.BYTES_PER_ELEMENT} bytes, ${this.meta[META.keySize]} UTF-16 code points`);423 if (value.length > this.meta[META.objSize])424 throw new RangeError(`SharedMap value ${value} does not fit in ${this.meta[META.objSize] * Uint16Array.BYTES_PER_ELEMENT} bytes, ${this.meta[META.objSize]} UTF-16 code points`);425 const lockHeld = opt && (opt.lockWrite || opt.lockExclusive);426 this.stats.set++;427 lockHeld || this._lockSharedWrite();428 try {429 this._set(key, value, lockHeld);430 lockHeld || this._unlockSharedWrite();431 } catch (e) {432 lockHeld || this._unlockSharedWrite();433 if (e instanceof Deadlock && !lockHeld) {434 this.lockExclusive();435 this.stats.deadlock++;436 try {437 this._set(key, value, true);438 this.unlockExclusive();439 } catch (e) {440 this.unlockExclusive();441 throw e;442 }443 } else444 throw e;445 }446 }447 /**448 * @private449 */450 _find(key, exclusive) {451 let slidingLock;452 try {453 /* Hash */454 let pos = this._hash(key);455 let previous = UINT32_UNDEFINED;456 this.stats.get++;457 exclusive || (slidingLock = this._lockLine(pos));458 /* Loop through the bucket chaining */459 while (pos !== UINT32_UNDEFINED && this.keysData[pos * this.meta[META.keySize]] !== 0) {460 if (this._match(key, pos)) {461 return { pos, previous };462 }463 previous = pos;464 pos = this.chaining[pos];465 if (pos !== UINT32_UNDEFINED && !exclusive)466 slidingLock = this._lockLineSliding(slidingLock, pos);467 }468 exclusive || this._unlockLine(slidingLock);469 return undefined;470 } catch (e) {471 exclusive || this._unlockLine(slidingLock);472 throw e;473 }474 }475 /**476 * Get an element, fully thread-safe, multiple get/set can execute in parallel477 * @param {string} key478 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite479 * @return {string|undefined}480 */481 get(key, opt) {482 let pos, val;483 const lockHeld = opt && (opt.lockWrite || opt.lockExclusive);484 lockHeld || this._lockSharedRead();485 try {486 pos = this._find(key, lockHeld);487 if (pos !== undefined) {488 val = this._decodeValue(pos.pos);489 lockHeld || this._unlockLine(pos.pos);490 }491 lockHeld || this._unlockSharedRead();492 } catch (e) {493 lockHeld || this._unlockSharedRead();494 if (e instanceof Deadlock && !lockHeld) {495 this.lockExclusive();496 this.stats.deadlock++;497 try {498 pos = this._find(key, true);499 if (pos !== undefined) {500 val = this._decodeValue(pos.pos);501 }502 this.unlockExclusive();503 } catch (e) {504 this.unlockExclusive();505 throw e;506 }507 } else508 throw e;509 }510 return val;511 }512 /**513 * Find an element, fully thread-safe, identical to get(key) !== undefined514 * @param {string} key515 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite516 * @return {boolean}517 */518 has(key, opt) {519 return this.get(key, opt) !== undefined;520 }521 /**522 * @private523 */524 _hash(s) {525 if (typeof s.hash === 'function')526 return s.hash(s) % this.meta[META.maxSize];527 if (typeof s.hash === 'number')528 return s.hash % this.meta[META.maxSize];529 else530 return _hash(s) % this.meta[META.maxSize];531 }532 /**533 * Delete an element, fully thread-safe, acquires an exlusive lock and it is very expensive534 * @param {string} key535 * @param {SharedMapOptions} [opt] options, { lockExclusive: true } if manually calling lockExlusive536 * @throws {RangeError} when the key does not exit537 * @throws {Error} when calling map.delete(key, value, { lockWrite: true, lockExclusive: false })538 * @return {void}539 */540 delete(key, opt) {541 /* delete is slow */542 const lockHeld = opt && opt.lockExclusive;543 if (opt && opt.lockWrite && !lockHeld) {544 throw new Error('delete requires an exclusive lock');545 }546 let find;547 try {548 lockHeld || this.lockExclusive();549 find = this._find(key, true);550 } catch (e) {551 lockHeld || this.unlockExclusive();552 throw e;553 }554 if (find === undefined) {555 lockHeld || this.unlockExclusive();556 throw new RangeError(`SharedMap does not contain key ${key}`);557 }558 this.stats.delete++;559 const { pos, previous } = find;560 const next = this.chaining[pos];561 this.keysData[pos * this.meta[META.keySize]] = 0;562 if (previous !== UINT32_UNDEFINED)563 this.chaining[previous] = UINT32_UNDEFINED;564 Atomics.sub(this.meta, META.length, 1);565 if (next === UINT32_UNDEFINED) {566 /* There was no further chaining, just delete this element */567 /* and unchain it from the previous */568 lockHeld || this.unlockExclusive();569 return;570 }571 /* Full rechaining */572 /* Some slight optimization avoiding copying some elements around573 * is possible, but the O(n) complexity is not574 */575 this.stats.rechains++;576 let el = next;577 let chain = [];578 while (el !== UINT32_UNDEFINED) {579 chain.push({ key: this._decodeKey(el), value: this._decodeValue(el) });580 this.keysData[el * this.meta[META.keySize]] = 0;581 Atomics.sub(this.meta, META.length, 1);582 el = this.chaining[el];583 }584 for (el of chain) {585 this._set(el.key, el.value, true);586 }587 lockHeld || this.unlockExclusive();588 }589 /**590 * @private591 */592 *_keys(exclusive) {593 for (let pos = 0; pos < this.meta[META.maxSize]; pos++) {594 exclusive || this._lockSharedRead();595 exclusive || this._lockLine(pos);596 if (this.keysData[pos * this.meta[META.keySize]] !== 0) {597 yield pos;598 } else {599 exclusive || this._unlockLine(pos);600 exclusive || this._unlockSharedRead();601 }602 }603 }604 /**605 * A generator that can be used to iterate over the keys, thread-safe but allows606 * additions and deletions during the iteration607 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite608 * @return {Iterable}609 */610 *keys(opt) {611 const lockHeld = opt && (opt.lockWrite || opt.lockExclusive);612 for (let pos of this._keys(lockHeld)) {613 const k = this._decodeKey(pos);614 lockHeld || this._unlockLine(pos);615 lockHeld || this._unlockSharedRead();616 yield k;617 }618 }619 /**620 * @callback mapCallback callback(currentValue[, key] )}621 * map.get(key)=currentValue is guaranteed while the callback runs,622 * You shall not manipulate the map in the callback, use an explicitly-locked623 * keys() in this case (look at the example for lockWrite)624 *625 * @param {string} currentValue626 * @param {string} [key]627 */628 /**629 * A thread-safe map(). Doesn't block additions or deletions630 * between two calls of the callback,631 * all map operations are guaranteed atomic,632 * map.get(index)=currentValue is guaranteed while the callback runs,633 * You shall not manipulate the map in the callback, use an explicitly-locked634 * keys() in this case (look at the example for lockWrite)635 *636 * @param {mapCallback} cb callback637 * @param {*} [thisArg] callback will have its this set to thisArg638 * @return {Array}639 */640 map(cb, thisArg) {641 const a = [];642 for (let pos of this._keys()) {643 const k = this._decodeKey(pos);644 const v = this._decodeValue(pos);645 try {646 a.push(cb.call(thisArg, v, k));647 this._unlockLine(pos);648 this._unlockSharedRead();649 } catch (e) {650 this._unlockLine(pos);651 this._unlockSharedRead();652 throw e;653 }654 }655 return a;656 }657 /**658 * @callback reduceCallback callback(accumulator, currentValue[, key] )}659 * all map operations are guaranteed atomic,660 * map.get(key)=currentValue is guaranteed while the callback runs,661 * You shall not manipulate the map in the callback, use an explicitly-locked662 * keys() in this case (look at the example for lockWrite)663 *664 * @param accumulator665 * @param {string} currentValue666 * @param {string} [key]667 */668 /**669 * A thread-safe reduce(). Doesn't block additions or deletions670 * between two calls of the callback,671 * map.get(key)=currentValue is guaranteed while the callback runs,672 * You shall not manipulate the map in the callback, use an explicitly-locked673 * keys() in this case (look at the example for lockWrite)674 *675 * @param {reduceCallback} cb callback676 * @param {*} initialValue initial value of the accumulator677 * @return {*}678 */679 reduce(cb, initialValue) {680 let a = initialValue;681 for (let pos of this._keys(false)) {682 const k = this._decodeKey(pos);683 const v = this._decodeValue(pos);684 try {685 a = cb(a, v, k);686 this._unlockLine(pos);687 this._unlockSharedRead();688 } catch (e) {689 this._unlockLine(pos);690 this._unlockSharedRead();691 throw e;692 }693 }694 return a;695 }696 /**697 * Clear the SharedMap698 * @return {void}699 */700 clear() {701 this.lockExclusive();702 this.keysData.fill(0);703 this.valuesData.fill(0);704 Atomics.store(this.meta, META.length, 0);705 this.unlockExclusive();706 }...

Full Screen

Full Screen

index.es.js

Source:index.es.js Github

copy

Full Screen

1const UINT32_MAX = 0xFFFFFFFF;2const UINT32_UNDEFINED = 0xFFFFFFFF;3/**4 * This is MurmurHash25 * @private6 * @param {string}7 * @return {number}8 */9function _hash(str) {10 var11 l = str.length,12 h = 17 ^ l,13 i = 0,14 k;15 while (l >= 4) {16 k =17 ((str.charCodeAt(i) & 0xff)) |18 ((str.charCodeAt(++i) & 0xff) << 8) |19 ((str.charCodeAt(++i) & 0xff) << 16) |20 ((str.charCodeAt(++i) & 0xff) << 14);21 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));22 k ^= k >>> 14;23 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));24 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;25 l -= 4;26 ++i;27 }28 /* eslint-disable no-fallthrough */29 switch (l) {30 case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;31 case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;32 case 1: h ^= (str.charCodeAt(i) & 0xff);33 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));34 }35 /* eslint-enable no-fallthrough */36 h ^= h >>> 13;37 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));38 h ^= h >>> 15;39 h = h >>> 0;40 return (h != UINT32_UNDEFINED) ? h : 1;41}42function align32(v) {43 return (v & 0xFFFFFFFFFFFFC) + ((v & 0x3) ? 0x4 : 0);44}45const META = {46 maxSize: 0,47 keySize: 1,48 objSize: 2,49 length: 350};51const LOCK = {52 SHAREDREAD: 0,53 READLOCK: 1,54 READERS: 2,55 SHAREDWRITE: 3,56 WRITELOCK: 4,57 WRITERS: 558};59class Deadlock extends Error {60 constructor(...params) {61 super(...params);62 }63}64/**65 * SharedMap66 * 67 * zero-dependency68 * high-performance69 * unordered70 * Vanilla JS implementation of SharedMap,71 * a synchronous multi-threading capable,72 * fine-grain-locked with deadlock recovery,73 * static memory allocated,74 * coalesced-chaining HashMap,75 * backed by SharedArrayBuffer76 * that supports deleting77 * and is capable of auto-defragmenting itself on delete unless almost full78 * compatible with both Node.js and SharedArrayBuffer-enabled browsers79 * @author Momtchil Momtchev <momtchil@momtchev.com>80 * @see http://github.com/mmomtchev/SharedMap81 */82class SharedMap {83 /**84 * Creates a new SharedMap85 * @param {number} maxSize - Maximum number of entries86 * @param {number} keySize - Maximum length of keys in UTF-16 codepoints87 * @param {number} objSize - Maximum length of values in UTF-16 codepoints88 * @return {SharedMap}89 */90 constructor(maxSize, keySize, objSize) {91 maxSize = align32(maxSize);92 keySize = align32(keySize);93 objSize = align32(objSize);94 if (!(maxSize > 0 && keySize > 0 && objSize > 0))95 throw new RangeError('maxSize, keySize and objSize must be positive numbers');96 this.storage = new SharedArrayBuffer(97 Object.keys(META).length * Uint32Array.BYTES_PER_ELEMENT98 + (keySize + objSize) * maxSize * Uint16Array.BYTES_PER_ELEMENT99 + maxSize * Uint32Array.BYTES_PER_ELEMENT100 + Math.ceil(maxSize / 32) * Int32Array.BYTES_PER_ELEMENT101 + Object.keys(LOCK).length * Int32Array.BYTES_PER_ELEMENT);102 let offset = 0;103 this.meta = new Uint32Array(this.storage, offset, Object.keys(META).length);104 offset += this.meta.byteLength;105 this.meta[META.maxSize] = maxSize;106 this.meta[META.keySize] = keySize;107 this.meta[META.objSize] = objSize;108 this.meta[META.length] = 0;109 this.keysData = new Uint16Array(this.storage, offset, this.meta[META.keySize] * this.meta[META.maxSize]);110 offset += this.keysData.byteLength;111 this.valuesData = new Uint16Array(this.storage, offset, this.meta[META.objSize] * this.meta[META.maxSize]);112 offset += this.valuesData.byteLength;113 this.chaining = new Uint32Array(this.storage, offset, this.meta[META.maxSize]);114 offset += this.chaining.byteLength;115 this.linelocks = new Int32Array(this.storage, offset, Math.ceil(maxSize / 32));116 offset += this.linelocks.byteLength;117 this.maplock = new Int32Array(this.storage, offset, Object.keys(LOCK).length);118 this.stats = { set: 0, delete: 0, collisions: 0, rechains: 0, get: 0, deadlock: 0 };119 }120 /**121 * Number of elements present122 * @return {number}123 */124 get length() {125 /* We do not hold a lock here */126 return Atomics.load(this.meta, META.length);127 }128 /**129 * Maximum number of elements allowed130 * @return {number}131 */132 get size() {133 return this.meta[META.maxSize];134 }135 /* eslint-disable no-constant-condition */136 /**137 * @private138 */139 _lock(l) {140 while (true) {141 let state;142 state = Atomics.exchange(this.maplock, l, 1);143 if (state == 0)144 return;145 Atomics.wait(this.maplock, l, state);146 }147 }148 /**149 * @private150 */151 _unlock(l) {152 const state = Atomics.exchange(this.maplock, l, 0);153 if (state == 0)154 throw new Error('maplock desync ' + l);155 Atomics.notify(this.maplock, l);156 }157 /**158 * @private159 */160 _lockLine(pos) {161 const bitmask = 1 << (pos % 32);162 const index = Math.floor(pos / 32);163 while (true) {164 const state = Atomics.or(this.linelocks, index, bitmask);165 if ((state & bitmask) == 0)166 return pos;167 Atomics.wait(this.linelocks, index, state);168 }169 }170 /* eslint-enable no-constant-condition */171 /**172 * @private173 */174 _unlockLine(pos) {175 const bitmask = 1 << (pos % 32);176 const notbitmask = (~bitmask) & UINT32_MAX;177 const index = Math.floor(pos / 32);178 const state = Atomics.and(this.linelocks, index, notbitmask);179 if ((state & bitmask) == 0)180 throw new Error('linelock desync ' + pos);181 Atomics.notify(this.linelocks, index);182 }183 /**184 * @private185 */186 _lockLineSliding(oldLock, newLock) {187 if (newLock <= oldLock)188 throw new Deadlock();189 this._lockLine(newLock);190 this._unlockLine(oldLock);191 return newLock;192 }193 /**194 * Acquire an exclusive lock,195 * All operations that need it, automatically acquire it,196 * Use only if you need to block all other threads from accessing the map;197 * The thread holding the lock can then call map.set(k, v, {lockHeld: true})198 * @return {void}199 */200 lockExclusive() {201 this._lock(LOCK.READLOCK);202 }203 /**204 * Release the exclusive lock205 * @return {void}206 */207 unlockExclusive() {208 this._unlock(LOCK.READLOCK);209 }210 /**211 * @private212 */213 _lockSharedRead() {214 this._lock(LOCK.SHAREDREAD);215 if (++this.maplock[LOCK.READERS] == 1)216 this._lock(LOCK.READLOCK);217 this._unlock(LOCK.SHAREDREAD);218 }219 /**220 * @private221 */222 _unlockSharedRead() {223 this._lock(LOCK.SHAREDREAD);224 if (--this.maplock[LOCK.READERS] == 0)225 this._unlock(LOCK.READLOCK);226 this._unlock(LOCK.SHAREDREAD);227 }228 /**229 * @private230 */231 _lockSharedWrite() {232 this._lockSharedRead();233 this._lock(LOCK.SHAREDWRITE);234 if (++this.maplock[LOCK.WRITERS] == 1)235 this._lock(LOCK.WRITELOCK);236 this._unlock(LOCK.SHAREDWRITE);237 }238 /**239 * @private240 */241 _unlockSharedWrite() {242 this._lock(LOCK.SHAREDWRITE);243 if (--this.maplock[LOCK.WRITERS] == 0)244 this._unlock(LOCK.WRITELOCK);245 this._unlock(LOCK.SHAREDWRITE);246 this._unlockSharedRead();247 }248 /**249 * Acquire a write lock,250 * All operations that need it, automatically acquire it,251 * Use only if you need to block all other threads from writing to the map,252 * The thread holding the lock can then call map.set(k, v, {lockHeld: true})253 * @example254 * myMap.lockWrite();255 * for (let k of myMap.keys({lockWrite: true}))256 * myMap.set(k,257 * myMap.get(k, {lockWrite: true}).toUpperCase(),258 * {lockWrite: true});259 * myMap.unlockWrite();260 * @return {void}261 */262 lockWrite() {263 this._lockSharedRead();264 this._lock(LOCK.WRITELOCK);265 }266 /**267 * Release the write lock268 * @return {void}269 */270 unlockWrite() {271 this._unlock(LOCK.WRITELOCK);272 this._unlockSharedRead();273 }274 /**275 * @private276 */277 _match(key, pos) {278 let i;279 for (i = 0; i < key.length; i++)280 if (this.keysData[pos * this.meta[META.keySize] + i] !== key.charCodeAt(i))281 break;282 return i === key.length && this.keysData[pos * this.meta[META.keySize] + i] === 0;283 }284 /**285 * @private286 */287 _decodeValue(pos) {288 const eos = this.valuesData.subarray(pos * this.meta[META.objSize], (pos + 1) * this.meta[META.objSize]).findIndex(x => x === 0);289 const end = eos < 0 ? (pos + 1) * this.meta[META.objSize] : pos * this.meta[META.objSize] + eos;290 return String.fromCharCode.apply(null, this.valuesData.subarray(pos * this.meta[META.objSize], end));291 }292 /**293 * @private294 */295 _decodeKey(pos) {296 const eos = this.keysData.subarray(pos * this.meta[META.keySize], (pos + 1) * this.meta[META.keySize]).findIndex(x => x === 0);297 const end = eos < 0 ? (pos + 1) * this.meta[META.keySize] : pos * this.meta[META.keySize] + eos;298 return String.fromCharCode.apply(null, this.keysData.subarray(pos * this.meta[META.keySize], end));299 }300 /**301 * These two are debugging aids302 * @private303 */304 /* c8 ignore next 8 */305 _decodeBucket(pos, n) {306 return `pos: ${pos}`307 + ` hash: ${this._hash(this._decodeKey(pos))}`308 + ` key: ${this._decodeKey(pos)}`309 + ` value: ${this._decodeValue(pos)}`310 + ` chain: ${this.chaining[pos]}`311 + ((n > 0 && this.chaining[pos] !== UINT32_UNDEFINED) ? '\n' + (this._decodeBucket(this.chaining[pos], n - 1)) : '');312 }313 /**314 * @private315 */316 /* c8 ignore next 5 */317 __printMap() {318 for (let i = 0; i < this.meta[META.maxSize]; i++)319 console.log(this._decodeBucket(i, 0));320 if (typeof process !== 'undefined') process.exit(1);321 }322 /**323 * @private324 */325 _write(pos, key, value) {326 let i;327 for (i = 0; i < key.length; i++)328 this.keysData[pos * this.meta[META.keySize] + i] = key.charCodeAt(i);329 this.keysData[pos * this.meta[META.keySize] + i] = 0;330 for (i = 0; i < value.length; i++)331 this.valuesData[pos * this.meta[META.objSize] + i] = value.charCodeAt(i);332 this.valuesData[pos * this.meta[META.objSize] + i] = 0;333 }334 /**335 * @private336 */337 _set(key, value, exclusive) {338 /* Hash */339 let pos = this._hash(key);340 /* Check for full table condition */341 if (Atomics.load(this.meta, META.length) === this.meta[META.maxSize])342 if (!this._find(key, exclusive))343 throw new RangeError('SharedMap is full');344 /* Find the first free bucket, remembering the last occupied one to chain it */345 let toChain;346 let slidingLock;347 exclusive || (slidingLock = this._lockLine(pos, exclusive));348 try {349 while (this.keysData[pos * this.meta[META.keySize]] !== 0) {350 this.stats.collisions++;351 /* Replacing existing key */352 if (this._match(key, pos)) {353 let i;354 for (i = 0; i < value.length; i++)355 this.valuesData[pos * this.meta[META.objSize] + i] = value.charCodeAt(i);356 this.valuesData[pos * this.meta[META.objSize] + i] = 0;357 exclusive || this._unlockLine(slidingLock);358 return;359 }360 if (this.chaining[pos] === UINT32_UNDEFINED || toChain !== undefined) {361 /* This is the last collision element, we will chain ourselves to it */362 if (toChain == undefined) {363 toChain = pos;364 pos = (pos + 1) % this.meta[META.maxSize];365 exclusive || (slidingLock = this._lockLine(pos));366 } else {367 /* Now lets find the first free position (or a match of a preexising key) */368 pos = (pos + 1) % this.meta[META.maxSize];369 exclusive || (slidingLock = this._lockLineSliding(slidingLock, pos));370 }371 } else {372 /* We are following the collision chain here */373 pos = this.chaining[pos];374 exclusive || (slidingLock = this._lockLineSliding(slidingLock, pos));375 }376 }377 /* Copy the element into place, chaining when needed */378 this._write(pos, key, value);379 this.chaining[pos] = UINT32_UNDEFINED;380 /* Use Atomics to increase the length, we do not hold an exclusive lock here */381 Atomics.add(this.meta, META.length, 1);382 if (toChain !== undefined) {383 this.chaining[toChain] = pos;384 exclusive || this._unlockLine(toChain);385 toChain = undefined;386 }387 exclusive || this._unlockLine(slidingLock);388 } catch (e) {389 if (!exclusive) {390 this._unlockLine(slidingLock);391 if (toChain !== undefined)392 this._unlockLine(toChain);393 }394 throw e;395 }396 }397 /**398 * @typedef SharedMapOptions399 * @type {object}400 * @property {boolean} lockWrite Already holding write lock, useful when manually locking with lockWrite401 * @property {boolean} lockExclusive Already holding exclusive lock, useful when manually locking with lockExclusive402 */403 /**404 * Add/replace an element, fully thread-safe, multiple get/set can execute in parallel405 * @param {string} key406 * @param {string|number} value407 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite408 * @throws {RangeError} when the map is full409 * @throws {RangeError} when the input values do not fit410 * @throws {TypeError} when the input values are of a wrong type411 * @return {void}412 */413 set(key, value, opt) {414 if (typeof key !== 'string' || key.length === 0)415 throw new TypeError(`SharedMap keys must be non-emptry strings, invalid key ${key}`);416 if (typeof value === 'number')417 value = value.toString();418 if (typeof value !== 'string')419 throw new TypeError('SharedMap can contain only strings and numbers which will be converted to strings');420 if (key.length > this.meta[META.keySize])421 throw new RangeError(`SharedMap key ${key} does not fit in ${this.meta[META.keySize] * Uint16Array.BYTES_PER_ELEMENT} bytes, ${this.meta[META.keySize]} UTF-16 code points`);422 if (value.length > this.meta[META.objSize])423 throw new RangeError(`SharedMap value ${value} does not fit in ${this.meta[META.objSize] * Uint16Array.BYTES_PER_ELEMENT} bytes, ${this.meta[META.objSize]} UTF-16 code points`);424 const lockHeld = opt && (opt.lockWrite || opt.lockExclusive);425 this.stats.set++;426 lockHeld || this._lockSharedWrite();427 try {428 this._set(key, value, lockHeld);429 lockHeld || this._unlockSharedWrite();430 } catch (e) {431 lockHeld || this._unlockSharedWrite();432 if (e instanceof Deadlock && !lockHeld) {433 this.lockExclusive();434 this.stats.deadlock++;435 try {436 this._set(key, value, true);437 this.unlockExclusive();438 } catch (e) {439 this.unlockExclusive();440 throw e;441 }442 } else443 throw e;444 }445 }446 /**447 * @private448 */449 _find(key, exclusive) {450 let slidingLock;451 try {452 /* Hash */453 let pos = this._hash(key);454 let previous = UINT32_UNDEFINED;455 this.stats.get++;456 exclusive || (slidingLock = this._lockLine(pos));457 /* Loop through the bucket chaining */458 while (pos !== UINT32_UNDEFINED && this.keysData[pos * this.meta[META.keySize]] !== 0) {459 if (this._match(key, pos)) {460 return { pos, previous };461 }462 previous = pos;463 pos = this.chaining[pos];464 if (pos !== UINT32_UNDEFINED && !exclusive)465 slidingLock = this._lockLineSliding(slidingLock, pos);466 }467 exclusive || this._unlockLine(slidingLock);468 return undefined;469 } catch (e) {470 exclusive || this._unlockLine(slidingLock);471 throw e;472 }473 }474 /**475 * Get an element, fully thread-safe, multiple get/set can execute in parallel476 * @param {string} key477 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite478 * @return {string|undefined}479 */480 get(key, opt) {481 let pos, val;482 const lockHeld = opt && (opt.lockWrite || opt.lockExclusive);483 lockHeld || this._lockSharedRead();484 try {485 pos = this._find(key, lockHeld);486 if (pos !== undefined) {487 val = this._decodeValue(pos.pos);488 lockHeld || this._unlockLine(pos.pos);489 }490 lockHeld || this._unlockSharedRead();491 } catch (e) {492 lockHeld || this._unlockSharedRead();493 if (e instanceof Deadlock && !lockHeld) {494 this.lockExclusive();495 this.stats.deadlock++;496 try {497 pos = this._find(key, true);498 if (pos !== undefined) {499 val = this._decodeValue(pos.pos);500 }501 this.unlockExclusive();502 } catch (e) {503 this.unlockExclusive();504 throw e;505 }506 } else507 throw e;508 }509 return val;510 }511 /**512 * Find an element, fully thread-safe, identical to get(key) !== undefined513 * @param {string} key514 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite515 * @return {boolean}516 */517 has(key, opt) {518 return this.get(key, opt) !== undefined;519 }520 /**521 * @private522 */523 _hash(s) {524 if (typeof s.hash === 'function')525 return s.hash(s) % this.meta[META.maxSize];526 if (typeof s.hash === 'number')527 return s.hash % this.meta[META.maxSize];528 else529 return _hash(s) % this.meta[META.maxSize];530 }531 /**532 * Delete an element, fully thread-safe, acquires an exlusive lock and it is very expensive533 * @param {string} key534 * @param {SharedMapOptions} [opt] options, { lockExclusive: true } if manually calling lockExlusive535 * @throws {RangeError} when the key does not exit536 * @throws {Error} when calling map.delete(key, value, { lockWrite: true, lockExclusive: false })537 * @return {void}538 */539 delete(key, opt) {540 /* delete is slow */541 const lockHeld = opt && opt.lockExclusive;542 if (opt && opt.lockWrite && !lockHeld) {543 throw new Error('delete requires an exclusive lock');544 }545 let find;546 try {547 lockHeld || this.lockExclusive();548 find = this._find(key, true);549 } catch (e) {550 lockHeld || this.unlockExclusive();551 throw e;552 }553 if (find === undefined) {554 lockHeld || this.unlockExclusive();555 throw new RangeError(`SharedMap does not contain key ${key}`);556 }557 this.stats.delete++;558 const { pos, previous } = find;559 const next = this.chaining[pos];560 this.keysData[pos * this.meta[META.keySize]] = 0;561 if (previous !== UINT32_UNDEFINED)562 this.chaining[previous] = UINT32_UNDEFINED;563 Atomics.sub(this.meta, META.length, 1);564 if (next === UINT32_UNDEFINED) {565 /* There was no further chaining, just delete this element */566 /* and unchain it from the previous */567 lockHeld || this.unlockExclusive();568 return;569 }570 /* Full rechaining */571 /* Some slight optimization avoiding copying some elements around572 * is possible, but the O(n) complexity is not573 */574 this.stats.rechains++;575 let el = next;576 let chain = [];577 while (el !== UINT32_UNDEFINED) {578 chain.push({ key: this._decodeKey(el), value: this._decodeValue(el) });579 this.keysData[el * this.meta[META.keySize]] = 0;580 Atomics.sub(this.meta, META.length, 1);581 el = this.chaining[el];582 }583 for (el of chain) {584 this._set(el.key, el.value, true);585 }586 lockHeld || this.unlockExclusive();587 }588 /**589 * @private590 */591 *_keys(exclusive) {592 for (let pos = 0; pos < this.meta[META.maxSize]; pos++) {593 exclusive || this._lockSharedRead();594 exclusive || this._lockLine(pos);595 if (this.keysData[pos * this.meta[META.keySize]] !== 0) {596 yield pos;597 } else {598 exclusive || this._unlockLine(pos);599 exclusive || this._unlockSharedRead();600 }601 }602 }603 /**604 * A generator that can be used to iterate over the keys, thread-safe but allows605 * additions and deletions during the iteration606 * @param {SharedMapOptions} [opt] options, { lockWrite: true } if manually calling lockWrite607 * @return {Iterable}608 */609 *keys(opt) {610 const lockHeld = opt && (opt.lockWrite || opt.lockExclusive);611 for (let pos of this._keys(lockHeld)) {612 const k = this._decodeKey(pos);613 lockHeld || this._unlockLine(pos);614 lockHeld || this._unlockSharedRead();615 yield k;616 }617 }618 /**619 * @callback mapCallback callback(currentValue[, key] )}620 * map.get(key)=currentValue is guaranteed while the callback runs,621 * You shall not manipulate the map in the callback, use an explicitly-locked622 * keys() in this case (look at the example for lockWrite)623 *624 * @param {string} currentValue625 * @param {string} [key]626 */627 /**628 * A thread-safe map(). Doesn't block additions or deletions629 * between two calls of the callback,630 * all map operations are guaranteed atomic,631 * map.get(index)=currentValue is guaranteed while the callback runs,632 * You shall not manipulate the map in the callback, use an explicitly-locked633 * keys() in this case (look at the example for lockWrite)634 *635 * @param {mapCallback} cb callback636 * @param {*} [thisArg] callback will have its this set to thisArg637 * @return {Array}638 */639 map(cb, thisArg) {640 const a = [];641 for (let pos of this._keys()) {642 const k = this._decodeKey(pos);643 const v = this._decodeValue(pos);644 try {645 a.push(cb.call(thisArg, v, k));646 this._unlockLine(pos);647 this._unlockSharedRead();648 } catch (e) {649 this._unlockLine(pos);650 this._unlockSharedRead();651 throw e;652 }653 }654 return a;655 }656 /**657 * @callback reduceCallback callback(accumulator, currentValue[, key] )}658 * all map operations are guaranteed atomic,659 * map.get(key)=currentValue is guaranteed while the callback runs,660 * You shall not manipulate the map in the callback, use an explicitly-locked661 * keys() in this case (look at the example for lockWrite)662 *663 * @param accumulator664 * @param {string} currentValue665 * @param {string} [key]666 */667 /**668 * A thread-safe reduce(). Doesn't block additions or deletions669 * between two calls of the callback,670 * map.get(key)=currentValue is guaranteed while the callback runs,671 * You shall not manipulate the map in the callback, use an explicitly-locked672 * keys() in this case (look at the example for lockWrite)673 *674 * @param {reduceCallback} cb callback675 * @param {*} initialValue initial value of the accumulator676 * @return {*}677 */678 reduce(cb, initialValue) {679 let a = initialValue;680 for (let pos of this._keys(false)) {681 const k = this._decodeKey(pos);682 const v = this._decodeValue(pos);683 try {684 a = cb(a, v, k);685 this._unlockLine(pos);686 this._unlockSharedRead();687 } catch (e) {688 this._unlockLine(pos);689 this._unlockSharedRead();690 throw e;691 }692 }693 return a;694 }695 /**696 * Clear the SharedMap697 * @return {void}698 */699 clear() {700 this.lockExclusive();701 this.keysData.fill(0);702 this.valuesData.fill(0);703 Atomics.store(this.meta, META.length, 0);704 this.unlockExclusive();705 }706}...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1const mb = require('mountebank');2mb.create({3}, function (error, server) {4 if (error) {5 throw error;6 }7 console.log('mountebank server started on port ' + server.port);8 server.addStub({9 {10 equals: {11 }12 }13 {14 is: {15 headers: {16 },17 body: {18 }19 }20 }21 });22 server.addStub({23 {24 equals: {25 }26 }27 {28 is: {29 headers: {30 },31 body: {32 }33 }34 }35 });36 server.addStub({37 {38 equals: {39 }40 }41 {42 is: {43 headers: {44 },45 body: {46 }47 }48 }49 });50 server.addStub({51 {52 equals: {53 }54 }55 {56 is: {57 headers: {58 },59 body: {60 }61 }62 }63 });64 server.addStub({65 {66 equals: {67 }68 }69 {70 is: {71 headers: {

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2var imposter = {3 {4 { equals: { method: 'GET', path: '/test' } }5 { is: { statusCode: 200 } }6 }7};8mb.create(imposter).then(function (server) {9 console.log('Imposter created at %s', server.url);10 setTimeout(function () {11 server.stop().then(function () {12 console.log('Imposter stopped');13 });14 }, 5000);15});16Contributions are welcome. Please fork the project, create a branch, and submit a pull request. If you're not sure what to work on, check out the [open issues](

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2mb.create({port: 2525, pidfile: 'mb.pid', logfile: 'mb.log', ipWhitelist: ['*']}, function (error, imposter) {3 imposter.addStub({4 {5 equals: {6 }7 }8 {9 is: {10 }11 }12 }, function (error, data) {13 console.log(data);14 });15});16var mb = require('mountebank');17mb.create({port: 2525, pidfile: 'mb.pid', logfile: 'mb.log', ipWhitelist: ['*']}, function (error, imposter) {18 imposter.addStub({19 {20 equals: {21 }22 }23 {24 is: {25 }26 }27 }, function (error, data) {28 console.log(data);29 });30});31var mb = require('mountebank');32mb.create({port: 2525, pidfile: 'mb.pid', logfile: 'mb.log', ipWhitelist: ['*']}, function (error, imposter) {33 imposter.addStub({34 {35 equals: {36 }37 }38 {39 is: {40 }41 }42 }, function (error, data) {43 console.log(data);44 });45});46var mb = require('mountebank');47mb.create({port: 2525, pidfile: 'mb.pid', logfile: 'mb.log', ipWhitelist: ['*']}, function (error, imposter) {48 imposter.addStub({49 {50 equals: {51 }

Full Screen

Using AI Code Generation

copy

Full Screen

1const mb = require('mountebank');2const imposter = { protocol: 'http', port: 3000, stubs: [{ responses: [{ is: { body: 'Hello World!' } }] }] };3mb.create(imposter).then(() => {4 if (mb.lockHeld()) {5 console.log('Lock held');6 } else {7 console.log('Lock not held');8 }9});10### `mb.create(imposter, options)`11Creates an imposter on the mountebank server. `imposter` is an object that follows the [Imposter JSON Schema](

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2var assert = require('assert');3mb.start(2525).then(function () {4 return mb.create({5 {6 {7 is: {8 }9 }10 }11 });12}).then(function () {13 return mb.get('/imposters');14}).then(function (response) {15 assert.equal(response.statusCode, 200);16 assert.equal(response.body.imposters[0].port, 2525);17 assert.equal(response.body.imposters[0].protocol, 'http');18 assert.equal(response.body.imposters[0].stubs.length, 1);19 assert.equal(response.body.imposters[0].stubs[0].responses[0].is.statusCode, 200);20 assert.equal(response.body.imposters[0].stubs[0].responses[0].is.body, 'Hello, World!');21}).then(function () {22 return mb.del('/imposters');23}).then(function () {24 return mb.get('/imposters');25}).then(function (response) {26 assert.equal(response.statusCode, 200);27 assert.equal(response.body.imposters.length, 0);28 return mb.stop();29}).catch(function (error) {30 console.error(error);31});32{33 "scripts": {34 },35 "dependencies": {36 }37}

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2var assert = require('assert');3var port = 2525;4var mbHelper = mb.create({port: port, pidfile: 'mb.pid', logfile: 'mb.log'});5mbHelper.start().then(function () {6 console.log('mountebank started');7 return mbHelper.get('/imposters');8}).then(function (response) {9 assert.equal(response.statusCode, 200);10 return mbHelper.post('/imposters', {11 stubs: [{12 responses: [{13 is: {14 headers: {15 },16 }17 }]18 }]19 });20}).then(function (response) {21 assert.equal(response.statusCode, 201);22 return mbHelper.get('/imposters/3000');23}).then(function (response) {24 assert.equal(response.statusCode, 200);25 return mbHelper.del('/imposters/3000');26}).then(function (response) {27 assert.equal(response.statusCode, 200);28 return mbHelper.get('/imposters');29}).then(function (response) {30 assert.equal(response.statusCode, 200);31 assert.deepEqual(response.body, {imposters: []});32 return mbHelper.stop();33}).then(function () {34 console.log('mountebank stopped');35}).catch(function (error) {36 console.error(error);37});38{39 "scripts": {40 },41 "dependencies": {42 }43}

Full Screen

Using AI Code Generation

copy

Full Screen

1var imposter = require('mountebank').createImposter(2525, 'http', 3000);2imposter.addStub({3 predicates: [{equals: {method: 'GET', path: '/test'}}],4 responses: [{is: {statusCode: 200, body: 'Hello world'}}]5});6imposter.start();7imposter.lockHeld().then(function (isLocked) {8 console.log(isLocked);9 imposter.stop();10});11var imposter = require('mountebank').createImposter(2525, 'http', 3000);12imposter.addStub({13 predicates: [{equals: {method: 'GET', path: '/test'}}],14 responses: [{is: {statusCode: 200, body: 'Hello world'}}]15});16imposter.start();17imposter.lockHeld().then(function (isLocked) {18 console.log(isLocked);19 imposter.stop();20});21var imposter = require('mountebank').createImposter(2525, 'http', 3000);22imposter.addStub({23 predicates: [{equals: {method: 'GET', path: '/test'}}],24 responses: [{is: {statusCode: 200, body: 'Hello world'}}]25});26imposter.start();27imposter.lockHeld().then(function (isLocked) {28 console.log(isLocked);29 imposter.stop();30});31var imposter = require('mountebank').createImposter(2525, 'http', 3000);32imposter.addStub({33 predicates: [{equals: {method: 'GET', path: '/test'}}],34 responses: [{is: {statusCode: 200, body: 'Hello world'}}]35});36imposter.start();37imposter.lockHeld().then(function (isLocked) {38 console.log(isLocked);39 imposter.stop();40});41var imposter = require('mountebank').createImposter(2525, 'http', 3000);42imposter.addStub({43 predicates: [{equals

Full Screen

Using AI Code Generation

copy

Full Screen

1const mb = require('mountebank');2client.lockHeld().then(function (response) {3 console.log(response);4});5const mb = require('mountebank');6const imposter = {7 {8 {9 is: {10 }11 }12 }13};14client.putImposter(imposter).then(function (response) {15 console.log(response);16});17const mb = require('mountebank');18client.getImposter(3000).then(function (response) {19 console.log(response);20});21const mb = require('mountebank');22client.deleteImposter(3000).then(function (response) {23 console.log(response);24});

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2var fs = require('fs');3var api = mb.create(url);4api.create({5 stubs: [{6 predicates: [{7 equals: {8 }9 }],10 responses: [{11 is: {12 headers: {13 },14 }15 }]16 }]17}, function () {18 api.get('/imposters', function (error, response) {19 console.log(response.body);20 });21 api.get('/imposters/8080', function (error, response) {22 console.log(response.body);23 });24 api.get('/imposters/8080/recordings', function (error, response) {25 console.log(response.body);26 });27 api.get('/imposters/8080/lockHeld', function (error, response) {28 console.log(response.body);29 });30 api.get('/imposters/8080/lockHeld', function (error, response) {31 console.log(response.body);32 });33});34api.get('/imposters/8080/lockHeld', function (error, response) {35 console.log(response.body);36});37api.get('/imposters/8080/lockHeld', function (error, response) {38 console.log(response.body);39});40api.get('/imposters/8080/lockHeld', function (error, response) {41 console.log(response.body);42});43api.get('/imposters/8080/lockHeld', function (error, response) {44 console.log(response.body);45});46api.get('/imposters/8080/lockH

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run mountebank automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful