Skip to content

Commit 368ab7c

Browse files
committed
usb/sagas: Retry USB device open on Linux if SecurityError occurs
On slow machines, udev rules may not have finished processing by the time we try to open the USB device. This causes a SecurityError, so we add a retry loop to handle this case. Closes: pybricks/support#2372
1 parent 3abc3f3 commit 368ab7c

1 file changed

Lines changed: 23 additions & 8 deletions

File tree

src/usb/sagas.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
// Copyright (c) 2025 The Pybricks Authors
2+
// Copyright (c) 2025-2026 The Pybricks Authors
33

44
import { firmwareVersion } from '@pybricks/firmware';
55
import { AnyAction } from 'redux';
@@ -128,6 +128,7 @@ function* handleUsbConnectPybricks(hotPlugDevice?: USBDevice): Generator {
128128
return;
129129
}
130130

131+
// TODO: show unexpected error message to user here
131132
console.error('Failed to request USB device:', reqDeviceErr);
132133
yield* put(usbDidFailToConnectPybricks());
133134
yield* cleanup();
@@ -140,13 +141,27 @@ function* handleUsbConnectPybricks(hotPlugDevice?: USBDevice): Generator {
140141
usbDevice = hotPlugDevice;
141142
}
142143

143-
const [, openErr] = yield* call(() => maybe(usbDevice.open()));
144-
if (openErr) {
145-
// TODO: show error message to user here
146-
console.error('Failed to open USB device:', openErr);
147-
yield* put(usbDidFailToConnectPybricks());
148-
yield* cleanup();
149-
return;
144+
for (let retry = 1; ; retry++) {
145+
const [, openErr] = yield* call(() => maybe(usbDevice.open()));
146+
if (openErr) {
147+
// On Linux/Android, the udev rules could still be processing, try
148+
// a few times before giving up.
149+
if (openErr.name === 'SecurityError' && retry <= 5) {
150+
console.debug(
151+
`Retrying USB device open (${retry}/5) after SecurityError on Linux`,
152+
);
153+
yield* delay(100);
154+
continue;
155+
}
156+
157+
// TODO: show error message to user here
158+
console.error('Failed to open USB device:', openErr);
159+
yield* put(usbDidFailToConnectPybricks());
160+
yield* cleanup();
161+
return;
162+
}
163+
164+
break;
150165
}
151166

152167
exitStack.push(() => usbDevice.close().catch(console.debug));

0 commit comments

Comments
 (0)