Skip to content

Commit aaad1df

Browse files
committed
fix: avoid starvation with task priority
Signed-off-by: Jérôme Benoit <[email protected]>
1 parent 7ca229f commit aaad1df

File tree

7 files changed

+119
-91
lines changed

7 files changed

+119
-91
lines changed

deno.json

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
"version": "0.5.9",
44
"exports": "./src/mod.ts",
55
"compilerOptions": {
6-
"lib": [
7-
"deno.worker"
8-
],
6+
"lib": ["deno.worker"],
97
"strict": true
108
},
119
"tasks": {
@@ -27,9 +25,7 @@
2725
"documentation": "deno doc ./src/mod.ts"
2826
},
2927
"test": {
30-
"include": [
31-
"./tests/**/*.test.mjs"
32-
]
28+
"include": ["./tests/**/*.test.mjs"]
3329
},
3430
"fmt": {
3531
"semiColons": false,
@@ -42,18 +38,8 @@
4238
"@std/testing": "jsr:@std/testing@^1.0.15"
4339
},
4440
"publish": {
45-
"include": [
46-
"LICENSE",
47-
"README.md",
48-
"deno.json",
49-
"src/**/*.ts"
50-
]
41+
"include": ["LICENSE", "README.md", "deno.json", "src/**/*.ts"]
5142
},
5243
"lock": false,
53-
"exclude": [
54-
"./coverage",
55-
"./dist/browser",
56-
"./dist/esm",
57-
"./npm"
58-
]
44+
"exclude": ["./coverage", "./dist/browser", "./dist/esm", "./npm"]
5945
}

src/queues/abstract-fixed-queue.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ export abstract class AbstractFixedQueue<T> implements IFixedQueue<T> {
162162
`Invalid fixed queue size: '${size.toString()}' is not an integer`,
163163
)
164164
}
165-
if (size < 0) {
166-
throw new RangeError(`Invalid fixed queue size: ${size.toString()} < 0`)
165+
if (size <= 0) {
166+
throw new RangeError(`Invalid fixed queue size: ${size.toString()} <= 0`)
167167
}
168168
}
169169
}

src/queues/fixed-priority-queue.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,33 @@ import type { IFixedQueue } from './queue-types.ts'
99
*/
1010
export class FixedPriorityQueue<T> extends AbstractFixedQueue<T>
1111
implements IFixedQueue<T> {
12+
private readonly agingFactor: number
13+
14+
/**
15+
* Constructs a FixedPriorityQueue.
16+
* @param size - Fixed queue size. @defaultValue defaultQueueSize
17+
* @param agingFactor - Aging factor to apply to items in priority points per millisecond. A higher value makes tasks age faster.
18+
* @returns IFixedQueue.
19+
*/
20+
public constructor(size?: number, agingFactor = 0.001) {
21+
super(size)
22+
this.agingFactor = agingFactor
23+
}
24+
1225
/** @inheritdoc */
1326
public enqueue(data: T, priority?: number): number {
1427
if (this.full()) {
1528
throw new Error('Fixed priority queue is full')
1629
}
1730
priority = priority ?? 0
31+
const now = performance.now()
1832
let insertionPhysicalIndex = -1
1933
let currentPhysicalIndex = this.start
2034
for (let i = 0; i < this.size; i++) {
21-
if (this.nodeArray[currentPhysicalIndex]!.priority > priority) {
35+
const node = this.nodeArray[currentPhysicalIndex]!
36+
const nodeEffectivePriority = node.priority -
37+
(now - node.timestamp) * this.agingFactor
38+
if (nodeEffectivePriority > priority) {
2239
insertionPhysicalIndex = currentPhysicalIndex
2340
break
2441
}
@@ -44,7 +61,7 @@ export class FixedPriorityQueue<T> extends AbstractFixedQueue<T>
4461
shiftPhysicalIndex = previousPhysicalIndex
4562
}
4663
}
47-
this.nodeArray[insertionPhysicalIndex] = { data, priority }
64+
this.nodeArray[insertionPhysicalIndex] = { data, priority, timestamp: now }
4865
return ++this.size
4966
}
5067
}

src/queues/fixed-queue.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ export class FixedQueue<T> extends AbstractFixedQueue<T>
1818
if (index >= this.capacity) {
1919
index -= this.capacity
2020
}
21-
this.nodeArray[index] = { data, priority: priority ?? 0 }
21+
this.nodeArray[index] = {
22+
data,
23+
priority: priority ?? 0,
24+
timestamp: performance.now(),
25+
}
2226
return ++this.size
2327
}
2428
}

src/queues/queue-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const defaultQueueSize = 2048
1212
export interface FixedQueueNode<T> {
1313
data: T
1414
priority: number
15+
timestamp: number
1516
}
1617

