@nxtedition/scheduler 2.0.4 → 3.0.1
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 -11
- package/lib/index.js +116 -68
- package/package.json +2 -2
package/lib/index.d.ts
CHANGED
|
@@ -1,25 +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
|
-
run<T, U>(fn: (opaque?: U) => Promise<T> | T, priority?:
|
|
23
|
-
acquire<U>(fn: (opaque?: U) => unknown, priority?:
|
|
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;
|
|
24
28
|
release(): void;
|
|
25
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' || Number.isNaN(p)) {
|
|
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,15 +121,11 @@ 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
126
|
run (
|
|
71
127
|
fn ,
|
|
72
|
-
priority
|
|
128
|
+
priority = Scheduler.NORMAL,
|
|
73
129
|
opaque ,
|
|
74
130
|
) {
|
|
75
131
|
return new Promise ((resolve, reject) => {
|
|
@@ -89,46 +145,18 @@ export class Scheduler {
|
|
|
89
145
|
})
|
|
90
146
|
}
|
|
91
147
|
|
|
92
|
-
acquire (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
opaque ,
|
|
96
|
-
) {
|
|
97
|
-
if (typeof priority === 'number' && Number.isInteger(priority) && priority >= 0) {
|
|
98
|
-
// Do nothing
|
|
99
|
-
} else if (priority == null) {
|
|
100
|
-
priority = Scheduler.NORMAL
|
|
101
|
-
} else if (typeof priority === 'string') {
|
|
102
|
-
if (priority === 'low') {
|
|
103
|
-
priority = Scheduler.LOW
|
|
104
|
-
} else if (priority === 'normal') {
|
|
105
|
-
priority = Scheduler.NORMAL
|
|
106
|
-
} else if (priority === 'high') {
|
|
107
|
-
priority = Scheduler.HIGH
|
|
108
|
-
} else {
|
|
109
|
-
throw new Error('Invalid priority')
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
throw new Error('Invalid priority')
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
let queue
|
|
116
|
-
if (priority > Scheduler.NORMAL) {
|
|
117
|
-
queue = this.#highQueue
|
|
118
|
-
} else if (priority < Scheduler.NORMAL) {
|
|
119
|
-
queue = this.#lowQueue
|
|
120
|
-
} else {
|
|
121
|
-
queue = this.#normalQueue
|
|
122
|
-
}
|
|
148
|
+
acquire (fn , priority = Scheduler.NORMAL, opaque ) {
|
|
149
|
+
const p = parsePriority(priority)
|
|
150
|
+
const queue = this.#queues[p + 3]
|
|
123
151
|
|
|
124
152
|
if (this.#stateView) {
|
|
125
|
-
if (this.#running < 1 || this.#stateView[RUNNING_INDEX] <
|
|
153
|
+
if (this.#running < 1 || this.#stateView[RUNNING_INDEX] < this.#concurrency) {
|
|
126
154
|
Atomics.add(this.#stateView, RUNNING_INDEX, 1)
|
|
127
155
|
this.#running += 1
|
|
128
156
|
fn(opaque)
|
|
129
157
|
return
|
|
130
158
|
}
|
|
131
|
-
} else if (this.#running < 1 || this.#running <
|
|
159
|
+
} else if ((this.#running < 1 && this.#concurrency > 0) || this.#running < this.#concurrency) {
|
|
132
160
|
this.#running += 1
|
|
133
161
|
fn(opaque)
|
|
134
162
|
return
|
|
@@ -137,6 +165,10 @@ export class Scheduler {
|
|
|
137
165
|
queue.arr.push(fn, opaque)
|
|
138
166
|
queue.cnt += 1
|
|
139
167
|
|
|
168
|
+
if (queue.cnt === 1) {
|
|
169
|
+
queue.age = fastNow
|
|
170
|
+
}
|
|
171
|
+
|
|
140
172
|
this.#deferred += 1
|
|
141
173
|
this.#pending += 1
|
|
142
174
|
}
|
|
@@ -153,38 +185,54 @@ export class Scheduler {
|
|
|
153
185
|
|
|
154
186
|
try {
|
|
155
187
|
this.#releasing = true
|
|
156
|
-
while (this.#pending > 0) {
|
|
157
|
-
let queue
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
188
|
+
while (this.#pending > 0 && (this.#running === 0 || running < this.#concurrency)) {
|
|
189
|
+
let queue = null
|
|
190
|
+
|
|
191
|
+
let idx = this.#queues.length - 1
|
|
192
|
+
|
|
193
|
+
// Avoid starvation by randomizing the starting queue.
|
|
194
|
+
{
|
|
195
|
+
this.#counter = (this.#counter + 1) & maxInt
|
|
196
|
+
if (this.#counter & 0b0000001) {
|
|
197
|
+
idx = 6 // highest: 50%
|
|
198
|
+
} else if (this.#counter & 0b0000010) {
|
|
199
|
+
idx = 5 // higher: 25%
|
|
200
|
+
} else if (this.#counter & 0b0000100) {
|
|
201
|
+
idx = 4 // high: 12.5%
|
|
202
|
+
} else if (this.#counter & 0b0001000) {
|
|
203
|
+
idx = 3 // normal: 6.25%
|
|
204
|
+
} else if (this.#counter & 0b0010000) {
|
|
205
|
+
idx = 2 // low: 3.125%
|
|
206
|
+
} else if (this.#counter & 0b0100000) {
|
|
207
|
+
idx = 1 // lower: 1.5625%
|
|
208
|
+
} else if (this.#counter & 0b1000000) {
|
|
209
|
+
idx = 0 // lowest: 0.78%
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (let n = idx; n >= 0 && (queue == null || queue.cnt === 0); n--) {
|
|
214
|
+
queue = this.#queues[n]
|
|
166
215
|
}
|
|
167
216
|
|
|
168
|
-
|
|
169
|
-
|
|
217
|
+
for (let n = this.#queues.length - 1; n > idx && (queue == null || queue.cnt === 0); n--) {
|
|
218
|
+
queue = this.#queues[n]
|
|
170
219
|
}
|
|
171
220
|
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
} else if ((this.#counter & 15) === 0 && this.#normalQueue.cnt > 0) {
|
|
175
|
-
queue = this.#normalQueue
|
|
221
|
+
if (queue == null || queue.cnt === 0) {
|
|
222
|
+
throw new Error('Invariant violation: pending > 0 but no tasks in queues')
|
|
176
223
|
}
|
|
177
224
|
|
|
178
225
|
const fn = queue.arr[queue.idx++]
|
|
179
226
|
const opaque = queue.arr[queue.idx++]
|
|
180
227
|
queue.cnt -= 1
|
|
228
|
+
queue.age = fastNow
|
|
181
229
|
|
|
182
230
|
if (queue.cnt === 0) {
|
|
183
231
|
queue.idx = 0
|
|
184
232
|
queue.arr.length = 0
|
|
185
233
|
} else if (queue.idx > 1024) {
|
|
186
|
-
queue.arr.splice(0, queue.idx)
|
|
187
234
|
queue.idx = 0
|
|
235
|
+
queue.arr.splice(0, queue.idx)
|
|
188
236
|
}
|
|
189
237
|
|
|
190
238
|
running = this.#stateView
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/scheduler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
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": "c88bafd0c4a7e250d9e4080d9f50a0a0a1a562ac"
|
|
24
24
|
}
|