Skip to content

Commit 7c94d1e

Browse files
committed
Add value-based stall detection to catch stuck progress
Also refactors the test-stall-detection test suite.
1 parent 2e495e0 commit 7c94d1e

File tree

3 files changed

+305
-251
lines changed

3 files changed

+305
-251
lines changed

lib/StallDetector.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export class StallDetector {
99

1010
private intervalId: ReturnType<typeof setInterval> | null = null
1111
private lastProgressTime = 0
12+
private lastProgressValue = 0
13+
private progressValueCount = 0
1214
private isActive = false
1315

1416
constructor(
@@ -30,6 +32,8 @@ export class StallDetector {
3032
}
3133

3234
this.lastProgressTime = Date.now()
35+
this.lastProgressValue = 0
36+
this.progressValueCount = 0
3337
this.isActive = true
3438

3539
log(
@@ -43,7 +47,9 @@ export class StallDetector {
4347
}
4448

4549
const now = Date.now()
46-
if (this._isProgressStalled(now)) {
50+
if (this._isProgressValueStalled()) {
51+
this._handleStall('progress value not changing')
52+
} else if (this._isProgressStalled(now)) {
4753
this._handleStall('no progress events received')
4854
}
4955
}, this.options.checkInterval)
@@ -62,9 +68,18 @@ export class StallDetector {
6268

6369
/**
6470
* Update progress information
71+
* @param progressValue The current progress value (bytes uploaded)
6572
*/
66-
updateProgress(): void {
73+
updateProgress(progressValue: number): void {
6774
this.lastProgressTime = Date.now()
75+
76+
// Track if the progress value has changed
77+
if (progressValue === this.lastProgressValue) {
78+
this.progressValueCount++
79+
} else {
80+
this.lastProgressValue = progressValue
81+
this.progressValueCount = 0
82+
}
6883
}
6984

7085
/**
@@ -82,6 +97,25 @@ export class StallDetector {
8297
return isStalled
8398
}
8499

100+
/**
101+
* Check if upload has stalled based on progress value not changing
102+
*/
103+
private _isProgressValueStalled(): boolean {
104+
// Calculate how many times we expect progress to have changed based on check intervals
105+
const expectedProgressChanges = Math.floor(
106+
this.options.stallTimeout / this.options.checkInterval,
107+
)
108+
const isStalled = this.progressValueCount >= expectedProgressChanges
109+
110+
if (isStalled) {
111+
log(
112+
`tus: progress value stuck at ${this.lastProgressValue} bytes for ${this.progressValueCount} checks`,
113+
)
114+
}
115+
116+
return isStalled
117+
}
118+
85119
/**
86120
* Handle a detected stall
87121
*/

lib/upload.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ export class BaseUpload {
872872
req.setProgressHandler((bytesSent) => {
873873
// Update per-request stall detector if active
874874
if (stallDetector) {
875-
stallDetector.updateProgress()
875+
stallDetector.updateProgress(start + bytesSent)
876876
}
877877
this._emitProgress(start + bytesSent, this._size)
878878
})
@@ -921,20 +921,19 @@ export class BaseUpload {
921921
)
922922
}
923923

924-
let response: HttpResponse
925924
if (value == null) {
926-
response = await this._sendRequest(req, undefined, stallDetector)
927-
} else {
928-
if (
929-
this.options.protocol === PROTOCOL_IETF_DRAFT_03 ||
930-
this.options.protocol === PROTOCOL_IETF_DRAFT_05
931-
) {
932-
req.setHeader('Upload-Complete', done ? '?1' : '?0')
933-
}
934-
response = await this._sendRequest(req, value, stallDetector)
925+
return await this._sendRequest(req, undefined, stallDetector)
926+
}
927+
928+
if (
929+
this.options.protocol === PROTOCOL_IETF_DRAFT_03 ||
930+
this.options.protocol === PROTOCOL_IETF_DRAFT_05
931+
) {
932+
req.setHeader('Upload-Complete', done ? '?1' : '?0')
935933
}
936934

937-
return response
935+
this._emitProgress(this._offset, this._size)
936+
return await this._sendRequest(req, value, stallDetector)
938937
}
939938

940939
/**

0 commit comments

Comments
 (0)