Repository files navigation

DiskQueue

A robust, thread-safe, and multi-process persistent queue.

Based very heavily on http://ayende.com/blog/3479/rhino-queues-storage-disk

Requirements and Environment

  • Works on dotnet standard 2.0 platforms
  • Requires access to filesystem storage
  • Only partial support for "Trim Self-Contained" deployments and executables (see below)

The file system is used to hold locks, so any bug in your file system may cause issues with DiskQueue -- although it tries to work around them.

Thanks to

These kind folks have helped in the development of DiskQueue

Basic Usage

  • PersistentQueue.WaitFor(...) is the main entry point. This will attempt to gain an exclusive lock on the given storage location. On first use, a directory will be created with the required files inside it.
  • This queue object can be shared among threads. Each thread should call OpenSession() to get its own session object.
  • Both IPersistentQueues and IPersistentQueueSessions should be wrapped in using() clauses, or otherwise disposed of properly. Failure to do this will result in lock contention -- you will get errors that the queue is still in use.

There is also a generic-typed PersistentQueue<T>(...); which will handle the serialisation and deserialization of elements in the queue, as long at the type is decorated with [Serializable]. You can also inject your own ISerializationStrategy<T> into your PersistentQueueSession<T> if you wish to have more granular control over Serialization/Deserialization, or if you wish to use your own serializer (e.g Json.NET).

Use new PersistentQueue<T>(...) in place of new PersistentQueue(...) or PersistentQueue.WaitFor<T>(...) in place of PersistentQueue.WaitFor(...) in any of the examples below.

Note: BinaryFormatter was removed from the default serializer. See https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide.

Example

Queue on one thread, consume on another; retry some exceptions.

Note this is one queue being shared between two sessions. You should not open two queue instances for one storage location at once.

IPersistentQueuequeue=newPersistentQueue("queue_a");vart1=newThread(()=>{while(HaveWork()){using(varsession=queue.OpenSession()){session.Enqueue(NextWorkItem());session.Flush();}}});vart2=newThread(()=>{while(true){using(varsession=queue.OpenSession()){vardata=session.Dequeue();if(data==null){Thread.Sleep(100);continue;}try{MaybeDoWork(data)
session.Flush();}catch(RetryException){continue;}catch{session.Flush();}}}});t1.Start();t2.Start();

Example

Batch up a load of work and have another thread work through it.

IPersistentQueuequeue=newPersistentQueue("batchQueue");varworker=newThread(()=>{using(varsession=queue.OpenSession()){byte[]data;while((data=session.Dequeue())!=null){MaybeDoWork(data)
session.Flush();}}});using(varsession=queue.OpenSession()){foreach(variteminLoadsOfStuff()){session.Enqueue(item);}session.Flush();}worker.IsBackground=true;// anything not complete when we close will be left on the queue for next time.worker.Start();

Transactions

Each session is a transaction. Any Enqueues or Dequeues will be rolled back when the session is disposed unless you call session.Flush(). Data will only be visible between threads once it has been flushed. Each flush incurs a performance penalty. By default, each flush is persisted to disk before continuing. You can get more speed at a safety cost by setting queue.ParanoidFlushing = false;

Data loss and transaction truncation

By default, DiskQueue will silently discard transaction blocks that have been truncated; it will throw an InvalidOperationException when transaction block markers are overwritten (this happens if more than one process is using the queue by mistake. It can also happen with some kinds of disk corruption). If you construct your queue with throwOnConflict: false, all recoverable transaction errors will be silently truncated. This should only be used when uptime is more important than data consistency.

using (var queue = new PersistentQueue(path, Constants._32Megabytes, throwOnConflict: false)) {
. . .
}

Global default settings

Each instance of a PersistentQueue has it's own settings for flush levels and corruption behaviour. You can set these individually after creating an instance, or globally with PersistentQueue.DefaultSettings. Default settings are applied to all queue instances in the same process created after the setting is changed.

For example, if performance is more important than crash safety:

PersistentQueue.DefaultSettings.ParanoidFlushing=false;PersistentQueue.DefaultSettings.TrimTransactionLogOnDispose=false;

Or if up-time is more important than detecting corruption early (often the case for embedded systems):

PersistentQueue.DefaultSettings.AllowTruncatedEntries=true;PersistentQueue.DefaultSettings.ParanoidFlushing=true;

Internal Logging

Some internal warnings and non-critical errors are logged through PersistentQueue.Log. This defaults to System.Console.WriteLine, but can be replace with any Action<string>

Removing or resetting queues

Queues create a directory and set of files for storage. You can remove all files for a queue with the HardDelete method. If you give true as the reset parameter, the directory will be written again.

This WILL delete ANY AND ALL files inside the queue directory. You should not call this method in normal use. If you start a queue with the same path as an existing directory, this method will delete the entire directory, not just the queue files.

varsubject=newPersistentQueue("queue_a");subject.HardDelete(true);// wipe any existing data and start again

Multi-Process Usage

Each IPersistentQueue gives exclusive access to the storage until it is disposed. There is a static helper method PersistentQueue.WaitFor("path", TimeSpan...) which will wait to gain access until other processes release the lock or the timeout expires. If each process uses the lock for a short time and wait long enough, they can share a storage location.

E.g.

...
void AddToQueue(byte[]data){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){session.Enqueue(data);session.Flush();}}byte[]ReadQueue(){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){vardata=session.Dequeue();session.Flush();returndata;}}
...

Cross-process Locking

DiskQueue tries very hard to make sure the lock files are managed correctly. You can use this as an inter-process lock if required. Simply open a session to acquire the lock, and dispose of the session to release it.

If you need the transaction semantics of sessions across multiple processes, try a more robust solution like https://github.com/i-e-b/SevenDigital.Messaging

Trim self-contained deployments and executables

The default serialiser for typed queues is NOT supported under "trimmed self-contained" projects.

You can either use the raw byte-based interfaces and do your own serialisation, or create and use a custom ISerializationStrategy<T> for your types.

See

About

A robust, thread-safe, and multi-process persistent queue library for dotnet

Topics

Resources

Stars

128 stars

Watchers

9 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

DiskQueue

