Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SerialPort.closePort not calling native interface on Windows when USB serial adapter is disconnected #107

Open
pietrygamat opened this issue Aug 5, 2021 · 5 comments

Comments

@pietrygamat
Copy link
Collaborator

pietrygamat commented Aug 5, 2021

As noted in #63, There are several methods in the library that are described in javadoc as returning true or false to signify a success, but in fact throwing an exception on failure. The particulary nasty stack for us is:
closePort -> removeEventListener -> setEventMask because in the case where USB serial adapter is already disconnected, this throws an exception before closePort -> serialInterface.closePort(portHandle); is fired.

As a result, the device is not released. Some other software we use that also queries serial ports still sees that ghost port (or sees multiple COM ports, with duplicated number if the adapter is reconnected), even when Device Manager does not.

@tresf
Copy link

tresf commented Aug 5, 2021

Perhaps this code:

Edit: Hmm... the editor says that swallows the exception, so, nope. :)

Show code
     public synchronized boolean closePort() throws SerialPortException {
-        checkPortOpened("closePort()");
-        if(eventListenerAdded){
-            removeEventListener();
-        }
-        boolean returnValue = serialInterface.closePort(portHandle);
-        if(returnValue){
-            maskAssigned = false;
-            portOpened = false;
+        boolean returnValue = false;
+        try {
+            checkPortOpened("closePort()");
+            if (eventListenerAdded) {
+                removeEventListener();
+            }
+        } finally{
+            returnValue = serialInterface.closePort(portHandle);
+            if (returnValue) {
+                maskAssigned = false;
+                portOpened = false;
+            }
         }
         return returnValue;
     }

@tresf
Copy link

tresf commented Aug 5, 2021

another idea, guard checkPortOpened() and only call once.

Click to see diff
--- a/src/main/java/jssc/SerialPort.java
+++ b/src/main/java/jssc/SerialPort.java
@@ -1049,8 +1049,17 @@ public class SerialPort {
      * 
      * @throws SerialPortException
      */
-    public synchronized boolean removeEventListener() throws SerialPortException {
-        checkPortOpened("removeEventListener()");
+    public synchronized boolean removeEventListener(boolean force) throws SerialPortException {
+        try {
+            checkPortOpened("removeEventListener()");
+        } catch(SerialPortException e) {
+            if(!force) {
+                throw e;
+            } else {
+                // Swallow exception, but put it to the terminal
+                e.printStackTrace();
+            }
+        }
         if(!eventListenerAdded){
             throw new SerialPortException(this, "removeEventListener()", SerialPortException.TYPE_CANT_REMOVE_LISTENER);
         }
@@ -1071,6 +1080,10 @@ public class SerialPort {
         return true;
     }
 
+    public synchronized boolean removeEventListener() throws SerialPortException {
+        return removeEventListener(false);
+    }
+
     /**
      * Close port. This method deletes event listener first, then closes the port
      *
@@ -1079,9 +1092,9 @@ public class SerialPort {
      * @throws SerialPortException
      */
     public synchronized boolean closePort() throws SerialPortException {
-        checkPortOpened("closePort()");
+        // checkPortOpened("closePort()"); // can this cause native closePort() to segfault?
         if(eventListenerAdded){
-            removeEventListener();
+            removeEventListener(true);
         }
         boolean returnValue = serialInterface.closePort(portHandle);
         if(returnValue){

@pietrygamat
Copy link
Collaborator Author

pietrygamat commented Aug 5, 2021

FYI, our production workaround is this, but this particular app is not very portable, so not much testing is done outside of Windows, and perhaps adhering to own javadoc and NOT throwing these exceptions in the first place is the way to go?

public class SerialPortEnhanced extends SerialPort {
    SerialPortEnhanced(String portName) {
        super(portName);
    }

    @Override
    public boolean setEventsMask(int mask) {
        try {
            return super.setEventsMask(mask);
        } catch (SerialPortException e) {
            return false;
        }
    }
}

@tresf
Copy link

tresf commented Aug 5, 2021

I think the proper solution will:

  • remove listeners regardless if the port is opened
  • attempt to close the port even if it is already closed

I do not see many use-cases for throwing an exception before cleanup, except perhaps the Thread.join call, but even then, I would expect eventThread.terminateThread() to be called AFTER Thread.join (not before) if currentThread().getId() differs.

@tresf
Copy link

tresf commented Aug 5, 2021

adhering to own javadoc and NOT throwing these exceptions in the first place is the way to go?

I do not see many use-cases for throwing an exception

🤣

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants