mini-semaphore 1.3.9 → 1.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  [![CircleCI](https://circleci.com/gh/jeffy-g/mini-semaphore/tree/master.svg?style=svg)](https://circleci.com/gh/jeffy-g/mini-semaphore/tree/master)
2
+ [![codecov](https://codecov.io/gh/jeffy-g/mini-semaphore/graph/badge.svg?token=SEEIAGR8HW)](https://codecov.io/gh/jeffy-g/mini-semaphore)
2
3
  ![GitHub](https://img.shields.io/github/license/jeffy-g/mini-semaphore?style=plastic)
4
+ [![npm version](https://badge.fury.io/js/mini-semaphore.svg)](https://badge.fury.io/js/mini-semaphore)
5
+ ![node](https://img.shields.io/node/v/mini-semaphore.svg?style=plastic)
6
+ ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/jeffy-g/mini-semaphore.svg?style=plastic)
7
+ ![npm bundle size](https://img.shields.io/bundlephobia/min/mini-semaphore?style=plastic)
8
+ ![npm](https://img.shields.io/npm/dm/mini-semaphore.svg?style=plastic)
9
+ ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/jeffy-g/mini-semaphore.svg?style=plastic)
3
10
 
4
11
  # Mini Semaphore (mini-semaphore
5
12
 
package/cjs/class.js CHANGED
@@ -1,42 +1,92 @@
1
- "use strict";
2
- /*!
3
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
4
- Copyright (C) 2020 jeffy-g <hirotom1107@gmail.com>
5
- Released under the MIT license
6
- https://opensource.org/licenses/mit-license.php
7
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.MiniSemaphore = void 0;
11
- const core = require("./core");
12
- const deque_1 = require("./deque");
13
- const a = core.acquire;
14
- const r = core.release;
15
- class MiniSemaphore {
16
- constructor(capacity) {
17
- this.limit = this.capacity = capacity;
18
- this.q = new deque_1.Deque(capacity);
19
- }
20
- acquire(lazy) {
21
- return a(this, lazy);
22
- }
23
- release() {
24
- r(this);
25
- }
26
- setRestriction(restriction) {
27
- this.limit = this.capacity = restriction;
28
- }
29
- get pending() {
30
- return this.q.length;
31
- }
32
- async flow(process, lazy) {
33
- await a(this, lazy);
34
- try {
35
- return await process();
36
- }
37
- finally {
38
- r(this);
39
- }
40
- }
41
- }
42
- exports.MiniSemaphore = MiniSemaphore;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MiniSemaphore = void 0;
4
+ /*!
5
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6
+ Copyright (C) 2020 jeffy-g <hirotom1107@gmail.com>
7
+ Released under the MIT license
8
+ https://opensource.org/licenses/mit-license.php
9
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10
+ */
11
+ /**
12
+ * @file minimal implementation of semaphore (class implementation
13
+ * @author jeffy-g <hirotom1107@gmail.com>
14
+ * @version 1.0
15
+ */
16
+ const core = require("./core");
17
+ const deque_1 = require("./deque");
18
+ const a = core.acquire;
19
+ const r = core.release;
20
+ /**
21
+ * #### Mini Semaphore
22
+ *
23
+ * + minimal implementation of semaphore
24
+ *
25
+ * @example
26
+ * import { MiniSemaphore } from "mini-semaphore";
27
+ *
28
+ * const s = new MiniSemaphore(10);
29
+ * async function fetchTypeData(type_id) {
30
+ * await s.acquire();
31
+ * try {
32
+ * return fetch(`https://esi.evetech.net/latest/universe/types/${type_id}/`);
33
+ * } finally {
34
+ * s.release();
35
+ * }
36
+ * }
37
+ *
38
+ * //
39
+ * // or automatic acquire/release
40
+ * //
41
+ * async function fetchTypeData(type_id) {
42
+ * return s.flow(async () => fetch(`https://esi.evetech.net/latest/universe/types/${type_id}/`));
43
+ * }
44
+ *
45
+ * @date 2020/2/7
46
+ * @version 1.0
47
+ */
48
+ class MiniSemaphore {
49
+ /**
50
+ * constructs a semaphore instance limited at `capacity`
51
+ *
52
+ * @param capacity limitation of concurrent async by `capacity`
53
+ */
54
+ constructor(capacity) {
55
+ this.limit = this.capacity = capacity;
56
+ this.q = new deque_1.Deque(capacity);
57
+ }
58
+ /**
59
+ * If there is enough capacity, execute the `resolve` immediately
60
+ *
61
+ * If not, put it in a queue and wait for the currently pending process to execute `release`
62
+ */
63
+ acquire(lazy) {
64
+ return a(this, lazy);
65
+ }
66
+ release() {
67
+ r(this);
68
+ }
69
+ setRestriction(restriction) {
70
+ this.limit = this.capacity = restriction;
71
+ }
72
+ get pending() {
73
+ return this.q.length;
74
+ }
75
+ /**
76
+ * automatic acquire/release
77
+ *
78
+ * @template {any} T description
79
+ * @param {() => Promise<T>} process
80
+ * @param {boolean=} lazy
81
+ */
82
+ async flow(process, lazy) {
83
+ await a(this, lazy);
84
+ try {
85
+ return await process();
86
+ }
87
+ finally {
88
+ r(this);
89
+ }
90
+ }
91
+ }
92
+ exports.MiniSemaphore = MiniSemaphore;
package/cjs/core.js CHANGED
@@ -1,41 +1,67 @@
1
- "use strict";
2
- /*!
3
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
4
- Copyright (C) 2020 jeffy-g <hirotom1107@gmail.com>
5
- Released under the MIT license
6
- https://opensource.org/licenses/mit-license.php
7
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.release = exports.acquire = void 0;
11
- const extras_1 = require("./extras");
12
- const box = (z, r) => {
13
- if (z.capacity > 0) {
14
- z.capacity--, r();
15
- }
16
- else {
17
- z.q.push(r);
18
- }
19
- };
20
- const acquire = (dis, lazy = true) => {
21
- return new Promise(r => {
22
- if (!lazy) {
23
- box(dis, r);
24
- }
25
- else {
26
- setTimeout(() => box(dis, r), 4);
27
- }
28
- });
29
- };
30
- exports.acquire = acquire;
31
- const release = (dis) => {
32
- dis.capacity++;
33
- if (dis.q.length) {
34
- dis.capacity -= 1, (dis.q.shift() || extras_1.THROW)();
35
- }
36
- if (dis.capacity > dis.limit) {
37
- console.warn("inconsistent release!");
38
- dis.capacity = dis.limit;
39
- }
40
- };
41
- exports.release = release;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.release = exports.acquire = void 0;
4
+ const extras_1 = require("./extras");
5
+ /**
6
+ * @typedef ISimplifiedLock
7
+ * @prop {(lazy?: boolean) => Promise<void>} acquire acquire the process rights&#64;param lazy Whether the privilege acquisition process is deffer. default `true`
8
+ * @prop {() => void} release release the pending of one
9
+ * @prop {(restriction: number) => void} setRestriction Change sharing restrictions to the value of `restriction`&#64;param {number} restriction
10
+ * @prop {number} pending Get the number of currently pending processes&#64;type {number}
11
+ * @prop {number} limit limitation
12
+ * @prop {number} capacity capacity
13
+ */
14
+ /**
15
+ * @typedef {<T>(f: () => Promise<T>, lazy?: boolean) => Promise<T>} TFlow
16
+ * @typedef {ISimplifiedLock & { flow: TFlow }} IFlowableLock
17
+ * @typedef {() => void} TVoidFunction
18
+ * @typedef {import("./deque").Deque} Deque
19
+ * @typedef {IFlowableLock & { readonly q: Deque }} TFlowableLock
20
+ */
21
+ /**
22
+ *
23
+ * @param {TFlowableLock} z
24
+ * @param {TVoidFunction} r
25
+ */
26
+ const box = (z, r) => {
27
+ if (z.capacity > 0) {
28
+ z.capacity--, r();
29
+ }
30
+ else {
31
+ z.q.push(r);
32
+ }
33
+ };
34
+ /**
35
+ *
36
+ * @param {TFlowableLock} dis
37
+ * @param {boolean} [lazy] default: true
38
+ * @returns {Promise<void>}
39
+ */
40
+ const acquire = (dis, lazy = true) => {
41
+ return new Promise(r => {
42
+ if (!lazy) {
43
+ box(dis, r);
44
+ }
45
+ else {
46
+ setTimeout(() => box(dis, r), 4);
47
+ }
48
+ });
49
+ };
50
+ exports.acquire = acquire;
51
+ /**
52
+ * @param {TFlowableLock} dis
53
+ * @returns {void}
54
+ */
55
+ const release = (dis) => {
56
+ if (dis.q.length) {
57
+ (dis.q.shift() || /* istanbul ignore next */ extras_1.THROW)();
58
+ }
59
+ else {
60
+ dis.capacity++;
61
+ }
62
+ if (dis.capacity > dis.limit) {
63
+ console.warn("inconsistent release!");
64
+ dis.capacity = dis.limit;
65
+ }
66
+ };
67
+ exports.release = release;
package/cjs/deque.js CHANGED
@@ -1,65 +1,102 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Deque = void 0;
4
- const am = (src, si, dst, di, len) => {
5
- for (let j = 0; j < len; ++j) {
6
- dst[j + di] = src[j + si];
7
- src[j + si] = void 0;
8
- }
9
- };
10
- const p2l = (n) => {
11
- n = n >>> 0;
12
- n = n - 1;
13
- n = n | (n >> 1);
14
- n = n | (n >> 2);
15
- n = n | (n >> 4);
16
- n = n | (n >> 8);
17
- n = n | (n >> 16);
18
- return n + 1;
19
- };
20
- const gc = (n) => {
21
- return p2l(Math.min(Math.max(16, n | 0), 1073741824));
22
- };
23
- class Deque {
24
- constructor(ic) {
25
- this._c = gc(ic);
26
- this._l = 0;
27
- this._f = 0;
28
- this._a = [];
29
- }
30
- push(s) {
31
- const l = this._l;
32
- if (this._c < l + 1) {
33
- rt(this, gc(this._c * 1.5 + 16));
34
- }
35
- const i = (this._f + l) & (this._c - 1);
36
- this._a[i] = s;
37
- this._l = l + 1;
38
- }
39
- shift() {
40
- const l = this._l;
41
- if (l === 0) {
42
- return void 0;
43
- }
44
- const f = this._f;
45
- const r = this._a[f];
46
- this._a[f] = void 0;
47
- this._f = (f + 1) & (this._c - 1);
48
- this._l = l - 1;
49
- return r;
50
- }
51
- get length() {
52
- return this._l;
53
- }
54
- }
55
- exports.Deque = Deque;
56
- const rt = (dis, n) => {
57
- const oc = dis._c;
58
- dis._c = n;
59
- const f = dis._f;
60
- const l = dis._l;
61
- if (f + l > oc) {
62
- const mc = (f + l) & (oc - 1);
63
- am(dis._a, 0, dis._a, oc, mc);
64
- }
65
- };
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Deque = void 0;
4
+ /**
5
+ * arrayMove
6
+ *
7
+ * @param src
8
+ * @param si source index
9
+ * @param dst
10
+ * @param di dest index
11
+ * @param len move count
12
+ */
13
+ const am = /* istanbul ignore next */ (src, si, dst, di, len) => {
14
+ for (let j = 0; j < len; ++j) {
15
+ dst[j + di] = src[j + si];
16
+ src[j + si] = void 0;
17
+ }
18
+ };
19
+ /**
20
+ * pow2AtLeast
21
+ * @param n
22
+ */
23
+ const p2l = (n) => {
24
+ n = n >>> 0;
25
+ n = n - 1;
26
+ n = n | (n >> 1);
27
+ n = n | (n >> 2);
28
+ n = n | (n >> 4);
29
+ n = n | (n >> 8);
30
+ n = n | (n >> 16);
31
+ return n + 1;
32
+ };
33
+ /**
34
+ * getCapacity
35
+ * @param n
36
+ */
37
+ const gc = (n) => {
38
+ // @ts-ignore typescript cannot allow (undefined | 0) expression
39
+ return p2l(Math.min(Math.max(16, n | 0), 1073741824));
40
+ };
41
+ /**
42
+ * ### Implementation restricted to FIFO
43
+ *
44
+ * this class is based on https://github.com/petkaantonov/deque/blob/master/js/deque.js
45
+ * Released under the MIT License: https://github.com/petkaantonov/deque/blob/master/LICENSE
46
+ */
47
+ class Deque {
48
+ /**
49
+ * default capacity `16`
50
+ * @param ic initial capacity
51
+ */
52
+ constructor(ic) {
53
+ this._c = gc(ic);
54
+ this._l = 0;
55
+ this._f = 0;
56
+ this._a = [];
57
+ }
58
+ /**
59
+ * @param s subject
60
+ */
61
+ push(s) {
62
+ const l = this._l;
63
+ if (this._c < l + 1) {
64
+ rt(this, gc(this._c * 1.5 + 16));
65
+ }
66
+ const i = (this._f + l) & (this._c - 1);
67
+ this._a[i] = s;
68
+ this._l = l + 1;
69
+ }
70
+ shift() {
71
+ const l = this._l;
72
+ /* istanbul ignore if */
73
+ if (l === 0) {
74
+ return void 0;
75
+ }
76
+ const f = this._f;
77
+ const r = this._a[f];
78
+ this._a[f] = void 0;
79
+ this._f = (f + 1) & (this._c - 1);
80
+ this._l = l - 1;
81
+ return r;
82
+ }
83
+ get length() {
84
+ return this._l;
85
+ }
86
+ }
87
+ exports.Deque = Deque;
88
+ /**
89
+ * resize to
90
+ *
91
+ * @param n expected capacity
92
+ */
93
+ const rt = (dis, n) => {
94
+ const oc = dis._c;
95
+ dis._c = n;
96
+ const lastIndex = dis._f + dis._l;
97
+ /* istanbul ignore next */
98
+ if (lastIndex > oc) {
99
+ const mc = (lastIndex) & (oc - 1);
100
+ am(dis._a, 0, dis._a, oc, mc);
101
+ }
102
+ };
package/cjs/extras.js CHANGED
@@ -1,7 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.THROW = void 0;
4
- const THROW = () => {
5
- throw new Error("mini-semaphore: inconsistent occurred");
6
- };
7
- exports.THROW = THROW;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.THROW = void 0;
4
+ const THROW = () => {
5
+ throw new Error("mini-semaphore: inconsistent occurred");
6
+ };
7
+ exports.THROW = THROW;
@@ -1,80 +1,137 @@
1
- "use strict";
2
- /*!
3
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
4
- Copyright (C) 2020 jeffy-g <hirotom1107@gmail.com>
5
- Released under the MIT license
6
- https://opensource.org/licenses/mit-license.php
7
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.restrictor = void 0;
11
- const c = require("./class");
12
- var restrictor;
13
- (function (restrictor) {
14
- const { MiniSemaphore: MS } = c;
15
- const internalLock = new MS(1);
16
- let locks = Object.create(null);
17
- const get = async (key, restriction) => {
18
- await internalLock.acquire(false);
19
- let lock = locks[key];
20
- if (!lock) {
21
- locks[key] = lock = new MS(restriction);
22
- }
23
- if (lock.limit !== restriction) {
24
- internalLock.release();
25
- throw new ReferenceError(`Cannot get object with different restriction: key: '${key}', lock.limit: ${lock.limit} <-> restriction: ${restriction},`);
26
- }
27
- internalLock.release();
28
- return lock;
29
- };
30
- restrictor.getLockByKey = async (key) => {
31
- await internalLock.acquire(false);
32
- const l = locks[key];
33
- internalLock.release();
34
- return l;
35
- };
36
- restrictor.cleanup = async (timeSpan, debug) => {
37
- await internalLock.acquire(false);
38
- const currentLocks = locks;
39
- const newLocks = Object.create(null);
40
- const keys = Object.keys(currentLocks);
41
- let eliminatedCount = 0;
42
- let eliminatedKeys;
43
- !timeSpan && (timeSpan = 1);
44
- timeSpan *= 1000;
45
- if (debug) {
46
- eliminatedKeys = [];
47
- }
48
- for (let i = 0, end = keys.length; i < end;) {
49
- const key = keys[i++];
50
- const s = currentLocks[key];
51
- if (s.last && Date.now() - s.last >= timeSpan) {
52
- eliminatedCount++;
53
- if (debug) {
54
- eliminatedKeys.push(key);
55
- }
56
- continue;
57
- }
58
- newLocks[key] = s;
59
- }
60
- locks = newLocks;
61
- internalLock.release();
62
- if (debug) {
63
- console.log(`eliminated: [\n${eliminatedKeys.join(",\n")}\n]` +
64
- "\n" +
65
- `lived: [\n${Object.keys(newLocks).join(",\n")}\n]`);
66
- }
67
- return eliminatedCount;
68
- };
69
- async function multi(key, restriction, pb) {
70
- const s = await get(key, restriction);
71
- const result = s.flow(pb);
72
- s.last = Date.now();
73
- return result;
74
- }
75
- restrictor.multi = multi;
76
- async function one(key, pb) {
77
- return multi(key, 1, pb);
78
- }
79
- restrictor.one = one;
80
- })(restrictor = exports.restrictor || (exports.restrictor = {}));
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.restrictor = void 0;
4
+ /*!
5
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6
+ Copyright (C) 2020 jeffy-g <hirotom1107@gmail.com>
7
+ Released under the MIT license
8
+ https://opensource.org/licenses/mit-license.php
9
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10
+ */
11
+ /**
12
+ * @file Utility module using `MiniSemaphore`
13
+ * @author jeffy-g <hirotom1107@gmail.com>
14
+ * @version 1.0
15
+ */
16
+ const c = require("./class");
17
+ /**
18
+ * @typedef {string | number} TLockRecordKey
19
+ */
20
+ /**
21
+ * Flow Restriction
22
+ */
23
+ var restrictor;
24
+ (function (restrictor) {
25
+ const { MiniSemaphore: MS } = c;
26
+ /**
27
+ * @internal
28
+ */
29
+ const internalLock = new MS(1);
30
+ /**
31
+ *
32
+ */
33
+ let locks = Object.create(null);
34
+ /**
35
+ *
36
+ * @param {TLockRecordKey} key
37
+ * @param {number} restriction
38
+ * @throws when different restriction
39
+ */
40
+ const get = async (key, restriction) => {
41
+ await internalLock.acquire(false);
42
+ let lock = locks[key];
43
+ if (!lock) {
44
+ locks[key] = lock = new MS(restriction);
45
+ }
46
+ if (lock.limit !== restriction) {
47
+ internalLock.release();
48
+ throw new ReferenceError(`Cannot get object with different restriction: key: '${key}', lock.limit: ${lock.limit} <-> restriction: ${restriction},`);
49
+ }
50
+ internalLock.release();
51
+ return lock;
52
+ };
53
+ /**
54
+ * get the semaphore associated with the value of `key`
55
+ *
56
+ * + ⚠️ The object to be retrieved with `key` must already be created with `multi` ore `one`
57
+ *
58
+ * @param {TLockRecordKey} key
59
+ * @returns `IFlowableLock` instance or `undefined`
60
+ */
61
+ restrictor.getLockByKey = async (key) => {
62
+ await internalLock.acquire(false);
63
+ const l = locks[key];
64
+ internalLock.release();
65
+ return l;
66
+ };
67
+ /**
68
+ * Eliminate unused instances for the `timeSpan` seconds
69
+ *
70
+ * @param {number} timeSpan specify unit as seconds
71
+ * @param {true} [debug] enable debug
72
+ * @returns {Promise<number>} eliminated count
73
+ * @date 2020/6/19
74
+ */
75
+ restrictor.cleanup = async (timeSpan, debug) => {
76
+ await internalLock.acquire(false);
77
+ const currentLocks = locks;
78
+ const newLocks = Object.create(null);
79
+ const keys = Object.keys(currentLocks);
80
+ let eliminatedCount = 0;
81
+ let eliminatedKeys;
82
+ !timeSpan && /* istanbul ignore next */ (timeSpan = 1);
83
+ timeSpan *= 1000;
84
+ if (debug) {
85
+ eliminatedKeys = [];
86
+ }
87
+ for (let i = 0, end = keys.length; i < end;) {
88
+ const key = keys[i++];
89
+ const s = currentLocks[key];
90
+ if (s.last && Date.now() - s.last >= timeSpan) {
91
+ eliminatedCount++;
92
+ if (debug) {
93
+ eliminatedKeys.push(key);
94
+ }
95
+ continue;
96
+ }
97
+ newLocks[key] = s;
98
+ }
99
+ locks = newLocks;
100
+ internalLock.release();
101
+ if (debug) {
102
+ console.log(`eliminated: [\n${eliminatedKeys.join(",\n")}\n]` +
103
+ "\n" +
104
+ `lived: [\n${Object.keys(newLocks).join(",\n")}\n]`);
105
+ }
106
+ return eliminatedCount;
107
+ };
108
+ /**
109
+ * Allocate a semaphore for each `key`, and limit the number of shares with the value of `restriction`
110
+ *
111
+ * @template {any} T
112
+ * @param {TLockRecordKey} key number or string as tag
113
+ * @param {number} restriction number of process restriction
114
+ * @param {() => Promise<T>} pb the process body
115
+ */
116
+ async function multi(key, restriction, pb) {
117
+ const s = await get(key, restriction);
118
+ const result = s.flow(pb);
119
+ s.last = Date.now();
120
+ return result;
121
+ }
122
+ restrictor.multi = multi;
123
+ /**
124
+ * synonym of `multi(key, 1, pb)`
125
+ *
126
+ * + use case
127
+ * * Avoid concurrent requests to the same url
128
+ *
129
+ * @template {any} T
130
+ * @param {TLockRecordKey} key number or string as tag
131
+ * @param {() => Promise<T>} pb the process body
132
+ */
133
+ async function one(key, pb) {
134
+ return multi(key, 1, pb);
135
+ }
136
+ restrictor.one = one;
137
+ })(restrictor || (exports.restrictor = restrictor = {}));