1718
/**

tests/queues/fixed-priority-queue.test.mjs

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('Fixed priority queue test suite', () => {
1010
new TypeError("Invalid fixed queue size: '' is not an integer"),
1111
)
1212
expect(() => new FixedPriorityQueue(-1)).toThrow(
13-
new RangeError('Invalid fixed queue size: -1 < 0'),
13+
new RangeError('Invalid fixed queue size: -1 <= 0'),
1414
)
1515
const fixedPriorityQueue = new FixedPriorityQueue()
1616
expect(fixedPriorityQueue.start).toBe(0)
@@ -27,49 +27,49 @@ describe('Fixed priority queue test suite', () => {
2727
expect(fixedPriorityQueue.size).toBe(1)
2828
expect(rtSize).toBe(fixedPriorityQueue.size)
2929
expect(fixedPriorityQueue.nodeArray).toMatchObject([
30-
{ data: 1, priority: 0 },
30+
{ data: 1, priority: 0, timestamp: expect.any(Number) },
3131
])
3232
expect(fixedPriorityQueue.capacity).toBe(queueSize)
3333
rtSize = fixedPriorityQueue.enqueue(2)
3434
expect(fixedPriorityQueue.start).toBe(0)
3535
expect(fixedPriorityQueue.size).toBe(2)
3636
expect(rtSize).toBe(fixedPriorityQueue.size)
3737
expect(fixedPriorityQueue.nodeArray).toMatchObject([
38-
{ data: 1, priority: 0 },
39-
{ data: 2, priority: 0 },
38+
{ data: 1, priority: 0, timestamp: expect.any(Number) },
39+
{ data: 2, priority: 0, timestamp: expect.any(Number) },
4040
])
4141
expect(fixedPriorityQueue.capacity).toBe(queueSize)
4242
rtSize = fixedPriorityQueue.enqueue(3)
4343
expect(fixedPriorityQueue.start).toBe(0)
4444
expect(fixedPriorityQueue.size).toBe(3)
4545
expect(rtSize).toBe(fixedPriorityQueue.size)
4646
expect(fixedPriorityQueue.nodeArray).toMatchObject([
47-
{ data: 1, priority: 0 },
48-
{ data: 2, priority: 0 },
49-
{ data: 3, priority: 0 },
47+
{ data: 1, priority: 0, timestamp: expect.any(Number) },
48+
{ data: 2, priority: 0, timestamp: expect.any(Number) },
49+
{ data: 3, priority: 0, timestamp: expect.any(Number) },
5050
])
5151
expect(fixedPriorityQueue.capacity).toBe(queueSize)
5252
rtSize = fixedPriorityQueue.enqueue(3, -1)
5353
expect(fixedPriorityQueue.start).toBe(0)
5454
expect(fixedPriorityQueue.size).toBe(4)
5555
expect(rtSize).toBe(fixedPriorityQueue.size)
5656
expect(fixedPriorityQueue.nodeArray).toMatchObject([
57-
{ data: 3, priority: -1 },
58-
{ data: 1, priority: 0 },
59-
{ data: 2, priority: 0 },
60-
{ data: 3, priority: 0 },
57+
{ data: 3, priority: -1, timestamp: expect.any(Number) },
58+
{ data: 1, priority: 0, timestamp: expect.any(Number) },
59+
{ data: 2, priority: 0, timestamp: expect.any(Number) },
60+
{ data: 3, priority: 0, timestamp: expect.any(Number) },
6161
])
6262
expect(fixedPriorityQueue.capacity).toBe(queueSize)
6363
rtSize = fixedPriorityQueue.enqueue(1, 1)
6464
expect(fixedPriorityQueue.start).toBe(0)
6565
expect(fixedPriorityQueue.size).toBe(5)
6666
expect(rtSize).toBe(fixedPriorityQueue.size)
6767
expect(fixedPriorityQueue.nodeArray).toMatchObject([
68-
{ data: 3, priority: -1 },
69-
{ data: 1, priority: 0 },
70-
{ data: 2, priority: 0 },
71-
{ data: 3, priority: 0 },
72-
{ data: 1, priority: 1 },
68+
{ data: 3, priority: -1, timestamp: expect.any(Number) },
69+
{ data: 1, priority: 0, timestamp: expect.any(Number) },
70+
{ data: 2, priority: 0, timestamp: expect.any(Number) },
71+
{ data: 3, priority: 0, timestamp: expect.any(Number) },
72+
{ data: 1, priority: 1, timestamp: expect.any(Number) },
7373
])
7474
expect(fixedPriorityQueue.capacity).toBe(queueSize)
7575
expect(() => fixedPriorityQueue.enqueue(4)).toThrow(
@@ -103,8 +103,8 @@ describe('Fixed priority queue test suite', () => {
103103
expect(rtItem).toBe(2)
104104
expect(fixedPriorityQueue.nodeArray).toMatchObject([
105105
undefined,
106-
{ data: 1, priority: 0 },
107-
{ data: 3, priority: 0 },
106+
{ data: 1, priority: 0, timestamp: expect.any(Number) },
107+
{ data: 3, priority: 0, timestamp: expect.any(Number) },
108108
])
109109
expect(fixedPriorityQueue.capacity).toBe(queueSize)
110110
rtItem = fixedPriorityQueue.dequeue()
@@ -114,7 +114,7 @@ describe('Fixed priority queue test suite', () => {
114114
expect(fixedPriorityQueue.nodeArray).toMatchObject([
115115
undefined,
116116
undefined,
117-
{ data: 3, priority: 0 },
117+
{ data: 3, priority: 0, timestamp: expect.any(Number) },
118118
])
119119
expect(fixedPriorityQueue.capacity).toBe(queueSize)
120120
rtItem = fixedPriorityQueue.dequeue()
@@ -147,22 +147,22 @@ describe('Fixed priority queue test suite', () => {
147147
expect(fixedPriorityQueue.start).toBe(0)
148148
expect(fixedPriorityQueue.size).toBe(3)
149149
expect(fixedPriorityQueue.nodeArray).toMatchObject([
150-
{ data: 2, priority: -1 },
151-
{ data: 1, priority: 0 },
152-
{ data: 3, priority: 0 },
150+
{ data: 2, priority: -1, timestamp: expect.any(Number) },
151+
{ data: 1, priority: 0, timestamp: expect.any(Number) },
152+
{ data: 3, priority: 0, timestamp: expect.any(Number) },
153153
])
154154
expect(fixedPriorityQueue.delete(2)).toBe(true)
155155
expect(fixedPriorityQueue.start).toBe(0)
156156
expect(fixedPriorityQueue.size).toBe(2)
157157
expect(fixedPriorityQueue.nodeArray).toMatchObject([
158-
{ data: 1, priority: 0 },
159-
{ data: 3, priority: 0 },
158+
{ data: 1, priority: 0, timestamp: expect.any(Number) },
159+
{ data: 3, priority: 0, timestamp: expect.any(Number) },
160160
])
161161
expect(fixedPriorityQueue.delete(3)).toBe(true)
162162
expect(fixedPriorityQueue.start).toBe(0)
163163
expect(fixedPriorityQueue.size).toBe(1)
164164
expect(fixedPriorityQueue.nodeArray).toMatchObject([
165-
{ data: 1, priority: 0 },
165+
{ data: 1, priority: 0, timestamp: expect.any(Number) },
166166
])
167167
expect(fixedPriorityQueue.delete(1)).toBe(true)
168168
expect(fixedPriorityQueue.start).toBe(0)
@@ -213,16 +213,24 @@ describe('Fixed priority queue test suite', () => {
213213
})
214214

215215
it('Verify clear() behavior', () => {
216-
const fixedPriorityQueue = new FixedPriorityQueue(2)
217-
fixedPriorityQueue.start = 1
218-
fixedPriorityQueue.size = 2
219-
fixedPriorityQueue.nodeArray = [
220-
{ data: 2, priority: 0 },
221-
{ data: 3, priority: 0 },
222-
]
216+
const queueSize = 3
217+
const fixedPriorityQueue = new FixedPriorityQueue(queueSize)
218+
fixedPriorityQueue.enqueue(1)
219+
fixedPriorityQueue.enqueue(2, -1)
220+
fixedPriorityQueue.enqueue(3)
221+
expect(fixedPriorityQueue.size).toBe(queueSize)
222+
expect(fixedPriorityQueue.nodeArray).toMatchObject([
223+
{ data: 2, priority: -1, timestamp: expect.any(Number) },
224+
{ data: 1, priority: 0, timestamp: expect.any(Number) },
225+
{ data: 3, priority: 0, timestamp: expect.any(Number) },
226+
])
223227
fixedPriorityQueue.clear()
224-
expect(fixedPriorityQueue.start).toBe(0)
225228
expect(fixedPriorityQueue.size).toBe(0)
226-
expect(fixedPriorityQueue.nodeArray).toStrictEqual([undefined, undefined])
229+
expect(fixedPriorityQueue.start).toBe(0)
230+
expect(fixedPriorityQueue.nodeArray).toStrictEqual([
231+
undefined,
232+
undefined,
233+
undefined,
234+
])
227235
})
228236
})

0 commit comments

Comments
 (0)