Skip to content

Commit 24b03de

Browse files
committed
fix: further edge race conditions
1 parent 1ec4f40 commit 24b03de

File tree

2 files changed

+24
-13
lines changed

2 files changed

+24
-13
lines changed

package/src/components/MessageInput/components/AudioRecorder/AudioRecordingButton.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ export const AudioRecordingButtonWithContext = (props: AudioRecordingButtonProps
157157
}
158158
if (status === 'recording') {
159159
uploadVoiceRecording(asyncMessagesMultiSendEnabled);
160+
} else {
161+
resetAudioRecording();
160162
}
161163
});
162164

package/src/state-store/audio-recorder-manager.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ const INITIAL_STATE: AudioRecorderManagerState = {
3333
export class AudioRecorderManager {
3434
state: StateStore<AudioRecorderManagerState>;
3535
/**
36-
* A boolean signifying whether we've tried to stop a recording while the
36+
* A Set signifying whether we've tried to stop a recording session while the
3737
* startRecording method is still executing. It is useful to identify race
3838
* conditions where we try to cancel the recording altogether while setting
3939
* up the recording session finishes (which can take up to 700ms on some
4040
* slower devices).
4141
* @private
4242
*/
43-
private hasPendingStopWhileStarting = false;
43+
private pendingStopRequestIds = new Set<number>();
4444
/**
4545
* A request ID for this recording session. Used to identify stale recording
4646
* sessions and respond adequately, avoiding race conditions.
@@ -53,8 +53,6 @@ export class AudioRecorderManager {
5353
}
5454

5555
reset() {
56-
this.startRequestId += 1;
57-
this.hasPendingStopWhileStarting = false;
5856
this.state.next(INITIAL_STATE);
5957
}
6058

@@ -82,7 +80,6 @@ export class AudioRecorderManager {
8280
if (isStarting || status === 'recording') {
8381
return true;
8482
}
85-
this.hasPendingStopWhileStarting = false;
8683
const requestId = ++this.startRequestId;
8784
this.state.partialNext({
8885
duration: 0,
@@ -101,21 +98,32 @@ export class AudioRecorderManager {
10198
this.onRecordingStatusUpdate,
10299
);
103100
} catch {
101+
this.pendingStopRequestIds.delete(requestId);
104102
if (requestId === this.startRequestId) {
105103
this.reset();
106104
}
107105
return false;
108106
}
109107

110108
const { accessGranted, recording } = recordingInfo;
111-
if (this.hasPendingStopWhileStarting || requestId !== this.startRequestId) {
112-
try {
113-
await NativeHandlers.Audio.stopRecording();
114-
} catch {
115-
// If stopRecording fails, the native implementation has already stopped the
116-
// recorder and so we do nothing.
109+
if (this.pendingStopRequestIds.has(requestId)) {
110+
if (accessGranted) {
111+
try {
112+
await NativeHandlers.Audio.stopRecording();
113+
} catch {
114+
// If stopRecording fails, the native implementation has already stopped the
115+
// recorder and so we do nothing.
116+
}
117+
}
118+
this.pendingStopRequestIds.delete(requestId);
119+
if (requestId === this.startRequestId) {
120+
this.reset();
117121
}
118-
this.reset();
122+
return accessGranted;
123+
}
124+
125+
if (requestId !== this.startRequestId) {
126+
// Stale start completion, ignore to avoid affecting newer attempts
119127
return accessGranted;
120128
}
121129

@@ -137,7 +145,8 @@ export class AudioRecorderManager {
137145
}
138146
const { isStarting, status } = this.state.getLatestValue();
139147
if (isStarting) {
140-
this.hasPendingStopWhileStarting = true;
148+
this.pendingStopRequestIds.add(this.startRequestId);
149+
this.reset();
141150
return;
142151
}
143152
if (status !== 'recording') {

0 commit comments

Comments
 (0)