Skip to content

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

Draft
hiddenalpha wants to merge 1 commit into
java-native:masterfrom
hiddenalpha:ReduceBusyWaitingCpuLoadInWaitEvents-20260615
Draft

Reduce busy-waiting CPU load in waitEvents()#201
hiddenalpha wants to merge 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 4 times, most recently from 37a1a81 to 781cc8f Compare June 16, 2026 09:07
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

Squashed-From: b5072ca
@hiddenalpha hiddenalpha force-pushed the ReduceBusyWaitingCpuLoadInWaitEvents-20260615 branch 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
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.


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 👍

Comment thread CMakeLists.txt
@pietrygamat

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.

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