legionio 1.7.6 → 1.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30ac0f16cae1d42de41e7ed922e2fd75d96093017f7d562e2596b9c20bea99b6
4
- data.tar.gz: 23fe67ca7f6171ea6e0ed13d50b382e78e1a7df4402cf1fe8174fcfea7d92ceb
3
+ metadata.gz: 6741515579e2c24f64b301136e023fb6ddd73b27f41779caace7df02cd15f667
4
+ data.tar.gz: c90f66af48bd6705b58c6c31344dd0a9486b8519cc48b98f3b1965d2571527db
5
5
  SHA512:
6
- metadata.gz: ada4b04b1a274b66d60ab44ae9abe4b8ea998576d7d4a5b679ab3099ad91615924fe4646f671b544820d0b1db6e2bbcc0ceea84f2e5ed762e3f1321500017d99
7
- data.tar.gz: b1a5d03f53e03039321f54529b0eae7abdc82462a20ee0dd0c1cb88927b15f606a07472b69cdeacc488217fc941d62074f83e567c09d3d26f21c821328309f14
6
+ metadata.gz: 5409680125318327c866c9f59dde8ce2b7b5a33cea5082bf2d654c763184c25e4658a24ef43d4b292f12546b314692da1b5d71ca6e25ca55e6095ea376d66d5f
7
+ data.tar.gz: 68a9358035224f600dfc673272d2cc82ecdfc7236a7225a2974721ad61b081571dbb190c77d25c8442bc3ff13c7cea44274b8331fd2942213a714167c56017c4
data/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.7.8] - 2026-04-01
6
+
7
+ ### Added
8
+ - `Legion::API::Settings` module with registered defaults via `merge_settings('api', ...)`, matching the pattern used by all other LegionIO gems
9
+ - Puma `persistent_timeout` (20s) and `first_data_timeout` (30s) now configurable via `Settings[:api][:puma]`
10
+
11
+ ### Changed
12
+ - Removed all inline `||` and `.fetch(..., default)` fallbacks for API settings in `service.rb` and `check_command.rb` — defaults now guaranteed by `merge_settings`
13
+
14
+ ## [1.7.7] - 2026-04-01
15
+
16
+ ### Changed
17
+ - Integrated legion-logging 1.4.3 Helper refactor: all log output now uses structured segment tagging, colored exception output, and thread-local task context
18
+ - Slimmed `Extensions::Helpers::Logger` to thin override; `derive_component_type`, `lex_gem_name`, `gem_spec_for_lex`, `log_lex_name` now live in legion-logging gem
19
+ - Added `handle_runner_exception` for runner-specific exception handling (TaskLog publish + HandledTask raise)
20
+ - Added `Legion::Context.with_task_context` and `.current_task_context` for thread-local task propagation
21
+ - Wrapped all 5 dispatch paths (Runner.run, Subscription#dispatch_runner, Base#runner, Ingress local/remote) with context propagation
22
+ - Migrated 13 `log.log_exception` call sites to `handle_exception` across actors, core, transport, and task helpers
23
+
5
24
  ## [1.7.6] - 2026-04-01
6
25
 
7
26
  ### Changed
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra/base'
4
+
5
+ module Legion
6
+ class API < Sinatra::Base
7
+ module Settings
8
+ def self.default
9
+ {
10
+ enabled: true,
11
+ port: 4567,
12
+ bind: '0.0.0.0',
13
+ puma: puma_defaults,
14
+ bind_retries: 3,
15
+ bind_retry_wait: 2,
16
+ tls: tls_defaults
17
+ }
18
+ end
19
+
20
+ def self.puma_defaults
21
+ {
22
+ min_threads: 10,
23
+ max_threads: 16,
24
+ persistent_timeout: 20,
25
+ first_data_timeout: 30
26
+ }
27
+ end
28
+
29
+ def self.tls_defaults
30
+ {
31
+ enabled: false
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ begin
39
+ Legion::Settings.merge_settings('api', Legion::API::Settings.default) if Legion.const_defined?('Settings', false)
40
+ rescue StandardError => e
41
+ if Legion.const_defined?('Logging', false) && Legion::Logging.respond_to?(:fatal)
42
+ Legion::Logging.fatal(e.message)
43
+ Legion::Logging.fatal(e.backtrace)
44
+ else
45
+ puts e.message
46
+ puts e.backtrace
47
+ end
48
+ end
@@ -366,8 +366,11 @@ module Legion
366
366
  stream do |out|
367
367
  full_text = +''
368
368
  pipeline_response = executor.call_stream do |chunk|
369
- full_text << chunk
370
- out << "event: text-delta\ndata: #{Legion::JSON.dump({ delta: chunk })}\n\n"
369
+ text = chunk.respond_to?(:content) ? chunk.content.to_s : chunk.to_s
370
+ next if text.empty?
371
+
372
+ full_text << text
373
+ out << "event: text-delta\ndata: #{Legion::JSON.dump({ delta: text })}\n\n"
371
374
  end
372
375
 
373
376
  if pipeline_response.tools.is_a?(Array) && !pipeline_response.tools.empty?
data/lib/legion/api.rb CHANGED
@@ -4,6 +4,7 @@ require 'sinatra/base'
4
4
  require 'legion/json'
5
5
  require_relative 'events'
6
6
  require_relative 'readiness'
7
+ require_relative 'api/default_settings'
7
8
 
8
9
  require_relative 'api/middleware/auth'
9
10
  require_relative 'api/middleware/body_limit'
@@ -260,8 +260,10 @@ module Legion
260
260
 
261
261
  def check_api(_options)
262
262
  require 'legion/api'
263
- port = (Legion::Settings[:api] || {})[:port] || 4567
264
- bind = (Legion::Settings[:api] || {})[:bind] || '127.0.0.1'
263
+ api_settings = Legion::Settings[:api]
264
+ port = api_settings[:port]
265
+ configured_bind = api_settings[:bind]
266
+ bind = %w[127.0.0.1 localhost ::1].include?(configured_bind) ? configured_bind : '127.0.0.1'
265
267
 
266
268
  Legion::API.set :port, port
267
269
  Legion::API.set :bind, bind
@@ -51,6 +51,24 @@ module Legion
51
51
  Legion::Logging.debug "[Context] session cleared: #{ctx&.session_id}" if defined?(Legion::Logging)
52
52
  Thread.current[:legion_session_context] = nil
53
53
  end
54
+
55
+ def current_task_context
56
+ Thread.current[:legion_context]
57
+ end
58
+
59
+ def with_task_context(message)
60
+ previous = Thread.current[:legion_context]
61
+ Thread.current[:legion_context] = {
62
+ task_id: message[:task_id],
63
+ conversation_id: message[:conversation_id],
64
+ chain_id: message[:chain_id],
65
+ function: message[:function],
66
+ runner_class: message[:runner_class]
67
+ }.compact
68
+ yield
69
+ ensure
70
+ Thread.current[:legion_context] = previous
71
+ end
54
72
  end
55
73
  end
56
74
  end
@@ -16,9 +16,12 @@ module Legion
16
16
  define_dsl_accessor :remote_invocable, default: true
17
17
 
18
18
  def runner
19
- Legion::Runner.run(runner_class: runner_class, function: function, check_subtask: check_subtask?, generate_task: generate_task?)
19
+ with_log_context(function) do
20
+ Legion::Runner.run(runner_class: runner_class, function: function,
21
+ check_subtask: check_subtask?, generate_task: generate_task?)
22
+ end
20
23
  rescue StandardError => e
21
- Legion::Logging.log_exception(e, component_type: :actor)
24
+ handle_exception(e)
22
25
  end
23
26
 
24
27
  def manual
@@ -31,7 +34,7 @@ module Legion
31
34
  klass.send(func, **args)
32
35
  end
33
36
  rescue StandardError => e
34
- Legion::Logging.log_exception(e, component_type: :actor)
37
+ handle_exception(e)
35
38
  end
36
39
 
37
40
  def function
@@ -24,7 +24,8 @@ module Legion
24
24
  log.debug "[Every] tick: #{self.class}" if defined?(log)
25
25
  skip_or_run { use_runner? ? runner : manual }
26
26
  rescue StandardError => e
27
- log.log_exception(e, payload_summary: "[Every] tick failed for #{self.class}", component_type: :actor) if defined?(log)
27
+ log.error "[Every] tick failed for #{self.class}: #{e.class}: #{e.message}" if defined?(log)
28
+ handle_exception(e) if defined?(log)
28
29
  ensure
29
30
  @executing.make_false
30
31
  end
@@ -35,7 +36,7 @@ module Legion
35
36
 
36
37
  @timer.execute
37
38
  rescue StandardError => e
38
- log.log_exception(e, component_type: :actor)
39
+ handle_exception(e)
39
40
  end
40
41
 
41
42
  def run_now?
@@ -52,7 +53,7 @@ module Legion
52
53
 
53
54
  @timer.shutdown
54
55
  rescue StandardError => e
55
- log.log_exception(e, component_type: :actor)
56
+ handle_exception(e)
56
57
  end
57
58
  end
58
59
  end
@@ -13,7 +13,7 @@ module Legion
13
13
  @loop = true
14
14
  async.run
15
15
  rescue StandardError => e
16
- Legion::Logging.log_exception(e, component_type: :actor)
16
+ handle_exception(e)
17
17
  end
18
18
 
19
19
  def run
@@ -27,7 +27,7 @@ check_subtask: check_subtask? }}"
27
27
  begin
