Am I missing something here or does the BlockingRingConsumer not actually block? And worse doesn't it just return garbage if poll is called without first checking availableToPoll?
The example sure looks like it... https://github.com/coralblocks/CoralRing/blob/main/src/main/...
Which if so isn't this like 1/4th a library for doing IPC? It doesn't seem to do much itself
You are never supposed to call poll() without first calling availableToPoll(). How can you poll something if you don't know if there is something available to poll? This is very different than a ConcurrentLinkedQueue where you can call poll() on an empty queue and get a null to indicate that the queue is empty. Also because the ring is a circular queue, you have to know what you can safely poll before polling. That's all done through availableToPoll().
NOTE: By garbage here I don't think you are talking about GC garbage.
counterexample: you can call ::poll(2) if there isn't anything to available. It will either block or return as desired. That's literally what I would expect a poll function to do: check if something is available; c.f. busy-polling.
Maybe it is a language difference?
Which means it doesn't block...
Blocking means the call itself doesn't return until there's data available. This is a completely non-blocking API. Which is fine, it's just very wrongly named in that case.
But that also means I don't really know why there's both "non-blocking" and "blocking" variants at that point if blocking isn't an option at all in the first place.
For the non-blocking ring, the producer never blocks on a full ring. It overwrites the circular ring. The consumer can still block on an empty ring.
So to make it clear:
Blocking ring => producer and consumer can block/wait
Non-Blocking ring => producer never blocks and consumer can still block on an empty ring. Consumer can also fall behind too much and disconnect.
And given these are designed for "ultra-low-latency" systems, I don't think that's a big problem because the best blocking strategy is probably just to spin.
It would be nice if the docs were nicer though, considering this is a paid product...
You busy spin when blocking (fastest) or you can use a WaitStrategy from https://www.github.com/coralblocks/CoralQueue. You can see an example here: https://github.com/coralblocks/CoralQueue/blob/main/src/main...
> And given these are designed for "ultra-low-latency" systems, I don't think that's a big problem because the best blocking strategy is probably just to spin.
If you have an isolated and dedicated CPU core for your thread, that's correct. Busy-spinning is the fastest/best strategy.
> It would be nice if the docs were nicer though, considering this is a paid product...
CoralRing and CoralQueue are open-source and free at GitHub. There are also a lot of documentation and explanations on the GitHub README.me page (front page). The code has also a lot of comments.
Regarding the docs, I was only looking at CoralRing, and it looks like CoralQueue has some additional documentation that applies to CoralRing as well. After reading through them everything makes a lot more sense.
Yeah, modern JVM is a true miracle and you can be x5 productive (and safe!) compared to C/C++
Do you have any recommendations for a low latency work queue (with in a jvm)?
I want to spawn millions of micro-second-tasks per second, to worker cores..
I am on a massive cache CPU so memory latency hasnt raised its ugly head yet
EDIT: not LMAX please...
The DiamondQueue should be soon available for free at the CoralQueue project on GitHub.
I toyed around the ring buffer pattern a decade ago, creating a unicast one (using CAS on entries, and eventually a logarithmic scan for next readable entry, not to brute-force-scan them all), but I'm not sure that its latency is much better than that of a regular ThreadPoolExecutor (the throughput could be better though).
Latency also depends on whether it spins or blocks when waiting for a slot to read or write.
If you want to give it a try: https://github.com/jeffhain/jodk/blob/master/src/net/jodk/th...
In most applications like this you'll see direct byte manipulation to byte buffers because you want to pull as much performance as possible.
There are fast serialization formats like SBE that people leverage for this as well.
Your transfer object needs to implement MemorySerializable. Below two examples from CoralRing's GitHub:
https://github.com/coralblocks/CoralRing/blob/main/src/main/...
https://github.com/coralblocks/CoralRing/blob/main/src/main/...
The second one effectively allows you to send anything you want (as bytes) through the ring, making CoralRing message agnostic.
Drop-in replacement for java.net.Socket using shared memory (and optionally, futex for notification)
- Resource allocation; you might want to give just specific amount of memory, CPU, network I/O to specific modules of a system, which is not really feasible within a single JVM
- Resource isolation; e.g. a memory leak in one module of the system will affect just that specific JVM instance but not others (similar to why browsers run tabs in multiple processes);
- Upgrades; you can put a new version of one module of the system into place without impacting the others; while the JVM does support this via dynamic classloading (as e.g. used in OSGi or Layrry, https://github.com/moditect/layrry), this becomes complex quickly, you can create classloader leaks, etc.
- Security; You might have (3rd-party) modules you want to keep isolated from the memory, data, config, etc. of other modules; in particular with the removal of the security manager, OS-enforced process isolation is the way to
Those stops can be enough to ruin your low latency requirements in the high percentiles. A common strategy is to divide workloads between jvms so that you meet the requirement.
Assembly, worst case scenario plain and simple C99+.
what you never want is to trigger the GC at a bad time
(you can do cache alignment too relatively easily)