legionio 1.5.13 → 1.5.14
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 +4 -4
- data/CHANGELOG.md +11 -0
- data/lib/legion/extensions.rb +10 -0
- data/lib/legion/service.rb +87 -14
- data/lib/legion/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 213403cc9241316f3d19263526f688166a256ecb38dd2fb45b4f782c9067e9e0
|
|
4
|
+
data.tar.gz: e93b0000542370d69c6b46323e217d3cace04429cf061dd2ba748040a560175a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c272f3d6e719737682c354a8313d5f3b93797ac44da5fea75f7e0473ffd27a6075eb2ba67665f2e6dfa55e37f3c2c986ae66ae3dcc4cdfbaa78c0fb2517a7fe5
|
|
7
|
+
data.tar.gz: fb92bae4cf4fddf1a9a47b88eb611d13bb4dfe7162315a560a9c9b75fd5250f41cf30a80cef166e88488794ef16a7c0d462e6018de4e460409e89d35ad33f41a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.5.14] - 2026-03-25
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Shutdown no longer hangs when network is unreachable — all component shutdowns wrapped in bounded timeouts via `shutdown_component` helper (#30)
|
|
7
|
+
- Reload path also wrapped with same timeout guards to prevent hangs during network-triggered reload (#30)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Network watchdog: background `Concurrent::TimerTask` monitors transport/data/cache connectivity, pauses actors after sustained failures, triggers `Legion.reload` when network restores (#30)
|
|
11
|
+
- `Legion::Extensions.pause_actors` suspends all `Every` timer tasks without destroying instances (#30)
|
|
12
|
+
- Watchdog is feature-flagged via `network.watchdog.enabled` (default: false), configurable threshold and interval (#30)
|
|
13
|
+
|
|
3
14
|
## [1.5.13] - 2026-03-25
|
|
4
15
|
|
|
5
16
|
### Fixed
|
data/lib/legion/extensions.rb
CHANGED
|
@@ -96,6 +96,16 @@ module Legion
|
|
|
96
96
|
Legion::Logging.info "Successfully shut down all actors (#{(Time.now - shutdown_start).round(1)}s)"
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
def pause_actors
|
|
100
|
+
@running_instances&.each do |inst|
|
|
101
|
+
timer = inst.instance_variable_get(:@timer)
|
|
102
|
+
timer&.shutdown if timer.respond_to?(:shutdown)
|
|
103
|
+
rescue StandardError => e
|
|
104
|
+
Legion::Logging.error "pause_actors: #{e.class}: #{e.message}" if defined?(Legion::Logging)
|
|
105
|
+
end
|
|
106
|
+
Legion::Logging.warn 'All actors paused' if defined?(Legion::Logging)
|
|
107
|
+
end
|
|
108
|
+
|
|
99
109
|
def load_extensions
|
|
100
110
|
@extensions ||= []
|
|
101
111
|
@loaded_extensions ||= []
|
data/lib/legion/service.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'timeout'
|
|
3
4
|
require_relative 'readiness'
|
|
4
5
|
require_relative 'process_role'
|
|
5
6
|
|
|
@@ -130,6 +131,7 @@ module Legion
|
|
|
130
131
|
api_settings = Legion::Settings[:api] || {}
|
|
131
132
|
@api_enabled = api && api_settings.fetch(:enabled, true)
|
|
132
133
|
setup_api if @api_enabled
|
|
134
|
+
setup_network_watchdog
|
|
133
135
|
Legion::Settings[:client][:ready] = true
|
|
134
136
|
Legion::Events.emit('service.ready')
|
|
135
137
|
end
|
|
@@ -465,13 +467,14 @@ module Legion
|
|
|
465
467
|
Legion::Settings[:client][:shutting_down] = true
|
|
466
468
|
Legion::Events.emit('service.shutting_down')
|
|
467
469
|
|
|
470
|
+
shutdown_network_watchdog
|
|
468
471
|
shutdown_audit_archiver
|
|
469
472
|
shutdown_api
|
|
470
473
|
|
|
471
474
|
Legion::Metrics.reset! if defined?(Legion::Metrics)
|
|
472
475
|
|
|
473
476
|
if defined?(Legion::Gaia) && Legion::Gaia.respond_to?(:started?) && Legion::Gaia.started?
|
|
474
|
-
Legion::Gaia.shutdown
|
|
477
|
+
shutdown_component('Gaia') { Legion::Gaia.shutdown }
|
|
475
478
|
Legion::Readiness.mark_not_ready(:gaia)
|
|
476
479
|
end
|
|
477
480
|
|
|
@@ -480,32 +483,33 @@ module Legion
|
|
|
480
483
|
@cluster_leader = nil
|
|
481
484
|
end
|
|
482
485
|
|
|
483
|
-
Legion::
|
|
486
|
+
ext_timeout = Legion::Settings.dig(:extensions, :shutdown_timeout) || 15
|
|
487
|
+
shutdown_component('Extensions', timeout: ext_timeout) { Legion::Extensions.shutdown }
|
|
484
488
|
Legion::Readiness.mark_not_ready(:extensions)
|
|
485
489
|
|
|
486
490
|
if Legion::Settings[:llm]&.dig(:connected)
|
|
487
|
-
Legion::LLM.shutdown
|
|
491
|
+
shutdown_component('LLM') { Legion::LLM.shutdown }
|
|
488
492
|
Legion::Readiness.mark_not_ready(:llm)
|
|
489
493
|
end
|
|
490
494
|
|
|
491
495
|
if defined?(Legion::Rbac) && Legion::Settings[:rbac]&.dig(:connected)
|
|
492
|
-
Legion::Rbac.shutdown
|
|
496
|
+
shutdown_component('Rbac') { Legion::Rbac.shutdown }
|
|
493
497
|
Legion::Readiness.mark_not_ready(:rbac)
|
|
494
498
|
end
|
|
495
499
|
|
|
496
|
-
Legion::Data.shutdown if Legion::Settings[:data][:connected]
|
|
500
|
+
shutdown_component('Data') { Legion::Data.shutdown } if Legion::Settings[:data][:connected]
|
|
497
501
|
Legion::Readiness.mark_not_ready(:data)
|
|
498
502
|
|
|
499
503
|
Legion::Leader.reset! if defined?(Legion::Leader)
|
|
500
504
|
|
|
501
|
-
Legion::Cache.shutdown
|
|
505
|
+
shutdown_component('Cache') { Legion::Cache.shutdown }
|
|
502
506
|
Legion::Readiness.mark_not_ready(:cache)
|
|
503
507
|
|
|
504
|
-
Legion::Transport::Connection.shutdown
|
|
508
|
+
shutdown_component('Transport') { Legion::Transport::Connection.shutdown }
|
|
505
509
|
Legion::Readiness.mark_not_ready(:transport)
|
|
506
510
|
|
|
507
511
|
shutdown_mtls_rotation
|
|
508
|
-
Legion::Crypt.shutdown
|
|
512
|
+
shutdown_component('Crypt') { Legion::Crypt.shutdown }
|
|
509
513
|
Legion::Readiness.mark_not_ready(:crypt)
|
|
510
514
|
|
|
511
515
|
Legion::Settings[:client][:ready] = false
|
|
@@ -513,29 +517,34 @@ module Legion
|
|
|
513
517
|
end
|
|
514
518
|
|
|
515
519
|
def reload
|
|
520
|
+
return if @reloading
|
|
521
|
+
|
|
522
|
+
@reloading = true
|
|
516
523
|
Legion::Logging.info 'Legion::Service.reload was called'
|
|
517
524
|
Legion::Settings[:client][:ready] = false
|
|
518
525
|
|
|
526
|
+
shutdown_network_watchdog
|
|
519
527
|
shutdown_api
|
|
520
528
|
|
|
521
529
|
if defined?(Legion::Gaia) && Legion::Gaia.respond_to?(:started?) && Legion::Gaia.started?
|
|
522
|
-
Legion::Gaia.shutdown
|
|
530
|
+
shutdown_component('Gaia') { Legion::Gaia.shutdown }
|
|
523
531
|
Legion::Readiness.mark_not_ready(:gaia)
|
|
524
532
|
end
|
|
525
533
|
|
|
526
|
-
Legion::
|
|
534
|
+
ext_timeout = Legion::Settings.dig(:extensions, :shutdown_timeout) || 15
|
|
535
|
+
shutdown_component('Extensions', timeout: ext_timeout) { Legion::Extensions.shutdown }
|
|
527
536
|
Legion::Readiness.mark_not_ready(:extensions)
|
|
528
537
|
|
|
529
|
-
Legion::Data.shutdown
|
|
538
|
+
shutdown_component('Data') { Legion::Data.shutdown }
|
|
530
539
|
Legion::Readiness.mark_not_ready(:data)
|
|
531
540
|
|
|
532
|
-
Legion::Cache.shutdown
|
|
541
|
+
shutdown_component('Cache') { Legion::Cache.shutdown }
|
|
533
542
|
Legion::Readiness.mark_not_ready(:cache)
|
|
534
543
|
|
|
535
|
-
Legion::Transport::Connection.shutdown
|
|
544
|
+
shutdown_component('Transport') { Legion::Transport::Connection.shutdown }
|
|
536
545
|
Legion::Readiness.mark_not_ready(:transport)
|
|
537
546
|
|
|
538
|
-
Legion::Crypt.shutdown
|
|
547
|
+
shutdown_component('Crypt') { Legion::Crypt.shutdown }
|
|
539
548
|
Legion::Readiness.mark_not_ready(:crypt)
|
|
540
549
|
|
|
541
550
|
Legion::Readiness.wait_until_not_ready(:transport, :data, :cache, :crypt)
|
|
@@ -569,9 +578,12 @@ module Legion
|
|
|
569
578
|
|
|
570
579
|
Legion::Crypt.cs
|
|
571
580
|
setup_api if @api_enabled
|
|
581
|
+
setup_network_watchdog
|
|
572
582
|
Legion::Settings[:client][:ready] = true
|
|
573
583
|
Legion::Events.emit('service.ready')
|
|
574
584
|
Legion::Logging.info 'Legion has been reloaded'
|
|
585
|
+
ensure
|
|
586
|
+
@reloading = false
|
|
575
587
|
end
|
|
576
588
|
|
|
577
589
|
def load_extensions
|
|
@@ -630,6 +642,67 @@ module Legion
|
|
|
630
642
|
nil
|
|
631
643
|
end
|
|
632
644
|
|
|
645
|
+
def shutdown_component(name, timeout: 5, &)
|
|
646
|
+
Timeout.timeout(timeout, &)
|
|
647
|
+
rescue Timeout::Error
|
|
648
|
+
Legion::Logging.warn "#{name} shutdown timed out after #{timeout}s, forcing"
|
|
649
|
+
rescue StandardError => e
|
|
650
|
+
Legion::Logging.warn "#{name} shutdown error: #{e.class}: #{e.message}"
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
def setup_network_watchdog
|
|
654
|
+
return unless Legion::Settings.dig(:network, :watchdog, :enabled)
|
|
655
|
+
|
|
656
|
+
@consecutive_failures = Concurrent::AtomicFixnum.new(0)
|
|
657
|
+
threshold = Legion::Settings.dig(:network, :watchdog, :failure_threshold) || 5
|
|
658
|
+
interval = Legion::Settings.dig(:network, :watchdog, :check_interval) || 15
|
|
659
|
+
|
|
660
|
+
@network_watchdog = Concurrent::TimerTask.new(execution_interval: interval) do
|
|
661
|
+
if network_healthy?
|
|
662
|
+
prev = @consecutive_failures.value
|
|
663
|
+
@consecutive_failures.value = 0
|
|
664
|
+
if prev >= threshold
|
|
665
|
+
Legion::Logging.info '[Watchdog] Network restored, triggering reload'
|
|
666
|
+
Thread.new { Legion.reload } unless @reloading
|
|
667
|
+
end
|
|
668
|
+
else
|
|
669
|
+
count = @consecutive_failures.increment
|
|
670
|
+
Legion::Logging.warn "[Watchdog] Network check failed (#{count}/#{threshold})"
|
|
671
|
+
if count == threshold
|
|
672
|
+
Legion::Logging.error '[Watchdog] Network failure threshold reached, pausing actors'
|
|
673
|
+
Legion::Extensions.pause_actors if Legion::Extensions.respond_to?(:pause_actors)
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
rescue StandardError => e
|
|
677
|
+
Legion::Logging.debug "[Watchdog] check error: #{e.message}"
|
|
678
|
+
end
|
|
679
|
+
@network_watchdog.execute
|
|
680
|
+
Legion::Logging.info "[Watchdog] Network watchdog started (interval=#{interval}s, threshold=#{threshold})"
|
|
681
|
+
rescue StandardError => e
|
|
682
|
+
Legion::Logging.warn "Network watchdog setup failed: #{e.message}"
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def shutdown_network_watchdog
|
|
686
|
+
@network_watchdog&.shutdown
|
|
687
|
+
@network_watchdog = nil
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def network_healthy?
|
|
691
|
+
return true if defined?(Legion::Transport::Connection) && Legion::Transport::Connection.lite_mode?
|
|
692
|
+
|
|
693
|
+
checks = []
|
|
694
|
+
checks << Legion::Transport::Connection.session_open? if Legion::Settings[:transport][:connected]
|
|
695
|
+
if Legion::Settings[:data][:connected] && defined?(Legion::Data::Connection)
|
|
696
|
+
checks << (Legion::Data::Connection.sequel&.test_connection rescue false) # rubocop:disable Style/RescueModifier
|
|
697
|
+
end
|
|
698
|
+
checks << Legion::Cache.connected? if Legion::Settings[:cache][:connected] && defined?(Legion::Cache)
|
|
699
|
+
return true if checks.empty?
|
|
700
|
+
|
|
701
|
+
checks.any?
|
|
702
|
+
rescue StandardError
|
|
703
|
+
false
|
|
704
|
+
end
|
|
705
|
+
|
|
633
706
|
private
|
|
634
707
|
|
|
635
708
|
def port_in_use?(bind, port)
|
data/lib/legion/version.rb
CHANGED