1
1
import { abortableSource } from 'abortable-iterator'
2
2
import { type Pushable , pushable } from 'it-pushable'
3
3
import defer , { type DeferredPromise } from 'p-defer'
4
+ import { raceSignal } from 'race-signal'
4
5
import { Uint8ArrayList } from 'uint8arraylist'
5
6
import { CodeError } from '../errors.js'
6
7
import type { Direction , ReadStatus , Stream , StreamStatus , StreamTimeline , WriteStatus } from '../connection/index.js'
7
8
import type { AbortOptions } from '../index.js'
8
9
import type { Source } from 'it-stream-types'
9
10
11
+ // copied from @libp 2p/logger to break a circular dependency
10
12
interface Logger {
11
13
( formatter : any , ...args : any [ ] ) : void
12
14
error : ( formatter : any , ...args : any [ ] ) => void
@@ -16,6 +18,7 @@ interface Logger {
16
18
17
19
const ERR_STREAM_RESET = 'ERR_STREAM_RESET'
18
20
const ERR_SINK_INVALID_STATE = 'ERR_SINK_INVALID_STATE'
21
+ const DEFAULT_SEND_CLOSE_WRITE_TIMEOUT = 5000
19
22
20
23
export interface AbstractStreamInit {
21
24
/**
@@ -68,6 +71,12 @@ export interface AbstractStreamInit {
68
71
* connection when closing the writable end of the stream. (default: 500)
69
72
*/
70
73
closeTimeout ?: number
74
+
75
+ /**
76
+ * After the stream sink has closed, a limit on how long it takes to send
77
+ * a close-write message to the remote peer.
78
+ */
79
+ sendCloseWriteTimeout ?: number
71
80
}
72
81
73
82
function isPromise ( res ?: any ) : res is Promise < void > {
@@ -94,6 +103,7 @@ export abstract class AbstractStream implements Stream {
94
103
private readonly onCloseWrite ?: ( ) => void
95
104
private readonly onReset ?: ( ) => void
96
105
private readonly onAbort ?: ( err : Error ) => void
106
+ private readonly sendCloseWriteTimeout : number
97
107
98
108
protected readonly log : Logger
99
109
@@ -113,6 +123,7 @@ export abstract class AbstractStream implements Stream {
113
123
this . timeline = {
114
124
open : Date . now ( )
115
125
}
126
+ this . sendCloseWriteTimeout = init . sendCloseWriteTimeout ?? DEFAULT_SEND_CLOSE_WRITE_TIMEOUT
116
127
117
128
this . onEnd = init . onEnd
118
129
this . onCloseRead = init ?. onCloseRead
@@ -128,7 +139,6 @@ export abstract class AbstractStream implements Stream {
128
139
this . log . trace ( 'source ended' )
129
140
}
130
141
131
- this . readStatus = 'closed'
132
142
this . onSourceEnd ( err )
133
143
}
134
144
} )
@@ -173,11 +183,19 @@ export abstract class AbstractStream implements Stream {
173
183
}
174
184
}
175
185
176
- this . log . trace ( 'sink finished reading from source' )
177
- this . writeStatus = 'done'
186
+ this . log . trace ( 'sink finished reading from source, write status is "%s"' , this . writeStatus )
187
+
188
+ if ( this . writeStatus === 'writing' ) {
189
+ this . writeStatus = 'closing'
190
+
191
+ this . log . trace ( 'send close write to remote' )
192
+ await this . sendCloseWrite ( {
193
+ signal : AbortSignal . timeout ( this . sendCloseWriteTimeout )
194
+ } )
195
+
196
+ this . writeStatus = 'closed'
197
+ }
178
198
179
- this . log . trace ( 'sink calling closeWrite' )
180
- await this . closeWrite ( options )
181
199
this . onSinkEnd ( )
182
200
} catch ( err : any ) {
183
201
this . log . trace ( 'sink ended with error, calling abort with error' , err )
@@ -196,6 +214,7 @@ export abstract class AbstractStream implements Stream {
196
214
}
197
215
198
216
this . timeline . closeRead = Date . now ( )
217
+ this . readStatus = 'closed'
199
218
200
219
if ( err != null && this . endErr == null ) {
201
220
this . endErr = err
@@ -207,6 +226,10 @@ export abstract class AbstractStream implements Stream {
207
226
this . log . trace ( 'source and sink ended' )
208
227
this . timeline . close = Date . now ( )
209
228
229
+ if ( this . status !== 'aborted' && this . status !== 'reset' ) {
230
+ this . status = 'closed'
231
+ }
232
+
210
233
if ( this . onEnd != null ) {
211
234
this . onEnd ( this . endErr )
212
235
}
@@ -221,6 +244,7 @@ export abstract class AbstractStream implements Stream {
221
244
}
222
245
223
246
this . timeline . closeWrite = Date . now ( )
247
+ this . writeStatus = 'closed'
224
248
225
249
if ( err != null && this . endErr == null ) {
226
250
this . endErr = err
@@ -232,6 +256,10 @@ export abstract class AbstractStream implements Stream {
232
256
this . log . trace ( 'sink and source ended' )
233
257
this . timeline . close = Date . now ( )
234
258
259
+ if ( this . status !== 'aborted' && this . status !== 'reset' ) {
260
+ this . status = 'closed'
261
+ }
262
+
235
263
if ( this . onEnd != null ) {
236
264
this . onEnd ( this . endErr )
237
265
}
@@ -266,16 +294,16 @@ export abstract class AbstractStream implements Stream {
266
294
const readStatus = this . readStatus
267
295
this . readStatus = 'closing'
268
296
269
- if ( readStatus === 'ready' ) {
270
- this . log . trace ( 'ending internal source queue' )
271
- this . streamSource . end ( )
272
- }
273
-
274
297
if ( this . status !== 'reset' && this . status !== 'aborted' && this . timeline . closeRead == null ) {
275
298
this . log . trace ( 'send close read to remote' )
276
299
await this . sendCloseRead ( options )
277
300
}
278
301
302
+ if ( readStatus === 'ready' ) {
303
+ this . log . trace ( 'ending internal source queue' )
304
+ this . streamSource . end ( )
305
+ }
306
+
279
307
this . log . trace ( 'closed readable end of stream' )
280
308
}
281
309
@@ -286,33 +314,26 @@ export abstract class AbstractStream implements Stream {
286
314
287
315
this . log . trace ( 'closing writable end of stream with starting write status "%s"' , this . writeStatus )
288
316
289
- const writeStatus = this . writeStatus
290
-
291
317
if ( this . writeStatus === 'ready' ) {
292
318
this . log . trace ( 'sink was never sunk, sink an empty array' )
293
- await this . sink ( [ ] )
294
- }
295
319
296
- this . writeStatus = 'closing'
320
+ await raceSignal ( this . sink ( [ ] ) , options . signal )
321
+ }
297
322
298
- if ( writeStatus === 'writing' ) {
323
+ if ( this . writeStatus === 'writing' ) {
299
324
// stop reading from the source passed to `.sink` in the microtask queue
300
325
// - this lets any data queued by the user in the current tick get read
301
326
// before we exit
302
327
await new Promise ( ( resolve , reject ) => {
303
328
queueMicrotask ( ( ) => {
304
329
this . log . trace ( 'aborting source passed to .sink' )
305
330
this . sinkController . abort ( )
306
- this . sinkEnd . promise . then ( resolve , reject )
331
+ raceSignal ( this . sinkEnd . promise , options . signal )
332
+ . then ( resolve , reject )
307
333
} )
308
334
} )
309
335
}
310
336
311
- if ( this . status !== 'reset' && this . status !== 'aborted' && this . timeline . closeWrite == null ) {
312
- this . log . trace ( 'send close write to remote' )
313
- await this . sendCloseWrite ( options )
314
- }
315
-
316
337
this . writeStatus = 'closed'
317
338
318
339
this . log . trace ( 'closed writable end of stream' )
@@ -357,6 +378,7 @@ export abstract class AbstractStream implements Stream {
357
378
const err = new CodeError ( 'stream reset' , ERR_STREAM_RESET )
358
379
359
380
this . status = 'reset'
381
+ this . timeline . reset = Date . now ( )
360
382
this . _closeSinkAndSource ( err )
361
383
this . onReset ?.( )
362
384
}
@@ -423,7 +445,7 @@ export abstract class AbstractStream implements Stream {
423
445
return
424
446
}
425
447
426
- this . log . trace ( 'muxer destroyed' )
448
+ this . log . trace ( 'stream destroyed' )
427
449
428
450
this . _closeSinkAndSource ( )
429
451
}
0 commit comments