oshi: [version 3.13.0] SystemInfo (and many classes available via it) is not thread-safe
Instances of SystemInfo
initialize its fields lazily, but without any synchronization, which leads to executions with data races and the possibilities of having either NullPointerException
or observing default values for primitive fields.
After taking a first look, I thought “maybe users are not supposed to use SystemInfo
concurrently”, but then I saw the question Are oshi SystemInfo instances thread safe?, and an answer
I’ve made reasonable efforts to try to avoid issues with thread safety, and many classes will work without a problem, I certainly can’t guarantee it
and realized that SystemInfo
is actually supposed to be used concurrently… I am going to present a only a few issues, but I would expect that there are more problems.
Problem 1: lazy initialization without synchronization
Consider SystemInfo.getHardware().getMemory().getTotal()
:
- a plain field
SystemInfo.hardware
is initialized lazily to a new instance of let’s sayLinuxHardwareAbstractionLayer
whenSystemInfo.getHardware
is called. - a plain field
LinuxHardwareAbstractionLayer.memory
is initialized lazily to a new instance of let’s sayLinuxGlobalMemory
whenLinuxHardwareAbstractionLayer.getMemory
is called. - a plain field
LinuxGlobalMemory.memTotal
is initialized lazily to along
value whenLinuxGlobalMemory.getTotal
is called.
All those fields are initialized without any memory synchronization. As a result writes to SystemInfo.hardware
field produced by SystemInfo.getHardware
method and reads from SystemInfo.hardware
field produced by the same SystemInfo.getHardware
method are not ordered with happens-before (HB) relation when SystemInfo.getHardware
is called concurrently (unless a user of SystemInfo
takes care of that…), and this means that executions contain data races. The mentioned reads of SystemInfo.hardware
is allowed by the Java memory model to observe both writes to this field produced by SystemInfo.getHardware
and the implicit write of the default value (this write exists in all executions regardless of whether the original code has it explicitly or not). The same is true for other mentioned fields, and thus there is no guarantee that we will not observe null
in SystemInfo.hardware
/LinuxHardwareAbstractionLayer.memory
and 0 in LinuxGlobalMemory.memTotal
.
Another slight sub-issue here is that concurrent executions of let’s say SystemInfo.getHardware
may create multiple instances of LinuxHardwareAbstractionLayer
. But this is because of a race condition that we have in the algorithm “check if hardware is initialized and initialize it if it’s not” (and a race condition is a different thing from a data race). But creating a few excessive objects is probably fine for this specific scenario (I don’t immediately see problems).
Problem 2: LinuxGlobalMemory.updateMeminfo
and lastUpdate
does not pave proper synchronization
A plain field LinuxGlobalMemory.lastUpdate
is being read and written in LinuxGlobalMemory.updateMeminfo
method without any synchronization. It leads to the things described in Problem 1, specifically there is no guarantee that reads from lastUpdate
in if (now - this.lastUpdate > 100)
ever see anything other than 0, which would lead each invocation of LinuxGlobalMemory.lastUpdate
to read from /proc/meminfo
.
A race condition similar to the one mentioned in Problem 1 is also applicable here: LinuxGlobalMemory.lastUpdate
may read from /proc/meminfo
multiple times when called concurrently because now - this.lastUpdate > 100
appears to be true for multiple threads. Depending on the cost of reading from /proc/meminfo
, it may or may not be a problem.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 17 (17 by maintainers)
OSHI was started in 2010 by @dblock and you can read about it in this blog post. There was some initial work on basic CPU and Memory stats on Windows, and using command lines in Mac and Linux but it mostly sat undeveloped. Still, the API skeleton was there and was easy to build on.
In 2015, I was working on a cloud-based distributed computing data mining problem utilizing JPPF. As part of optimizing my use of cloud resources (that my company paid for) I wanted to know how much memory my Java code was using. JPPF’s library used the
OperatingSystemMXBean
’sgetFreePhysicalMemory()
method which was useless on Linux, as “free” always decreases to zero even though there’s plenty “available”. I searched for a Java alternative and found none other than SIGAR (which had stopped development then) and OSHI (which had the same bug, but was open source and fixable). I submitted a PR to fix the bug… and since I wanted to learn more about JNA decided to add more. And more. And more. 😃In the process i learned git, maven, and a bunch of other stuff. I am not a professional software engineer, just an obsessed enthusiast who loves to learn and solve problems. Most of OSHI is an exercise in web-searching for the appropriate C code or command lines and porting it to Java via JNA.
I’ve had a lot of things I’ve wanted to fix for a while, which is why 4.X is happening, but it will mostly be trying to stabilize the API, getting rid of all the (crappy) static caches, trying to be a little bit smarter (but not perfect) with thread safety. But I’m hoping to drift back into “maintenance” mode after this overhaul, and letting the 5.x team take over with many more years of experience than I have.
There are some other contributors who have a grand vision for an OSHI 5.X. I am really looking forward to turning over the project to professionals.