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.
@@ -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
- def initialize(dispatcher, name, **kwargs, &block)
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
- run(&block) if block_given?
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
- logger.info "Process[#{name}] exited: Pid:#{@pid} Status: #{status.exitstatus.inspect} Termsig: #{status.termsig.inspect} Success: #{status.success?}"
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
- def term
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
- def kill
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
- def run(&block)
128
+ # == Запустить процесс
129
+ #
130
+ # Вызывает {#start_fork} с блоком или runnable.
131
+ def run
63
132
  return if terminating?
64
133
 
65
- @block = block
66
- start_fork(&@block)
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
- def initialize(dispatcher, name, **kwargs, &block)
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
- run(&block) if block_given?
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
- def term
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
- def kill
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
- def run(&block)
119
+ # == Запустить поток
120
+ #
121
+ # Вызывает {#start_thread} с блоком или runnable.
122
+ def run
63
123
  return if terminating?
64
124
 
65
- @block = block
66
- start_thread(&@block)
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
- end
80
-
81
-
150
+ end
82
151
  end
83
152
  end
84
-
@@ -1,6 +1,6 @@
1
1
  module MainLoop
2
2
 
3
- VERSION = '0.1.3'.freeze
3
+ VERSION = '0.1.4'.freeze
4
4
 
5
5
  end
6
6
 
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.3.16874
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: 2019-09-30 00:00:00.000000000 Z
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.0.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: []