A robust, thread-safe, and multi-process persistent queue.

Based very heavily on http://ayende.com/blog/3479/rhino-queues-storage-disk

Requirements and Environment

  • Works on dotnet standard 2.0 platforms
  • Requires access to filesystem storage
  • Only partial support for "Trim Self-Contained" deployments and executables (see below)

The file system is used to hold locks, so any bug in your file system may cause issues with DiskQueue -- although it tries to work around them.

Thanks to

These kind folks have helped in the development of DiskQueue

Basic Usage

  • PersistentQueue.WaitFor(...) is the main entry point. This will attempt to gain an exclusive lock on the given storage location. On first use, a directory will be created with the required files inside it.
  • This queue object can be shared among threads. Each thread should call OpenSession() to get its own session object.
  • Both IPersistentQueues and IPersistentQueueSessions should be wrapped in using() clauses, or otherwise disposed of properly. Failure to do this will result in lock contention -- you will get errors that the queue is still in use.

There is also a generic-typed PersistentQueue<T>(...); which will handle the serialisation and deserialization of elements in the queue, as long at the type is decorated with [Serializable]. You can also inject your own ISerializationStrategy<T> into your PersistentQueueSession<T> if you wish to have more granular control over Serialization/Deserialization, or if you wish to use your own serializer (e.g Json.NET).

Use new PersistentQueue<T>(...) in place of new PersistentQueue(...) or PersistentQueue.WaitFor<T>(...) in place of PersistentQueue.WaitFor(...) in any of the examples below.

Note: BinaryFormatter was removed from the default serializer. See https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide.

Example

Queue on one thread, consume on another; retry some exceptions.

Note this is one queue being shared between two sessions. You should not open two queue instances for one storage location at once.

IPersistentQueuequeue=newPersistentQueue("queue_a");vart1=newThread(()=>{while(HaveWork()){using(varsession=queue.OpenSession()){session.Enqueue(NextWorkItem());session.Flush();}}});vart2=newThread(()=>{while(true){using(varsession=queue.OpenSession()){vardata=session.Dequeue();if(data==null){Thread.Sleep(100);continue;}try{MaybeDoWork(data)
session.Flush();}catch(RetryException){continue;}catch{session.Flush();}}}});t1.Start();t2.Start();

Example

Batch up a load of work and have another thread work through it.

IPersistentQueuequeue=newPersistentQueue("batchQueue");varworker=newThread(()=>{using(varsession=queue.OpenSession()){byte[]data;while((data=session.Dequeue())!=null){MaybeDoWork(data)
session.Flush();}}});using(varsession=queue.OpenSession()){foreach(variteminLoadsOfStuff()){session.Enqueue(item);}session.Flush();}worker.IsBackground=true;// anything not complete when we close will be left on the queue for next time.worker.Start();

Transactions

Each session is a transaction. Any Enqueues or Dequeues will be rolled back when the session is disposed unless you call session.Flush(). Data will only be visible between threads once it has been flushed. Each flush incurs a performance penalty. By default, each flush is persisted to disk before continuing. You can get more speed at a safety cost by setting queue.ParanoidFlushing = false;

Data loss and transaction truncation

By default, DiskQueue will silently discard transaction blocks that have been truncated; it will throw an InvalidOperationException when transaction block markers are overwritten (this happens if more than one process is using the queue by mistake. It can also happen with some kinds of disk corruption). If you construct your queue with throwOnConflict: false, all recoverable transaction errors will be silently truncated. This should only be used when uptime is more important than data consistency.

using (var queue = new PersistentQueue(path, Constants._32Megabytes, throwOnConflict: false)) {
. . .
}

Global default settings

Each instance of a PersistentQueue has it's own settings for flush levels and corruption behaviour. You can set these individually after creating an instance, or globally with PersistentQueue.DefaultSettings. Default settings are applied to all queue instances in the same process created after the setting is changed.

For example, if performance is more important than crash safety:

PersistentQueue.DefaultSettings.ParanoidFlushing=false;PersistentQueue.DefaultSettings.TrimTransactionLogOnDispose=false;

Or if up-time is more important than detecting corruption early (often the case for embedded systems):

PersistentQueue.DefaultSettings.AllowTruncatedEntries=true;PersistentQueue.DefaultSettings.ParanoidFlushing=true;

Internal Logging

Some internal warnings and non-critical errors are logged through PersistentQueue.Log. This defaults to System.Console.WriteLine, but can be replace with any Action<string>

Removing or resetting queues

Queues create a directory and set of files for storage. You can remove all files for a queue with the HardDelete method. If you give true as the reset parameter, the directory will be written again.

This WILL delete ANY AND ALL files inside the queue directory. You should not call this method in normal use. If you start a queue with the same path as an existing directory, this method will delete the entire directory, not just the queue files.

varsubject=newPersistentQueue("queue_a");subject.HardDelete(true);// wipe any existing data and start again

Multi-Process Usage

Each IPersistentQueue gives exclusive access to the storage until it is disposed. There is a static helper method PersistentQueue.WaitFor("path", TimeSpan...) which will wait to gain access until other processes release the lock or the timeout expires. If each process uses the lock for a short time and wait long enough, they can share a storage location.

E.g.

...
void AddToQueue(byte[]data){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){session.Enqueue(data);session.Flush();}}byte[]ReadQueue(){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){vardata=session.Dequeue();session.Flush();returndata;}}
...

Cross-process Locking

DiskQueue tries very hard to make sure the lock files are managed correctly. You can use this as an inter-process lock if required. Simply open a session to acquire the lock, and dispose of the session to release it.

If you need the transaction semantics of sessions across multiple processes, try a more robust solution like https://github.com/i-e-b/SevenDigital.Messaging

Trim self-contained deployments and executables

The default serialiser for typed queues is NOT supported under "trimmed self-contained" projects.

You can either use the raw byte-based interfaces and do your own serialisation, or create and use a custom ISerializationStrategy<T> for your types.

See

About

A robust, thread-safe, and multi-process persistent queue library for dotnet

Topics

Resources

Stars

128 stars

Watchers

9 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

DiskQueue

