Skip to content

Commit

Permalink
Better handling of closed stream errors (#1304)
Browse files Browse the repository at this point in the history
Add catches to explicitly close streams if a ClosedChannelException is thrown
Add better lifecycle management for streamable interfaces
  • Loading branch information
akberenz authored Dec 3, 2024
1 parent 6fa6ce6 commit b1573f8
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 78 deletions.
5 changes: 2 additions & 3 deletions src/qz/communication/DeviceIO.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package qz.communication;

public interface DeviceIO {
public interface DeviceIO extends DeviceListener {

String getVendorId();

Expand All @@ -11,8 +11,7 @@ public interface DeviceIO {

boolean isOpen();

void close() throws DeviceException;

void close();

void setStreaming(boolean streaming);

Expand Down
4 changes: 3 additions & 1 deletion src/qz/communication/DeviceListener.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package qz.communication;

public interface DeviceListener {

/**
* Cleanup task for when a socket closes while a device is still streaming
*/
void close();

}
8 changes: 5 additions & 3 deletions src/qz/communication/FileIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
import qz.ws.PrintSocketClient;
import qz.ws.StreamEvent;

import java.nio.channels.ClosedChannelException;
import java.nio.file.Path;
import java.nio.file.WatchKey;
import java.util.ArrayList;

public class FileIO {
public class FileIO implements DeviceListener {
public static final String SANDBOX_DATA_SUFFIX = "sandbox";
public static final String GLOBAL_DATA_SUFFIX = "shared";
public static final int FILE_LISTENER_DEFAULT_LINES = 10;
Expand Down Expand Up @@ -133,7 +134,7 @@ public void setWk(WatchKey wk) {
this.wk = wk;
}

public void fileChanged(String fileName, String type, String fileData) {
public void fileChanged(String fileName, String type, String fileData) throws ClosedChannelException {
StreamEvent evt = new StreamEvent(StreamEvent.Stream.FILE, StreamEvent.Type.ACTION)
.withData("file", getOriginalPath().resolve(fileName))
.withData("eventType", type);
Expand All @@ -145,13 +146,14 @@ public void fileChanged(String fileName, String type, String fileData) {
PrintSocketClient.sendStream(session, evt);
}

public void sendError(String message) {
public void sendError(String message) throws ClosedChannelException {
StreamEvent eventErr = new StreamEvent(StreamEvent.Stream.FILE, StreamEvent.Type.ERROR)
.withData("message", message);
PrintSocketClient.sendStream(session, eventErr);
}


@Override
public void close() {
if (wk != null) {
wk.cancel();
Expand Down
21 changes: 15 additions & 6 deletions src/qz/communication/H4J_HidIO.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
package qz.communication;

import org.hid4java.HidDevice;
import qz.ws.SocketConnection;

import javax.usb.util.UsbUtil;

public class H4J_HidIO implements DeviceIO {
public class H4J_HidIO implements DeviceIO, DeviceListener {

private HidDevice device;

private boolean streaming;

private DeviceOptions dOpts;
private SocketConnection websocket;

public H4J_HidIO(DeviceOptions dOpts) throws DeviceException {
this(H4J_HidUtilities.findDevice(dOpts));

public H4J_HidIO(DeviceOptions dOpts, SocketConnection websocket) throws DeviceException {
this(H4J_HidUtilities.findDevice(dOpts), dOpts, websocket);
}

public H4J_HidIO(HidDevice device) throws DeviceException {
private H4J_HidIO(HidDevice device, DeviceOptions dOpts, SocketConnection websocket) throws DeviceException {
this.dOpts = dOpts;
this.websocket = websocket;
if (device == null) {
throw new DeviceException("HID device could not be found");
}
Expand All @@ -30,7 +36,7 @@ public void open() {
}

public boolean isOpen() {
return device.isOpen();
return !device.isClosed();
}

public void setStreaming(boolean active) {
Expand Down Expand Up @@ -91,11 +97,14 @@ public void sendFeatureReport(byte[] data, Byte reportId) throws DeviceException
}
}

@Override
public void close() {
setStreaming(false);
// Remove orphaned reference
websocket.removeDevice(dOpts);
if (isOpen()) {
device.close();
}
streaming = false;
}

}
8 changes: 4 additions & 4 deletions src/qz/communication/H4J_HidListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public H4J_HidListener(Session session) {
@Override
public void hidFailure(HidServicesEvent hidServicesEvent) {
log.debug("Device failure: {}", hidServicesEvent.getHidDevice().getProduct());
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Failure"));
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Failure"), this);
}

@Override
Expand All @@ -42,19 +42,19 @@ public void hidDataReceived(HidServicesEvent hidServicesEvent) {
hex.put(UsbUtil.toHexString(b));
}

PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Data Received", hex));
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Data Received", hex), this);
}

@Override
public void hidDeviceDetached(HidServicesEvent hidServicesEvent) {
log.debug("Device detached: {}", hidServicesEvent.getHidDevice().getProduct());
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Detached"));
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Detached"), this);
}

@Override
public void hidDeviceAttached(HidServicesEvent hidServicesEvent) {
log.debug("Device attached: {}", hidServicesEvent.getHidDevice().getProduct());
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Attached"));
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Attached"), this);
}

private StreamEvent createStreamAction(HidDevice device, String action) {
Expand Down
17 changes: 12 additions & 5 deletions src/qz/communication/PJHA_HidIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import purejavahidapi.InputReportListener;
import purejavahidapi.PureJavaHidApi;
import qz.utils.SystemUtilities;
import qz.ws.SocketConnection;

import javax.usb.util.UsbUtil;
import java.io.IOException;
Expand All @@ -22,13 +23,16 @@ public class PJHA_HidIO implements DeviceIO {
private static final int BUFFER_SIZE = 32;
private Vector<byte[]> dataBuffer;
private boolean streaming;
private DeviceOptions dOpts;
private SocketConnection websocket;


public PJHA_HidIO(DeviceOptions dOpts) throws DeviceException {
this(PJHA_HidUtilities.findDevice(dOpts));
public PJHA_HidIO(DeviceOptions dOpts, SocketConnection websocket) throws DeviceException {
this(PJHA_HidUtilities.findDevice(dOpts), dOpts, websocket);
}

public PJHA_HidIO(HidDeviceInfo deviceInfo) throws DeviceException {
private PJHA_HidIO(HidDeviceInfo deviceInfo, DeviceOptions dOpts, SocketConnection websocket) throws DeviceException {
this.dOpts = dOpts;
this.websocket = websocket;
if (deviceInfo == null) {
throw new DeviceException("HID device could not be found");
}
Expand Down Expand Up @@ -129,7 +133,11 @@ public void sendFeatureReport(byte[] data, Byte reportId) throws DeviceException

}

@Override
public void close() {
setStreaming(false);
// Remove orphaned reference
websocket.removeDevice(dOpts);
if (isOpen()) {
try {
device.setInputReportListener(null);
Expand All @@ -140,7 +148,6 @@ public void close() {
}
}

streaming = false;
device = null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/qz/communication/PJHA_HidListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ public void close() {
@Override
public void onDeviceRemoval(HidDevice device) {
log.debug("Device detached: {}", device.getHidDeviceInfo().getProductString());
PrintSocketClient.sendStream(session, createStreamAction(device, "Device Detached"));
PrintSocketClient.sendStream(session, createStreamAction(device, "Device Detached"), this);
}
}
31 changes: 20 additions & 11 deletions src/qz/communication/SerialIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
import qz.common.ByteArrayBuilder;
import qz.utils.ByteUtilities;
import qz.utils.DeviceUtilities;
import qz.ws.SocketConnection;

import java.io.IOException;

/**
* @author Tres
*/
public class SerialIO {
public class SerialIO implements DeviceListener {

private static final Logger log = LogManager.getLogger(SerialIO.class);

Expand All @@ -28,14 +29,17 @@ public class SerialIO {

private ByteArrayBuilder data = new ByteArrayBuilder();

private SocketConnection websocket;


/**
* Controller for serial communications
*
* @param portName Port name to open, such as "COM1" or "/dev/tty0/"
*/
public SerialIO(String portName) {
public SerialIO(String portName, SocketConnection websocket) {
this.portName = portName;
this.websocket = websocket;
}

/**
Expand Down Expand Up @@ -257,26 +261,31 @@ public void sendData(JSONObject params, SerialOptions opts) throws JSONException
/**
* Closes the serial port, if open.
*
* @return Boolean indicating success.
* @throws SerialPortException If the port fails to close.
*/
public boolean close() throws SerialPortException {
@Override
public void close() {
// Remove orphaned reference
websocket.removeSerialPort(portName);

if (!isOpen()) {
log.warn("Serial port [{}] is not open.", portName);
return false;
}

boolean closed = port.closePort();
if (closed) {
log.info("Serial port [{}] closed successfully.", portName);
} else {
try {
boolean closed = port.closePort();
if (closed) {
log.info("Serial port [{}] closed successfully.", portName);
} else {
// Handle ambiguity in JSSCs API
throw new SerialPortException(portName, "closePort", "Port not closed");
}
} catch(SerialPortException e) {
log.warn("Serial port [{}] was not closed properly.", portName);
}

port = null;
portName = null;

return closed;
}

private Integer min(Integer a, Integer b) {
Expand Down
26 changes: 21 additions & 5 deletions src/qz/communication/SocketIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.apache.logging.log4j.Logger;
import qz.utils.DeviceUtilities;
import qz.utils.NetworkUtilities;
import qz.ws.SocketConnection;

import java.io.DataInputStream;
import java.io.DataOutputStream;
Expand All @@ -15,7 +16,7 @@
import java.nio.charset.Charset;
import java.util.ArrayList;

public class SocketIO {
public class SocketIO implements DeviceListener {

private static final Logger log = LogManager.getLogger(SocketIO.class);

Expand All @@ -27,10 +28,13 @@ public class SocketIO {
private DataOutputStream dataOut;
private DataInputStream dataIn;

public SocketIO(String host, int port, Charset encoding) {
private SocketConnection websocket;

public SocketIO(String host, int port, Charset encoding, SocketConnection websocket) {
this.host = host;
this.port = port;
this.encoding = encoding;
this.websocket = websocket;
}

public boolean open() throws IOException {
Expand Down Expand Up @@ -68,9 +72,21 @@ public String processSocketResponse() throws IOException {
return null;
}

public void close() throws IOException {
dataOut.close();
socket.close();
@Override
public void close() {
// Remove orphaned reference
websocket.removeNetworkSocket(String.format("%s:%s", host, port));

try {
dataOut.close();
} catch(IOException e) {
log.warn("Could not close socket output stream", e);
}
try {
socket.close();
} catch(IOException e) {
log.warn("Could not close socket", e);
}
}

public String getHost() {
Expand Down
Loading

0 comments on commit b1573f8

Please sign in to comment.