padenot / ringbuf.js

Wait-free thread-safe single-consumer single-producer ring buffer using SharedArrayBuffer
https://ringbuf-js.netlify.app/
Mozilla Public License 2.0
201 stars 18 forks source link

A possible `reset` method to drop all stored data from ringbuffer #16

Closed danigb closed 1 year ago

danigb commented 1 year ago

Hi,

First of all, thanks for this great library! 🙏

I'm building a DAW-style application that communicates an audio producer worker with a consumer worklet using this ringbuf.js library.

The problem comes when the user changes the playhead position: we would need to discard the previous generated buffers that are already stored in the ringbuffer.

The possible solution I'm thinking of would consist in:

If you agree, I could do a tentative PR (although I don't know how could be tested...)

How does it sound?

padenot commented 1 year ago

You can't really remove data from the producer thread with this design (only the consumer can adjust the read_ptr), so what you should do is to ask the consumer to drop the data. Having the producer remove data from the queue wouldn't be safe.

Out-of-band signaling can work here: the producer tells the consumer to drop the data using another atomic or some other communication mechanism.

danigb commented 1 year ago

That was my first attempt: the client consume the "invalid" buffers... but the problem is that the consumer doesn't know how much data to consume, because at the same time, the producer is creating new buffers at the new playback position.

So either the producers needs to "wait" for the client to consume all buffers, or another kind of sync mechanism is required...

The only other solution I can think of is to add timestamps to the beginning of the buffer, so the consumer know which buffers needs to be dropped....

Do you have any other ideas or can point me to any possible solutions? How consumer could know how many buffers to consume to "catch up" with the producer? 🤔

UPDATE: Can you briefly explain my why this is not possible in this design or point me to any document/paper that explain the current architecture? And... do you know if there's any other kind of ringbuffer-like structure that allows this "reset" from the producer side?

Thanks

padenot commented 1 year ago

UPDATE: Can you briefly explain my why this is not possible in this design or point me to any document/paper that explain the current architecture? And... do you know if there's any other kind of ringbuffer-like structure that allows this "reset" from the producer side?

In this design, only the consumer can remove elements from the ring buffer, and only the producer can put elements in the ring buffer. If, e.g. the consumer start putting an element in the ring buffer by using push, there's a good chance that the write index becomes desynchronized with reality, and you end up writing over elements or all sorts of things.

You can see it for yourself for your particular case: reading the pop, method, simulating in your head two threads executing it at the same time, you fetch the same indices twice, pop the same values twice, and then increment the read index twice, which is incorrect, because it means you're skipping some values.

A more general queue, such as an MPMC queue (multi-producer / multi-consumer) queue might work, but is a lot more complicated to write, reason about, and won't have performances as predictable.