Class: SecurityBox::Pool

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

Overview

Bounded pool of :oneshot Sandboxes.

What it gives you:

  • a hard cap on concurrent sandbox evaluations (size);
  • cheap checkout/checkin of pre-built Sandbox objects;
  • basic metrics (evals, total/average wall time).

What it does NOT give you (measured in stage 4, docs/plan/stages/stage_4.md): parallelism. invoke holds the GVL for the whole guest lifetime, so concurrent evals on threads serialize. For real parallelism use RactorPool, which runs workers on Ractors with a shared Engine+Module.

Usage:

pool = SecurityBox::Pool.new(:lean, size: 4)
pool.eval("1 + 1")                # => Result (checks out a sandbox)
pool.checkout { |sandbox| ... }   # manual checkout block
pool.shutdown

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(profile = nil, size: 4, **options) ⇒ Pool

Returns a new instance of Pool.

Raises:

  • (ArgumentError)


24
25
26
27
28
29
30
31
32
33
34
# File 'lib/security_box/pool.rb', line 24

def initialize(profile = nil, size: 4, **options)
  @size = Integer(size)
  raise ArgumentError, "size must be >= 1" if @size < 1

  @config = resolve_config(profile, options)
  @sandboxes = Queue.new
  @mutex = Mutex.new
  @created = 0
  @closed = false
  @metrics = { evals: 0, total_ms: 0.0 }
end

Instance Attribute Details

#size ⇒ Object (readonly)

Returns the value of attribute size.



22
23
24
# File 'lib/security_box/pool.rb', line 22

def size
  @size
end

Instance Method Details

#checkout ⇒ Object

Checks a sandbox out of the pool for the duration of the block. Blocks while all size sandboxes are busy.

Raises:



38
39
40
41
42
43
44
45
46
47
# File 'lib/security_box/pool.rb', line 38

def checkout
  raise PoolClosed, "pool is closed" if closed?

  sandbox = acquire
  begin
    yield sandbox
  ensure
    release(sandbox)
  end
end

#closed? ⇒ Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/security_box/pool.rb', line 69

def closed?
  @mutex.synchronize { @closed }
end

#eval(code, **overrides) ⇒ Object

Checks a sandbox out and runs code on it. Per-call overrides are forwarded to Sandbox#eval.



51
52
53
54
55
56
# File 'lib/security_box/pool.rb', line 51

def eval(code, **overrides)
  t0 = monotonic_ms
  result = checkout { |sandbox| sandbox.eval(code, **overrides) }
  record(monotonic_ms - t0)
  result
end

#metrics ⇒ Object

Snapshot of the counters: created:, evals:, total_ms:, avg_ms:.



60
61
62
63
64
65
66
67
# File 'lib/security_box/pool.rb', line 60

def metrics
  @mutex.synchronize do
    evals = @metrics[:evals]
    { size: @size, created: @created, evals: evals,
      total_ms: @metrics[:total_ms].round(2),
      avg_ms: evals.zero? ? 0.0 : (@metrics[:total_ms] / evals).round(2) }
  end
end

#shutdown ⇒ Object

Closes the pool. Sandboxes currently checked out keep working until released; threads blocked on an empty pool keep waiting (callers must ensure they do not shut down while checkouts are still being awaited).



76
77
78
# File 'lib/security_box/pool.rb', line 76

def shutdown
  @mutex.synchronize { @closed = true }
end