@nxtedition/scheduler 2.0.3 → 3.0.0
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/lib/index.d.ts +15 -10
- package/lib/index.js +138 -65
- package/package.json +2 -2
package/lib/index.d.ts
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
|
+
export type Priority = -3 | -2 | -1 | 0 | 1 | 2 | 3 | 'lowest' | 'lower' | 'low' | 'normal' | 'high' | 'higher' | 'highest';
|
|
2
|
+
export declare function parsePriority(p: Priority | string | number): number;
|
|
1
3
|
export declare class Scheduler {
|
|
2
4
|
#private;
|
|
3
|
-
static
|
|
4
|
-
static
|
|
5
|
-
static
|
|
5
|
+
static LOWEST: Priority;
|
|
6
|
+
static LOWER: Priority;
|
|
7
|
+
static LOW: Priority;
|
|
8
|
+
static NORMAL: Priority;
|
|
9
|
+
static HIGH: Priority;
|
|
10
|
+
static HIGHER: Priority;
|
|
11
|
+
static HIGHEST: Priority;
|
|
6
12
|
get concurrency(): number;
|
|
7
13
|
get stats(): {
|
|
8
14
|
deferred: number;
|
|
9
15
|
running: number;
|
|
10
16
|
pending: number;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
normalLimit: number;
|
|
16
|
-
highLimit: number;
|
|
17
|
+
queues: {
|
|
18
|
+
count: number;
|
|
19
|
+
age: number;
|
|
20
|
+
}[];
|
|
17
21
|
};
|
|
18
22
|
static makeSharedState(concurrency: number): SharedArrayBuffer;
|
|
19
23
|
constructor(opts: SharedArrayBuffer | {
|
|
20
24
|
concurrency?: number;
|
|
21
25
|
});
|
|
22
|
-
|
|
26
|
+
run<T, U>(fn: (opaque?: U) => Promise<T> | T, priority?: Priority, opaque?: U): Promise<T>;
|
|
27
|
+
acquire<U>(fn: (opaque?: U) => unknown, priority?: Priority, opaque?: U): void;
|
|
23
28
|
release(): void;
|
|
24
29
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,17 +1,76 @@
|
|
|
1
1
|
const RUNNING_INDEX = 0
|
|
2
2
|
const CONCURRENCY_INDEX = 1
|
|
3
3
|
|
|
4
|
+
const maxInt = 2147483647
|
|
5
|
+
|
|
6
|
+
let fastNow = Date.now()
|
|
7
|
+
setInterval(() => {
|
|
8
|
+
fastNow = Date.now()
|
|
9
|
+
}, 1e3).unref()
|
|
10
|
+
|
|
4
11
|
class FastQueue {
|
|
5
12
|
idx = 0
|
|
6
13
|
cnt = 0
|
|
7
14
|
arr = []
|
|
8
|
-
|
|
15
|
+
age = fastNow
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export function parsePriority(p ) {
|
|
35
|
+
if (typeof p === 'number') {
|
|
36
|
+
// Do nothing...
|
|
37
|
+
} else if (p === 'lowest') {
|
|
38
|
+
return -3
|
|
39
|
+
} else if (p === 'lower') {
|
|
40
|
+
return -2
|
|
41
|
+
} else if (p === 'low') {
|
|
42
|
+
return -1
|
|
43
|
+
} else if (p === 'normal') {
|
|
44
|
+
return 0
|
|
45
|
+
} else if (p === 'high') {
|
|
46
|
+
return 1
|
|
47
|
+
} else if (p === 'higher') {
|
|
48
|
+
return 2
|
|
49
|
+
} else if (p === 'highest') {
|
|
50
|
+
return 3
|
|
51
|
+
} else {
|
|
52
|
+
p = Number(p)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof p !== 'number') {
|
|
56
|
+
return 0
|
|
57
|
+
} else if (p < -3) {
|
|
58
|
+
return -3
|
|
59
|
+
} else if (p > 3) {
|
|
60
|
+
return 3
|
|
61
|
+
} else {
|
|
62
|
+
return Math.trunc(p)
|
|
63
|
+
}
|
|
9
64
|
}
|
|
10
65
|
|
|
11
66
|
export class Scheduler {
|
|
12
|
-
static
|
|
13
|
-
static
|
|
14
|
-
static
|
|
67
|
+
static LOWEST = -3
|
|
68
|
+
static LOWER = -2
|
|
69
|
+
static LOW = -1
|
|
70
|
+
static NORMAL = 0
|
|
71
|
+
static HIGH = 1
|
|
72
|
+
static HIGHER = 2
|
|
73
|
+
static HIGHEST = 3
|
|
15
74
|
|
|
16
75
|
#concurrency
|
|
17
76
|
#stateView
|
|
@@ -22,9 +81,15 @@ export class Scheduler {
|
|
|
22
81
|
#releasing = false
|
|
23
82
|
#deferred = 0
|
|
24
83
|
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
84
|
+
#queues = [
|
|
85
|
+
new FastQueue(), // 0 lowest
|
|
86
|
+
new FastQueue(), // 1 lower
|
|
87
|
+
new FastQueue(), // 2 low
|
|
88
|
+
new FastQueue(), // 3 normal
|
|
89
|
+
new FastQueue(), // 4 high
|
|
90
|
+
new FastQueue(), // 5 higher
|
|
91
|
+
new FastQueue(), // 6 highest
|
|
92
|
+
]
|
|
28
93
|
|
|
29
94
|
get concurrency() {
|
|
30
95
|
return this.#concurrency
|
|
@@ -35,12 +100,7 @@ export class Scheduler {
|
|
|
35
100
|
deferred: this.#deferred,
|
|
36
101
|
running: this.#running,
|
|
37
102
|
pending: this.#pending,
|
|
38
|
-
|
|
39
|
-
normalCount: this.#normalQueue.cnt,
|
|
40
|
-
highCount: this.#highQueue.cnt,
|
|
41
|
-
lowLimit: this.#lowQueue.lim,
|
|
42
|
-
normalLimit: this.#normalQueue.lim,
|
|
43
|
-
highLimit: this.#highQueue.lim,
|
|
103
|
+
queues: this.#queues.map((q) => ({ count: q.cnt, age: q.age })),
|
|
44
104
|
}
|
|
45
105
|
}
|
|
46
106
|
|
|
@@ -61,58 +121,54 @@ export class Scheduler {
|
|
|
61
121
|
} else {
|
|
62
122
|
this.#concurrency = opts?.concurrency || Infinity
|
|
63
123
|
}
|
|
64
|
-
|
|
65
|
-
this.#lowQueue.lim = this.#concurrency / 4
|
|
66
|
-
this.#normalQueue.lim = this.#concurrency - this.#lowQueue.lim
|
|
67
|
-
this.#highQueue.lim = this.#concurrency
|
|
68
124
|
}
|
|
69
125
|
|
|
70
|
-
|
|
71
|
-
fn
|
|
72
|
-
priority
|
|
73
|
-
opaque
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
}
|
|
126
|
+
run (
|
|
127
|
+
fn ,
|
|
128
|
+
priority = Scheduler.NORMAL,
|
|
129
|
+
opaque ,
|
|
130
|
+
) {
|
|
131
|
+
return new Promise ((resolve, reject) => {
|
|
132
|
+
this.acquire(
|
|
133
|
+
async (o) => {
|
|
134
|
+
try {
|
|
135
|
+
resolve(await fn(o))
|
|
136
|
+
} catch (err) {
|
|
137
|
+
reject(err)
|
|
138
|
+
} finally {
|
|
139
|
+
this.release()
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
priority,
|
|
143
|
+
opaque,
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
}
|
|
92
147
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} else if (priority < Scheduler.NORMAL) {
|
|
97
|
-
queue = this.#lowQueue
|
|
98
|
-
} else {
|
|
99
|
-
queue = this.#normalQueue
|
|
100
|
-
}
|
|
148
|
+
acquire (fn , priority = Scheduler.NORMAL, opaque ) {
|
|
149
|
+
const p = parsePriority(priority)
|
|
150
|
+
const queue = this.#queues[p + 3]
|
|
101
151
|
|
|
102
152
|
if (this.#stateView) {
|
|
103
|
-
if (this.#running < 1 || this.#stateView[RUNNING_INDEX] <
|
|
153
|
+
if (this.#running < 1 || this.#stateView[RUNNING_INDEX] < this.#concurrency) {
|
|
104
154
|
Atomics.add(this.#stateView, RUNNING_INDEX, 1)
|
|
105
155
|
this.#running += 1
|
|
106
|
-
|
|
156
|
+
fn(opaque)
|
|
157
|
+
return
|
|
107
158
|
}
|
|
108
|
-
} else if (this.#running < 1 || this.#running <
|
|
159
|
+
} else if (this.#running < 1 || this.#running < this.#concurrency) {
|
|
109
160
|
this.#running += 1
|
|
110
|
-
|
|
161
|
+
fn(opaque)
|
|
162
|
+
return
|
|
111
163
|
}
|
|
112
164
|
|
|
113
165
|
queue.arr.push(fn, opaque)
|
|
114
166
|
queue.cnt += 1
|
|
115
167
|
|
|
168
|
+
if (queue.cnt === 1) {
|
|
169
|
+
queue.age = fastNow
|
|
170
|
+
}
|
|
171
|
+
|
|
116
172
|
this.#deferred += 1
|
|
117
173
|
this.#pending += 1
|
|
118
174
|
}
|
|
@@ -130,30 +186,47 @@ export class Scheduler {
|
|
|
130
186
|
try {
|
|
131
187
|
this.#releasing = true
|
|
132
188
|
while (this.#pending > 0) {
|
|
133
|
-
let queue
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
} else {
|
|
189
|
+
let queue = null
|
|
190
|
+
|
|
191
|
+
// Avoid starvation by randomizing the starting queue.
|
|
192
|
+
|
|
193
|
+
let idx = 6 // highest: 50%
|
|
194
|
+
if (this.#counter & 0b0000010) {
|
|
195
|
+
idx = 5 // higher: 25%
|
|
196
|
+
} else if (this.#counter & 0b0000100) {
|
|
197
|
+
idx = 4 // high: 12.5%
|
|
198
|
+
} else if (this.#counter & 0b0001000) {
|
|
199
|
+
idx = 3 // normal: 6.25%
|
|
200
|
+
} else if (this.#counter & 0b0010000) {
|
|
201
|
+
idx = 2 // low: 3.125%
|
|
202
|
+
} else if (this.#counter & 0b0100000) {
|
|
203
|
+
idx = 1 // lower: 1.5625%
|
|
204
|
+
} else if (this.#counter & 0b1000000) {
|
|
205
|
+
idx = 0 // lowest: 0.78%
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (let n = idx; n >= 0 && (queue == null || queue.cnt === 0); n--) {
|
|
209
|
+
queue = this.#queues[n]
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (let n = 6; n > idx && (queue == null || queue.cnt === 0); n--) {
|
|
213
|
+
queue = this.#queues[n]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (queue == null || queue.cnt === 0) {
|
|
141
217
|
throw new Error('Invariant violation: pending > 0 but no tasks in queues')
|
|
142
218
|
}
|
|
143
219
|
|
|
144
|
-
if (this.#running > 0 && running >=
|
|
220
|
+
if (this.#running > 0 && running >= this.#concurrency) {
|
|
145
221
|
break
|
|
146
222
|
}
|
|
147
223
|
|
|
148
|
-
|
|
149
|
-
queue = this.#lowQueue
|
|
150
|
-
} else if ((this.#counter & 15) === 0 && this.#normalQueue.cnt > 0) {
|
|
151
|
-
queue = this.#normalQueue
|
|
152
|
-
}
|
|
224
|
+
this.#counter = (this.#counter + 1) & maxInt
|
|
153
225
|
|
|
154
226
|
const fn = queue.arr[queue.idx++]
|
|
155
227
|
const opaque = queue.arr[queue.idx++]
|
|
156
228
|
queue.cnt -= 1
|
|
229
|
+
queue.age = fastNow
|
|
157
230
|
|
|
158
231
|
if (queue.cnt === 0) {
|
|
159
232
|
queue.idx = 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/scheduler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -20,5 +20,5 @@
|
|
|
20
20
|
"rimraf": "^6.1.2",
|
|
21
21
|
"typescript": "^5.9.3"
|
|
22
22
|
},
|
|
23
|
-
"gitHead": "
|
|
23
|
+
"gitHead": "dc18f1fb0cf53929205b2de3cb53c08b631a4a9d"
|
|
24
24
|
}
|