@nxtedition/scheduler 2.0.4 → 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 -11
- package/lib/index.js +114 -65
- 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') {
|
|
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.#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
|
}
|
|
@@ -154,30 +186,47 @@ export class Scheduler {
|
|
|
154
186
|
try {
|
|
155
187
|
this.#releasing = true
|
|
156
188
|
while (this.#pending > 0) {
|
|
157
|
-
let queue
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
} 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) {
|
|
165
217
|
throw new Error('Invariant violation: pending > 0 but no tasks in queues')
|
|
166
218
|
}
|
|
167
219
|
|
|
168
|
-
if (this.#running > 0 && running >=
|
|
220
|
+
if (this.#running > 0 && running >= this.#concurrency) {
|
|
169
221
|
break
|
|
170
222
|
}
|
|
171
223
|
|
|
172
|
-
|
|
173
|
-
queue = this.#lowQueue
|
|
174
|
-
} else if ((this.#counter & 15) === 0 && this.#normalQueue.cnt > 0) {
|
|
175
|
-
queue = this.#normalQueue
|
|
176
|
-
}
|
|
224
|
+
this.#counter = (this.#counter + 1) & maxInt
|
|
177
225
|
|
|
178
226
|
const fn = queue.arr[queue.idx++]
|
|
179
227
|
const opaque = queue.arr[queue.idx++]
|
|
180
228
|
queue.cnt -= 1
|
|
229
|
+
queue.age = fastNow
|
|
181
230
|
|
|
182
231
|
if (queue.cnt === 0) {
|
|
183
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
|
}
|