A robust, thread-safe, and multi-process persistent queue.

Based very heavily on http://ayende.com/blog/3479/rhino-queues-storage-disk

Requirements and Environment

  • Works on dotnet standard 2.0 platforms
  • Requires access to filesystem storage
  • Only partial support for "Trim Self-Contained" deployments and executables (see below)

The file system is used to hold locks, so any bug in your file system may cause issues with DiskQueue -- although it tries to work around them.

Thanks to

These kind folks have helped in the development of DiskQueue

Basic Usage

  • PersistentQueue.WaitFor(...) is the main entry point. This will attempt to gain an exclusive lock on the given storage location. On first use, a directory will be created with the required files inside it.
  • This queue object can be shared among threads. Each thread should call OpenSession() to get its own session object.
  • Both IPersistentQueues and IPersistentQueueSessions should be wrapped in using() clauses, or otherwise disposed of properly. Failure to do this will result in lock contention -- you will get errors that the queue is still in use.

There is also a generic-typed PersistentQueue<T>(...); which will handle the serialisation and deserialization of elements in the queue, as long at the type is decorated with [Serializable]. You can also inject your own ISerializationStrategy<T> into your PersistentQueueSession<T> if you wish to have more granular control over Serialization/Deserialization, or if you wish to use your own serializer (e.g Json.NET).

Use new PersistentQueue<T>(...) in place of new PersistentQueue(...) or PersistentQueue.WaitFor<T>(...) in place of PersistentQueue.WaitFor(...) in any of the examples below.

Note: BinaryFormatter was removed from the default serializer. See https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide.

Example

Queue on one thread, consume on another; retry some exceptions.

Note this is one queue being shared between two sessions. You should not open two queue instances for one storage location at once.

IPersistentQueuequeue=newPersistentQueue("queue_a");vart1=newThread(()=>{while(HaveWork()){using(varsession=queue.OpenSession()){session.Enqueue(NextWorkItem());session.Flush();}}});vart2=newThread(()=>{while(true){using(varsession=queue.OpenSession()){vardata=session.Dequeue();if(data==null){Thread.Sleep(100);continue;}try{MaybeDoWork(data)
session.Flush();}catch(RetryException){continue;}catch{session.Flush();}}}});t1.Start();t2.Start();

Example

Batch up a load of work and have another thread work through it.

IPersistentQueuequeue=newPersistentQueue("batchQueue");varworker=newThread(()=>{using(varsession=queue.OpenSession()){byte[]data;while((data=session.Dequeue())!=null){MaybeDoWork(data)
session.Flush();}}});using(varsession=queue.OpenSession()){foreach(variteminLoadsOfStuff()){session.Enqueue(item);}session.Flush();}worker.IsBackground=true;// anything not complete when we close will be left on the queue for next time.worker.Start();

Transactions

Each session is a transaction. Any Enqueues or Dequeues will be rolled back when the session is disposed unless you call session.Flush(). Data will only be visible between threads once it has been flushed. Each flush incurs a performance penalty. By default, each flush is persisted to disk before continuing. You can get more speed at a safety cost by setting queue.ParanoidFlushing = false;

Data loss and transaction truncation

By default, DiskQueue will silently discard transaction blocks that have been truncated; it will throw an InvalidOperationException when transaction block markers are overwritten (this happens if more than one process is using the queue by mistake. It can also happen with some kinds of disk corruption). If you construct your queue with throwOnConflict: false, all recoverable transaction errors will be silently truncated. This should only be used when uptime is more important than data consistency.

using (var queue = new PersistentQueue(path, Constants._32Megabytes, throwOnConflict: false)) {
. . .
}

Global default settings

Each instance of a PersistentQueue has it's own settings for flush levels and corruption behaviour. You can set these individually after creating an instance, or globally with PersistentQueue.DefaultSettings. Default settings are applied to all queue instances in the same process created after the setting is changed.

For example, if performance is more important than crash safety:

PersistentQueue.DefaultSettings.ParanoidFlushing=false;PersistentQueue.DefaultSettings.TrimTransactionLogOnDispose=false;

Or if up-time is more important than detecting corruption early (often the case for embedded systems):

PersistentQueue.DefaultSettings.AllowTruncatedEntries=true;PersistentQueue.DefaultSettings.ParanoidFlushing=true;

Internal Logging

Some internal warnings and non-critical errors are logged through PersistentQueue.Log. This defaults to System.Console.WriteLine, but can be replace with any Action<string>

Removing or resetting queues

Queues create a directory and set of files for storage. You can remove all files for a queue with the HardDelete method. If you give true as the reset parameter, the directory will be written again.

This WILL delete ANY AND ALL files inside the queue directory. You should not call this method in normal use. If you start a queue with the same path as an existing directory, this method will delete the entire directory, not just the queue files.

varsubject=newPersistentQueue("queue_a");subject.HardDelete(true);// wipe any existing data and start again

Multi-Process Usage

Each IPersistentQueue gives exclusive access to the storage until it is disposed. There is a static helper method PersistentQueue.WaitFor("path", TimeSpan...) which will wait to gain access until other processes release the lock or the timeout expires. If each process uses the lock for a short time and wait long enough, they can share a storage location.

E.g.

...
void AddToQueue(byte[]data){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){session.Enqueue(data);session.Flush();}}byte[]ReadQueue(){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){vardata=session.Dequeue();session.Flush();returndata;}}
...

Cross-process Locking

DiskQueue tries very hard to make sure the lock files are managed correctly. You can use this as an inter-process lock if required. Simply open a session to acquire the lock, and dispose of the session to release it.

If you need the transaction semantics of sessions across multiple processes, try a more robust solution like https://github.com/i-e-b/SevenDigital.Messaging

Trim self-contained deployments and executables

The default serialiser for typed queues is NOT supported under "trimmed self-contained" projects.

You can either use the raw byte-based interfaces and do your own serialisation, or create and use a custom ISerializationStrategy<T> for your types.

See

About

A robust, thread-safe, and multi-process persistent queue library for dotnet

Topics

Resources

Stars

128 stars

Watchers

9 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

DiskQueue

A robust, thread-safe, and multi-process persistent queue.

