legionio 0.1.1 → 0.2.0
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 +5 -5
- data/.circleci/config.yml +98 -0
- data/.rspec +2 -0
- data/.rubocop.yml +38 -8
- data/CHANGELOG.md +7 -0
- data/Gemfile +12 -9
- data/LICENSE.txt +21 -0
- data/README.md +46 -0
- data/Rakefile +1 -1
- data/bin/console +3 -2
- data/bin/legion +9 -6
- data/bin/test +28 -1
- data/bitbucket-pipelines.yml +13 -8
- data/legion.gemspec +27 -21
- data/lib/legion.rb +14 -5
- data/lib/legion/exceptions/handled_task.rb +6 -0
- data/lib/legion/exceptions/missingargument.rb +2 -2
- data/lib/legion/extensions.rb +151 -0
- data/lib/legion/extensions/actors/base.rb +53 -0
- data/lib/legion/extensions/actors/every.rb +50 -0
- data/lib/legion/extensions/actors/loop.rb +34 -0
- data/lib/legion/extensions/actors/nothing.rb +15 -0
- data/lib/legion/extensions/actors/once.rb +42 -0
- data/lib/legion/extensions/actors/poll.rb +90 -0
- data/lib/legion/extensions/actors/subscription.rb +120 -0
- data/lib/legion/extensions/builders/actors.rb +62 -0
- data/lib/legion/extensions/builders/base.rb +38 -0
- data/lib/legion/extensions/builders/helpers.rb +26 -0
- data/lib/legion/extensions/builders/runners.rb +54 -0
- data/lib/legion/extensions/core.rb +84 -0
- data/lib/legion/extensions/helpers/base.rb +88 -0
- data/lib/legion/extensions/helpers/core.rb +20 -0
- data/lib/legion/extensions/helpers/lex.rb +22 -0
- data/lib/legion/extensions/helpers/logger.rb +48 -0
- data/lib/legion/extensions/helpers/task.rb +42 -0
- data/lib/legion/extensions/helpers/transport.rb +45 -0
- data/lib/legion/extensions/transport.rb +156 -0
- data/lib/legion/process.rb +8 -3
- data/lib/legion/runner.rb +57 -0
- data/lib/legion/runner/log.rb +12 -0
- data/lib/legion/runner/status.rb +72 -0
- data/lib/legion/service.rb +44 -27
- data/lib/legion/supervison.rb +10 -24
- data/lib/legion/version.rb +1 -1
- metadata +184 -46
- data/lib/legion/extension/loader.rb +0 -96
- data/lib/legion/runners/runner.rb +0 -58
data/lib/legion.rb
CHANGED
@@ -1,12 +1,21 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
Process.setproctitle('Legion')
|
2
|
+
require 'concurrent-ruby'
|
3
|
+
require 'securerandom'
|
4
|
+
# require 'legion/exceptions'
|
4
5
|
require 'legion/version'
|
5
6
|
require 'legion/process'
|
6
7
|
require 'legion/service'
|
8
|
+
require 'legion/extensions'
|
7
9
|
|
8
|
-
# Base Legion Module to start the world
|
9
10
|
module Legion
|
10
|
-
|
11
|
-
|
11
|
+
attr_reader :service
|
12
|
+
|
13
|
+
def self.start
|
14
|
+
@service = Legion::Service.new
|
15
|
+
Legion::Logging.info("Started Legion v#{Legion::VERSION}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.shutdown
|
19
|
+
@service.shutdown
|
20
|
+
end
|
12
21
|
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'legion/extensions/core'
|
4
|
+
require 'legion/runner'
|
5
|
+
|
6
|
+
module Legion
|
7
|
+
module Extensions
|
8
|
+
class << self
|
9
|
+
def setup
|
10
|
+
hook_extensions
|
11
|
+
end
|
12
|
+
|
13
|
+
def hook_extensions
|
14
|
+
@timer_tasks = []
|
15
|
+
@loop_tasks = []
|
16
|
+
@once_tasks = []
|
17
|
+
@poll_tasks = []
|
18
|
+
@subscription_tasks = []
|
19
|
+
@actors = []
|
20
|
+
|
21
|
+
find_extensions
|
22
|
+
load_extensions
|
23
|
+
end
|
24
|
+
|
25
|
+
def shutdown
|
26
|
+
@subscription_tasks.each do |task|
|
27
|
+
task[:threadpool].shutdown
|
28
|
+
task[:threadpool].wait_for_termination(2)
|
29
|
+
task[:threadpool].kill
|
30
|
+
end
|
31
|
+
@loop_tasks.each { |task| task[:running_class].cancel if task[:running_class].respond_to?(:cancel) }
|
32
|
+
@once_tasks.each { |task| task[:running_class].cancel if task[:running_class].respond_to?(:cancel) }
|
33
|
+
@timer_tasks.each { |task| task[:running_class].cancel if task[:running_class].respond_to?(:cancel) }
|
34
|
+
@poll_tasks.each { |task| task[:running_class].cancel if task[:running_class].respond_to?(:cancel) }
|
35
|
+
|
36
|
+
Legion::Logging.info 'Successfully shut down all actors'
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_extensions
|
40
|
+
@extensions ||= {}
|
41
|
+
@loaded_extensions ||= []
|
42
|
+
@extensions.each do |extension, values|
|
43
|
+
if values.key(:enabled) && !values[:enabled]
|
44
|
+
Legion::Logging.info "Skipping #{extension} because it's disabled"
|
45
|
+
next
|
46
|
+
end
|
47
|
+
|
48
|
+
if Legion::Settings[:extensions].key?(extension.to_sym) && Legion::Settings[:extensions][extension.to_sym].key?(:enabled) && !Legion::Settings[:extensions][extension.to_sym][:enabled] # rubocop:disable Layout/LineLength
|
49
|
+
next
|
50
|
+
end
|
51
|
+
|
52
|
+
unless load_extension(extension, values)
|
53
|
+
Legion::Logging.warn("#{extension} failed to load")
|
54
|
+
next
|
55
|
+
end
|
56
|
+
@loaded_extensions.push(extension)
|
57
|
+
end
|
58
|
+
Legion::Logging.info "#{@extensions.count} extensions loaded with subscription:#{@subscription_tasks.count},every:#{@timer_tasks.count},poll:#{@poll_tasks.count},once:#{@once_tasks.count},loop:#{@loop_tasks.count}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_extension(extension, values)
|
62
|
+
return unless gem_load(values[:gem_name], extension)
|
63
|
+
|
64
|
+
extension = Kernel.const_get(values[:extension_class])
|
65
|
+
has_logger = extension.respond_to?(:log)
|
66
|
+
extension.autobuild
|
67
|
+
|
68
|
+
require 'legion/transport/messages/lex_register'
|
69
|
+
Legion::Transport::Messages::LexRegister.new(function: 'save', opts: extension.runners).publish
|
70
|
+
|
71
|
+
if extension.respond_to?(:meta_actors) && extension.meta_actors.is_a?(Array)
|
72
|
+
extension.meta_actors.each do |_key, actor|
|
73
|
+
extension.log.debug("hooking meta actor: #{actor}") if has_logger
|
74
|
+
hook_actor(**actor)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
extension.actors.each do |_key, actor|
|
79
|
+
extension.log.debug("hooking literal actor: #{actor}") if has_logger
|
80
|
+
hook_actor(**actor)
|
81
|
+
end
|
82
|
+
extension.log.info 'Loaded'
|
83
|
+
rescue StandardError => e
|
84
|
+
Legion::Logging.error e.message
|
85
|
+
Legion::Logging.error e.backtrace
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
89
|
+
def hook_actor(extension:, extension_name:, actor_class:, size: 1, **opts)
|
90
|
+
size = 1 unless size.is_a? Integer
|
91
|
+
extension_hash = {
|
92
|
+
extension: extension,
|
93
|
+
extension_name: extension_name,
|
94
|
+
actor_class: actor_class,
|
95
|
+
size: size,
|
96
|
+
**opts
|
97
|
+
}
|
98
|
+
extension_hash[:running_class] = if actor_class.ancestors.include? Legion::Extensions::Actors::Subscription
|
99
|
+
actor_class
|
100
|
+
else
|
101
|
+
actor_class.new
|
102
|
+
end
|
103
|
+
|
104
|
+
if actor_class.ancestors.include? Legion::Extensions::Actors::Every
|
105
|
+
@timer_tasks.push(extension_hash)
|
106
|
+
elsif actor_class.ancestors.include? Legion::Extensions::Actors::Once
|
107
|
+
@once_tasks.push(extension_hash)
|
108
|
+
elsif actor_class.ancestors.include? Legion::Extensions::Actors::Loop
|
109
|
+
@loop_tasks.push(extension_hash)
|
110
|
+
elsif actor_class.ancestors.include? Legion::Extensions::Actors::Poll
|
111
|
+
@poll_tasks.push(extension_hash)
|
112
|
+
elsif actor_class.ancestors.include? Legion::Extensions::Actors::Subscription
|
113
|
+
extension_hash[:threadpool] = Concurrent::FixedThreadPool.new(100)
|
114
|
+
size.times do
|
115
|
+
extension_hash[:threadpool].post do
|
116
|
+
actor_class.new.async.subscribe
|
117
|
+
end
|
118
|
+
end
|
119
|
+
@subscription_tasks.push(extension_hash)
|
120
|
+
else
|
121
|
+
Legion::Logging.fatal 'did not match any actor classes'
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def gem_load(gem_name, name)
|
126
|
+
gem_path = "#{Gem::Specification.find_by_name(gem_name).gem_dir}/lib/legion/extensions/#{name}"
|
127
|
+
require gem_path
|
128
|
+
true
|
129
|
+
rescue LoadError => e
|
130
|
+
Legion::Logging.error e.message
|
131
|
+
Legion::Logging.error e.backtrace
|
132
|
+
Legion::Logging.error "gem_path: #{gem_path}" unless gem_path.nil?
|
133
|
+
false
|
134
|
+
end
|
135
|
+
|
136
|
+
def find_extensions
|
137
|
+
@extensions ||= {}
|
138
|
+
Gem::Specification.all_names.each do |gem|
|
139
|
+
next unless gem[0..3] == 'lex-'
|
140
|
+
|
141
|
+
lex = gem.split('-')
|
142
|
+
@extensions[lex[1]] = { full_gem_name: gem,
|
143
|
+
gem_name: "lex-#{lex[1]}",
|
144
|
+
extension_name: lex[1],
|
145
|
+
version: lex[2],
|
146
|
+
extension_class: "Legion::Extensions::#{lex[1].capitalize}" }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hashdiff'
|
4
|
+
|
5
|
+
module Legion
|
6
|
+
module Extensions
|
7
|
+
module Actors
|
8
|
+
module Base
|
9
|
+
include Legion::Extensions::Helpers::Lex
|
10
|
+
|
11
|
+
def runner
|
12
|
+
# runner_class:, function:, task_id: nil, args: nil, check_subtask: true, generate_task: true, parent_id: nil, master_id: nil, **opts
|
13
|
+
Legion::Runner.run(runner_class: runner_class, function: function, check_subtask: check_subtask, generate_task: generate_task)
|
14
|
+
rescue StandardError => e
|
15
|
+
Legion::Logging.error e.message
|
16
|
+
Legion::Logging.error e.backtrace
|
17
|
+
end
|
18
|
+
|
19
|
+
def manual
|
20
|
+
# Legion::Runner.run(runner_class: runner_class, function: runner_function, check_subtask: false, generate_task: false, args: args)
|
21
|
+
runner_class.send(runner_function, args)
|
22
|
+
rescue StandardError => e
|
23
|
+
Legion::Logging.error e.message
|
24
|
+
Legion::Logging.error e.backtrace
|
25
|
+
end
|
26
|
+
|
27
|
+
def function
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def use_runner?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def args
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
|
39
|
+
def check_subtask?
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_task?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def enabled?
|
48
|
+
true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Legion
|
6
|
+
module Extensions
|
7
|
+
module Actors
|
8
|
+
class Every
|
9
|
+
include Legion::Extensions::Actors::Base
|
10
|
+
|
11
|
+
def initialize(**_opts)
|
12
|
+
@timer = Concurrent::TimerTask.new(execution_interval: time, timeout_interval: timeout, run_now: run_now?) do
|
13
|
+
use_runner? ? runner : manual
|
14
|
+
end
|
15
|
+
|
16
|
+
@timer.execute
|
17
|
+
rescue StandardError => e
|
18
|
+
Legion::Logging.error e.message
|
19
|
+
Legion::Logging.error e.backtrace
|
20
|
+
end
|
21
|
+
|
22
|
+
def time
|
23
|
+
1
|
24
|
+
end
|
25
|
+
|
26
|
+
def timeout
|
27
|
+
5
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_now?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def action(**_opts)
|
35
|
+
Legion::Logging.warn 'An extension is using the default block from Legion::Extensions::Runners::Every'
|
36
|
+
end
|
37
|
+
|
38
|
+
def cancel
|
39
|
+
Legion::Logging.debug 'Cancelling Legion Timer'
|
40
|
+
return true unless @timer.respond_to?(:shutdown)
|
41
|
+
|
42
|
+
@timer.shutdown
|
43
|
+
rescue StandardError => e
|
44
|
+
Legion::Logging.error e.message
|
45
|
+
Legion::Logging.error e.backtrace
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Legion
|
6
|
+
module Extensions
|
7
|
+
module Actors
|
8
|
+
class Loop
|
9
|
+
include Concurrent::Async
|
10
|
+
include Legion::Extensions::Actors::Base
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@loop = true
|
14
|
+
async.run
|
15
|
+
rescue StandardError => e
|
16
|
+
Legion::Logging.error e
|
17
|
+
Legion::Logging.error e.backtrace
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
action while @loop
|
22
|
+
end
|
23
|
+
|
24
|
+
def action(**_opts)
|
25
|
+
Legion::Logging.warn 'An extension is using the default action for a loop'
|
26
|
+
end
|
27
|
+
|
28
|
+
def cancel
|
29
|
+
@loop = false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Legion
|
6
|
+
module Extensions
|
7
|
+
module Actors
|
8
|
+
class Once
|
9
|
+
include Legion::Extensions::Actors::Base
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
return if disabled?
|
13
|
+
|
14
|
+
if respond_to? :functions
|
15
|
+
functions.each do
|
16
|
+
function
|
17
|
+
@task = Concurrent::ScheduledTask.execute(delay) do
|
18
|
+
use_runner ? runner : manual
|
19
|
+
end
|
20
|
+
end
|
21
|
+
else
|
22
|
+
@task = Concurrent::ScheduledTask.execute(delay) do
|
23
|
+
use_runner ? runner : manual
|
24
|
+
end
|
25
|
+
end
|
26
|
+
rescue StandardError => e
|
27
|
+
Legion::Logging.error e
|
28
|
+
end
|
29
|
+
|
30
|
+
def delay
|
31
|
+
1.0
|
32
|
+
end
|
33
|
+
|
34
|
+
def cancel
|
35
|
+
return if disabled?
|
36
|
+
|
37
|
+
@task.cancel unless @task.cancelled?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require 'hashdiff'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
module Legion
|
8
|
+
module Extensions
|
9
|
+
module Actors
|
10
|
+
class Poll
|
11
|
+
include Legion::Extensions::Actors::Base
|
12
|
+
|
13
|
+
def initialize # rubocop:disable Metrics/AbcSize
|
14
|
+
log.debug "Starting timer for #{self.class} with #{{ execution_interval: time, timeout_interval: timeout, run_now: run_now?, check_subtask: check_subtask? }}"
|
15
|
+
@timer = Concurrent::TimerTask.new(execution_interval: time, timeout_interval: timeout, run_now: run_now?) do
|
16
|
+
t1 = Time.now
|
17
|
+
log.debug "Running #{self.class}"
|
18
|
+
old_result = Legion::Cache.get(cache_name)
|
19
|
+
log.debug "Cached value for #{self.class}: #{old_result}"
|
20
|
+
results = Legion::JSON.load(Legion::JSON.dump(manual))
|
21
|
+
Legion::Cache.set(cache_name, results, time * 2)
|
22
|
+
|
23
|
+
unless old_result.nil?
|
24
|
+
results[:diff] = Hashdiff.diff(results, old_result, numeric_tolerance: 0.0, array_path: false) do |_path, obj1, obj2|
|
25
|
+
if int_percentage_normalize.positive? && obj1.is_a?(Integer) && obj2.is_a?(Integer)
|
26
|
+
obj1.between?(obj2 * (1 - int_percentage_normalize), obj2 * (1 + int_percentage_normalize))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
results[:changed] = results[:diff].count.positive?
|
30
|
+
|
31
|
+
Legion::Logging.info results[:diff] if results[:changed]
|
32
|
+
Legion::Transport::Messages::CheckSubtask.new(runner_class: runner_class.to_s,
|
33
|
+
function: runner_function,
|
34
|
+
result: results,
|
35
|
+
type: 'poll_result',
|
36
|
+
polling: true).publish
|
37
|
+
end
|
38
|
+
|
39
|
+
sleep_time = 1 - (Time.now - t1)
|
40
|
+
sleep(sleep_time) if sleep_time.positive?
|
41
|
+
log.debug("#{self.class} result: #{results}")
|
42
|
+
results
|
43
|
+
rescue StandardError => e
|
44
|
+
Legion::Logging.fatal e.message
|
45
|
+
Legion::Logging.fatal e.backtrace
|
46
|
+
end
|
47
|
+
@timer.execute
|
48
|
+
rescue StandardError => e
|
49
|
+
Legion::Logging.error e.message
|
50
|
+
Legion::Logging.error e.backtrace
|
51
|
+
end
|
52
|
+
|
53
|
+
def cache_name
|
54
|
+
lex_name.to_s + '_' + runner_name
|
55
|
+
end
|
56
|
+
|
57
|
+
def int_percentage_normalize
|
58
|
+
0.00
|
59
|
+
end
|
60
|
+
|
61
|
+
def time
|
62
|
+
9
|
63
|
+
end
|
64
|
+
|
65
|
+
def run_now?
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_subtask?
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
def timeout
|
74
|
+
5
|
75
|
+
end
|
76
|
+
|
77
|
+
def action(_payload = {})
|
78
|
+
Legion::Logging.warn 'An extension is using the default block from Legion::Extensions::Runners::Every'
|
79
|
+
end
|
80
|
+
|
81
|
+
def cancel
|
82
|
+
Legion::Logging.debug 'Cancelling Legion Poller'
|
83
|
+
@timer.shutdown
|
84
|
+
rescue StandardError => e
|
85
|
+
Legion::Logging.error e.message
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|