28
28
  skip_or_run { poll_cycle }
29
29
  rescue StandardError => e
30
- Legion::Logging.log_exception(e, level: :fatal, component_type: :actor)
30
+ handle_exception(e, level: :fatal)
31
31
  ensure
32
32
  @executing.make_false
33
33
  end
@@ -37,7 +37,7 @@ check_subtask: check_subtask? }}"
37
37
  end
38
38
  @timer.execute
39
39
  rescue StandardError => e
40
- Legion::Logging.log_exception(e, component_type: :actor)
40
+ handle_exception(e)
41
41
  end
42
42
 
43
43
  def poll_cycle
@@ -69,7 +69,7 @@ check_subtask: check_subtask? }}"
69
69
  log.debug("#{self.class} result: #{results}")
70
70
  results
71
71
  rescue StandardError => e
72
- Legion::Logging.log_exception(e, level: :fatal, component_type: :actor)
72
+ handle_exception(e, level: :fatal)
73
73
  end
74
74
 
75
75
  def cache_name
@@ -92,7 +92,7 @@ check_subtask: check_subtask? }}"
92
92
  Legion::Logging.debug 'Cancelling Legion Poller'
93
93
  @timer.shutdown
94
94
  rescue StandardError => e
95
- Legion::Logging.log_exception(e, component_type: :actor)
95
+ handle_exception(e)
96
96
  end
