Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ env:

jobs:
ubuntu:
runs-on: [ubuntu-latest]
# Should be kept as old as possible to have better compatibility to

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIR, we produce production binaries with dockcross in workflows/cross-compile.yml, so this can probably safely be reverted. Reference: #190

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh 🙂 yes. Looks like I was missing that.

# runtime environments out in the whild. Newer runtimes are nearly
# always able to use libs linked with older toolchains. But not
# vice-versa.
runs-on: [ubuntu-22.04]
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -106,4 +110,4 @@ jobs:
java-version: 11
distribution: temurin

- run: mvn -P "${{ matrix.profile }}" --batch-mode
- run: mvn -P "${{ matrix.profile }}" --batch-mode
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.5)
Comment thread
tresf marked this conversation as resolved.
cmake_policy(SET CMP0048 NEW)
cmake_policy(SET CMP0042 NEW)

Expand Down
52 changes: 50 additions & 2 deletions src/main/cpp/_nix_based/jssc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -907,13 +907,61 @@ const jint events[] = {INTERRUPT_BREAK,
//EV_RXFLAG, //Not supported
EV_TXEMPTY};


/* backwards compatibility symbol. New callers should use `waitEvents2()`. */
JNIEXPORT jobjectArray JNICALL
Java_jssc_SerialNativeInterface_waitEvents( JNIEnv*env, jobject that, jlong portHandle ){
return Java_jssc_SerialNativeInterface_waitEvents2(env, that, portHandle, -1);
}


