Skip to content

Commit 09fe484

Browse files
committed
Windows: don't block other threads while waiting
1 parent 8a71956 commit 09fe484

File tree

2 files changed

+93
-46
lines changed

2 files changed

+93
-46
lines changed

java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUSBDevice.java

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public class WindowsUSBDevice extends USBDeviceImpl {
4444

4545
private static final System.Logger LOG = System.getLogger(WindowsUSBDevice.class.getName());
4646

47+
private static final int RETRY_LATER = 0;
48+
private static final int TRY_NEXT_CHILD = 1;
49+
private static final int SUCCESS = 2;
50+
4751
private final WindowsAsyncTask asyncTask;
4852
private List<InterfaceHandle> interfaceHandles;
4953
// device paths by interface number (first interface of function)
@@ -100,7 +104,32 @@ public synchronized void close() {
100104
showAsOpen = false;
101105
}
102106

103-
public synchronized void claimInterface(int interfaceNumber) {
107+
public void claimInterface(int interfaceNumber) {
108+
// When a device is plugged in, a notification is sent. For composite devices, it is a notification
109+
// that the composite device is ready. Each composite function will be registered separately and
110+
// the related information will be available with a delay. So for composite functions, several
111+
// retries might be needed until the device path is available.
112+
var numRetries = 30; // 30 x 100ms
113+
while (true) {
114+
if (claimInteraceSynchronized(interfaceNumber))
115+
return; // success
116+
117+
numRetries -= 1;
118+
if (numRetries == 0)
119+
throw new USBException("claiming interface failed (function has no device path, might be missing WinUSB driver)");
120+
121+
// sleep and retry
122+
try {
123+
LOG.log(DEBUG, "Sleeping for 100ms...");
124+
//noinspection BusyWait
125+
Thread.sleep(100);
126+
} catch (InterruptedException e) {
127+
Thread.currentThread().interrupt();
128+
}
129+
}
130+
}
131+
132+
private synchronized boolean claimInteraceSynchronized(int interfaceNumber) {
104133
checkIsOpen();
105134

106135
getInterfaceWithCheck(interfaceNumber, false);
@@ -117,7 +146,7 @@ public synchronized void claimInterface(int interfaceNumber) {
117146
if (firstIntfHandle.deviceHandle == null) {
118147
var devicePath = getInterfaceDevicePath(firstIntfHandle.interfaceNumber);
119148
if (devicePath == null)
120-
throw new USBException("claiming interface failed (function has no device path, might be missing WinUSB driver)");
149+
return false; // retry later
121150

122151
LOG.log(DEBUG, "opening device {0}", devicePath);
123152

@@ -160,6 +189,7 @@ public synchronized void claimInterface(int interfaceNumber) {
160189

161190
firstIntfHandle.deviceOpenCount += 1;
162191
setClaimed(interfaceNumber, true);
192+
return true;
163193
}
164194

165195
@Override
@@ -208,6 +238,15 @@ public synchronized void releaseInterface(int interfaceNumber) {
208238
WinUSB.WinUsb_Free(firstIntfHandle.winusbHandle);
209239
firstIntfHandle.winusbHandle = null;
210240

241+
var path = (String)getUniqueId();
242+
if (firstIntfHandle.interfaceNumber != 0) {
243+
if (devicePaths != null)
244+
path = devicePaths.get(firstIntfHandle.interfaceNumber);
245+
else
246+
path = null;
247+
}
248+
LOG.log(DEBUG, "closing device {0}", path);
249+
211250
Kernel32.CloseHandle(firstIntfHandle.deviceHandle);
212251
firstIntfHandle.deviceHandle = null;
213252
}
@@ -487,46 +526,29 @@ private String getInterfaceDevicePath(int interfaceNumber) {
487526
return devicePath;
488527
}
489528

490-
// When a device is plugged in, a notification is sent. For composite devices, it is a notification
491-
// that the information for the composite device is ready. Each composite function will be registered
492-
// separately and the related information will be available with a delay. So for composite functions,
493-
// several retries might be needed until the device path is available.
494-
var numRetries = 30; // 30 x 100ms
495-
while (true) {
496-
try (var deviceInfoSet = DeviceInfoSet.ofPath(parentDevicePath)) {
497-
if (!deviceInfoSet.isCompositeDevice())
498-
throwException("internal error: interface belongs to a separate function but device is not composite");
499-
500-
var childrenInstanceIDs = deviceInfoSet.getStringListProperty(Children);
501-
if (childrenInstanceIDs == null) {
502-
LOG.log(DEBUG, "missing children instance IDs for device {0}", parentDevicePath);
503-
504-
} else {
505-
LOG.log(DEBUG, "children instance IDs: {0}", childrenInstanceIDs);
506-
507-
for (var instanceId : childrenInstanceIDs) {
508-
var res = fetchChildDevicePath(instanceId, interfaceNumber);
509-
if (res == 1)
510-
return devicePaths.get(interfaceNumber); // success
511-
if (res == -1)
512-
break; // retry later
513-
}
514-
}
529+
try (var deviceInfoSet = DeviceInfoSet.ofPath(parentDevicePath)) {
530+
if (!deviceInfoSet.isCompositeDevice())
531+
throwException("internal error: interface belongs to a composite function but device is not composite");
515532

516-
numRetries -= 1;
517-
if (numRetries == 0)
518-
return null; // failure
533+
var childrenInstanceIDs = deviceInfoSet.getStringListProperty(Children);
534+
if (childrenInstanceIDs == null) {
535+
LOG.log(DEBUG, "missing children instance IDs for device {0}", parentDevicePath);
536+
return null;
519537

520-
// sleep and retry
521-
try {
522-
LOG.log(DEBUG, "Sleeping for 100ms...");
523-
//noinspection BusyWait
524-
Thread.sleep(100);
525-
} catch (InterruptedException e) {
526-
Thread.currentThread().interrupt();
538+
} else {
539+
LOG.log(DEBUG, "children instance IDs: {0}", childrenInstanceIDs);
540+
541+
for (var instanceId : childrenInstanceIDs) {
542+
var res = fetchChildDevicePath(instanceId, interfaceNumber);
543+
if (res == SUCCESS)
544+
return devicePaths.get(interfaceNumber);
545+
if (res == RETRY_LATER)
546+
return null; // retry later
527547
}
528548
}
529549
}
550+
551+
return null; // retry later
530552
}
531553

532554
private int fetchChildDevicePath(String instanceId, int interfaceNumber) {
@@ -536,28 +558,28 @@ private int fetchChildDevicePath(String instanceId, int interfaceNumber) {
536558
var hardwareIds = deviceInfoSet.getStringListProperty(HardwareIds);
537559
if (hardwareIds == null) {
538560
LOG.log(DEBUG, "child device {0} has no hardware IDs", instanceId);
539-
return 0; // continue with next child
561+
return TRY_NEXT_CHILD;
540562
}
541563

542564
var extractedNumber = extractInterfaceNumber(hardwareIds);
543565
if (extractedNumber == -1) {
544566
LOG.log(DEBUG, "child device {0} has no interface number", instanceId);
545-
return 0; // continue with next child
567+
return TRY_NEXT_CHILD;
546568
}
547569

548570
if (extractedNumber != interfaceNumber)
549-
return 0; // continue with next child
571+
return TRY_NEXT_CHILD;
550572

551573
var devicePath = deviceInfoSet.getDevicePathByGUID(instanceId);
552574
if (devicePath == null) {
553575
LOG.log(INFO, "Child device {0} has no device path", instanceId);
554-
return -1; // retry later
576+
return RETRY_LATER;
555577
}
556578

557579
if (devicePaths == null)
558580
devicePaths = new HashMap<>();
559581
devicePaths.put(interfaceNumber, devicePath);
560-
return 1; // success
582+
return SUCCESS;
561583
}
562584
}
563585

java-does-usb/src/test/java/net/codecrete/usb/special/MonitorDevices.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,52 @@
77

88
package net.codecrete.usb.special;
99

10-
import net.codecrete.usb.USB;
10+
import net.codecrete.usb.*;
1111

1212
import java.io.IOException;
1313

1414
/**
15-
* Sample program displaying information when USB devices are connected or disconnected.
15+
* Test program that communicates with the device as soon as it has been pugged in.
1616
* <p>
1717
* Quit with Ctrl-C or whatever stops a program on your platform.
1818
* </p>
1919
*/
2020
public class MonitorDevices {
2121

2222
public static void main(String[] args) throws IOException {
23-
USB.setOnDeviceConnected((device) -> System.out.println("Connected: " + device.toString()));
23+
USB.setOnDeviceConnected((device) -> {
24+
System.out.println("Connected: " + device.toString());
25+
talkToTestDevice(device);
26+
});
2427
USB.setOnDeviceDisconnected((device) -> System.out.println("Disconnected: " + device.toString()));
2528

26-
for (var device : USB.getAllDevices())
29+
for (var device : USB.getAllDevices()) {
2730
System.out.println("Present: " + device.toString());
31+
talkToTestDevice(device);
32+
}
2833
System.out.println("Monitoring...");
2934

3035
//noinspection ResultOfMethodCallIgnored
3136
System.in.read();
3237
}
38+
39+
private static void talkToTestDevice(USBDevice device) {
40+
if (device.vendorId() != 0xcafe)
41+
return; // no test device
42+
43+
int interfaceNumber = device.productId() == 0xcea0 ? 2 : 0;
44+
device.open();
45+
device.claimInterface(interfaceNumber);
46+
var response = device.controlTransferIn(
47+
new USBControlTransfer(USBRequestType.VENDOR, USBRecipient.INTERFACE,
48+
(byte) 0x05, (short) 0, (short) interfaceNumber),
49+
1
50+
);
51+
52+
if (response.length == 1 || interfaceNumber == response[0]) {
53+
System.out.println("Device responded");
54+
} else {
55+
System.err.println("Invalid response from device");
56+
}
57+
}
3358
}

0 commit comments

Comments
 (0)