agentic examples

The Concurrency Key

The Concurrency Key: "at most one sync per TENANT, any number of tenants at once" is the concurrency control every multi-tenant job system eventually needs - global limits are too blunt (one tenant's backlog throttles everyone) and no limits are too sharp (two syncs for the same tenant race each other's writes). SolidQueue spells it concurrency_key; here it's one Mutex-guarded registry of per-key RateLimits, and the overflow policy is EXPLICIT: block, or skip.

Scheduling & Concurrency Round 14 Rosa Gutiérrez exit 0

source on github

bundle exec ruby examples/concurrency_key.rb

a real captured run

THE CONCURRENCY KEY (at most one sync per tenant; tenants in parallel)

  six concurrent requests (3 per tenant):
    acme runs overlapping each other:    0 (must be 0)
    globex runs overlapping each other:  0 (must be 0)
    cross-tenant overlaps:               5 (must be > 0 - parallelism preserved)

  cron fires twice while acme's sync is already running:
    verdicts: [:skipped, :skipped] - skipped, not queued.

  the two postures are different PROMISES and the call site names
  which one it makes: serialized() means every request eventually
  runs, in order, alone (backfills); skip_if_running() means
  running-now is proof enough (crons - a second sync would do the
  same work twice). the registry hands out ONE limiter per key
  under a lock, because two fibers discovering tenant 'initech'
  simultaneously must agree on THE mutex, not mint rivals. global
  limits ration CAPACITY; keyed limits enforce CORRECTNESS - most
  incidents blamed on load are actually two workers holding the
  same tenant.

source

# frozen_string_literal: true

# The Concurrency Key: "at most one sync per TENANT, any number of
# tenants at once" is the concurrency control every multi-tenant job
# system eventually needs - global limits are too blunt (one tenant's
# backlog throttles everyone) and no limits are too sharp (two syncs
# for the same tenant race each other's writes). SolidQueue spells it
# concurrency_key; here it's one Mutex-guarded registry of per-key
# RateLimits, and the overflow policy is EXPLICIT: block, or skip.
#
#   bundle exec ruby examples/concurrency_key.rb
#
# Runs offline; interleavings are recorded and judged.

require class="s">"bundler/setup"
require class="s">"agentic"
require class="s">"async"

Agentic.logger.level = class="y">:fatal

# Per-key serialization: limit(key) is a RateLimit.new(1), created
# once per key under a lock (two fibers discovering a new tenant at
# the same instant must agree on THE limiter, not each mint their own)
class ConcurrencyKeys
  def initialize
    @limits = {}
    @lock = Mutex.new
  end

  def limit(key)
    @lock.synchronize { @limits[key] ||= Agentic:class="y">:RateLimit.new(1) }
  end

  # SolidQueue's two overflow postures, made explicit at the call site
  def serialized(key, &work) = limit(key).acquire(&work)

  def skip_if_running(key, &work)
    limit(key).try_acquire(&work) ? class="y">:ran : class="y">:skipped
  end
end

KEYS = ConcurrencyKeys.new
TIMELINE = []
T0 = Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC)

def sync_tenant(tenant, run_id)
  orchestrator = Agentic:class="y">:PlanOrchestrator.new
  task = Agentic:class="y">:Task.new(description: class="s">"sync:#{tenant}:#{run_id}", agent_spec: {class="s">"name" => class="s">"s", class="s">"instructions" => class="s">"w"})
  orchestrator.add_task(task, agent: ->(_t) {
    TIMELINE << [tenant, run_id, class="y">:start, Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) - T0]
    sleep(0.04)
    TIMELINE << [tenant, run_id, class="y">:end, Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) - T0]
    class="y">:ok
  })
  orchestrator.execute_plan
end

puts class="s">"THE CONCURRENCY KEY (at most one sync per tenant; tenants in parallel)"
puts

# Six sync requests: two tenants, three requests each, all at once
Sync do
  requests = [[class="s">"acme", 1], [class="s">"acme", 2], [class="s">"globex", 1], [class="s">"acme", 3], [class="s">"globex", 2], [class="s">"globex", 3]]
  requests.map { |tenant, run_id|
    Async do
      KEYS.serialized(class="s">"sync/#{tenant}") { sync_tenant(tenant, run_id) }
    end
  }.each(&class="y">:wait)
end

# Judge the interleaving: per tenant, runs must not overlap; across
# tenants, they MUST have overlapped (or the key was too blunt)
overlaps = ->(events) {
  spans = events.group_by { |t, r, _, _| [t, r] }.values.map { |es|
    [es.find { |e| e[2] == class="y">:start }[3], es.find { |e| e[2] == class="y">:end }[3]]
  }
  spans.combination(2).count { |(s1, e1), (s2, e2)| s1 < e2 && s2 < e1 }
}
acme = TIMELINE.select { |t, _, _, _| t == class="s">"acme" }
globex = TIMELINE.select { |t, _, _, _| t == class="s">"globex" }
cross = overlaps.call(TIMELINE)

puts class="s">"  six concurrent requests (3 per tenant):"
puts format(class="s">"    acme runs overlapping each other:    %d (must be 0)", overlaps.call(acme))
puts format(class="s">"    globex runs overlapping each other:  %d (must be 0)", overlaps.call(globex))
puts format(class="s">"    cross-tenant overlaps:               %d (must be > 0 - parallelism preserved)", cross - overlaps.call(acme) - overlaps.call(globex))
puts

# The other posture: a cron fires while a sync is already running
verdicts = nil
Sync do
  holder = Async { KEYS.serialized(class="s">"sync/acme") { sleep(0.03) } }
  sleep(0.005)
  verdicts = 2.times.map { KEYS.skip_if_running(class="s">"sync/acme") { sync_tenant(class="s">"acme", 99) } }
  holder.wait
end
puts class="s">"  cron fires twice while acme's sync is already running:"
puts class="s">"    verdicts: #{verdicts.inspect} - skipped, not queued."
puts
puts class="s">"  the two postures are different PROMISES and the call site names"
puts class="s">"  which one it makes: serialized() means every request eventually"
puts class="s">"  runs, in order, alone (backfills); skip_if_running() means"
puts class="s">"  running-now is proof enough (crons - a second sync would do the"
puts class="s">"  same work twice). the registry hands out ONE limiter per key"
puts class="s">"  under a lock, because two fibers discovering tenant 'initech'"
puts class="s">"  simultaneously must agree on THE mutex, not mint rivals. global"
puts class="s">"  limits ration CAPACITY; keyed limits enforce CORRECTNESS - most"
puts class="s">"  incidents blamed on load are actually two workers holding the"
puts class="s">"  same tenant."