Skip to content

Commit

Permalink
Windows: improved memory cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelbl committed Jan 3, 2024
1 parent 5be0873 commit 29b4ac0
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -342,29 +342,30 @@ private MemorySegment getVariableLengthProperty(MemorySegment propertyKey, int p
* @return the device path
*/
static String getDevicePath(String instanceId, MemorySegment interfaceGuid) {
try (var arena = Arena.ofConfined();
var deviceInfoSet = DeviceInfoSet.ofPresentDevices(interfaceGuid, instanceId)) {

// retrieve first element of enumeration
var errorState = allocateErrorState(arena);
var devIntfData = _SP_DEVICE_INTERFACE_DATA.allocate(arena);
_SP_DEVICE_INTERFACE_DATA.cbSize$set(devIntfData, (int) devIntfData.byteSize());
if (SetupAPI2.SetupDiEnumDeviceInterfaces(deviceInfoSet.devInfoSet, NULL, interfaceGuid, 0, devIntfData,
errorState) == 0)
throwLastError(errorState, "internal error (SetupDiEnumDeviceInterfaces)");

// get device path
// (SP_DEVICE_INTERFACE_DETAIL_DATA_W is of variable length and requires a bigger allocation so
// the device path fits)
final var devicePathOffset = 4;
var intfDetailData = arena.allocate(4L + 260 * 2);
_SP_DEVICE_INTERFACE_DETAIL_DATA_W.cbSize$set(intfDetailData,
(int) _SP_DEVICE_INTERFACE_DETAIL_DATA_W.sizeof());
if (SetupAPI2.SetupDiGetDeviceInterfaceDetailW(deviceInfoSet.devInfoSet, devIntfData, intfDetailData,
(int) intfDetailData.byteSize(), NULL, NULL, errorState) == 0)
throwLastError(errorState, "Internal error (SetupDiGetDeviceInterfaceDetailW)");

return Win.createStringFromSegment(intfDetailData.asSlice(devicePathOffset));
try (var deviceInfoSet = DeviceInfoSet.ofPresentDevices(interfaceGuid, instanceId)) {
return deviceInfoSet.getDevicePathForGuid(interfaceGuid);
}
}

private String getDevicePathForGuid(MemorySegment interfaceGuid) {
// retrieve first element of enumeration
devIntfData = _SP_DEVICE_INTERFACE_DATA.allocate(arena);
_SP_DEVICE_INTERFACE_DATA.cbSize$set(devIntfData, (int) devIntfData.byteSize());
if (SetupAPI2.SetupDiEnumDeviceInterfaces(devInfoSet, NULL, interfaceGuid, 0, devIntfData,
errorState) == 0)
throwLastError(errorState, "internal error (SetupDiEnumDeviceInterfaces)");

// get device path
// (SP_DEVICE_INTERFACE_DETAIL_DATA_W is of variable length and requires a bigger allocation so
// the device path fits)
final var devicePathOffset = 4;
var intfDetailData = arena.allocate(4L + 260 * 2);
_SP_DEVICE_INTERFACE_DETAIL_DATA_W.cbSize$set(intfDetailData,
(int) _SP_DEVICE_INTERFACE_DETAIL_DATA_W.sizeof());
if (SetupAPI2.SetupDiGetDeviceInterfaceDetailW(devInfoSet, devIntfData, intfDetailData,
(int) intfDetailData.byteSize(), NULL, NULL, errorState) == 0)
throwLastError(errorState, "Internal error (SetupDiGetDeviceInterfaceDetailW)");

return Win.createStringFromSegment(intfDetailData.asSlice(devicePathOffset));
}
}
84 changes: 50 additions & 34 deletions java-does-usb/src/test/java/net/codecrete/usb/special/Unplug.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

import java.io.IOException;
import java.util.HashMap;
import java.util.Optional;
import java.util.stream.Stream;

import static java.time.Duration.*;

Expand All @@ -24,23 +26,25 @@
* </p>
*/
public class Unplug {
/**
* Loopback test device vendor ID
*/
static final int VID_LOOPBACK = 0xcafe;
/**
* Loopback test device product ID
*/
static final int PID_LOOPBACK = 0xceaf;
/**
* Loopback test device loopback interface number
*/
static final int LOOPBACK_INTF = 0;

private static final int LOOPBACK_EP_OUT = 1;
private static final int LOOPBACK_EP_IN = 2;
private static final int ECHO_EP_OUT = 3;
private static final int ECHO_EP_IN = 3;
static final DeviceConfig loopbackDevice = new DeviceConfig(
0xcafe,
0xceaf,
0,
1,
2,
3,
3
);

static final DeviceConfig compositeDevice = new DeviceConfig(
0xcafe,
0xcea0,
3,
1,
2,
-1,
-1
);

private static final HashMap<UsbDevice, DeviceWorker> activeDevices = new HashMap<>();

Expand All @@ -57,56 +61,63 @@ public static void main(String[] args) throws IOException {
}

private static void onPluggedDevice(UsbDevice device) {
if (!isTestDevice(device))
var config = getTestDeviceConfig(device);
if (config.isEmpty())
return;

var worker = new DeviceWorker(device);
var worker = new DeviceWorker(device, config.get());
activeDevices.put(device, worker);
worker.start();
}

private static void onUnpluggedDevice(UsbDevice device) {
if (!isTestDevice(device))
var config = getTestDeviceConfig(device);
if (config.isEmpty())
return;

var worker = activeDevices.remove(device);
worker.setDisconnectTime(System.currentTimeMillis());
worker.join();
}

@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private static boolean isTestDevice(UsbDevice device) {
return device.getVendorId() == VID_LOOPBACK && device.getProductId() == PID_LOOPBACK;
private static Optional<DeviceConfig> getTestDeviceConfig(UsbDevice device) {
return Stream.of(loopbackDevice, compositeDevice)
.filter(config -> device.getVendorId() == config.vid() && device.getProductId() == config.pid())
.findFirst();
}

static class DeviceWorker {

private final UsbDevice device;
private final DeviceConfig config;

private final int seed;

private long disconnectTime;

private final HashMap<Thread, Work> workTracking = new HashMap<>();

DeviceWorker(UsbDevice device) {
DeviceWorker(UsbDevice device, DeviceConfig config) {
this.device = device;
this.config = config;
this.seed = (int) System.currentTimeMillis();
}

void start() {
System.out.println("Device connected");

device.open();
device.claimInterface(LOOPBACK_INTF);
device.claimInterface(config.interfaceNumber());

// start loopback sender and receiver
startThread((seed & 1) != 0 ? this::sendLoopbackDataStream : this::sendLoopbackData);
startThread((seed & 2) != 0 ? this::receiveLoopbackDataStream : this::receiveLoopbackData);

// start echo sender and receiver
startThread(this::sendEcho);
startThread(this::receiveEcho);
if (config.endpointEchoOut() > 0) {
startThread(this::sendEcho);
startThread(this::receiveEcho);
}
}

private void startThread(Runnable action) {
Expand Down Expand Up @@ -186,7 +197,7 @@ private void sendLoopbackData() {
//noinspection InfiniteLoopStatement
while (true) {
prng.fill(data);
device.transferOut(LOOPBACK_EP_OUT, data, 1000);
device.transferOut(config.endpointLoopbackOut(), data, 1000);
logWork(data.length);
}
}
Expand All @@ -196,7 +207,7 @@ private void receiveLoopbackData() {
var prng = new PRNG();
//noinspection InfiniteLoopStatement
while (true) {
byte[] data = device.transferIn(LOOPBACK_EP_IN);
byte[] data = device.transferIn(config.endpointLoopbackIn());
int index = prng.verify(data);
if (index >= 0)
throw new RuntimeException("invalid data received");
Expand All @@ -208,7 +219,7 @@ private void sendLoopbackDataStream() {
logStart("sending loopback data with output stream", 300_000);
var prng = new PRNG();
var data = new byte[5000];
try (var os = device.openOutputStream(LOOPBACK_EP_OUT)) {
try (var os = device.openOutputStream(config.endpointLoopbackOut())) {
//noinspection InfiniteLoopStatement
while (true) {
prng.fill(data);
Expand All @@ -223,7 +234,7 @@ private void sendLoopbackDataStream() {
private void receiveLoopbackDataStream() {
logStart("receiving loopback data with input stream", 300_000);
var prng = new PRNG();
try (var is = device.openInputStream(LOOPBACK_EP_IN)) {
try (var is = device.openInputStream(config.endpointLoopbackIn())) {
//noinspection InfiniteLoopStatement
while (true) {
var data = new byte[2000];
Expand All @@ -243,7 +254,7 @@ private void sendEcho() {
var data = new byte[] { 0x03, 0x45, 0x73, (byte)0xb3, (byte)0x9f, 0x3f, 0x00, 0x6a };
//noinspection InfiniteLoopStatement
while (true) {
device.transferOut(ECHO_EP_OUT, data);
device.transferOut(config.endpointEchoOut(), data);
logWork(1);
sleep(100);
}
Expand All @@ -253,12 +264,12 @@ private void receiveEcho() {
logStart("receiving echo", 14);
//noinspection InfiniteLoopStatement
while (true) {
device.transferIn(ECHO_EP_IN);
device.transferIn(config.endpointEchoIn());
logWork(1);
}
}

@SuppressWarnings("SameParameterValue")
@SuppressWarnings({"SameParameterValue", "java:S2925"})
private static void sleep(long millis) {
try {
Thread.sleep(millis);
Expand Down Expand Up @@ -324,4 +335,9 @@ int verify(byte[] data, int len) {
return -1;
}
}

record DeviceConfig(int vid, int pid, int interfaceNumber,
int endpointLoopbackOut, int endpointLoopbackIn,
int endpointEchoOut, int endpointEchoIn
) {}
}

0 comments on commit 29b4ac0

Please sign in to comment.