I have an interesting problem that I need to solve on some production code. We are currently developing a web service that will be called from many different applications and will essentially be used to send emails. Whenever a new email is sent we will eventually need to add a receipt for that email into the database, but ideally we don't want to do this immediately so we will be building up a buffer over time. Once the buffer reaches a certain length, or after a sufficient time has passed the contents of the buffer will be flushed into the database.
Think of it like this, when a thread sends an email it will lock the buffer in order to add it's log without interference and maintain thread safety. If it sees that the buffer is of a certain size (in this example we will say 1000) than it is the thread's responsibility to write it all to the database (I think this is inefficient, but I'm using Service Stack as our web framework so if there's a way to delegate this task I would rather go with that approach).
Now, since writing to the database may be time consuming we want to add a secondary buffer to be used. So once one buffer is full all new requests will log their work into the second buffer while the first one is being flushed. Similarly once the second buffer is full all the threads will move back to the first buffer will the second is being flushed.
The primary issues we need to solve:
- When a thread decides it needs to flush one of the buffers it needs to indicate to all new threads to start logging to the second buffer (This should be as trivial as changing some variable or pointer to point to the empty buffer)
- If there are currently threads blocked when the current user of the critical section decides to flush the log it needs to re-activate all the blocked threads and point them to the second buffer
I'm more concerned with the second bullet-point. What is the best way to re-awaken all blocked threads, but instead of allowing them to enter the critical section for the first buffer make them try to attain a lock for the empty one?
EDIT
Based on the comments below I came up with something that I may think will work. I wasn't aware that thread-safe data structures existed.
private readonly ConcurrentQueue<EmailResponse> _logBuffer = new ConcurrentQueue<EmailResponse>();
private readonly object _lockobject = new object();
private const int BufferThreshold = 1000;
public void AddToBuffer(EmailResponse email)
{
_logBuffer.Enqueue(email);
Monitor.Enter(_lockobject);
if (_logBuffer.Count >= BufferThreshold)
Task.Run(async () =>
{
EmailResponse response;
for (var i = 0; i < BufferThreshold; i++)
if (_logBuffer.TryDequeue(out response))
await AddMail(response);
Monitor.Exit(_lockobject);
});
else Monitor.Exit(_lockobject);
}