97
97
  end
98
98
  end
@@ -25,7 +25,7 @@ module Legion
25
25
  @queue = queue.new
26
26
  @queue.channel.prefetch(prefetch) if defined? prefetch
27
27
  rescue StandardError => e
28
- log.log_exception(e, level: :fatal, component_type: :actor)
28
+ handle_exception(e, level: :fatal)
29
29
  end
30
30
 
31
31
  def create_queue
@@ -83,12 +83,12 @@ module Legion
83
83
 
84
84
  cancel if Legion::Settings[:client][:shutting_down]
85
85
  rescue StandardError => e
86
- log.log_exception(e, payload_summary: "[Subscription] message processing failed: #{lex_name}/#{fn}", component_type: :actor)
86
+ handle_exception(e, lex: lex_name, fn: fn, routing_key: delivery_info.routing_key)
87
87
  @queue.reject(delivery_info.delivery_tag) if manual_ack
88
88
  end
89
89
  log.info "[Subscription] prepared: #{lex_name}/#{runner_name}"
90
90
  rescue StandardError => e
91
- log.log_exception(e, level: :fatal, payload_summary: 'Subscription#prepare failed', component_type: :actor)
91
+ handle_exception(e, level: :fatal)
92
92
  end
93
93
 
94
94
  def activate
@@ -175,7 +175,7 @@ module Legion
175
175
 
