Skip to content

Commit b1573f8

Browse files
authored
Better handling of closed stream errors (#1304)
Add catches to explicitly close streams if a ClosedChannelException is thrown Add better lifecycle management for streamable interfaces
1 parent 6fa6ce6 commit b1573f8

17 files changed

+170
-78
lines changed

src/qz/communication/DeviceIO.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package qz.communication;
22

3-
public interface DeviceIO {
3+
public interface DeviceIO extends DeviceListener {
44

55
String getVendorId();
66

@@ -11,8 +11,7 @@ public interface DeviceIO {
1111

1212
boolean isOpen();
1313

14-
void close() throws DeviceException;
15-
14+
void close();
1615

1716
void setStreaming(boolean streaming);
1817

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package qz.communication;
22

33
public interface DeviceListener {
4-
4+
/**
5+
* Cleanup task for when a socket closes while a device is still streaming
6+
*/
57
void close();
68

79
}

src/qz/communication/FileIO.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
import qz.ws.PrintSocketClient;
1010
import qz.ws.StreamEvent;
1111

12+
import java.nio.channels.ClosedChannelException;
1213
import java.nio.file.Path;
1314
import java.nio.file.WatchKey;
1415
import java.util.ArrayList;
1516

16-
public class FileIO {
17+
public class FileIO implements DeviceListener {
1718
public static final String SANDBOX_DATA_SUFFIX = "sandbox";
1819
public static final String GLOBAL_DATA_SUFFIX = "shared";
1920
public static final int FILE_LISTENER_DEFAULT_LINES = 10;
@@ -133,7 +134,7 @@ public void setWk(WatchKey wk) {
133134
this.wk = wk;
134135
}
135136

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

148-
public void sendError(String message) {
149+
public void sendError(String message) throws ClosedChannelException {
149150
StreamEvent eventErr = new StreamEvent(StreamEvent.Stream.FILE, StreamEvent.Type.ERROR)
150151
.withData("message", message);
151152
PrintSocketClient.sendStream(session, eventErr);
152153
}
153154

154155

156+
@Override
155157
public void close() {
156158
if (wk != null) {
157159
wk.cancel();

src/qz/communication/H4J_HidIO.java

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
package qz.communication;
22

33
import org.hid4java.HidDevice;
4+
import qz.ws.SocketConnection;
45

56
import javax.usb.util.UsbUtil;
67

7-
public class H4J_HidIO implements DeviceIO {
8+
public class H4J_HidIO implements DeviceIO, DeviceListener {
89

910
private HidDevice device;
1011

1112
private boolean streaming;
1213

14+
private DeviceOptions dOpts;
15+
private SocketConnection websocket;
1316

14-
public H4J_HidIO(DeviceOptions dOpts) throws DeviceException {
15-
this(H4J_HidUtilities.findDevice(dOpts));
17+
18+
public H4J_HidIO(DeviceOptions dOpts, SocketConnection websocket) throws DeviceException {
19+
this(H4J_HidUtilities.findDevice(dOpts), dOpts, websocket);
1620
}
1721

18-
public H4J_HidIO(HidDevice device) throws DeviceException {
22+
private H4J_HidIO(HidDevice device, DeviceOptions dOpts, SocketConnection websocket) throws DeviceException {
23+
this.dOpts = dOpts;
24+
this.websocket = websocket;
1925
if (device == null) {
2026
throw new DeviceException("HID device could not be found");
2127
}
@@ -30,7 +36,7 @@ public void open() {
3036
}
3137

3238
public boolean isOpen() {
33-
return device.isOpen();
39+
return !device.isClosed();
3440
}
3541

3642
public void setStreaming(boolean active) {
@@ -91,11 +97,14 @@ public void sendFeatureReport(byte[] data, Byte reportId) throws DeviceException
9197
}
9298
}
9399

100+
@Override
94101
public void close() {
102+
setStreaming(false);
103+
// Remove orphaned reference
104+
websocket.removeDevice(dOpts);
95105
if (isOpen()) {
96106
device.close();
97107
}
98-
streaming = false;
99108
}
100109

101110
}

src/qz/communication/H4J_HidListener.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public H4J_HidListener(Session session) {
3030
@Override
3131
public void hidFailure(HidServicesEvent hidServicesEvent) {
3232
log.debug("Device failure: {}", hidServicesEvent.getHidDevice().getProduct());
33-
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Failure"));
33+
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Failure"), this);
3434
}
3535

3636
@Override
@@ -42,19 +42,19 @@ public void hidDataReceived(HidServicesEvent hidServicesEvent) {
4242
hex.put(UsbUtil.toHexString(b));
4343
}
4444

45-
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Data Received", hex));
45+
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Data Received", hex), this);
4646
}
4747

4848
@Override
4949
public void hidDeviceDetached(HidServicesEvent hidServicesEvent) {
5050
log.debug("Device detached: {}", hidServicesEvent.getHidDevice().getProduct());
51-
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Detached"));
51+
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Detached"), this);
5252
}
5353

5454
@Override
5555
public void hidDeviceAttached(HidServicesEvent hidServicesEvent) {
5656
log.debug("Device attached: {}", hidServicesEvent.getHidDevice().getProduct());
57-
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Attached"));
57+
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Attached"), this);
5858
}
5959

6060
private StreamEvent createStreamAction(HidDevice device, String action) {

src/qz/communication/PJHA_HidIO.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import purejavahidapi.InputReportListener;
88
import purejavahidapi.PureJavaHidApi;
99
import qz.utils.SystemUtilities;
10+
import qz.ws.SocketConnection;
1011

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

26-
27-
public PJHA_HidIO(DeviceOptions dOpts) throws DeviceException {
28-
this(PJHA_HidUtilities.findDevice(dOpts));
29+
public PJHA_HidIO(DeviceOptions dOpts, SocketConnection websocket) throws DeviceException {
30+
this(PJHA_HidUtilities.findDevice(dOpts), dOpts, websocket);
2931
}
3032

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

130134
}
131135

136+
@Override
132137
public void close() {
138+
setStreaming(false);
139+
// Remove orphaned reference
140+
websocket.removeDevice(dOpts);
133141
if (isOpen()) {
134142
try {
135143
device.setInputReportListener(null);
@@ -140,7 +148,6 @@ public void close() {
140148
}
141149
}
142150

143-
streaming = false;
144151
device = null;
145152
}
146153

src/qz/communication/PJHA_HidListener.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ public void close() {
4545
@Override
4646
public void onDeviceRemoval(HidDevice device) {
4747
log.debug("Device detached: {}", device.getHidDeviceInfo().getProductString());
48-
PrintSocketClient.sendStream(session, createStreamAction(device, "Device Detached"));
48+
PrintSocketClient.sendStream(session, createStreamAction(device, "Device Detached"), this);
4949
}
5050
}

src/qz/communication/SerialIO.java

+20-11
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
import qz.common.ByteArrayBuilder;
1010
import qz.utils.ByteUtilities;
1111
import qz.utils.DeviceUtilities;
12+
import qz.ws.SocketConnection;
1213

1314
import java.io.IOException;
1415

1516
/**
1617
* @author Tres
1718
*/
18-
public class SerialIO {
19+
public class SerialIO implements DeviceListener {
1920

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

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

2930
private ByteArrayBuilder data = new ByteArrayBuilder();
3031

32+
private SocketConnection websocket;
33+
3134

3235
/**
3336
* Controller for serial communications
3437
*
3538
* @param portName Port name to open, such as "COM1" or "/dev/tty0/"
3639
*/
37-
public SerialIO(String portName) {
40+
public SerialIO(String portName, SocketConnection websocket) {
3841
this.portName = portName;
42+
this.websocket = websocket;
3943
}
4044

4145
/**
@@ -257,26 +261,31 @@ public void sendData(JSONObject params, SerialOptions opts) throws JSONException
257261
/**
258262
* Closes the serial port, if open.
259263
*
260-
* @return Boolean indicating success.
261264
* @throws SerialPortException If the port fails to close.
262265
*/
263-
public boolean close() throws SerialPortException {
266+
@Override
267+
public void close() {
268+
// Remove orphaned reference
269+
websocket.removeSerialPort(portName);
270+
264271
if (!isOpen()) {
265272
log.warn("Serial port [{}] is not open.", portName);
266-
return false;
267273
}
268274

269-
boolean closed = port.closePort();
270-
if (closed) {
271-
log.info("Serial port [{}] closed successfully.", portName);
272-
} else {
275+
try {
276+
boolean closed = port.closePort();
277+
if (closed) {
278+
log.info("Serial port [{}] closed successfully.", portName);
279+
} else {
280+
// Handle ambiguity in JSSCs API
281+
throw new SerialPortException(portName, "closePort", "Port not closed");
282+
}
283+
} catch(SerialPortException e) {
273284
log.warn("Serial port [{}] was not closed properly.", portName);
274285
}
275286

276287
port = null;
277288
portName = null;
278-
279-
return closed;
280289
}
281290

282291
private Integer min(Integer a, Integer b) {

src/qz/communication/SocketIO.java

+21-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.apache.logging.log4j.Logger;
88
import qz.utils.DeviceUtilities;
99
import qz.utils.NetworkUtilities;
10+
import qz.ws.SocketConnection;
1011

1112
import java.io.DataInputStream;
1213
import java.io.DataOutputStream;
@@ -15,7 +16,7 @@
1516
import java.nio.charset.Charset;
1617
import java.util.ArrayList;
1718

18-
public class SocketIO {
19+
public class SocketIO implements DeviceListener {
1920

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

@@ -27,10 +28,13 @@ public class SocketIO {
2728
private DataOutputStream dataOut;
2829
private DataInputStream dataIn;
2930

30-
public SocketIO(String host, int port, Charset encoding) {
31+
private SocketConnection websocket;
32+
33+
public SocketIO(String host, int port, Charset encoding, SocketConnection websocket) {
3134
this.host = host;
3235
this.port = port;
3336
this.encoding = encoding;
37+
this.websocket = websocket;
3438
}
3539

3640
public boolean open() throws IOException {
@@ -68,9 +72,21 @@ public String processSocketResponse() throws IOException {
6872
return null;
6973
}
7074

71-
public void close() throws IOException {
72-
dataOut.close();
73-
socket.close();
75+
@Override
76+
public void close() {
77+
// Remove orphaned reference
78+
websocket.removeNetworkSocket(String.format("%s:%s", host, port));
79+
80+
try {
81+
dataOut.close();
82+
} catch(IOException e) {
83+
log.warn("Could not close socket output stream", e);
84+
}
85+
try {
86+
socket.close();
87+
} catch(IOException e) {
88+
log.warn("Could not close socket", e);
89+
}
7490
}
7591

7692
public String getHost() {

0 commit comments

Comments
 (0)