Quick Start¶
require "ractor_queue"
# Create a bounded queue (capacity rounds up to next power of two, minimum 4096)
q = RactorQueue.new(capacity: 256)
Non-Blocking Operations¶
try_push and try_pop never block. They return immediately with a success indicator or the value:
q.try_push(42) # => true (enqueued)
q.try_push(:hello) # => true
q.try_pop # => 42
q.try_pop # => :hello
q.try_pop # => RactorQueue::EMPTY (queue is empty)
# nil is an unambiguous payload — EMPTY is a distinct sentinel
q.try_push(nil) # => true
q.try_pop # => nil (the nil we pushed)
q.try_pop # => RactorQueue::EMPTY (queue is now empty)
Check for empty with identity comparison (equal?, not ==):
v = q.try_pop
unless v.equal?(RactorQueue::EMPTY)
process(v) # v may be nil — that's a real payload
end
Blocking Operations¶
push and pop spin-wait until space is available (push) or an item is available (pop):
q.push(99) # => self (blocks until space; chainable)
q.pop # => 99 (blocks until an item is present)
Blocking operations use an exponential backoff: first 16 retries call Thread.pass, then subsequent retries call sleep(0.0001). This keeps latency low under light contention while preventing scheduler thrashing under heavy load.
Timeouts¶
Both push and pop accept an optional timeout: (in seconds). If the deadline expires, RactorQueue::TimeoutError is raised:
q.push(1, timeout: 0.5) # raises TimeoutError after 500 ms if still full
q.pop(timeout: 0.5) # raises TimeoutError after 500 ms if still empty
# timeout: 0 means "try exactly once, then raise"
q.pop(timeout: 0) # raises TimeoutError immediately if empty
State Queries¶
q.size # => Integer (approximate element count)
q.empty? # => true / false (approximate)
q.full? # => true / false (approximate)
q.capacity # => Integer (exact allocated capacity)
State queries are approximate under concurrency — by the time you act on the result, it may have changed.
Shareable Validation¶
With validate_shareable: true, the queue raises NotShareableError at push time for any non-shareable object, catching Ractor isolation mistakes before they propagate:
safe_q = RactorQueue.new(capacity: 64, validate_shareable: true)
safe_q.push(42) # ok — Integer is always shareable
safe_q.push("hello".freeze) # ok — frozen String is shareable
safe_q.push(:symbol) # ok — Symbols are always shareable
safe_q.push([1, 2, 3]) # raises RactorQueue::NotShareableError
safe_q.push({ key: "val" }) # raises RactorQueue::NotShareableError
Using with Ractors¶
The queue is always Ractor.shareable? — pass it directly to Ractor constructors:
q = RactorQueue.new(capacity: 1024)
producer = Ractor.new(q) do |queue|
100.times { |i| queue.push(i * i) }
queue.push(:done)
end
consumer = Ractor.new(q) do |queue|
results = []
loop do
v = queue.pop
break if v == :done
results << v
end
results
end
producer.value
puts consumer.value.inspect # [0, 1, 4, 9, 16, ...]
See Ractor Patterns for 1P1C, worker pool, and queue pool examples.