176
176
  cancel if Legion::Settings[:client][:shutting_down]
177
177
  rescue StandardError => e
178
- log.log_exception(e, payload_summary: "[Subscription] message processing failed: #{lex_name}/#{fn}", component_type: :actor)
178
+ handle_exception(e)
179
179
  log.warn "[Subscription] nacking message for #{lex_name}/#{fn}"
180
180
  @queue.reject(delivery_info.delivery_tag) if manual_ack
181
181
  end
@@ -207,11 +207,14 @@ module Legion
207
207
 
208
208
  def dispatch_runner(message, runner_cls, function, check_subtask, generate_task)
209
209
  run_block = lambda {
210
- Legion::Runner.run(**message,
211
- runner_class: runner_cls,
212
- function: function,
213
- check_subtask: check_subtask,
214
- generate_task: generate_task)
210
+ ctx = message.merge(runner_class: runner_cls.to_s, function: function.to_s)
211
+ Legion::Context.with_task_context(ctx) do
212
+ Legion::Runner.run(**message,
213
+ runner_class: runner_cls,
214
+ function: function,
215
+ check_subtask: check_subtask,
216
+ generate_task: generate_task)
217
+ end
215
218
  }
216
219
 
217
220
  if defined?(Legion::Telemetry::OpenInference)
@@ -235,7 +235,7 @@ module Legion
235
235
  lex_class.const_set(:Data, Module.new { extend Legion::Extensions::Data })
236
236
  end
237
237
  rescue StandardError => e
238
- log.log_exception(e, payload_summary: "[Core] auto_generate_data failed for #{name}", component_type: :builder)
238
+ handle_exception(e, lex: lex_name, operation: 'auto_generate_data')
239
239
  end
240
240
  end
241
241
  end
@@ -9,78 +9,19 @@ module Legion
9
9
  include Legion::Extensions::Helpers::Base
10
10
  include Legion::Logging::Helper
11
11
 
12
- def handle_exception(exception, task_id: nil, **opts)
13
- spec = gem_spec_for_lex
14
- log.log_exception(exception,
15
- lex: log_lex_name,
16
- component_type: derive_component_type,
17
- gem_name: lex_gem_name,
18
- lex_version: spec&.version&.to_s,
19
- gem_path: spec&.full_gem_path,
20
- source_code_uri: spec&.metadata&.[]('source_code_uri'),
21
- handled: true,
22
- payload_summary: opts.empty? ? nil : opts,
23
- task_id: task_id)
12
+ def handle_runner_exception(exception, task_id: nil, **opts) # rubocop:disable Style/ArgumentsForwarding
13
+ handle_exception(exception, task_id: task_id, **opts) # rubocop:disable Style/ArgumentsForwarding
24
14
 
25
15
  unless task_id.nil?
26
16
  Legion::Transport::Messages::TaskLog.new(
27
17
  task_id: task_id,
28
18
  runner_class: to_s,
29
- entry: {
30
- exception: true,
31
- message: exception.message,
32
- **opts
33
- }
19
+ entry: { exception: true, message: exception.message, **opts }
34
20
  ).publish
35
21
  end
36
22
 
37
23
  raise Legion::Exception::HandledTask
38
24
  end
39
-
40
- private
41
-
42
- def derive_component_type
43
- parts = respond_to?(:calling_class_array) ? calling_class_array : self.class.to_s.split('::')
44
- match = parts.find { |p| Legion::Extensions::Helpers::Base::NAMESPACE_BOUNDARIES.include?(p) }
45
- case match
46
- when 'Runners' then :runner
47
- when 'Actor', 'Actors' then :actor
48
- when 'Transport' then :transport
49
- when 'Helpers' then :helper
50
- when 'Data' then :data
51
- else :unknown
52
- end
53
- rescue StandardError
54
- :unknown
55
- end
56
-
57
- def lex_gem_name
58
- base_name = log_lex_name
59
- return nil unless base_name
60
-
61
- "lex-#{base_name}"
62
- rescue StandardError
63
- nil
64
- end
65
-
66
- def gem_spec_for_lex
67
- name = lex_gem_name
68
- return nil unless name
69
-
70
- Gem::Specification.find_by_name(name)
71
- rescue Gem::MissingSpecError
72
- nil
73
- end
74
-
75
- def log_lex_name
76
- if respond_to?(:segments)
77
- segments.join('-')
78
- else
79
- derive_log_tag
80
- end
81
- rescue StandardError
82
- nil
83
- end
84
25
  end