Based very heavily on http://ayende.com/blog/3479/rhino-queues-storage-disk

Requirements and Environment

  • Works on dotnet standard 2.0 platforms
  • Requires access to filesystem storage
  • Only partial support for "Trim Self-Contained" deployments and executables (see below)

The file system is used to hold locks, so any bug in your file system may cause issues with DiskQueue -- although it tries to work around them.

Thanks to

These kind folks have helped in the development of DiskQueue

Basic Usage

  • PersistentQueue.WaitFor(...) is the main entry point. This will attempt to gain an exclusive lock on the given storage location. On first use, a directory will be created with the required files inside it.
  • This queue object can be shared among threads. Each thread should call OpenSession() to get its own session object.
  • Both IPersistentQueues and IPersistentQueueSessions should be wrapped in using() clauses, or otherwise disposed of properly. Failure to do this will result in lock contention -- you will get errors that the queue is still in use.

There is also a generic-typed PersistentQueue<T>(...); which will handle the serialisation and deserialization of elements in the queue, as long at the type is decorated with [Serializable]. You can also inject your own ISerializationStrategy<T> into your PersistentQueueSession<T> if you wish to have more granular control over Serialization/Deserialization, or if you wish to use your own serializer (e.g Json.NET).

Use new PersistentQueue<T>(...) in place of new PersistentQueue(...) or PersistentQueue.WaitFor<T>(...) in place of PersistentQueue.WaitFor(...) in any of the examples below.

Note: BinaryFormatter was removed from the default serializer. See https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide.

Example

Queue on one thread, consume on another; retry some exceptions.

Note this is one queue being shared between two sessions. You should not open two queue instances for one storage location at once.

IPersistentQueuequeue=newPersistentQueue("queue_a");vart1=newThread(()=>{while(HaveWork()){using(varsession=queue.OpenSession()){session.Enqueue(NextWorkItem());session.Flush();}}});vart2=newThread(()=>{while(true){using(varsession=queue.OpenSession()){vardata=session.Dequeue();if(data==null){Thread.Sleep(100);continue;}try{MaybeDoWork(data)
session.Flush();}catch(RetryException){continue;}catch{session.Flush();}}}});t1.Start();t2.Start();

Example

Batch up a load of work and have another thread work through it.

IPersistentQueuequeue=newPersistentQueue("batchQueue");varworker=newThread(()=>{using(varsession=queue.OpenSession()){byte[]data;while((data=session.Dequeue())!=null){MaybeDoWork(data)
session.Flush();}}});using(varsession=queue.OpenSession()){foreach(variteminLoadsOfStuff()){session.Enqueue(item);}session.Flush();}worker.IsBackground=true;// anything not complete when we close will be left on the queue for next time.worker.Start();

Transactions

Each session is a transaction. Any Enqueues or Dequeues will be rolled back when the session is disposed unless you call session.Flush(). Data will only be visible between threads once it has been flushed. Each flush incurs a performance penalty. By default, each flush is persisted to disk before continuing. You can get more speed at a safety cost by setting queue.ParanoidFlushing = false;

Data loss and transaction truncation

By default, DiskQueue will silently discard transaction blocks that have been truncated; it will throw an InvalidOperationException when transaction block markers are overwritten (this happens if more than one process is using the queue by mistake. It can also happen with some kinds of disk corruption). If you construct your queue with throwOnConflict: false, all recoverable transaction errors will be silently truncated. This should only be used when uptime is more important than data consistency.

using (var queue = new PersistentQueue(path, Constants._32Megabytes, throwOnConflict: false)) {
. . .
}

Global default settings

Each instance of a PersistentQueue has it's own settings for flush levels and corruption behaviour. You can set these individually after creating an instance, or globally with PersistentQueue.DefaultSettings. Default settings are applied to all queue instances in the same process created after the setting is changed.

For example, if performance is more important than crash safety:

PersistentQueue.DefaultSettings.ParanoidFlushing=false;PersistentQueue.DefaultSettings.TrimTransactionLogOnDispose=false;

Or if up-time is more important than detecting corruption early (often the case for embedded systems):

PersistentQueue.DefaultSettings.AllowTruncatedEntries=true;PersistentQueue.DefaultSettings.ParanoidFlushing=true;

Internal Logging

Some internal warnings and non-critical errors are logged through PersistentQueue.Log. This defaults to System.Console.WriteLine, but can be replace with any Action<string>

Removing or resetting queues

Queues create a directory and set of files for storage. You can remove all files for a queue with the HardDelete method. If you give true as the reset parameter, the directory will be written again.

This WILL delete ANY AND ALL files inside the queue directory. You should not call this method in normal use. If you start a queue with the same path as an existing directory, this method will delete the entire directory, not just the queue files.

varsubject=newPersistentQueue("queue_a");subject.HardDelete(true);// wipe any existing data and start again

Multi-Process Usage

Each IPersistentQueue gives exclusive access to the storage until it is disposed. There is a static helper method PersistentQueue.WaitFor("path", TimeSpan...) which will wait to gain access until other processes release the lock or the timeout expires. If each process uses the lock for a short time and wait long enough, they can share a storage location.

E.g.

...
void AddToQueue(byte[]data){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){session.Enqueue(data);session.Flush();}}byte[]ReadQueue(){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){vardata=session.Dequeue();session.Flush();returndata;}}
...

Cross-process Locking

DiskQueue tries very hard to make sure the lock files are managed correctly. You can use this as an inter-process lock if required. Simply open a session to acquire the lock, and dispose of the session to release it.

If you need the transaction semantics of sessions across multiple processes, try a more robust solution like https://github.com/i-e-b/SevenDigital.Messaging

Trim self-contained deployments and executables

The default serialiser for typed queues is NOT supported under "trimmed self-contained" projects.

You can either use the raw byte-based interfaces and do your own serialisation, or create and use a custom ISerializationStrategy<T> for your types.

See

About

A robust, thread-safe, and multi-process persistent queue library for dotnet

Topics

Resources

Stars

128 stars

Watchers

9 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

DiskQueue

A robust, thread-safe, and multi-process persistent queue.

