1
1
/* eslint-disable @typescript-eslint/no-non-null-assertion */
2
2
3
3
import { CodeError , ERR_INVALID_PARAMETERS } from '@libp2p/interface'
4
+ import { PeerMap } from '@libp2p/peer-collections'
5
+ import pDefer from 'p-defer'
4
6
import PQueue from 'p-queue'
5
7
import type { PeerId } from '@libp2p/interface'
8
+ import type { AbortOptions } from 'it-pushable'
9
+ import type { DeferredPromise } from 'p-defer'
6
10
import type { QueueAddOptions , Options , Queue } from 'p-queue'
7
11
8
12
// Port of lower_bound from https://en.cppreference.com/w/cpp/algorithm/lower_bound
@@ -26,7 +30,9 @@ function lowerBound<T> (array: readonly T[], value: T, comparator: (a: T, b: T)
26
30
return first
27
31
}
28
32
29
- interface RunFunction { ( ) : Promise < unknown > }
33
+ interface RunFunction < T > {
34
+ ( options ?: AbortOptions ) : Promise < T >
35
+ }
30
36
31
37
export interface PeerPriorityQueueOptions extends QueueAddOptions {
32
38
peerId : PeerId
@@ -35,17 +41,17 @@ export interface PeerPriorityQueueOptions extends QueueAddOptions {
35
41
interface PeerJob {
36
42
priority : number
37
43
peerId : PeerId
38
- run : RunFunction
44
+ run : RunFunction < any >
39
45
}
40
46
41
47
/**
42
48
* Port of https://github.com/sindresorhus/p-queue/blob/main/source/priority-queue.ts
43
49
* that adds support for filtering jobs by peer id
44
50
*/
45
- class PeerPriorityQueue implements Queue < RunFunction , PeerPriorityQueueOptions > {
51
+ class PeerPriorityQueue implements Queue < RunFunction < unknown > , PeerPriorityQueueOptions > {
46
52
readonly #queue: PeerJob [ ] = [ ]
47
53
48
- enqueue ( run : RunFunction , options ?: Partial < PeerPriorityQueueOptions > ) : void {
54
+ enqueue ( run : RunFunction < unknown > , options ?: Partial < PeerPriorityQueueOptions > ) : void {
49
55
const peerId = options ?. peerId
50
56
const priority = options ?. priority ?? 0
51
57
@@ -71,23 +77,23 @@ class PeerPriorityQueue implements Queue<RunFunction, PeerPriorityQueueOptions>
71
77
this . #queue. splice ( index , 0 , element )
72
78
}
73
79
74
- dequeue ( ) : RunFunction | undefined {
80
+ dequeue ( ) : RunFunction < unknown > | undefined {
75
81
const item = this . #queue. shift ( )
76
82
return item ?. run
77
83
}
78
84
79
- filter ( options : Readonly < Partial < PeerPriorityQueueOptions > > ) : RunFunction [ ] {
85
+ filter ( options : Readonly < Partial < PeerPriorityQueueOptions > > ) : Array < RunFunction < unknown > > {
80
86
if ( options . peerId != null ) {
81
87
const peerId = options . peerId
82
88
83
89
return this . #queue. filter (
84
90
( element : Readonly < PeerPriorityQueueOptions > ) => peerId . equals ( element . peerId )
85
- ) . map ( ( element : Readonly < { run : RunFunction } > ) => element . run )
91
+ ) . map ( ( element : Readonly < { run : RunFunction < unknown > } > ) => element . run )
86
92
}
87
93
88
94
return this . #queue. filter (
89
95
( element : Readonly < PeerPriorityQueueOptions > ) => element . priority === options . priority
90
- ) . map ( ( element : Readonly < { run : RunFunction } > ) => element . run )
96
+ ) . map ( ( element : Readonly < { run : RunFunction < unknown > } > ) => element . run )
91
97
}
92
98
93
99
get size ( ) : number {
@@ -99,20 +105,78 @@ class PeerPriorityQueue implements Queue<RunFunction, PeerPriorityQueueOptions>
99
105
* Extends PQueue to add support for querying queued jobs by peer id
100
106
*/
101
107
export class PeerJobQueue extends PQueue < PeerPriorityQueue , PeerPriorityQueueOptions > {
108
+ private readonly results : PeerMap < DeferredPromise < any > | true >
109
+
102
110
constructor ( options : Options < PeerPriorityQueue , PeerPriorityQueueOptions > = { } ) {
103
111
super ( {
104
112
...options ,
105
113
queueClass : PeerPriorityQueue
106
114
} )
115
+
116
+ this . results = new PeerMap ( )
107
117
}
108
118
109
119
/**
110
- * Returns true if this queue has a job for the passed peer id that has not yet
111
- * started to run
120
+ * Returns true if this queue has a job for the passed peer id that has not
121
+ * yet started to run
112
122
*/
113
123
hasJob ( peerId : PeerId ) : boolean {
114
124
return this . sizeBy ( {
115
125
peerId
116
126
} ) > 0
117
127
}
128
+
129
+ /**
130
+ * Returns a promise for the result of the job in the queue for the passed
131
+ * peer id.
132
+ */
133
+ async joinJob < Result = void > ( peerId : PeerId ) : Promise < Result > {
134
+ let deferred = this . results . get ( peerId )
135
+
136
+ if ( deferred == null ) {
137
+ throw new CodeError ( 'No job found for peer id' , 'ERR_NO_JOB_FOR_PEER_ID' )
138
+ }
139
+
140
+ if ( deferred === true ) {
141
+ // a job has been added but so far nothing has tried to join the job
142
+ deferred = pDefer < Result > ( )
143
+ this . results . set ( peerId , deferred )
144
+ }
145
+
146
+ return deferred . promise
147
+ }
148
+
149
+ async add < T > ( fn : RunFunction < T > , opts : PeerPriorityQueueOptions ) : Promise < T > {
150
+ const peerId = opts ?. peerId
151
+
152
+ if ( peerId == null ) {
153
+ throw new CodeError ( 'missing peer id' , ERR_INVALID_PARAMETERS )
154
+ }
155
+
156
+ this . results . set ( opts . peerId , true )
157
+
158
+ return super . add ( async ( opts ?: AbortOptions ) => {
159
+ try {
160
+ const value = await fn ( opts )
161
+
162
+ const deferred = this . results . get ( peerId )
163
+
164
+ if ( deferred != null && deferred !== true ) {
165
+ deferred . resolve ( value )
166
+ }
167
+
168
+ return value
169
+ } catch ( err ) {
170
+ const deferred = this . results . get ( peerId )
171
+
172
+ if ( deferred != null && deferred !== true ) {
173
+ deferred . reject ( err )
174
+ }
175
+
176
+ throw err
177
+ } finally {
178
+ this . results . delete ( peerId )
179
+ }
180
+ } , opts ) as Promise < T >
181
+ }
118
182
}
0 commit comments