Class: Ratomic::Pool

Inherits:
Object
  • Object
show all
Defined in:
lib/ratomic/pool.rb

Overview

A Ractor-safe ownership-transfer pool for mutable Ruby objects.

Pool follows a Rust-inspired ownership-transfer model: a pooled object has one active owner at a time. #checkout moves ownership from the pool to the caller; #checkin moves ownership back to the pool. Ruby enforces stale caller references dynamically with Ractor::MovedError.

This is ownership transfer, not borrowing. Pool never lends shared mutable references across Ractors.

Pool uses a private coordinator Ractor and caller-owned Ractor::Port reply ports. Objects are moved to callers on checkout and moved back to the pool on checkin. This is intentionally different from sharing the same mutable object between Ractors: at any instant, exactly one Ractor owns a checked-out object.

Examples:

Reuse mutable buffers safely

BUFFERS = Ratomic::Pool.new(4, 1.0) { [] }
BUFFERS.with do |buffer|
  buffer.clear
  buffer << :change
end

Instance Method Summary collapse

Constructor Details

#initialize(size = 5, timeout = 1.0) ⇒ Pool

Create a pool and seed it with size objects from the factory block.

Parameters:

  • size (Integer) (defaults to: 5)

    number of pooled objects

  • timeout (Numeric, nil) (defaults to: 1.0)

    checkout timeout in seconds, or nil to wait indefinitely

Yield Returns:

  • (Object)

    mutable object to store in the pool

Raises:

  • (ArgumentError)

    if size is not positive

  • (LocalJumpError)

    if no factory block is given



34
35
36
37
38
39
40
41
42
43
# File 'lib/ratomic/pool.rb', line 34

def initialize(size = 5, timeout = 1.0)
  raise ArgumentError, "pool size must be positive" if size <= 0
  raise LocalJumpError, "no block given" unless block_given?

  @timeout = timeout&.to_f
  @control = self.class.send(:new_control_ractor)
  size.times { @control.send([:checkin, yield], move: true) }
  freeze
  Ractor.make_shareable(self)
end

Instance Method Details

#checkin(object) ⇒ nil

Return an object to the pool.

This moves ownership from the caller back to the pool. The caller must not use the object after calling this method; Ruby raises Ractor::MovedError for stale references.

Parameters:

  • object (Object)

    previously checked-out pooled object

Returns:

  • (nil)


71
72
73
74
# File 'lib/ratomic/pool.rb', line 71

def checkin(object)
  @control.send([:checkin, object], move: true)
  nil
end

#checkoutObject?

Checkout one object from the pool.

The returned object has been moved from the pool to the caller. The caller owns it until it is passed to #checkin.

Returns:

  • (Object, nil)

    pooled object, or nil after timeout



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/ratomic/pool.rb', line 51

def checkout
  reply = Ractor::Port.new
  request_id = reply.object_id
  @control << [:checkout, request_id, reply]
  receive_checkout_reply(reply)
rescue Timeout::Error
  nil
ensure
  @control << [:cancel, request_id] if request_id
  reply&.close unless reply&.closed?
end

#with {|object| ... } ⇒ Object

Checkout an object, yield it, then move it back to the pool.

This is the preferred API because it guarantees checkin through an ensure block. If checkout times out, raises Ratomic::Error and does not yield.

Yield Parameters:

  • object (Object)

    checked-out pooled object

Returns:

  • (Object)

    block return value

Raises:



84
85
86
87
88
89
90
91
# File 'lib/ratomic/pool.rb', line 84

def with
  object = checkout
  raise Ratomic::Error, "pool checkout timeout" if object.nil?

  yield object
ensure
  checkin(object) unless object.nil?
end