From 83bca61385eb3f04b81d07b5de2f618fe4a5aa6f Mon Sep 17 00:00:00 2001 From: iskryzhytskyi Date: Sun, 15 Mar 2020 17:08:34 +0200 Subject: [PATCH] feat: locking timeout --- README.md | 2 -- examples/README.md | 7 ---- examples/shared-resource/thread-main.js | 44 ----------------------- examples/shared-resource/thread-worker.js | 38 -------------------- test.js | 1 + test/lock-time.js | 18 ++++++++++ test/steps.js | 8 +---- test/test-utils.js | 10 ++++++ test/thread-worker.js | 6 +--- web-locks.js | 21 +++++++---- 10 files changed, 45 insertions(+), 110 deletions(-) delete mode 100644 examples/README.md delete mode 100644 examples/shared-resource/thread-main.js delete mode 100644 examples/shared-resource/thread-worker.js create mode 100644 test/lock-time.js create mode 100644 test/test-utils.js diff --git a/README.md b/README.md index 1e15115..fac0dc2 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,6 @@ await locks.request('Resource name', async lock => { }); ``` -More examples [here](./examples/README.md) - ## License This implementation of Web Locks API is [MIT licensed](./LICENSE). diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 6c7f935..0000000 --- a/examples/README.md +++ /dev/null @@ -1,7 +0,0 @@ -## shared-resource - -* A simple example of parallel working on the same resource from several threads -* run -```bash -node examples/shared-resource/thread-main.js -``` diff --git a/examples/shared-resource/thread-main.js b/examples/shared-resource/thread-main.js deleted file mode 100644 index e793dcd..0000000 --- a/examples/shared-resource/thread-main.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -const path = require('path'); -const threads = require('worker_threads'); -const { locks } = require('../..'); - -const startWork = () => - new Promise(resolve => { - const workerFile = path.resolve(__dirname, 'thread-worker.js'); - - const resource = new SharedArrayBuffer(8); - - const worker1 = new threads.Worker(workerFile, { workerData: resource }); - const worker2 = new threads.Worker(workerFile, { workerData: resource }); - const worker3 = new threads.Worker(workerFile, { workerData: resource }); - - locks.attach(worker1); - locks.attach(worker2); - locks.attach(worker3); - - let counter = 0; - const done = message => { - if (message.status === 'done') { - counter++; - if (counter === 3) { - console.log('After processing:', resource); - - resolve(); - } - } - }; - - worker1.on('message', done); - worker2.on('message', done); - worker3.on('message', done); - - locks.request('resource', async () => { - worker1.postMessage({ isMyProgram: true, task: 'someWorkWithData' }); - worker2.postMessage({ isMyProgram: true, task: 'someWorkWithData' }); - worker3.postMessage({ isMyProgram: true, task: 'someWorkWithData' }); - }); - }); - -startWork(); diff --git a/examples/shared-resource/thread-worker.js b/examples/shared-resource/thread-worker.js deleted file mode 100644 index 016b212..0000000 --- a/examples/shared-resource/thread-worker.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -const threads = require('worker_threads'); -const { locks } = require('../../'); - -const SLEEPING_TIME_MS = 1000; - -const sleep = () => - new Promise(resolve => { - setTimeout(resolve, SLEEPING_TIME_MS); - }); - -const someWorkWithData = async () => { - await locks.request('resource', {}, async () => { - await sleep(); - const view = new Int8Array(threads.workerData); - for (let i = 0; i < view.length; i++) { - view[i] *= threads.threadId; - view[i] += threads.threadId; - } - - console.log('info', threads.threadId, view); - }); - threads.parentPort.postMessage({ status: 'done' }); - process.exit(0); -}; - -const worksMapper = { - someWorkWithData, -}; - -threads.parentPort.on('message', async message => { - if (!message.isMyProgram) { - return; - } - - await worksMapper[message.task](); -}); diff --git a/test.js b/test.js index 8f48423..4853b65 100644 --- a/test.js +++ b/test.js @@ -8,6 +8,7 @@ const tests = [ 'deadlock', 'recursive-deadlock', 'thread-main', + 'lock-time', ]; (async () => { diff --git a/test/lock-time.js b/test/lock-time.js new file mode 100644 index 0000000..345cab3 --- /dev/null +++ b/test/lock-time.js @@ -0,0 +1,18 @@ +'use strict'; + +const assert = require('assert').strict; +const { locks } = require('..'); +const { sleep } = require('./test-utils'); + +const TIME_TO_PROCESS = 2000; +const TIME_TO_LOCK = 100; + +module.exports = async () => { + const startTs = Date.now(); + + await locks.request('LockTime', { timeout: TIME_TO_LOCK }, async () => { + await sleep(TIME_TO_PROCESS); + }); + + assert.strictEqual(Date.now() - startTs < TIME_TO_PROCESS, true); +}; diff --git a/test/steps.js b/test/steps.js index 7ceb9b7..71b5642 100644 --- a/test/steps.js +++ b/test/steps.js @@ -2,13 +2,7 @@ const assert = require('assert').strict; const { locks } = require('..'); - -const sleep = msec => - new Promise(resolve => { - setTimeout(() => { - resolve(); - }, msec); - }); +const { sleep } = require('./test-utils'); let counter = 0; diff --git a/test/test-utils.js b/test/test-utils.js new file mode 100644 index 0000000..c1aca09 --- /dev/null +++ b/test/test-utils.js @@ -0,0 +1,10 @@ +'use strict'; + +const sleep = msec => + new Promise(resolve => { + setTimeout(resolve, msec); + }); + +module.exports = { + sleep, +}; diff --git a/test/thread-worker.js b/test/thread-worker.js index 03bb534..b9b9c92 100644 --- a/test/thread-worker.js +++ b/test/thread-worker.js @@ -1,14 +1,10 @@ 'use strict'; const threads = require('worker_threads'); +const { sleep } = require('./test-utils'); const { locks } = require('..'); -const sleep = msec => - new Promise(resolve => { - setTimeout(resolve, msec); - }); - (async () => { if (threads.threadId === 2) { await sleep(10); diff --git a/web-locks.js b/web-locks.js index ae40d86..bdf728d 100644 --- a/web-locks.js +++ b/web-locks.js @@ -11,12 +11,13 @@ const UNLOCKED = 1; let locks = null; // LockManager instance class Lock { - constructor(name, mode = 'exclusive', buffer = null) { + constructor({ name, mode = 'exclusive', buffer = null, timeout = null }) { this.name = name; this.mode = mode; // 'exclusive' or 'shared' this.queue = []; this.owner = false; this.trying = false; + this.timeout = timeout; this.buffer = buffer ? buffer : new SharedArrayBuffer(4); this.flag = new Int32Array(this.buffer, 0, 1); if (!buffer) Atomics.store(this.flag, 0, UNLOCKED); @@ -39,10 +40,16 @@ class Lock { this.owner = true; this.trying = false; const { handler, resolve } = this.queue.shift(); - handler(this).finally(() => { + + const endWork = () => { this.leave(); resolve(); - }); + }; + + if (typeof this.timeout === 'number') { + setTimeout(endWork, this.timeout); + } + handler(this).finally(endWork); } leave() { @@ -89,11 +96,11 @@ class LockManager { handler = options; options = {}; } - const { mode = 'exclusive', signal = null } = options; + const { mode = 'exclusive', signal = null, timeout } = options; let lock = this.collection.get(name); if (!lock) { - lock = new Lock(name, mode); + lock = new Lock({ name, mode, timeout }); this.collection.set(name, lock); const { buffer } = lock; const message = { webLocks: true, kind: 'create', name, mode, buffer }; @@ -147,9 +154,9 @@ class LockManager { receive(message) { if (!message.webLocks) return; - const { kind, name, mode, buffer } = message; + const { kind, name, mode, buffer, timeout } = message; if (kind === 'create') { - const lock = new Lock(name, mode, buffer); + const lock = new Lock({ name, mode, buffer, timeout }); this.collection.set(name, lock); return; }