Based very heavily on http://ayende.com/blog/3479/rhino-queues-storage-disk

Requirements and Environment

  • Works on dotnet standard 2.0 platforms
  • Requires access to filesystem storage
  • Only partial support for "Trim Self-Contained" deployments and executables (see below)

The file system is used to hold locks, so any bug in your file system may cause issues with DiskQueue -- although it tries to work around them.

Thanks to

These kind folks have helped in the development of DiskQueue

Basic Usage

  • PersistentQueue.WaitFor(...) is the main entry point. This will attempt to gain an exclusive lock on the given storage location. On first use, a directory will be created with the required files inside it.
  • This queue object can be shared among threads. Each thread should call OpenSession() to get its own session object.
  • Both IPersistentQueues and IPersistentQueueSessions should be wrapped in using() clauses, or otherwise disposed of properly. Failure to do this will result in lock contention -- you will get errors that the queue is still in use.

There is also a generic-typed PersistentQueue<T>(...); which will handle the serialisation and deserialization of elements in the queue, as long at the type is decorated with [Serializable]. You can also inject your own ISerializationStrategy<T> into your PersistentQueueSession<T> if you wish to have more granular control over Serialization/Deserialization, or if you wish to use your own serializer (e.g Json.NET).

Use new PersistentQueue<T>(...) in place of new PersistentQueue(...) or PersistentQueue.WaitFor<T>(...) in place of PersistentQueue.WaitFor(...) in any of the examples below.

Note: BinaryFormatter was removed from the default serializer. See https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide.

Example

Queue on one thread, consume on another; retry some exceptions.

Note this is one queue being shared between two sessions. You should not open two queue instances for one storage location at once.

IPersistentQueuequeue=newPersistentQueue("queue_a");vart1=newThread(()=>{while(HaveWork()){using(varsession=queue.OpenSession()){session.Enqueue(NextWorkItem());session.Flush();}}});vart2=newThread(()=>{while(true){using(varsession=queue.OpenSession()){vardata=session.Dequeue();if(data==null){Thread.Sleep(100);continue;}try{MaybeDoWork(data)
session.Flush();}catch(RetryException){continue;}catch{session.Flush();}}}});t1.Start();t2.Start();

Example

Batch up a load of work and have another thread work through it.

IPersistentQueuequeue=newPersistentQueue("batchQueue");varworker=newThread(()=>{using(varsession=queue.OpenSession()){byte[]data;while((data=session.Dequeue())!=null){MaybeDoWork(data)
session.Flush();}}});using(varsession=queue.OpenSession()){foreach(variteminLoadsOfStuff()){session.Enqueue(item);}session.Flush();}worker.IsBackground=true;// anything not complete when we close will be left on the queue for next time.worker.Start();

Transactions

Each session is a transaction. Any Enqueues or Dequeues will be rolled back when the session is disposed unless you call session.Flush(). Data will only be visible between threads once it has been flushed. Each flush incurs a performance penalty. By default, each flush is persisted to disk before continuing. You can get more speed at a safety cost by setting queue.ParanoidFlushing = false;

Data loss and transaction truncation

By default, DiskQueue will silently discard transaction blocks that have been truncated; it will throw an InvalidOperationException when transaction block markers are overwritten (this happens if more than one process is using the queue by mistake. It can also happen with some kinds of disk corruption). If you construct your queue with throwOnConflict: false, all recoverable transaction errors will be silently truncated. This should only be used when uptime is more important than data consistency.

using (var queue = new PersistentQueue(path, Constants._32Megabytes, throwOnConflict: false)) {
. . .
}

Global default settings

Each instance of a PersistentQueue has it's own settings for flush levels and corruption behaviour. You can set these individually after creating an instance, or globally with PersistentQueue.DefaultSettings. Default settings are applied to all queue instances in the same process created after the setting is changed.

For example, if performance is more important than crash safety:

PersistentQueue.DefaultSettings.ParanoidFlushing=false;PersistentQueue.DefaultSettings.TrimTransactionLogOnDispose=false;

Or if up-time is more important than detecting corruption early (often the case for embedded systems):

PersistentQueue.DefaultSettings.AllowTruncatedEntries=true;PersistentQueue.DefaultSettings.ParanoidFlushing=true;

Internal Logging

Some internal warnings and non-critical errors are logged through PersistentQueue.Log. This defaults to System.Console.WriteLine, but can be replace with any Action<string>

Removing or resetting queues

Queues create a directory and set of files for storage. You can remove all files for a queue with the HardDelete method. If you give true as the reset parameter, the directory will be written again.

This WILL delete ANY AND ALL files inside the queue directory. You should not call this method in normal use. If you start a queue with the same path as an existing directory, this method will delete the entire directory, not just the queue files.

varsubject=newPersistentQueue("queue_a");subject.HardDelete(true);// wipe any existing data and start again

Multi-Process Usage

Each IPersistentQueue gives exclusive access to the storage until it is disposed. There is a static helper method PersistentQueue.WaitFor("path", TimeSpan...) which will wait to gain access until other processes release the lock or the timeout expires. If each process uses the lock for a short time and wait long enough, they can share a storage location.

E.g.

...
void AddToQueue(byte[]data){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){session.Enqueue(data);session.Flush();}}byte[]ReadQueue(){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){vardata=session.Dequeue();session.Flush();returndata;}}
...

Cross-process Locking

DiskQueue tries very hard to make sure the lock files are managed correctly. You can use this as an inter-process lock if required. Simply open a session to acquire the lock, and dispose of the session to release it.

If you need the transaction semantics of sessions across multiple processes, try a more robust solution like https://github.com/i-e-b/SevenDigital.Messaging

Trim self-contained deployments and executables

The default serialiser for typed queues is NOT supported under "trimmed self-contained" projects.

You can either use the raw byte-based interfaces and do your own serialisation, or create and use a custom ISerializationStrategy<T> for your types.

See

About

A robust, thread-safe, and multi-process persistent queue library for dotnet

Topics

Resources

Stars

128 stars

Watchers

9 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

DiskQueue

A robust, thread-safe, and multi-process persistent queue.

Based very heavily on http://ayende.com/blog/3479/rhino-queues-storage-disk

