Skip to content

Reduce busy-waiting CPU load in waitEvents()#201

Merged
tresf merged 1 commit into
java-native:masterfrom
hiddenalpha:ReduceBusyWaitingCpuLoadInWaitEvents-20260615
Jun 26, 2026
Merged

Reduce busy-waiting CPU load in waitEvents()#201
tresf merged 1 commit into
java-native:masterfrom
hiddenalpha:ReduceBusyWaitingCpuLoadInWaitEvents-20260615

Conversation

@hiddenalpha

@hiddenalpha hiddenalpha commented Jun 16, 2026

Copy link
Copy Markdown

The 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.

For example: 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 core 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.

Edit: Unfortunately I've no idea how to fix macOS CICD build :(

@hiddenalpha hiddenalpha force-pushed the ReduceBusyWaitingCpuLoadInWaitEvents-20260615 branch 5 times, most recently from 781cc8f to 116784b Compare June 16, 2026 09:19
@hiddenalpha hiddenalpha marked this pull request as ready for review June 16, 2026 09:35
Comment thread .github/workflows/build.yml Outdated
Comment thread src/main/java/jssc/SerialPort.java Outdated
Comment thread CMakeLists.txt
@pietrygamat

pietrygamat commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Unfortunately I've no idea how to fix macOS CICD build :(

The macos-latest runner seems to evolve faster that this project can handle ;P.
The issue seems to be with the older SDK version the pipeline manually downloads and forces:

[MT] DVTSDK: Skipped SDK /Applications/Xcode_16.4.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk; its version (10.9) is below required minimum (14.0) for the macosx platform.

We can fix the build by either using older macos runner (macos-14 instead of macos-latest) or bumping macos-deployment-target to 14.0+ (simplify the pipeline too). @tresf, you may want to select the right approach that also avoids pitfals such as #187 .

@hiddenalpha hiddenalpha marked this pull request as draft June 16, 2026 23:03
@tresf

tresf commented Jun 17, 2026

Copy link
Copy Markdown

We can fix the build by either using older macos runner (macos-14 instead of macos-latest) or bumping macos-deployment-target to 14.0+ (simplify the pipeline too). @tresf, you may want to select the right approach that also avoids pitfals such as #187 .

@pietrygamat thanks for digging into this... I have to admit each time I revisit this my memory is more and more foggy what we can and can't do with the older SDKs.

I bump into this a lot with another project and this is what we do over there...

  macos:
    strategy:
      fail-fast: false
      matrix:
        arch: [ x86_64, arm64 ]
        include:
          - arch: x86_64
            os: macos-15-intel
            xcode: "16.4"
          - arch: arm64
            os: macos-15
            xcode: "16.4"

... specifically though, we leverage an environment variable MACOSX_DEPLOYMENT_TARGET. Although that project has dependencies that influence this value, to the best of my knowledge, JSSC doesn't, so we should be able to leverage this to target any OS we wish. Back in the day I thought that this was a false-promise and that the SDK variable was a joke, but the other project proved my suspicions false... Quoting:

@Mr-Emoticon63 are you on Intel or Apple Silicon?

Finally, we have conversation in here. Also, I’m using Intel because my laptop is from 2015.

Thanks, I asked because of the following statement early into the thread...

Sadly, I don't believe this does anything useful because the Homebrew dependencies are pre-compiled for the target macOS version, which is currently set to macos-13 per:

