Class: RailsAiBridge::Introspector::ParallelRunner

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_ai_bridge/introspector/parallel_runner.rb

Overview

Runs multiple introspectors concurrently using a fixed thread pool.

Requires +concurrent-ruby+ (already a transitive dependency of Rails). Falls back gracefully when the gem is unavailable — callers should check ParallelRunner.available? before using this runner.

Pool size and per-future timeout are driven by Configuration (+parallel_pool_size+ and +parallel_timeout_seconds+), so hosts can tune concurrency without touching this class.

Each introspector is executed in its own future. Errors — including per-future timeouts — are captured per-introspector and returned as +{ error: message }+ hashes, matching the convention used throughout #call.

Examples:

Basic usage

results = ParallelRunner.call(introspectors, Rails.application)
results[:schema] #=> { tables: { ... } }
results[:slow]   #=> { error: "timed out after 10s" }

Class Method Summary collapse

Class Method Details

.available?Boolean

Returns +true+ when parallel execution is safe to use.

Parallel execution is considered unsafe when:

  • +concurrent-ruby+ is not loaded (+Concurrent::Future+ is undefined)
  • ActiveRecord's connection pool has only one slot (common in transactional tests or SQLite single-connection setups)

Returns:

  • (Boolean)


63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/rails_ai_bridge/introspector/parallel_runner.rb', line 63

def available?
  return false unless defined?(Concurrent::Future) == 'constant'

  if defined?(ActiveRecord::Base)
    pool_size = begin
      ActiveRecord::Base.connection_pool.size
    rescue StandardError
      nil
    end
    return false if pool_size && pool_size <= 1
  end

  true
end

.call(introspectors, app) ⇒ Hash{Symbol => Object}

Runs +introspectors+ concurrently and returns a Hash of results.

The number of threads is +min(introspectors.size, parallel_pool_size)+. Each future is given +parallel_timeout_seconds+ to complete; if it exceeds the timeout the result is +{ error: "timed out after Ns" }+.

The thread pool is always shut down in an +ensure+ block, even if an unexpected exception escapes.

Parameters:

  • introspectors (Hash{Symbol => Class})

    name → introspector class

  • app (Rails::Application)

    the host application

Returns:

  • (Hash{Symbol => Object})

    results keyed by introspector name



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/rails_ai_bridge/introspector/parallel_runner.rb', line 39

def call(introspectors, app)
  return {} if introspectors.empty?

  cfg      = RailsAiBridge.configuration
  size     = [introspectors.size, cfg.parallel_pool_size].min
  timeout  = cfg.parallel_timeout_seconds
  pool     = Concurrent::FixedThreadPool.new(size)
  futures  = schedule_futures(introspectors, app, pool)
  collect_results(futures, timeout)
ensure
  if pool
    pool.shutdown
    pool.kill unless pool.wait_for_termination(timeout || 10)
  end
end