ringbuffer API design

I’ve been unhappy with the ringbuffer API. It’s totally unclear how it’s supposed to be used.

The existing API looks something like this;

get_read_element()
put_read_element()

get_write_element()
put_write_element()

Great, huh? obvious – not.

Naively, I think the expectation is for something like this;

ringbuffer_read()
ringbuffer_write()

read() get the oldest unread element, write() writes to the ringbuffer, overwriting the oldest element if there are no free elements.

The problem though, which led to the existing API, is that the ringbuffer is lock-free and so multi-reader and multi-writer; as such. if we read an element, someone else can completely over-write its contents WHILE WE’RE READING IT.

As such, the existing API acts when reading to *detach* the element from the ringbuffer, so no other thread can touch it, and the same when writing; we detach an element, write to it, then place it back into the ringbuffer.

In fact I’ve realised this problem/behaviour all turns upon whether or not the ringbuffer is beig used to store transient or permanent data. What I mean by this is that we arrange the ringbuffer either so that each element is pointing to some permanently allocated store for that element (like an audio buffer) or we arrange the ringbufer so that each element is holding a void pointer, which is valid for one read (so we have say a freelist of audio buffers, and we write into the ringbuffer a pointer to a filled audio buffer we’ve obtained from the freelist).

If each element has some store permanent associated with it (this being done typically when the ringbuffer is initialized) then we have the problem earlier described; if we read by just getting a pointer, which is to say obtain a pointer to a filled audio buffer, another thread could perfectly well write into that buffer as we’re still using it. So we need to detach the element from the ringbuffer, which gives us the get_read_element() / put_read_element() style semantics.

OTOH, if we have a freelist of audio buffers, and we take a buffer from there, fill it, then put a void pointer to it into the ringbuffer, then the act of reading is the act of taking that void pointer and the reading thread can then do something with it and when it’s done, put it back on the freelist. With this arrangement, we can have simply read() and write() – write will as in the previous paragraph overwrite the existing void pointer, but that’s fine; that’s not going to interfere with anyone who’s already obtained a copy of that void pointer. (One thing we do need to look out for is when a write to the ringbuffer overwrites an exiting element, where the ringbuffer is full; the writer must get back the void pointer he has overwritten, so that the user can do something with it – such as returning it to a freelist – rather than it being lost).

In the spirit of having one thing perform one task, the ringbuffer shall now have read() / write() semantics, and the user must look after his pointers.