This statement is slightly misleading... My vague memory of this all was that the SDK level needs to be compatible with the current OS level and that macOS generally provides backwards compatibility for two versions. (Prior to macOS 11, the versions were in point format, so "two versions" changes definitions slightly if we're discussing older macOS versions)... however your feedback in combination with @barracuda156's words make me less sure of this...

I have seen too many instances where arbitrarily high hardcoded deployment target

What's puzzling to me is whether or not a binary produced using e.g. 15.2 can be RUN on e.g. minos 11.0 at all (or in the case of Intel 14.2 vs. 10.13). Regardless, my branch here just mimics what Homebrew does by copying the same exact minos value into our build system. My statements about this "not working" were clearly incorrect based on your testing.

This is to say there's some SDK backwards-compatibility that I'm clearly missing, but this explains why @messmerd's original PR fixes your issue. Here's the Intel download from my PR: (this link will eventually expire) if you can test it out on your machine. Meanwhile, I'm trying to setup a macOS 12 VM to test the same for Apple Silicon.

That's a lot to say that MACOSX_DEPLOYMENT_TARGET seems to work well as long as we don't compile against any APIs that are specific to newer macOS versions (which I don't think we do).

Copying the other project's strategy it does still use a hard-coded runner version macos-15[-intel] which I think is sane considering macos-14 will start throwing deprecation warnings next month.

Edit: Whoops, sorry for the copy/paste tags above.

@hiddenalpha

hiddenalpha commented Jun 18, 2026

Copy link
Copy Markdown
Author

... either using older macos runner (macos-14 instead of macos-latest) ...
... bumping macos-deployment-target to 14.0+ ...
... I bump into this a lot with another project and this is what we do over there: ...
... we should be able to leverage this to target any OS we wish. ...
... Back in the day I thought that this was a false-promise and that the SDK variable was a joke, but the other project proved my suspicions false ...

I did not yet understand the comments about MacOS. So what should jssc do? I'll give one of the suggestions a try 🙂

Edit 1: I could not find anything that would look like 14.0+-ish in MacOSX-SDKs. Will try another suggestion then.

@tresf

tresf commented Jun 18, 2026

Copy link
Copy Markdown

So what should jssc do

Your PR can just use an old runner, even if it's EOL in a month. @pietrygamat and I will have to look for a more permanent solution, but it doesn't necessarily need to be fixed here.

hiddenalpha added a commit to hiddenalpha/jssc that referenced this pull request Jun 22, 2026
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.

For example: 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 core 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.

Not perfect, but this patch at least provides a way to wait if nothing
is ready. That "feature" is off by default and can be enabled
individually.

Edit: TryFix CICD build
> - try fix error:
>   "Compatibility with CMake < 3.5 has been removed from CMake."
> - Use an older ubuntu to broaden runtime compatiblity
> - try fix error:
>   "Failed to locate 'make', requesting installation of command line developer tools"
> - Revert some non-working MacOS fixes
> - Explain why to use older ubuntu version
> - Update checked-in JNI header file as generated by JDK
> - GuessFix MacOS CICD
>   java-native#201 (comment)
> - Drop unneccessary compatibility symbols. as discussed in:
>   java-native#201 (comment)
> - Cleanup JavaDoc comment

Squashed-From: 8279b5d
@hiddenalpha hiddenalpha force-pushed the ReduceBusyWaitingCpuLoadInWaitEvents-20260615 branch from 116784b to a63a20d Compare June 22, 2026 10:14
@hiddenalpha hiddenalpha marked this pull request as ready for review June 22, 2026 10:17
@hiddenalpha hiddenalpha requested a review from tresf June 22, 2026 10:17
hiddenalpha added a commit to hiddenalpha/jssc that referenced this pull request Jun 23, 2026
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.

For example: 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 core 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.

Not perfect, but this patch at least provides a way to wait if nothing
is ready. That "feature" is off by default and can be enabled
individually.

Edit: TryFix CICD build
> - try fix error:
>   "Compatibility with CMake < 3.5 has been removed from CMake."
> - Use an older ubuntu to broaden runtime compatiblity
> - try fix error:
>   "Failed to locate 'make', requesting installation of command line developer tools"
> - Revert some non-working MacOS fixes
> - Explain why to use older ubuntu version
> - Update checked-in JNI header file as generated by JDK
> - GuessFix MacOS CICD
>   java-native#201 (comment)
> - Drop unneccessary compatibility symbols. as discussed in:
>   java-native#201 (comment)
> - Cleanup JavaDoc comment
> - Whops. Fix the log-damp counter.

Squashed-From: eeab9b4
@hiddenalpha hiddenalpha force-pushed the ReduceBusyWaitingCpuLoadInWaitEvents-20260615 branch from a63a20d to cbb0cd8 Compare June 23, 2026 08:15
@tresf

tresf commented Jun 23, 2026

Copy link
Copy Markdown

I tested the baseline functionality for regressions, none were found. I did not test the new timeout feature, just regression tested the old feature.

Since Windows already behaved this way, there's a part of me that wants this to just be the new default with a sane value to bring better parity however the code comments suggest that there are conditions where this is undesired, so I'm fine with it as-is.

I do like the current implementation of setting this globally so all connections behave the same way, so no objections here. Perhaps the last enhancement I would request is to provide a recommended value for others experiencing this issue (e.g. 2ms?), since the implementation may be otherwise lost upon the casual onlooker.

Comment thread src/main/java/jssc/SerialPort.java
Comment thread src/main/java/jssc/SerialNativeInterface.java Outdated
Comment thread src/main/java/jssc/SerialPort.java Outdated
Comment thread src/main/java/jssc/SerialPort.java
hiddenalpha added a commit to hiddenalpha/jssc that referenced this pull request Jun 25, 2026
hiddenalpha added a commit to hiddenalpha/jssc that referenced this pull request Jun 25, 2026
hiddenalpha added a commit to hiddenalpha/jssc that referenced this pull request Jun 25, 2026
hiddenalpha added a commit to hiddenalpha/jssc that referenced this pull request Jun 25, 2026
hiddenalpha added a commit to hiddenalpha/jssc that referenced this pull request Jun 25, 2026
hiddenalpha added a commit to hiddenalpha/jssc that referenced this pull request Jun 25, 2026
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.

For example: 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 core 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.

Not perfect, but this patch at least provides a way to wait if nothing
is ready. That "feature" is off by default and can be enabled
individually.

Edit:
> - try fix error:
>   "Compatibility with CMake < 3.5 has been removed from CMake."
> - Use an older ubuntu to broaden runtime compatiblity
> - try fix error:
>   "Failed to locate 'make', requesting installation of command line developer tools"
> - Revert some non-working MacOS fixes
> - Explain why to use older ubuntu version
> - Update checked-in JNI header file as generated by JDK
> - GuessFix MacOS CICD
>   java-native#201 (comment)
> - Drop unneccessary compatibility symbols. as discussed in:
>   java-native#201 (comment)
> - Cleanup JavaDoc comment
> - Whops. Fix the log-damp counter.
> - Drop convenience overload
>   See java-native#201 (comment)
> - Allow `-1` in setter to disable again.
>   See java-native#201 (comment)
> - Give hint about using 100ms
>   java-native#201 (comment)

Squashed-From: e86ce17
@hiddenalpha hiddenalpha force-pushed the ReduceBusyWaitingCpuLoadInWaitEvents-20260615 branch from cbb0cd8 to b32964b Compare June 25, 2026 12:14
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.

For example: 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 core 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.

Not perfect, but this patch at least provides a way to wait if nothing
is ready. That "feature" is off by default and can be enabled
individually.

Edit:
> - try fix error:
>   "Compatibility with CMake < 3.5 has been removed from CMake."
> - Use an older ubuntu to broaden runtime compatiblity
> - try fix error:
>   "Failed to locate 'make', requesting installation of command line developer tools"
> - Revert some non-working MacOS fixes
> - Explain why to use older ubuntu version
> - Update checked-in JNI header file as generated by JDK
> - GuessFix MacOS CICD
>   java-native#201 (comment)
> - Drop unneccessary compatibility symbols. as discussed in:
>   java-native#201 (comment)
> - Cleanup JavaDoc comment
> - Whops. Fix the log-damp counter.
> - Drop convenience overload
>   See java-native#201 (comment)
> - Allow `-1` in setter to disable again.
>   See java-native#201 (comment)
> - Give hint about using 100ms
>   java-native#201 (comment)
> - Skip timeout in EventThread ctor while state init
>   java-native#201 (comment)

Squashed-From: 03704d8
hiddenalpha added a commit to hiddenalpha/jssc that referenced this pull request Jun 25, 2026
@hiddenalpha hiddenalpha force-pushed the ReduceBusyWaitingCpuLoadInWaitEvents-20260615 branch from b32964b to 476e6f3 Compare June 25, 2026 12:55
@hiddenalpha hiddenalpha requested a review from pietrygamat June 26, 2026 07:09
@tresf tresf merged commit 2032c0d into java-native:master Jun 26, 2026
13 checks passed
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

Successfully merging this pull request may close these issues.

3 participants