| Crates.io | qoxide |
| lib.rs | qoxide |
| version | 1.3.0 |
| created_at | 2025-12-02 00:29:17.210259+00 |
| updated_at | 2025-12-05 04:59:46.614761+00 |
| description | A lightweight local job queue backed by SQLite |
| homepage | |
| repository | https://github.com/Haizzz/qoxide |
| max_upload_size | |
| id | 1960914 |
| size | 67,183 |
A lightweight local job queue built in Rust, backed by SQLite.
Vec<u8>)Add to your Cargo.toml:
[dependencies]
qoxide = "1.0"
use qoxide::QoxideQueue;
// Create an in-memory queue
let mut queue = QoxideQueue::new();
// Add a message
let id = queue.add(b"my job data".to_vec())?;
// Reserve the next pending message (atomic)
let (id, payload) = queue.reserve()?;
// Process the job...
// Mark as complete on success
queue.complete(id)?;
// Or mark as failed to return to pending state
queue.fail(id)?;
use qoxide::QoxideQueue;
// Configure with builder pattern
let mut queue = QoxideQueue::builder()
.path("./my_queue.db") // optional: file-backed persistence
.max_attempts(3) // optional: move to DLQ after 3 failed attempts
.build();
let id = queue.add(b"job".to_vec())?;
let (id, _) = queue.reserve()?;
// After 3 failed attempts, message moves to DLQ
let state = queue.fail(id)?;
// Inspect dead letters
let dead_ids = queue.dead_letters()?;
for id in &dead_ids {
let payload = queue.get(*id)?;
// Process dead letter...
}
// Requeue or remove dead letters
queue.requeue_dead_letters(&dead_ids)?;
for id in dead_ids {
queue.remove(id)?;
}
let sizes = queue.size()?;
println!("Total: {}", sizes.total);
println!("Pending: {}", sizes.pending);
println!("Reserved: {}", sizes.reserved);
println!("Completed: {}", sizes.completed);
println!("Dead: {}", sizes.dead);
| Method | Description |
|---|---|
QoxideQueue::new() |
Create in-memory queue |
QoxideQueue::builder() |
Create queue with builder pattern |
builder.path(path) |
Set file path for persistence |
builder.max_attempts(n) |
Set max attempts before DLQ |
builder.build() |
Build the queue |
add(payload) |
Add message, returns message ID |
reserve() |
Atomically reserve next pending message |
complete(id) |
Mark message as completed |
fail(id) |
Fail message (requeue or move to DLQ) |
get(id) |
Get payload by message ID |
remove(id) |
Remove a message permanently |
size() |
Get queue size breakdown by state |
dead_letters() |
Get IDs of all dead letter messages |
requeue_dead_letters(&[ids]) |
Move dead letters back to pending |
PENDING → RESERVED → COMPLETED
↓
(fail)
↓
┌───────┴───────┐
↓ ↓
PENDING DEAD
(retry) (max retries)
Messages are processed in FIFO order. reserve() always returns the oldest pending message.
The reserve() operation is atomic - it selects and updates the message state in a single SQL statement using UPDATE ... RETURNING, preventing race conditions.
:memory:): Data is lost when the queue is droppedfail() always returns message to pendingfail() moves message to DLQ after n failed attemptsremove() to clean upRun benchmarks with:
cargo bench
Benchmarks include:
queue_add: Single message enqueuequeue_add_large_payload: 1MB payload enqueuequeue_reserve: Reserve from queues of 1k, 10k, 100k messagesqueue_interactions: Full add→reserve→fail→reserve→complete cycle# Run tests
cargo test
# Run benchmarks
cargo bench
MIT License - see LICENSE for details.