44import { TransferProgressEvent } from "@azure/core-http" ;
55import { Readable } from "stream" ;
66
7- import { AbortSignal , AbortSignalLike , AbortError } from "@azure/abort-controller" ;
8-
97export type ReadableStreamGetter = ( offset : number ) => Promise < NodeJS . ReadableStream > ;
108
119export interface RetriableReadableStreamOptions {
12- /**
13- * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation.
14- * For example, use the @azure/abort-controller to create an `AbortSignal`.
15- *
16- * @type {AbortSignalLike }
17- * @memberof RetriableReadableStreamOptions
18- */
19- abortSignal ?: AbortSignalLike ;
20-
2110 /**
2211 * Max retry count (>=0), undefined or invalid value means no retry
2312 *
@@ -46,9 +35,15 @@ export interface RetriableReadableStreamOptions {
4635 * @memberof RetriableReadableStreamOptions
4736 */
4837 doInjectErrorOnce ?: boolean ;
49- }
5038
51- const ABORT_ERROR = new AbortError ( "The operation was aborted." ) ;
39+ /**
40+ * A threshold, not a limit. Dictates the amount of data that a stream buffers before it stops asking for more data.
41+ *
42+ * @type {number }
43+ * @memberof RetriableReadableStreamOptions
44+ */
45+ highWaterMark ?: number ;
46+ }
5247
5348/**
5449 * ONLY AVAILABLE IN NODE.JS RUNTIME.
@@ -59,7 +54,6 @@ const ABORT_ERROR = new AbortError("The operation was aborted.");
5954 * @extends {Readable }
6055 */
6156export class RetriableReadableStream extends Readable {
62- private aborter : AbortSignalLike ;
6357 private start : number ;
6458 private offset : number ;
6559 private end : number ;
@@ -69,10 +63,6 @@ export class RetriableReadableStream extends Readable {
6963 private maxRetryRequests : number ;
7064 private onProgress ?: ( progress : TransferProgressEvent ) => void ;
7165 private options : RetriableReadableStreamOptions ;
72- private abortHandler = ( ) => {
73- this . source . pause ( ) ;
74- this . emit ( "error" , ABORT_ERROR ) ;
75- } ;
7666
7767 /**
7868 * Creates an instance of RetriableReadableStream.
@@ -92,8 +82,7 @@ export class RetriableReadableStream extends Readable {
9282 count : number ,
9383 options : RetriableReadableStreamOptions = { }
9484 ) {
95- super ( ) ;
96- this . aborter = options . abortSignal || AbortSignal . none ;
85+ super ( { highWaterMark : options . highWaterMark } ) ;
9786 this . getter = getter ;
9887 this . source = source ;
9988 this . start = offset ;
@@ -104,96 +93,101 @@ export class RetriableReadableStream extends Readable {
10493 this . onProgress = options . onProgress ;
10594 this . options = options ;
10695
107- this . aborter . addEventListener ( "abort" , this . abortHandler ) ;
108-
109- this . setSourceDataHandler ( ) ;
110- this . setSourceEndHandler ( ) ;
111- this . setSourceErrorHandler ( ) ;
96+ this . setSourceEventHandlers ( ) ;
11297 }
11398
11499 public _read ( ) {
115- if ( ! this . aborter . aborted ) {
116- this . source . resume ( ) ;
117- }
100+ this . source . resume ( ) ;
118101 }
119102
120- private setSourceDataHandler ( ) {
121- this . source . on ( "data" , ( data : Buffer ) => {
122- if ( this . options . doInjectErrorOnce ) {
123- this . options . doInjectErrorOnce = undefined ;
124- this . source . pause ( ) ;
125- this . source . removeAllListeners ( "data" ) ;
126- this . source . emit ( "end" ) ;
127- return ;
128- }
103+ private setSourceEventHandlers ( ) {
104+ this . source . on ( "data" , this . sourceDataHandler ) ;
105+ this . source . on ( "end" , this . sourceErrorOrEndHandler ) ;
106+ this . source . on ( "error" , this . sourceErrorOrEndHandler ) ;
107+ }
129108
130- // console.log(
131- // `Offset: ${this.offset}, Received ${data.length} from internal stream`
132- // );
133- this . offset += data . length ;
134- if ( this . onProgress ) {
135- this . onProgress ( { loadedBytes : this . offset - this . start } ) ;
136- }
137- if ( ! this . push ( data ) ) {
138- this . source . pause ( ) ;
139- }
140- } ) ;
109+ private removeSourceEventHandlers ( ) {
110+ this . source . removeListener ( "data" , this . sourceDataHandler ) ;
111+ this . source . removeListener ( "end" , this . sourceErrorOrEndHandler ) ;
112+ this . source . removeListener ( "error" , this . sourceErrorOrEndHandler ) ;
141113 }
142114
143- private setSourceEndHandler ( ) {
144- this . source . on ( "end" , ( ) => {
115+ private sourceDataHandler = ( data : Buffer ) => {
116+ if ( this . options . doInjectErrorOnce ) {
117+ this . options . doInjectErrorOnce = undefined ;
118+ this . source . pause ( ) ;
119+ this . source . removeAllListeners ( "data" ) ;
120+ this . source . emit ( "end" ) ;
121+ return ;
122+ }
123+
124+ // console.log(
125+ // `Offset: ${this.offset}, Received ${data.length} from internal stream`
126+ // );
127+ this . offset += data . length ;
128+ if ( this . onProgress ) {
129+ this . onProgress ( { loadedBytes : this . offset - this . start } ) ;
130+ }
131+ if ( ! this . push ( data ) ) {
132+ this . source . pause ( ) ;
133+ }
134+ } ;
135+
136+ private sourceErrorOrEndHandler = ( err ?: Error ) => {
137+ if ( err && err . name === "AbortError" ) {
138+ this . destroy ( err ) ;
139+ return ;
140+ }
141+
142+ // console.log(
143+ // `Source stream emits end or error, offset: ${
144+ // this.offset
145+ // }, dest end : ${this.end}`
146+ // );
147+ this . removeSourceEventHandlers ( ) ;
148+ if ( this . offset - 1 === this . end ) {
149+ this . push ( null ) ;
150+ } else if ( this . offset <= this . end ) {
145151 // console.log(
146- // `Source stream emits end, offset: ${
147- // this.offset
148- // }, dest end : ${this.end}`
152+ // `retries: ${this.retries}, max retries: ${this.maxRetries}`
149153 // );
150- if ( this . offset - 1 === this . end ) {
151- this . aborter . removeEventListener ( "abort" , this . abortHandler ) ;
152- this . push ( null ) ;
153- } else if ( this . offset <= this . end ) {
154- // console.log(
155- // `retries: ${this.retries}, max retries: ${this.maxRetries}`
156- // );
157- if ( this . retries < this . maxRetryRequests ) {
158- this . retries += 1 ;
159- this . getter ( this . offset )
160- . then ( ( newSource ) => {
161- this . source = newSource ;
162- this . setSourceDataHandler ( ) ;
163- this . setSourceEndHandler ( ) ;
164- this . setSourceErrorHandler ( ) ;
165- } )
166- . catch ( ( error ) => {
167- this . emit ( "error" , error ) ;
168- } ) ;
169- } else {
170- this . emit (
171- "error" ,
172- new Error (
173- // tslint:disable-next-line:max-line-length
174- `Data corruption failure: received less data than required and reached maxRetires limitation. Received data offset: ${ this
175- . offset - 1 } , data needed offset: ${ this . end } , retries: ${
176- this . retries
177- } , max retries: ${ this . maxRetryRequests } `
178- )
179- ) ;
180- }
154+ if ( this . retries < this . maxRetryRequests ) {
155+ this . retries += 1 ;
156+ this . getter ( this . offset )
157+ . then ( ( newSource ) => {
158+ this . source = newSource ;
159+ this . setSourceEventHandlers ( ) ;
160+ } )
161+ . catch ( ( error ) => {
162+ this . destroy ( error ) ;
163+ } ) ;
181164 } else {
182- this . emit (
183- "error" ,
165+ this . destroy (
184166 new Error (
185- `Data corruption failure: Received more data than original request, data needed offset is ${
186- this . end
187- } , received offset: ${ this . offset - 1 } `
167+ // tslint:disable-next-line:max-line-length
168+ `Data corruption failure: received less data than required and reached maxRetires limitation. Received data offset: ${ this
169+ . offset - 1 } , data needed offset: ${ this . end } , retries: ${
170+ this . retries
171+ } , max retries: ${ this . maxRetryRequests } `
188172 )
189173 ) ;
190174 }
191- } ) ;
192- }
175+ } else {
176+ this . destroy (
177+ new Error (
178+ `Data corruption failure: Received more data than original request, data needed offset is ${
179+ this . end
180+ } , received offset: ${ this . offset - 1 } `
181+ )
182+ ) ;
183+ }
184+ } ;
185+
186+ _destroy ( error : Error | null , callback : ( error ?: Error ) => void ) : void {
187+ // remove listener from source and release source
188+ this . removeSourceEventHandlers ( ) ;
189+ ( this . source as Readable ) . destroy ( ) ;
193190
194- private setSourceErrorHandler ( ) {
195- this . source . on ( "error" , ( error ) => {
196- this . emit ( "error" , error ) ;
197- } ) ;
191+ callback ( error === null ? undefined : error ) ;
198192 }
199193}
0 commit comments