Still, I think the left-right crate could benefit from one optimization I came up with. This is what the lib says in the docs:
> When WriteHandle::publish is called, the writer, atomically swaps the reader pointer to point to the other T. It then waits for the epochs of all current readers to change, and then replays the operational log to bring the stale copy up to date.
I think the lib could postpone the log replaying until the next publish. Chances are all readers will have the new epoch by than = the writer thread won't have to wait at all.
The first 3 years of my PhD summarized in one sentence.
I think you can't even do a new publish if all readers haven't finished switching to a new epoch after a previous publish. Otherwise, you risk corrupting readers that are still on the original epoch.
- create two ring buffers (left/right; I called them hot/cold)
- store the hot ring buffer in an atomic pointer
- single reader swaps hot and cold and waits for writers to finish
It was quite a journey, at first I thought I invented a novel concurrency schema. However, it turns out that it was simply a mix of my ignorance and hubris! :-)
Still, I had a lot of fun while designing this data structure and I believe it made a nice story. Ask me anything!
[0] https://github.com/BurntSushi/fst
[1] https://blog.burntsushi.net/transducers/
The main limitation of it is that it doesn't support removal, but if removals are infrequent you can workaround that with another fst with removed items (and periodically rebuild the whole thing)
That way, classe users will be warned by the IDE that they should use a try-with-resources when acquiring a Reader.
For the sake of completeness here, Java compiler will warn about it, but that warning is disabled by default.
Or, more generally, minimizing the odds of waiting on a lock, without resorting to a complex lock free scheme?
A disadvantage of that approach is that even without any lock contention you would still have two writes to memory for every lock acquisition.
ByteBuffer.allocateDirect should do that IIRC. This allows you to use the standard ConcurrentHashMap while being able to get a stable pointer for use by the rust logic.
Another simple way, if we don't like the idea of triggering GCs manually,is to allocate the same buffer both off-heap and on-heap: use the off-heap one for actual key storage, and the on-heap one just to generate heap memory pressure.
1. Technical
The contract is given - I'm receiving usernames as CharSequence(String). I could change that, but that would likely require changes in the SQL parser - not simple at all. Alternatively, I could pass the whole CharSequence to Rust - but passing objects over the JNI boundary comes with perf. penalty (when compared to passing primitive) and we avoid that whenever possible. Or I could encode CharSequence content to a temporary offheap buffer and pass just the pointer to Rust. But this brings back some of the questions from the article - like who owns the buffer?
2. Other reasons
I realized this was a possibility only when I was nearly done with the design (this whole endeavor took less than one day) and I felt the urge to finish it. Also: This article wouldn't have been created!