Requirements and Environment

  • Works on dotnet standard 2.0 platforms
  • Requires access to filesystem storage
  • Only partial support for "Trim Self-Contained" deployments and executables (see below)

The file system is used to hold locks, so any bug in your file system may cause issues with DiskQueue -- although it tries to work around them.

Thanks to

These kind folks have helped in the development of DiskQueue

Basic Usage

  • PersistentQueue.WaitFor(...) is the main entry point. This will attempt to gain an exclusive lock on the given storage location. On first use, a directory will be created with the required files inside it.
  • This queue object can be shared among threads. Each thread should call OpenSession() to get its own session object.
  • Both IPersistentQueues and IPersistentQueueSessions should be wrapped in using() clauses, or otherwise disposed of properly. Failure to do this will result in lock contention -- you will get errors that the queue is still in use.

There is also a generic-typed PersistentQueue<T>(...); which will handle the serialisation and deserialization of elements in the queue, as long at the type is decorated with [Serializable]. You can also inject your own ISerializationStrategy<T> into your PersistentQueueSession<T> if you wish to have more granular control over Serialization/Deserialization, or if you wish to use your own serializer (e.g Json.NET).

Use new PersistentQueue<T>(...) in place of new PersistentQueue(...) or PersistentQueue.WaitFor<T>(...) in place of PersistentQueue.WaitFor(...) in any of the examples below.

Note: BinaryFormatter was removed from the default serializer. See https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide.

Example

Queue on one thread, consume on another; retry some exceptions.

Note this is one queue being shared between two sessions. You should not open two queue instances for one storage location at once.

IPersistentQueuequeue=newPersistentQueue("queue_a");vart1=newThread(()=>{while(HaveWork()){using(varsession=queue.OpenSession()){session.Enqueue(NextWorkItem());session.Flush();}}});vart2=newThread(()=>{while(true){using(varsession=queue.OpenSession()){vardata=session.Dequeue();if(data==null){Thread.Sleep(100);continue;}try{MaybeDoWork(data)
session.Flush();}catch(RetryException){continue;}catch{session.Flush();}}}});t1.Start();t2.Start();

Example

Batch up a load of work and have another thread work through it.

IPersistentQueuequeue=newPersistentQueue("batchQueue");varworker=newThread(()=>{using(varsession=queue.OpenSession()){byte[]data;while((data=session.Dequeue())!=null){MaybeDoWork(data)
session.Flush();}}});using(varsession=queue.OpenSession()){foreach(variteminLoadsOfStuff()){session.Enqueue(item);}session.Flush();}worker.IsBackground=true;// anything not complete when we close will be left on the queue for next time.worker.Start();

Transactions

Each session is a transaction. Any Enqueues or Dequeues will be rolled back when the session is disposed unless you call session.Flush(). Data will only be visible between threads once it has been flushed. Each flush incurs a performance penalty. By default, each flush is persisted to disk before continuing. You can get more speed at a safety cost by setting queue.ParanoidFlushing = false;

Data loss and transaction truncation

By default, DiskQueue will silently discard transaction blocks that have been truncated; it will throw an InvalidOperationException when transaction block markers are overwritten (this happens if more than one process is using the queue by mistake. It can also happen with some kinds of disk corruption). If you construct your queue with throwOnConflict: false, all recoverable transaction errors will be silently truncated. This should only be used when uptime is more important than data consistency.

using (var queue = new PersistentQueue(path, Constants._32Megabytes, throwOnConflict: false)) {
. . .
}

Global default settings

Each instance of a PersistentQueue has it's own settings for flush levels and corruption behaviour. You can set these individually after creating an instance, or globally with PersistentQueue.DefaultSettings. Default settings are applied to all queue instances in the same process created after the setting is changed.

For example, if performance is more important than crash safety:

PersistentQueue.DefaultSettings.ParanoidFlushing=false;PersistentQueue.DefaultSettings.TrimTransactionLogOnDispose=false;

Or if up-time is more important than detecting corruption early (often the case for embedded systems):

PersistentQueue.DefaultSettings.AllowTruncatedEntries=true;PersistentQueue.DefaultSettings.ParanoidFlushing=true;

Internal Logging

Some internal warnings and non-critical errors are logged through PersistentQueue.Log. This defaults to System.Console.WriteLine, but can be replace with any Action<string>

Removing or resetting queues

Queues create a directory and set of files for storage. You can remove all files for a queue with the HardDelete method. If you give true as the reset parameter, the directory will be written again.

This WILL delete ANY AND ALL files inside the queue directory. You should not call this method in normal use. If you start a queue with the same path as an existing directory, this method will delete the entire directory, not just the queue files.

varsubject=newPersistentQueue("queue_a");subject.HardDelete(true);// wipe any existing data and start again

Multi-Process Usage

Each IPersistentQueue gives exclusive access to the storage until it is disposed. There is a static helper method PersistentQueue.WaitFor("path", TimeSpan...) which will wait to gain access until other processes release the lock or the timeout expires. If each process uses the lock for a short time and wait long enough, they can share a storage location.

E.g.

...
void AddToQueue(byte[]data){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){session.Enqueue(data);session.Flush();}}byte[]ReadQueue(){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){vardata=session.Dequeue();session.Flush();returndata;}}
...

Cross-process Locking

DiskQueue tries very hard to make sure the lock files are managed correctly. You can use this as an inter-process lock if required. Simply open a session to acquire the lock, and dispose of the session to release it.

If you need the transaction semantics of sessions across multiple processes, try a more robust solution like https://github.com/i-e-b/SevenDigital.Messaging

Trim self-contained deployments and executables

The default serialiser for typed queues is NOT supported under "trimmed self-contained" projects.

You can either use the raw byte-based interfaces and do your own serialisation, or create and use a custom ISerializationStrategy<T> for your types.

See

About

A robust, thread-safe, and multi-process persistent queue library for dotnet

Topics

Resources

Stars

128 stars

Watchers

9 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

DiskQueue

A robust, thread-safe, and multi-process persistent queue.

Based very heavily on http://ayende.com/blog/3479/rhino-queues-storage-disk