/* OK */
/*
* Collecting data for EventListener class (Linux have no implementation of "WaitCommEvent" function from Windows)
*
*/
JNIEXPORT jobjectArray JNICALL Java_jssc_SerialNativeInterface_waitEvents
(JNIEnv *env, jobject, jlong portHandle) {
JNIEXPORT jobjectArray JNICALL
Java_jssc_SerialNativeInterface_waitEvents2(
JNIEnv*env, jobject, jlong portHandle, jint waitEventsTimeoutMs
){
int err;

/* Code in `LinuxEventThread.run()` (in `SerialPort.java`) calls us
* in an infinite-loop. As a work-around, it uses a (very) small
* sleep, to not utilize a full CPU all the time. But still, this
* permanently wastes a lot of CPU cycles (that many, that it is a
* problem in our production use-case). The win32 code uses
* `OVERLAPPED` structs and `WaitSingleObject()` which already
* provide that kind of "wait" mechanism. But we do not have a
* win32-API here. As this impl here returns immediately, we'll
* first ask `poll()` (if available and enabled). This way we can
* "emulate" to actually wait if nothing is ready.
* See also JavaDoc of `SerialPort.setWaitEventsTimeoutMs(int)`. */
int const isFeatureEnabled = (waitEventsTimeoutMs >= 1);
if( isFeatureEnabled ){
#if !HAVE_POLL
static unsigned cnt = 0;
if( ((cnt + 1) & 0xFFFF) == 1 ){
fprintf(stderr, "WARN: waitEventsTimeoutMs not available on your platform, as `poll()` not available.\n");
}
#else
struct pollfd pfd = {0};
pfd.fd = portHandle;
pfd.events = POLLIN | POLLPRI | POLLRDHUP;
err = poll(&pfd, 1, waitEventsTimeoutMs);
if( err == -1 ) switch( errno ){
case EINTR:
/* Got interrupted by signal. Go report events we have so far. */
break;
default:
/* some error occurred. */
err = errno; /* bkup `errno` before calling into `FindClass()` */
jclass exClz = env->FindClass("java/lang/RuntimeException");
if( exClz ) env->ThrowNew(exClz, strerror(err));
return NULL;
}
#endif
}

jclass intClass = env->FindClass("[I");
if( intClass == NULL ) return NULL;
Expand Down
14 changes: 12 additions & 2 deletions src/main/cpp/windows/jssc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,22 @@ JNIEXPORT jboolean JNICALL Java_jssc_SerialNativeInterface_sendBreak
return returnValue;
}


/* backwards compatibility symbol. New callers should use `waitEvents2()`. */
JNIEXPORT jobjectArray JNICALL
Java_jssc_SerialNativeInterface_waitEvents( JNIEnv*env, jobject that, jlong portHandle ){
return Java_jssc_SerialNativeInterface_waitEvents2(env, that, portHandle, -1);
}


/*
* Wait event
* portHandle - port handle
*/
JNIEXPORT jobjectArray JNICALL Java_jssc_SerialNativeInterface_waitEvents
(JNIEnv *env, jobject, jlong portHandle) {
JNIEXPORT jobjectArray JNICALL
Java_jssc_SerialNativeInterface_waitEvents2(
JNIEnv*env, jobject, jlong portHandle, jint/*unused on windows*/
){
HANDLE hComm = (HANDLE)portHandle;
DWORD lpEvtMask = 0;
DWORD lpNumberOfBytesTransferred = 0;
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/jssc/SerialNativeInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,19 @@ public static String getLibraryVersion() {
* @return Method returns two-dimensional array containing event types and their values
* (<b>events[i][0] - event type</b>, <b>events[i][1] - event value</b>).
*/
public native int[][] waitEvents(long handle);
public int[][] waitEvents(long handle){ return waitEvents2(handle, -1); }

/**
* Wait events
*
* @param handle handle of opened port
*
* @param waitEventsTimeoutMs See {@link SerialPort#setWaitEventsTimeoutMs(int)}.
*
* @return Method returns two-dimensional array containing event types and their values
* (<b>events[i][0] - event type</b>, <b>events[i][1] - event value</b>).
*/
public native int[][] waitEvents2(long handle, int waitEventsTimeoutMs);

/**
* Change RTS line state
Expand Down
31 changes: 30 additions & 1 deletion src/main/java/jssc/SerialPort.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class SerialPort {
private final String portName;
private volatile boolean portOpened = false;
private boolean maskAssigned = false;
private int waitEventsTimeoutMs = -1;

//since 2.2.0 ->
private volatile Method methodErrorOccurred = null;
Expand Down Expand Up @@ -920,6 +921,34 @@ public boolean setFlowControlMode(int mask) throws SerialPortException {
return serialInterface.setFlowControlMode(portHandle, mask);
}

/**
* Reduce busy-waiting CPU load in {@link #waitEvents()}.
*
* (This is irrelevant for windows)
*
* The {@link #waitEvents()} implementation on non-windows systems
* usually returns immediately. This is unfortunate for the callers
* which want to await events in an infinite-loop. As doing so would
* burn lot of CPU time.
*
* This setting can be used to reduce that load. For regular
* incoming data events this does not cause any further delays.
* {@link #waitEvents()} still will reports most of the events as
* soon they become available, even before the specified timeout got
* reached.
*
* BUT BE AWARE: Enabling this might delay delivery of some special
* serial-events (like 'DCD line changed' or 'RI line changed') by
* the amount of time specified. So you have to decide yourself if
* you can/will afford this trade.
*/
public void setWaitEventsTimeoutMs(int waitEventsTimeoutMs) {
if (waitEventsTimeoutMs <= 0) {
throw new IllegalArgumentException(String.valueOf(waitEventsTimeoutMs));
}
this.waitEventsTimeoutMs = waitEventsTimeoutMs;
}

/**
* Get flow control mode
*
Expand Down Expand Up @@ -951,7 +980,7 @@ public boolean sendBreak(int duration)throws SerialPortException {
}

private int[][] waitEvents() {
return serialInterface.waitEvents(portHandle);
return serialInterface.waitEvents2(portHandle, waitEventsTimeoutMs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain the rationale behind keeping the old API intact? We don't really promise the ability to swap native binaries and from what I can tell, the old waitEvents becomes inaccessible now so what is the intent here? Is it for the edge-case someone swaps the binary or the ability to revert if this causes issue?

@hiddenalpha hiddenalpha Jun 16, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL:DR; Not strictly necessary, just preferrable for the project where I have to use jssc 🙈

Is it for the edge-case someone swaps the binary?

This is the closest fit 🙂 Unluckily the project I'm working on (which uses JSSC) has an overly complicated setup.

This project, not intentionally but more indirectly seems to depends on deploying the binary through another way than the java parts (agree, ugly). Due to way too complicated reasons. There's a way too long chain of technical details why this is the case 😔 So yes, the java part in unlucky circumstances can be a mismatching version compared to the shared objects 🙁

.class .so With Compatibility Without
old old works fine works fine
new new works fine works fine
old new works fine Runtime Linking Error <- here
new old Runtime Linking Error Runtime Linking Error

I'll throw that question in my team and we'll see what happens 🙂 Maybe we come up with an easlier migration strategy.

Edit: Hmm. I think I should verify again, if that "separate-distribution" thing really is true 🤔 Need to have a look with co-workers.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the code change in this PR is essentially native-only, I feel like one could objective just reduce the entropy by half by simply not touching the JAR at all but I'm probably misunderstanding something. I totally get the "tech-debt" stuff... I've moved all of my systems away from letting the JAR deploy the native lib per #92 so I'm 100% with you there, I just don't particularly care for namespacing obsolete code that's never to be used again. I'm probably misunderstanding something but I feel like you can:

  1. Keep the existing native signature; deploy the native lib without touching the jar/class files
  2. Eventually deploy the jar/class files

... assuming your versioning of the two must be independent. Sorry if I'm missing something or minimizing your struggles. The project is very grateful to have you help with the C++ portions, so we can certainly tackle this as you wish and add @Deprecated to clean it up later, I probably just misunderstand the use-case.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had another look onto our situation. Looks like parts of my assumption were wrong.

This means, we should be safe to get rid of the backwards compatibility stuff 🥳

I'll give it a try 👍

}

/**
Expand Down
Loading