85
26
  end
86
27
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base'
4
+ require_relative 'logger'
4
5
  require 'legion/transport'
5
6
  require 'legion/transport/messages/task_update'
6
7
  require 'legion/transport/messages/task_log'
@@ -10,6 +11,7 @@ module Legion
10
11
  module Helpers
11
12
  module Task
12
13
  include Legion::Extensions::Helpers::Base
14
+ include Legion::Extensions::Helpers::Logger
13
15
 
14
16
  def generate_task_log(task_id:, function:, runner_class: to_s, **payload)
15
17
  begin
@@ -19,7 +21,7 @@ module Legion
19
21
  return true if Legion::Data::Model::TaskLog.insert(task_id: task_id, function_id: function_id, entry: Legion::JSON.dump(payload))
20
22
  end
21
23
  rescue StandardError => e
22
- log.log_exception(e, level: :warn, payload_summary: 'generate_task_log failed, reverting to rmq message', component_type: :helper)
24
+ handle_exception(e, level: :warn)
23
25
  end
24
26
  Legion::Transport::Messages::TaskLog.new(task_id: task_id, runner_class: runner_class, function: function, entry: payload).publish
25
27
  end
@@ -43,7 +45,7 @@ module Legion
43
45
  end
44
46
  Legion::Transport::Messages::TaskUpdate.new(**update_hash).publish
45
47
  rescue StandardError => e
46
- log.log_exception(e, level: :fatal, component_type: :helper)
48
+ handle_exception(e, level: :fatal)
47
49
  raise e
48
50
  end
49
51
 
@@ -25,7 +25,8 @@ module Legion
25
25
  auto_generate_messages
26
26
  log.info "[Transport] built exchanges=#{@exchanges.count} queues=#{@queues.count} for #{lex_name}"
27
27
  rescue StandardError => e
28
- log.log_exception(e, payload_summary: "[Transport] build failed for #{lex_name}", component_type: :transport)
28
+ log.error "[Transport] build failed for #{lex_name}"
29
+ handle_exception(e, lex: lex_name)
29
30
  end
30
31
 
31
32
  def generate_base_modules
@@ -175,7 +176,7 @@ module Legion
175
176
  to = to.is_a?(String) ? Kernel.const_get(to).new : to.new
176
177
  to.bind(from, routing_key: routing_key)
177
178
  rescue StandardError => e
178
- log.log_exception(e, level: :fatal, payload_summary: { from: from, to: to, routing_key: routing_key }, component_type: :transport)
179
+ handle_exception(e, level: :fatal, from: from, to: to, routing_key: routing_key)
179
180
  end
180
181
 
181
182
  def e_to_q
@@ -82,17 +82,21 @@ module Legion
82
82
  if local_runner?(rc)
83
83
  Legion::Logging.debug "[Ingress] local short-circuit: #{rc}.#{fn}" if defined?(Legion::Logging)
84
84
  klass = rc.is_a?(String) ? Kernel.const_get(rc) : rc
85
- return klass.send(fn.to_sym, **message)
85
+ ctx = message.merge(runner_class: rc.to_s, function: fn.to_s)
86
+ return Legion::Context.with_task_context(ctx) { klass.send(fn.to_sym, **message) }
86
87
  end
87
88
 
88
89
  runner_block = lambda {
89
- Legion::Runner.run(
90
- runner_class: rc,
91
- function: fn,
92
- check_subtask: check_subtask,
93
- generate_task: generate_task,
94
- **message
95
- )
90
+ ctx = message.merge(runner_class: rc.to_s, function: fn.to_s)
91
+ Legion::Context.with_task_context(ctx) do
92
+ Legion::Runner.run(
93
+ runner_class: rc,
94
+ function: fn,
95
+ check_subtask: check_subtask,
96
+ generate_task: generate_task,
97
+ **message
98
+ )
99
+ end
96
100
  }
