Skip to content

Commit 814d85b

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 814d85b

1 file changed

Lines changed: 26 additions & 8 deletions

File tree

src/usb/sagas.ts

Lines changed: 26 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';
@@ -37,6 +37,7 @@ import { pybricksHubCapabilitiesCharacteristicUUID } from '../ble-pybricks-servi
3737
import { firmwareInstallPybricks } from '../firmware/actions';
3838
import { RootState } from '../reducers';
3939
import { assert, defined, ensureError, maybe } from '../utils';
40+
import { isLinux } from '../utils/os';
4041
import { pythonVersionToSemver } from '../utils/version';
4142
import {
4243
usbConnectPybricks,
@@ -128,6 +129,7 @@ function* handleUsbConnectPybricks(hotPlugDevice?: USBDevice): Generator {
128129
return;
129130
}
130131

132+
// TODO: show unexpected error message to user here
131133
console.error('Failed to request USB device:', reqDeviceErr);
132134
yield* put(usbDidFailToConnectPybricks());
133135
yield* cleanup();
@@ -140,13 +142,29 @@ function* handleUsbConnectPybricks(hotPlugDevice?: USBDevice): Generator {
140142
usbDevice = hotPlugDevice;
141143
}
142144

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

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

0 commit comments

Comments
 (0)