Requirements and Environment

  • Works on dotnet standard 2.0 platforms
  • Requires access to filesystem storage
  • Only partial support for "Trim Self-Contained" deployments and executables (see below)

The file system is used to hold locks, so any bug in your file system may cause issues with DiskQueue -- although it tries to work around them.

Thanks to

These kind folks have helped in the development of DiskQueue

Basic Usage

  • PersistentQueue.WaitFor(...) is the main entry point. This will attempt to gain an exclusive lock on the given storage location. On first use, a directory will be created with the required files inside it.
  • This queue object can be shared among threads. Each thread should call OpenSession() to get its own session object.
  • Both IPersistentQueues and IPersistentQueueSessions should be wrapped in using() clauses, or otherwise disposed of properly. Failure to do this will result in lock contention -- you will get errors that the queue is still in use.

There is also a generic-typed PersistentQueue<T>(...); which will handle the serialisation and deserialization of elements in the queue, as long at the type is decorated with [Serializable]. You can also inject your own ISerializationStrategy<T> into your PersistentQueueSession<T> if you wish to have more granular control over Serialization/Deserialization, or if you wish to use your own serializer (e.g Json.NET).

Use new PersistentQueue<T>(...) in place of new PersistentQueue(...) or PersistentQueue.WaitFor<T>(...) in place of PersistentQueue.WaitFor(...) in any of the examples below.

Note: BinaryFormatter was removed from the default serializer. See https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide.

Example

Queue on one thread, consume on another; retry some exceptions.

Note this is one queue being shared between two sessions. You should not open two queue instances for one storage location at once.

IPersistentQueuequeue=newPersistentQueue("queue_a");vart1=newThread(()=>{while(HaveWork()){using(varsession=queue.OpenSession()){session.Enqueue(NextWorkItem());session.Flush();}}});vart2=newThread(()=>{while(true){using(varsession=queue.OpenSession()){vardata=session.Dequeue();if(data==null){Thread.Sleep(100);continue;}try{MaybeDoWork(data)
session.Flush();}catch(RetryException){continue;}catch{session.Flush();}}}});t1.Start();t2.Start();

Example

Batch up a load of work and have another thread work through it.

IPersistentQueuequeue=newPersistentQueue("batchQueue");varworker=newThread(()=>{using(varsession=queue.OpenSession()){byte[]data;while((data=session.Dequeue())!=null){MaybeDoWork(data)
session.Flush();}}});using(varsession=queue.OpenSession()){foreach(variteminLoadsOfStuff()){session.Enqueue(item);}session.Flush();}worker.IsBackground=true;// anything not complete when we close will be left on the queue for next time.worker.Start();

Transactions

Each session is a transaction. Any Enqueues or Dequeues will be rolled back when the session is disposed unless you call session.Flush(). Data will only be visible between threads once it has been flushed. Each flush incurs a performance penalty. By default, each flush is persisted to disk before continuing. You can get more speed at a safety cost by setting queue.ParanoidFlushing = false;

Data loss and transaction truncation

By default, DiskQueue will silently discard transaction blocks that have been truncated; it will throw an InvalidOperationException when transaction block markers are overwritten (this happens if more than one process is using the queue by mistake. It can also happen with some kinds of disk corruption). If you construct your queue with throwOnConflict: false, all recoverable transaction errors will be silently truncated. This should only be used when uptime is more important than data consistency.

using (var queue = new PersistentQueue(path, Constants._32Megabytes, throwOnConflict: false)) {
. . .
}

Global default settings

Each instance of a PersistentQueue has it's own settings for flush levels and corruption behaviour. You can set these individually after creating an instance, or globally with PersistentQueue.DefaultSettings. Default settings are applied to all queue instances in the same process created after the setting is changed.

For example, if performance is more important than crash safety:

PersistentQueue.DefaultSettings.ParanoidFlushing=false;PersistentQueue.DefaultSettings.TrimTransactionLogOnDispose=false;

Or if up-time is more important than detecting corruption early (often the case for embedded systems):

PersistentQueue.DefaultSettings.AllowTruncatedEntries=true;PersistentQueue.DefaultSettings.ParanoidFlushing=true;

Internal Logging

Some internal warnings and non-critical errors are logged through PersistentQueue.Log. This defaults to System.Console.WriteLine, but can be replace with any Action<string>

Removing or resetting queues

Queues create a directory and set of files for storage. You can remove all files for a queue with the HardDelete method. If you give true as the reset parameter, the directory will be written again.

This WILL delete ANY AND ALL files inside the queue directory. You should not call this method in normal use. If you start a queue with the same path as an existing directory, this method will delete the entire directory, not just the queue files.

varsubject=newPersistentQueue("queue_a");subject.HardDelete(true);// wipe any existing data and start again

Multi-Process Usage

Each IPersistentQueue gives exclusive access to the storage until it is disposed. There is a static helper method PersistentQueue.WaitFor("path", TimeSpan...) which will wait to gain access until other processes release the lock or the timeout expires. If each process uses the lock for a short time and wait long enough, they can share a storage location.

E.g.

...
void AddToQueue(byte[]data){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){session.Enqueue(data);session.Flush();}}byte[]ReadQueue(){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){vardata=session.Dequeue();session.Flush();returndata;}}
...

Cross-process Locking

DiskQueue tries very hard to make sure the lock files are managed correctly. You can use this as an inter-process lock if required. Simply open a session to acquire the lock, and dispose of the session to release it.

If you need the transaction semantics of sessions across multiple processes, try a more robust solution like https://github.com/i-e-b/SevenDigital.Messaging

Trim self-contained deployments and executables

The default serialiser for typed queues is NOT supported under "trimmed self-contained" projects.

You can either use the raw byte-based interfaces and do your own serialisation, or create and use a custom ISerializationStrategy<T> for your types.

See

About

A robust, thread-safe, and multi-process persistent queue library for dotnet

Topics

Resources

Stars

128 stars

Watchers

9 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

DiskQueue

A robust, thread-safe, and multi-process persistent queue.

Based very heavily on http://ayende.com/blog/3479/rhino-queues-storage-disk