97
101
 
98
102
  if defined?(Legion::Telemetry::OpenInference)
data/lib/legion/runner.rb CHANGED
@@ -2,12 +2,13 @@
2
2
 
3
3
  require_relative 'runner/log'
4
4
  require_relative 'runner/status'
5
+ require_relative 'context'
5
6
  require 'legion/transport'
6
7
  require 'legion/transport/messages/check_subtask'
7
8
 
8
9
  module Legion
9
10
  module Runner
10
- def self.run(runner_class:, function:, task_id: nil, args: nil, check_subtask: true, generate_task: true, parent_id: nil, master_id: nil, catch_exceptions: false, **opts) # rubocop:disable Layout/LineLength, Metrics/CyclomaticComplexity, Metrics/ParameterLists, Metrics/MethodLength, Metrics/PerceivedComplexity
11
+ def self.run(runner_class:, function:, task_id: nil, args: nil, check_subtask: true, generate_task: true, parent_id: nil, master_id: nil, catch_exceptions: false, **opts) # rubocop:disable Layout/LineLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/ParameterLists, Metrics/MethodLength, Metrics/PerceivedComplexity
11
12
  started_at = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
12
13
  lex_tag = derive_lex_tag(runner_class)
13
14
  rlog = runner_logger(lex_tag)
@@ -31,24 +32,38 @@ module Legion
31
32
  # result = Fiber.new { Fiber.yield runner_class.send(function, **args) }
32
33
  raise 'No Function defined' if function.nil?
33
34
 
34
- result = runner_class.send(function, **args)
35
- rescue Legion::Exception::HandledTask => e
36
- rlog.debug "[Runner] HandledTask raised in #{runner_class}##{function}: #{e.message}"
37
- status = 'task.exception'
38
- result = { error: {} }
39
- rescue StandardError => e
40
- rlog.error "[Runner] exception in #{runner_class}##{function}: #{e.message}"
41
- status = 'task.exception'
42
- result = { success: false, status: status, error: { message: e.message, backtrace: e.backtrace } }
43
- runner_class.handle_exception(e,
44
- **opts,
45
- runner_class: runner_class,
46
- args: args,
47
- function: function,
48
- task_id: task_id,
49
- generate_task: generate_task,
50
- check_subtask: check_subtask)
51
- raise e unless catch_exceptions
35
+ result = nil
36
+ status = nil
37
+ Legion::Context.with_task_context(opts.merge(task_id: task_id, function: function, runner_class: runner_class.to_s)) do
38
+ result = if runner_class.respond_to?(:with_log_context)
39
+ runner_class.with_log_context(function) { runner_class.send(function, **args) }
40
+ else
41
+ runner_class.send(function, **args)
42
+ end
43
+ rescue Legion::Exception::HandledTask => e
44
+ rlog.debug "[Runner] HandledTask raised in #{runner_class}##{function}: #{e.message}"
45
+ status = 'task.exception'
46
+ result = { error: {} }
47
+ rescue StandardError => e
48
+ rlog.error "[Runner] exception in #{runner_class}##{function}: #{e.message}"
49
+ status = 'task.exception'
50
+ result = { success: false, status: status, error: { message: e.message, backtrace: e.backtrace } }
51
+ if runner_class.respond_to?(:handle_runner_exception)
52
+ begin
53
+ runner_class.handle_runner_exception(e,
54
+ **opts,
55
+ runner_class: runner_class,
56
+ args: args,
57
+ function: function,
58
+ task_id: task_id,
59
+ generate_task: generate_task,
60
+ check_subtask: check_subtask)
61
+ rescue Legion::Exception::HandledTask => handled
62
+ rlog.debug "[Runner] HandledTask raised while handling exception in #{runner_class}##{function}: #{handled.message}"
63
+ end
64
+ end
65
+ raise e unless catch_exceptions
66
+ end
52
67
  ensure
53
68
  status = 'task.completed' if status.nil?
