main_loop 0.1.3.16874 → 0.1.4.367214
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/README.md +419 -21
- data/lib/main_loop/bus.rb +81 -5
- data/lib/main_loop/dispatcher.rb +102 -5
- data/lib/main_loop/handler.rb +102 -7
- data/lib/main_loop/loop.rb +132 -7
- data/lib/main_loop/process_handler.rb +91 -13
- data/lib/main_loop/thread_handler.rb +80 -12
- data/lib/main_loop/version.rb +1 -1
- data/lib/main_loop.rb +41 -2
- metadata +34 -6
|
@@ -1,35 +1,95 @@
|
|
|
1
1
|
require 'main_loop/handler'
|
|
2
2
|
|
|
3
|
+
# = MainLoop::ProcessHandler
|
|
4
|
+
#
|
|
5
|
+
# Управляет субпроцессами через Kernel.fork.
|
|
6
|
+
#
|
|
7
|
+
# == Жизненный цикл процесса
|
|
8
|
+
#
|
|
9
|
+
# 1. {#run} вызывает {#start_fork}
|
|
10
|
+
# 2. {#start_fork} создает fork с обработкой ошибок
|
|
11
|
+
# 3. process выполняется и завершается
|
|
12
|
+
# 4. {#reap(status)} получает статус завершения
|
|
13
|
+
# 5. {#handle_retry} решает, повторить или отправить term
|
|
14
|
+
#
|
|
15
|
+
# == Сигналы для процесса
|
|
16
|
+
#
|
|
17
|
+
# При {#term}:
|
|
18
|
+
# - Если не терминация: Process.kill('TERM', pid) + вызовы @runnable.on_term / @on_term
|
|
19
|
+
# - Если терминация: Process.kill('KILL', pid)
|
|
20
|
+
#
|
|
21
|
+
# == Пример использования
|
|
22
|
+
#
|
|
23
|
+
# MainLoop::ProcessHandler.new dispatcher, 'worker', retry_count: 3, logger: logger do
|
|
24
|
+
# sleep 2
|
|
25
|
+
# exit! 0
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# == См. также
|
|
29
|
+
# - {MainLoop::Handler} — базовый класс
|
|
30
|
+
# - {MainLoop::ThreadHandler} — обработчики потоков
|
|
31
|
+
|
|
3
32
|
module MainLoop
|
|
4
33
|
class ProcessHandler < MainLoop::Handler
|
|
5
|
-
|
|
6
34
|
attr_reader :pid
|
|
7
35
|
|
|
8
|
-
|
|
36
|
+
# == Инициализация
|
|
37
|
+
#
|
|
38
|
+
# @param dispatcher [Dispatcher] ссылка на диспетчер
|
|
39
|
+
# @param name [String] имя обработчика
|
|
40
|
+
# @param runnable [Object] объект с методами run и on_term (опционально)
|
|
41
|
+
# @param retry_count [Integer, :unlimited] количество повторов
|
|
42
|
+
# @param logger [Logger] логгер
|
|
43
|
+
# @option runnable [Object] nil
|
|
44
|
+
# @option retry_count [Integer, :unlimited] 0
|
|
45
|
+
def initialize(dispatcher, name, runnable: nil, **kwargs, &block)
|
|
9
46
|
super
|
|
10
47
|
@handler_type = 'Process'
|
|
11
48
|
@pid = nil
|
|
12
49
|
dispatcher.add_handler(self)
|
|
13
50
|
|
|
14
|
-
|
|
51
|
+
if runnable
|
|
52
|
+
unless runnable.respond_to?(:run) && runnable.respond_to?(:on_term)
|
|
53
|
+
raise TypeError, "Runnable object must respond to :run and :on_term"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@runnable = runnable
|
|
58
|
+
@block = block
|
|
59
|
+
|
|
60
|
+
run
|
|
15
61
|
end
|
|
16
62
|
|
|
63
|
+
# == Получить PID процесса
|
|
64
|
+
#
|
|
65
|
+
# @return [Integer|nil] PID процесса или nil если не создан
|
|
17
66
|
def id
|
|
18
67
|
@pid
|
|
19
68
|
end
|
|
20
69
|
|
|
70
|
+
# == Обработка завершения процесса
|
|
71
|
+
#
|
|
72
|
+
# @param status [Process::Status|nil] статус завершения или nil если неизвестен
|
|
21
73
|
def reap(status)
|
|
22
|
-
|
|
74
|
+
if status
|
|
75
|
+
logger.info "Process[#{name}] exited: Pid:#{@pid} Status: #{status.exitstatus.inspect} Termsig: #{status.termsig.inspect} Success: #{status.success?}"
|
|
76
|
+
@success = !!status.success?
|
|
77
|
+
else
|
|
78
|
+
logger.info "Process[#{name}] exited: Pid:#{@pid} with unknown status"
|
|
79
|
+
@success = true # TODO или false?
|
|
80
|
+
end
|
|
23
81
|
@pid = nil
|
|
24
82
|
@finished = true
|
|
25
|
-
@success = !!status.success?
|
|
26
83
|
|
|
27
84
|
return if terminating?
|
|
28
85
|
|
|
29
86
|
handle_retry
|
|
30
87
|
end
|
|
31
88
|
|
|
32
|
-
|
|
89
|
+
# == Отправить сигнал терминации
|
|
90
|
+
#
|
|
91
|
+
# @param *_args (Unused)
|
|
92
|
+
def term(*_args)
|
|
33
93
|
unless @pid
|
|
34
94
|
@terminating_at ||= Time.now
|
|
35
95
|
logger.debug "Process[#{name}] alredy terminated. Skipped."
|
|
@@ -43,12 +103,18 @@ module MainLoop
|
|
|
43
103
|
else
|
|
44
104
|
@terminating_at ||= Time.now
|
|
45
105
|
logger.info "Process[#{name}] send terminate: Pid:#{@pid}"
|
|
106
|
+
|
|
107
|
+
@runnable&.on_term(@pid) rescue nil
|
|
46
108
|
@on_term&.call(@pid) rescue nil
|
|
109
|
+
|
|
47
110
|
::Process.kill('TERM', @pid) rescue nil
|
|
48
111
|
end
|
|
49
112
|
end
|
|
50
113
|
|
|
51
|
-
|
|
114
|
+
# == Принудительно завершить процесс
|
|
115
|
+
#
|
|
116
|
+
# @param *_args (Unused)
|
|
117
|
+
def kill(*_args)
|
|
52
118
|
unless @pid
|
|
53
119
|
logger.debug "Process[#{name}] alredy Killed. Skipped."
|
|
54
120
|
return
|
|
@@ -59,24 +125,36 @@ module MainLoop
|
|
|
59
125
|
::Process.kill('KILL', @pid) rescue nil
|
|
60
126
|
end
|
|
61
127
|
|
|
62
|
-
|
|
128
|
+
# == Запустить процесс
|
|
129
|
+
#
|
|
130
|
+
# Вызывает {#start_fork} с блоком или runnable.
|
|
131
|
+
def run
|
|
63
132
|
return if terminating?
|
|
64
133
|
|
|
65
|
-
@
|
|
66
|
-
|
|
134
|
+
if @runnable
|
|
135
|
+
start_fork { @runnable.run }
|
|
136
|
+
elsif @block
|
|
137
|
+
start_fork(&@block)
|
|
138
|
+
end
|
|
67
139
|
end
|
|
68
140
|
|
|
141
|
+
# == Fork-блок (protected)
|
|
142
|
+
#
|
|
143
|
+
# Создает дочерний процесс и настраивает обработку ошибок.
|
|
144
|
+
#
|
|
145
|
+
# @yield执行 блок кода в дочернем процессе
|
|
146
|
+
# @return [Integer] PID дочернего процесса
|
|
69
147
|
protected
|
|
70
148
|
|
|
71
149
|
def start_fork
|
|
72
150
|
@pid = Kernel.fork do
|
|
73
151
|
yield
|
|
152
|
+
rescue StandardError => e
|
|
153
|
+
logger.error "Process[#{name}] crashed: #{e.message}"
|
|
154
|
+
exit!(1)
|
|
74
155
|
end
|
|
75
156
|
@finished = false
|
|
76
157
|
logger.info "Process[#{name}] created: Pid:#{@pid}"
|
|
77
158
|
end
|
|
78
|
-
|
|
79
|
-
|
|
80
159
|
end
|
|
81
160
|
end
|
|
82
|
-
|
|
@@ -1,23 +1,72 @@
|
|
|
1
1
|
require 'main_loop/handler'
|
|
2
2
|
|
|
3
|
+
# = MainLoop::ThreadHandler
|
|
4
|
+
#
|
|
5
|
+
# Управляет потоками через Thread.new.
|
|
6
|
+
#
|
|
7
|
+
# == Особенности потоков
|
|
8
|
+
#
|
|
9
|
+
# - Потоки не имеют status завершения (как процессы)
|
|
10
|
+
# - Поэтому @success = false для всех завершений (кроме graceful term)
|
|
11
|
+
# - Завершение потока публикуется в ensure блоке
|
|
12
|
+
#
|
|
13
|
+
# == Сигналы для потока
|
|
14
|
+
#
|
|
15
|
+
# При {#term}:
|
|
16
|
+
# - Если не терминация: вызовы @runnable.on_term / @on_term
|
|
17
|
+
# - Если терминация: @thread.kill (FORCE)
|
|
18
|
+
#
|
|
19
|
+
# == Пример использования
|
|
20
|
+
#
|
|
21
|
+
# MainLoop::ThreadHandler.new dispatcher, 'worker', retry_count: 0, logger: logger do |thread|
|
|
22
|
+
# loop do
|
|
23
|
+
# sleep 1
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# == См. также
|
|
28
|
+
# - {MainLoop::Handler} — базовый класс
|
|
29
|
+
# - {MainLoop::ProcessHandler} — обработчики процессов
|
|
30
|
+
|
|
3
31
|
module MainLoop
|
|
4
32
|
class ThreadHandler < MainLoop::Handler
|
|
5
|
-
|
|
6
33
|
attr_reader :thread
|
|
7
34
|
|
|
8
|
-
|
|
35
|
+
# == Инициализация
|
|
36
|
+
#
|
|
37
|
+
# @param dispatcher [Dispatcher] ссылка на диспетчер
|
|
38
|
+
# @param name [String] имя обработчика
|
|
39
|
+
# @param runnable [Object] объект с методами run и on_term (опционально)
|
|
40
|
+
# @param retry_count [Integer, :unlimited] количество повторов
|
|
41
|
+
# @param logger [Logger] логгер
|
|
42
|
+
def initialize(dispatcher, name, runnable: nil, **kwargs, &block)
|
|
9
43
|
super
|
|
10
44
|
@handler_type = 'Thread'
|
|
11
45
|
@thread = nil
|
|
12
46
|
dispatcher.add_handler(self)
|
|
13
47
|
|
|
14
|
-
|
|
48
|
+
if runnable
|
|
49
|
+
unless runnable.respond_to?(:run) && runnable.respond_to?(:on_term)
|
|
50
|
+
raise TypeError, "Runnable object must respond to :run and :on_term"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@runnable = runnable
|
|
55
|
+
@block = block
|
|
56
|
+
|
|
57
|
+
run
|
|
15
58
|
end
|
|
16
59
|
|
|
60
|
+
# == Получить ID потока
|
|
61
|
+
#
|
|
62
|
+
# @return [String] object_id потока в виде строки
|
|
17
63
|
def id
|
|
18
64
|
@thread&.object_id.to_s
|
|
19
65
|
end
|
|
20
66
|
|
|
67
|
+
# == Обработка завершения потока
|
|
68
|
+
#
|
|
69
|
+
# @param status [String] статус завершения (описание)
|
|
21
70
|
def reap(status)
|
|
22
71
|
logger.info "Thread[#{name}] exited: thread:#{@thread} Status:#{status}"
|
|
23
72
|
@thread = nil
|
|
@@ -29,7 +78,10 @@ module MainLoop
|
|
|
29
78
|
handle_retry
|
|
30
79
|
end
|
|
31
80
|
|
|
32
|
-
|
|
81
|
+
# == Терминация потока
|
|
82
|
+
#
|
|
83
|
+
# @param *_args (Unused)
|
|
84
|
+
def term(*_args)
|
|
33
85
|
unless @thread
|
|
34
86
|
@terminating_at ||= Time.now
|
|
35
87
|
logger.debug "Thread[#{name}] alredy terminated. Skipped."
|
|
@@ -44,11 +96,16 @@ module MainLoop
|
|
|
44
96
|
@terminating_at ||= Time.now
|
|
45
97
|
@success = true
|
|
46
98
|
logger.info "Thread[#{name}] send terminate: thread:#{@thread}"
|
|
99
|
+
|
|
100
|
+
@runnable&.on_term(@thread) rescue nil
|
|
47
101
|
@on_term&.call(@thread) rescue nil
|
|
48
102
|
end
|
|
49
103
|
end
|
|
50
104
|
|
|
51
|
-
|
|
105
|
+
# == Принудительно завершить поток
|
|
106
|
+
#
|
|
107
|
+
# @param *_args (Unused)
|
|
108
|
+
def kill(*_args)
|
|
52
109
|
unless @thread
|
|
53
110
|
logger.debug "Thread[#{name}] alredy Killed. Skipped."
|
|
54
111
|
return
|
|
@@ -59,26 +116,37 @@ module MainLoop
|
|
|
59
116
|
@thread.kill rescue nil
|
|
60
117
|
end
|
|
61
118
|
|
|
62
|
-
|
|
119
|
+
# == Запустить поток
|
|
120
|
+
#
|
|
121
|
+
# Вызывает {#start_thread} с блоком или runnable.
|
|
122
|
+
def run
|
|
63
123
|
return if terminating?
|
|
64
124
|
|
|
65
|
-
@
|
|
66
|
-
|
|
125
|
+
if @runnable
|
|
126
|
+
start_thread { @runnable.run(self) }
|
|
127
|
+
elsif @block
|
|
128
|
+
start_thread(&@block)
|
|
129
|
+
end
|
|
67
130
|
end
|
|
68
131
|
|
|
132
|
+
# == Create-thread блок (protected)
|
|
133
|
+
#
|
|
134
|
+
# Создает новый поток и настраивает обработку ошибок и завершения.
|
|
135
|
+
#
|
|
136
|
+
# @yield执行 блок кода в потоке
|
|
137
|
+
# @return [Thread] созданный поток
|
|
69
138
|
protected
|
|
70
139
|
|
|
71
140
|
def start_thread
|
|
72
141
|
@thread = Thread.new do
|
|
73
142
|
yield(self)
|
|
143
|
+
rescue StandardError => e
|
|
144
|
+
logger.error "Thread[#{name}] crashed: #{e.message}"
|
|
74
145
|
ensure
|
|
75
146
|
publish("reap:#{id}:exited")
|
|
76
147
|
end
|
|
77
148
|
@finished = false
|
|
78
149
|
logger.info "Thread[#{name}] created: thread:#{@thread}"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
150
|
+
end
|
|
82
151
|
end
|
|
83
152
|
end
|
|
84
|
-
|
data/lib/main_loop/version.rb
CHANGED
data/lib/main_loop.rb
CHANGED
|
@@ -1,10 +1,50 @@
|
|
|
1
|
+
# YARD-документация для main_loop
|
|
2
|
+
# Основной модуль библиотеки для управления субпроцессами и потоками
|
|
3
|
+
|
|
1
4
|
require 'main_loop/bus'
|
|
2
5
|
require 'main_loop/loop'
|
|
3
6
|
require 'main_loop/dispatcher'
|
|
4
7
|
require 'main_loop/process_handler'
|
|
5
8
|
require 'main_loop/thread_handler'
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
# = MainLoop
|
|
11
|
+
#
|
|
12
|
+
# Главный модуль библиотеки для управления субпроцессами и потоками.
|
|
13
|
+
#
|
|
14
|
+
# Основные компоненты:
|
|
15
|
+
# - {MainLoop::Bus} — канал обмена событиями между компонентами
|
|
16
|
+
# - {MainLoop::Dispatcher} — координация обработчиков и управление жизненным циклом
|
|
17
|
+
# - {MainLoop::Loop} — главный цикл обработки событий и сигналов
|
|
18
|
+
# - {MainLoop::Handler} — абстрактный базовый класс для обработчиков
|
|
19
|
+
# - {MainLoop::ProcessHandler} — управление субпроцессами
|
|
20
|
+
# - {MainLoop::ThreadHandler} — управление потоками
|
|
21
|
+
#
|
|
22
|
+
# == Пример использования
|
|
23
|
+
#
|
|
24
|
+
# require 'main_loop'
|
|
25
|
+
# require 'logger'
|
|
26
|
+
#
|
|
27
|
+
# logger = Logger.new(STDOUT)
|
|
28
|
+
# logger.level = Logger::DEBUG
|
|
29
|
+
#
|
|
30
|
+
# bus = MainLoop::Bus.new
|
|
31
|
+
# dispatcher = MainLoop::Dispatcher.new(bus, timeout: 10, logger: logger)
|
|
32
|
+
# mainloop = MainLoop::Loop.new(bus, dispatcher, logger: logger)
|
|
33
|
+
#
|
|
34
|
+
# MainLoop::ProcessHandler.new dispatcher, 'worker', retry_count: 3, logger: logger do
|
|
35
|
+
# sleep 2
|
|
36
|
+
# exit! 0
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# mainloop.run
|
|
40
|
+
#
|
|
41
|
+
# == См. также
|
|
42
|
+
# - {MainLoop::Bus}
|
|
43
|
+
# - {MainLoop::Dispatcher}
|
|
44
|
+
# - {MainLoop::Loop}
|
|
45
|
+
# - {MainLoop::Handler}
|
|
46
|
+
# - {MainLoop::ProcessHandler}
|
|
47
|
+
# - {MainLoop::ThreadHandler}
|
|
8
48
|
|
|
9
49
|
module MainLoop
|
|
10
50
|
|
|
@@ -13,4 +53,3 @@ module MainLoop
|
|
|
13
53
|
end
|
|
14
54
|
|
|
15
55
|
end
|
|
16
|
-
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: main_loop
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4.367214
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samoilenko Yuri
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-04-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -72,6 +72,20 @@ dependencies:
|
|
|
72
72
|
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
74
|
version: '0'
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: rubycritic
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
type: :development
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
75
89
|
- !ruby/object:Gem::Dependency
|
|
76
90
|
name: simplecov
|
|
77
91
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -100,6 +114,20 @@ dependencies:
|
|
|
100
114
|
- - ">="
|
|
101
115
|
- !ruby/object:Gem::Version
|
|
102
116
|
version: '0'
|
|
117
|
+
- !ruby/object:Gem::Dependency
|
|
118
|
+
name: simplecov-cobertura
|
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0'
|
|
124
|
+
type: :development
|
|
125
|
+
prerelease: false
|
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
103
131
|
- !ruby/object:Gem::Dependency
|
|
104
132
|
name: timeouter
|
|
105
133
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -135,7 +163,7 @@ homepage: https://github.com/RnD-Soft/main_loop
|
|
|
135
163
|
licenses:
|
|
136
164
|
- MIT
|
|
137
165
|
metadata: {}
|
|
138
|
-
post_install_message:
|
|
166
|
+
post_install_message:
|
|
139
167
|
rdoc_options: []
|
|
140
168
|
require_paths:
|
|
141
169
|
- lib
|
|
@@ -150,8 +178,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
150
178
|
- !ruby/object:Gem::Version
|
|
151
179
|
version: '0'
|
|
152
180
|
requirements: []
|
|
153
|
-
rubygems_version: 3.
|
|
154
|
-
signing_key:
|
|
181
|
+
rubygems_version: 3.2.33
|
|
182
|
+
signing_key:
|
|
155
183
|
specification_version: 4
|
|
156
184
|
summary: Main Loop implementation to control subprocesses and threads
|
|
157
185
|
test_files: []
|