Requirements and Environment

  • Works on dotnet standard 2.0 platforms
  • Requires access to filesystem storage
  • Only partial support for "Trim Self-Contained" deployments and executables (see below)

The file system is used to hold locks, so any bug in your file system may cause issues with DiskQueue -- although it tries to work around them.

Thanks to

These kind folks have helped in the development of DiskQueue

Basic Usage

  • PersistentQueue.WaitFor(...) is the main entry point. This will attempt to gain an exclusive lock on the given storage location. On first use, a directory will be created with the required files inside it.
  • This queue object can be shared among threads. Each thread should call OpenSession() to get its own session object.
  • Both IPersistentQueues and IPersistentQueueSessions should be wrapped in using() clauses, or otherwise disposed of properly. Failure to do this will result in lock contention -- you will get errors that the queue is still in use.

There is also a generic-typed PersistentQueue<T>(...); which will handle the serialisation and deserialization of elements in the queue, as long at the type is decorated with [Serializable]. You can also inject your own ISerializationStrategy<T> into your PersistentQueueSession<T> if you wish to have more granular control over Serialization/Deserialization, or if you wish to use your own serializer (e.g Json.NET).

Use new PersistentQueue<T>(...) in place of new PersistentQueue(...) or PersistentQueue.WaitFor<T>(...) in place of PersistentQueue.WaitFor(...) in any of the examples below.

Note: BinaryFormatter was removed from the default serializer. See https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide.

Example

Queue on one thread, consume on another; retry some exceptions.

Note this is one queue being shared between two sessions. You should not open two queue instances for one storage location at once.

IPersistentQueuequeue=newPersistentQueue("queue_a");vart1=newThread(()=>{while(HaveWork()){using(varsession=queue.OpenSession()){session.Enqueue(NextWorkItem());session.Flush();}}});vart2=newThread(()=>{while(true){using(varsession=queue.OpenSession()){vardata=session.Dequeue();if(data==null){Thread.Sleep(100);continue;}try{MaybeDoWork(data)
session.Flush();}catch(RetryException){continue;}catch{session.Flush();}}}});t1.Start();t2.Start();

Example

Batch up a load of work and have another thread work through it.

IPersistentQueuequeue=newPersistentQueue("batchQueue");varworker=newThread(()=>{using(varsession=queue.OpenSession()){byte[]data;while((data=session.Dequeue())!=null){MaybeDoWork(data)
session.Flush();}}});using(varsession=queue.OpenSession()){foreach(variteminLoadsOfStuff()){session.Enqueue(item);}session.Flush();}worker.IsBackground=true;// anything not complete when we close will be left on the queue for next time.worker.Start();

Transactions

Each session is a transaction. Any Enqueues or Dequeues will be rolled back when the session is disposed unless you call session.Flush(). Data will only be visible between threads once it has been flushed. Each flush incurs a performance penalty. By default, each flush is persisted to disk before continuing. You can get more speed at a safety cost by setting queue.ParanoidFlushing = false;

Data loss and transaction truncation

By default, DiskQueue will silently discard transaction blocks that have been truncated; it will throw an InvalidOperationException when transaction block markers are overwritten (this happens if more than one process is using the queue by mistake. It can also happen with some kinds of disk corruption). If you construct your queue with throwOnConflict: false, all recoverable transaction errors will be silently truncated. This should only be used when uptime is more important than data consistency.

using (var queue = new PersistentQueue(path, Constants._32Megabytes, throwOnConflict: false)) {
. . .
}

Global default settings

Each instance of a PersistentQueue has it's own settings for flush levels and corruption behaviour. You can set these individually after creating an instance, or globally with PersistentQueue.DefaultSettings. Default settings are applied to all queue instances in the same process created after the setting is changed.

For example, if performance is more important than crash safety:

PersistentQueue.DefaultSettings.ParanoidFlushing=false;PersistentQueue.DefaultSettings.TrimTransactionLogOnDispose=false;

Or if up-time is more important than detecting corruption early (often the case for embedded systems):

PersistentQueue.DefaultSettings.AllowTruncatedEntries=true;PersistentQueue.DefaultSettings.ParanoidFlushing=true;

Internal Logging

Some internal warnings and non-critical errors are logged through PersistentQueue.Log. This defaults to System.Console.WriteLine, but can be replace with any Action<string>

Removing or resetting queues

Queues create a directory and set of files for storage. You can remove all files for a queue with the HardDelete method. If you give true as the reset parameter, the directory will be written again.

This WILL delete ANY AND ALL files inside the queue directory. You should not call this method in normal use. If you start a queue with the same path as an existing directory, this method will delete the entire directory, not just the queue files.

varsubject=newPersistentQueue("queue_a");subject.HardDelete(true);// wipe any existing data and start again

Multi-Process Usage

Each IPersistentQueue gives exclusive access to the storage until it is disposed. There is a static helper method PersistentQueue.WaitFor("path", TimeSpan...) which will wait to gain access until other processes release the lock or the timeout expires. If each process uses the lock for a short time and wait long enough, they can share a storage location.

E.g.

...
void AddToQueue(byte[]data){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){session.Enqueue(data);session.Flush();}}byte[]ReadQueue(){Thread.Sleep(150);using(varqueue=PersistentQueue.WaitFor(SharedStorage,TimeSpan.FromSeconds(30)))using(varsession=queue.OpenSession()){vardata=session.Dequeue();session.Flush();returndata;}}
...

Cross-process Locking

DiskQueue tries very hard to make sure the lock files are managed correctly. You can use this as an inter-process lock if required. Simply open a session to acquire the lock, and dispose of the session to release it.

If you need the transaction semantics of sessions across multiple processes, try a more robust solution like https://github.com/i-e-b/SevenDigital.Messaging

Trim self-contained deployments and executables

The default serialiser for typed queues is NOT supported under "trimmed self-contained" projects.

You can either use the raw byte-based interfaces and do your own serialisation, or create and use a custom ISerializationStrategy<T> for your types.

See

About

A robust, thread-safe, and multi-process persistent queue library for dotnet

Topics

Resources

Stars

128 stars

Watchers

9 watching

Forks

Releases

Packages

Used by

Contributors

Languages