54
69
  duration_ms = ((::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - started_at) * 1000).round
@@ -142,8 +142,10 @@ module Legion
142
142
  setup_metrics
143
143
  setup_task_outcome_observer
144
144
 
145
- api_settings = Legion::Settings[:api] || {}
146
- @api_enabled = api && api_settings.fetch(:enabled, true)
145
+ require 'sinatra/base'
146
+ require 'legion/api/default_settings'
147
+ api_settings = Legion::Settings[:api]
148
+ @api_enabled = api && api_settings[:enabled]
147
149
  setup_api if @api_enabled
148
150
  setup_network_watchdog
149
151
  Legion::Settings[:client][:ready] = true
@@ -264,38 +266,48 @@ module Legion
264
266
  )
265
267
  end
266
268
 
267
- def setup_api
269
+ def setup_api # rubocop:disable Metrics/MethodLength
268
270
  if @api_thread&.alive?
269
271
  Legion::Logging.warn 'API already running, skipping duplicate setup_api call'
270
272
  return
271
273
  end
272
274
 
273
275
  require 'legion/api'
274
- api_settings = Legion::Settings[:api] || {}
275
- port = api_settings[:port] || 4567
276
- bind = api_settings[:bind] || '0.0.0.0'
276
+ api_settings = Legion::Settings[:api]
277
+ port = api_settings[:port]
278
+ bind = api_settings[:bind]
277
279
 
278
280
  Legion::API.set :port, port
279
281
  Legion::API.set :bind, bind
280
282
  Legion::API.set :server, :puma
281
283
  Legion::API.set :environment, :production
282
284
 
285
+ puma_cfg = api_settings[:puma]
286
+ min_threads = puma_cfg[:min_threads]
287
+ max_threads = puma_cfg[:max_threads]
288
+ thread_spec = "#{min_threads}:#{max_threads}"
289
+ puma_timeouts = {
290
+ persistent_timeout: puma_cfg[:persistent_timeout],
291
+ first_data_timeout: puma_cfg[:first_data_timeout]
292
+ }.compact
293
+
283
294
  tls_cfg = build_api_tls_config(api_settings)
284
295
  if tls_cfg
285
296
  Legion::API.set :ssl_bind_options, tls_cfg
286
- Legion::API.set :server_settings, { quiet: true, **ssl_server_settings(tls_cfg, bind, port) }
297
+ Legion::API.set :server_settings, { quiet: true, Threads: thread_spec, **puma_timeouts,
298
+ **ssl_server_settings(tls_cfg, bind, port) }
287
299
  Legion::Logging.info "Starting Legion API (TLS) on #{bind}:#{port}"
288
300
  else
289
301
  require 'puma'
290
302
  puma_log = ::Puma::LogWriter.new(StringIO.new, StringIO.new)
291
- Legion::API.set :server_settings, { log_writer: puma_log, quiet: true }
303
+ Legion::API.set :server_settings, { log_writer: puma_log, quiet: true, Threads: thread_spec, **puma_timeouts }
292
304
  Legion::Logging.info "Starting Legion API on #{bind}:#{port}"
293
305
  end
294
306
 
295
307
  @api_thread = Thread.new do
296
308
  retries = 0
297
- max_retries = api_settings.fetch(:bind_retries, 3)
298
- retry_wait = api_settings.fetch(:bind_retry_wait, 2)
309
+ max_retries = api_settings[:bind_retries]
310
+ retry_wait = api_settings[:bind_retry_wait]
299
311
 
300
312
  begin
301
313
  raise Errno::EADDRINUSE, "port #{port} already bound" if port_in_use?(bind, port)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.7.6'
4
+ VERSION = '1.7.8'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legionio
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.6
4
+ version: 1.7.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -472,6 +472,7 @@ files:
472
472
  - lib/legion/api/codegen.rb
473
473
  - lib/legion/api/coldstart.rb
474
474
  - lib/legion/api/costs.rb
475
+ - lib/legion/api/default_settings.rb
475
476
  - lib/legion/api/events.rb
476
477
  - lib/legion/api/extensions.rb
477
478
  - lib/legion/api/gaia.rb