senotrusov-ruby-daemonic-threads 1.0.1
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.
- data/LICENSE +239 -0
- data/README +1 -0
- data/lib/ruby-daemonic-threads.rb +37 -0
- data/lib/ruby-daemonic-threads/config.rb +49 -0
- data/lib/ruby-daemonic-threads/daemons.rb +49 -0
- data/lib/ruby-daemonic-threads/http.rb +19 -0
- data/lib/ruby-daemonic-threads/http/daemon.rb +98 -0
- data/lib/ruby-daemonic-threads/http/request.rb +378 -0
- data/lib/ruby-daemonic-threads/http/server.rb +78 -0
- data/lib/ruby-daemonic-threads/patches/timezone.rb +37 -0
- data/lib/ruby-daemonic-threads/process.rb +53 -0
- data/lib/ruby-daemonic-threads/prototype.rb +194 -0
- data/lib/ruby-daemonic-threads/queues.rb +72 -0
- data/lib/ruby-daemonic-threads/runner.rb +134 -0
- metadata +110 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
# Copyright 2009 Stanislav Senotrusov <senotrusov@gmail.com>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
|
17
|
+
# To reproduce race condition, start daemon and do concurrent HTTP get for some resource
|
18
|
+
# ab -c 100 -n 1000 http://127.0.0.1:4000/daemon/foo_resources/7.xml
|
19
|
+
|
20
|
+
# Rails default to lazy load this, so it gaves random exceptions in multithreaded env
|
21
|
+
ActiveSupport::TimeWithZone
|
22
|
+
|
23
|
+
# @@loaded_zones hash must be mutexed
|
24
|
+
class TZInfo::Timezone
|
25
|
+
@@loaded_zones_mutex = Mutex.new
|
26
|
+
|
27
|
+
class << self
|
28
|
+
alias_method :unsafe_get, :get
|
29
|
+
|
30
|
+
def get(identifier)
|
31
|
+
@@loaded_zones_mutex.synchronize do
|
32
|
+
unsafe_get(identifier)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
# Copyright 2009 Stanislav Senotrusov <senotrusov@gmail.com>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
|
17
|
+
class DaemonicThreads::Process
|
18
|
+
|
19
|
+
def initialize(controller)
|
20
|
+
@controller = controller
|
21
|
+
@name = controller.name
|
22
|
+
|
23
|
+
@config = DaemonicThreads::Config.new(RAILS_ROOT + '/config/daemons.yml')
|
24
|
+
@http = DaemonicThreads::HTTP::Server.new(self)
|
25
|
+
@queues = DaemonicThreads::Queues.new(self)
|
26
|
+
@daemons = DaemonicThreads::Daemons.new(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :controller, :name, :config, :http, :queues, :daemons
|
30
|
+
|
31
|
+
def start
|
32
|
+
@queues.restore
|
33
|
+
|
34
|
+
@http.start
|
35
|
+
@daemons.start
|
36
|
+
end
|
37
|
+
|
38
|
+
def join
|
39
|
+
@http.join
|
40
|
+
@daemons.join
|
41
|
+
end
|
42
|
+
|
43
|
+
def stop
|
44
|
+
@http.stop
|
45
|
+
@daemons.stop
|
46
|
+
end
|
47
|
+
|
48
|
+
def before_exit
|
49
|
+
@queues.store
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,194 @@
|
|
1
|
+
|
2
|
+
# Copyright 2009 Stanislav Senotrusov <senotrusov@gmail.com>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
|
17
|
+
# Демон - экземпляр некоторого класса, запущенный в процессе.
|
18
|
+
# В одном процессе параллельно могут быть запущено произвольное число демонов.
|
19
|
+
#
|
20
|
+
# Исключение из initialize() останавливает весь процесс, потому что непонятно - как и что там запустилось и будут ли теперь работать корректно start и join.
|
21
|
+
#
|
22
|
+
# Как правило, initialize() не переопределяется, а для запуска тредов, поддемонов, сетевых подключение используется initialize_daemon().
|
23
|
+
#
|
24
|
+
# initialize_daemon() более толерантен к ошибкам.
|
25
|
+
# Исключение из группы IPSocket::SOCKET_EXEPTIONS полученное из initialize_daemon() перестартовывает демон.
|
26
|
+
# Тем не менее, остальные исключения, полученные из initialize_daemon() останавливают весь процесс
|
27
|
+
#
|
28
|
+
# Исключения, полученные из тредов, перестартовывает демон.
|
29
|
+
#
|
30
|
+
# После отработки initialize в произвольной последовательности вызываются join() и stop().
|
31
|
+
# join() и stop() вызывается только один раз.
|
32
|
+
# Исключение в stop() останавливает весь процесс, потому что непонятно - всему ли была доведена команда остановиться.
|
33
|
+
# Исключение в join() останавливает весь процесс, потому что непонятно - всё ли остановилось.
|
34
|
+
#
|
35
|
+
# Таким образом, реализуется несколько боязливая стратегия работы - если что-то идёт не так, процесс останавливается.
|
36
|
+
# Рестарт демонов происходит только когда ошибка понятна, и она, скорее всего, не приводит к фатальным последствиям.
|
37
|
+
# Такой ошибкой является сбой сети.
|
38
|
+
# Если в ваших демонах есть другие понятные вам ошибки - ловити их сами или декларируйте в RESTART_ON.
|
39
|
+
#
|
40
|
+
# Если тред запустит самостоятельно демона, то он может получить из этого демона exception.
|
41
|
+
# Если этот exception не относится к категории тех, которые решаются перезапуском демона, то весь процесс останавливается.
|
42
|
+
# На самом деле, когда вы получаете такой exception из spawn_daemon @runner.process.controller.stop уже выполнен. Вот-вот всё закроется.
|
43
|
+
|
44
|
+
|
45
|
+
# Вывод inspect очень большой, потому что он долго ходит по перекрёсным ссылкам.
|
46
|
+
# Сюрпризом будет то, что обычный exception no method error, message которого выполняет internaly some kind of inspect,
|
47
|
+
# может выполнятся секунду-другую, если exception случился паралельно.
|
48
|
+
|
49
|
+
|
50
|
+
module DaemonicThreads::Prototype
|
51
|
+
RESTART_ON = IPSocket::SOCKET_EXEPTIONS + [DaemonicThreads::MustTerminatedState] # TODO: inheritable
|
52
|
+
|
53
|
+
def initialize(name, runner, parent = nil)
|
54
|
+
@name = name
|
55
|
+
@runner = runner
|
56
|
+
@config = runner.config
|
57
|
+
@process = runner.process
|
58
|
+
@logger = Rails.logger
|
59
|
+
@parent = parent
|
60
|
+
|
61
|
+
@config["queues"].each do |queue_handler, queue_name|
|
62
|
+
instance_variable_set("@#{queue_handler}", @process.queues[queue_name])
|
63
|
+
end if @config["queues"]
|
64
|
+
|
65
|
+
@threads = ThreadGroup.new
|
66
|
+
@daemons = []
|
67
|
+
@creatures_mutex = Mutex.new
|
68
|
+
@stop_condition = ConditionVariable.new
|
69
|
+
@must_terminate = false
|
70
|
+
|
71
|
+
initialize_http if respond_to?(:initialize_http)
|
72
|
+
end
|
73
|
+
|
74
|
+
attr_reader :logger
|
75
|
+
|
76
|
+
def join
|
77
|
+
@creatures_mutex.synchronize do
|
78
|
+
@stop_condition.wait(@creatures_mutex) unless @must_terminate
|
79
|
+
end
|
80
|
+
|
81
|
+
deinitialize_http if respond_to?(:deinitialize_http)
|
82
|
+
|
83
|
+
@daemons.each {|daemon| daemon.join }
|
84
|
+
|
85
|
+
@threads.list.each {|thread| thread.join }
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def stop
|
90
|
+
@creatures_mutex.synchronize do
|
91
|
+
@must_terminate = true
|
92
|
+
@stop_condition.signal
|
93
|
+
end
|
94
|
+
|
95
|
+
@daemons.each {|daemon| daemon.stop }
|
96
|
+
|
97
|
+
@threads.list.each do |thread|
|
98
|
+
@config["queues"].each do |queue_handler, queue_name|
|
99
|
+
instance_variable_get("@#{queue_handler}").release_blocked thread
|
100
|
+
end
|
101
|
+
end if @config["queues"]
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def perform_initialize_daemon(*args)
|
106
|
+
initialize_daemon(*args) if respond_to? :initialize_daemon
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# Можно запускать из initialize_daemon или из любого треда
|
111
|
+
def spawn_daemon name, klass, *args
|
112
|
+
@creatures_mutex.synchronize do
|
113
|
+
raise(DaemonicThreads::MustTerminatedState, "Unable to spawn new daemons after stop() is called") if @must_terminate
|
114
|
+
|
115
|
+
# Мы не ловим никаких exceptions, потому что они поймаются или panic_on_exception (тред или http-запрос) или runner-ом (initialize, initialize_daemon).
|
116
|
+
# Полагаться на себя тот должен, кто spawn_daemon вызвал из треда, запущенного без помощи spawn_thread, а значит без должной обработки ошибок.
|
117
|
+
|
118
|
+
@daemons.push(daemon = klass.new(name, @runner, self))
|
119
|
+
daemon.perform_initialize_daemon(*args)
|
120
|
+
return daemon
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
# Можно запускать из initialize_daemon или из любого треда
|
126
|
+
def spawn_thread(thread_name, *args)
|
127
|
+
@creatures_mutex.synchronize do
|
128
|
+
raise(DaemonicThreads::MustTerminatedState, "Unable to spawn new threads after stop() is called") if @must_terminate
|
129
|
+
|
130
|
+
thread_title = "#{self.class}#thread:`#{thread_name}' @name:`#{@name}'"
|
131
|
+
|
132
|
+
@threads.add(thread = Thread.new { execute_thread(thread_name, thread_title, args) } )
|
133
|
+
|
134
|
+
return thread
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def execute_thread(thread_name, thread_title, args)
|
139
|
+
|
140
|
+
panic_on_exception(thread_title) do
|
141
|
+
Thread.current[:title] = thread_title
|
142
|
+
Thread.current[:started_at] = Time.now
|
143
|
+
|
144
|
+
if block_given?
|
145
|
+
yield(*args)
|
146
|
+
elsif respond_to?(thread_name)
|
147
|
+
__send__(thread_name, *args)
|
148
|
+
else
|
149
|
+
raise("Thread block was not given or method `#{thread_name}' not found. Don't know what to do.")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
ensure
|
154
|
+
panic_on_exception("#{thread_title} -- Release ActiveRecord connection to pool") { ActiveRecord::Base.clear_active_connections! }
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
def panic_on_exception(title = nil, handler = nil)
|
159
|
+
yield
|
160
|
+
rescue *(RESTART_ON) => exception
|
161
|
+
begin
|
162
|
+
exception.log!(@logger, :warn, title, (@process.controller.env == "production" ? :inspect : :inspect_with_backtrace))
|
163
|
+
handler.call(exception) if handler
|
164
|
+
@runner.restart_daemon
|
165
|
+
rescue Exception => handler_exception
|
166
|
+
begin
|
167
|
+
handler_exception.log!(@logger, :fatal, title)
|
168
|
+
ensure
|
169
|
+
@process.controller.stop
|
170
|
+
end
|
171
|
+
end
|
172
|
+
rescue Exception => exception
|
173
|
+
begin
|
174
|
+
exception.log!(@logger, :fatal, title)
|
175
|
+
handler.call(exception) if handler
|
176
|
+
rescue Exception => handler_exception
|
177
|
+
handler_exception.log!(@logger, :fatal, title)
|
178
|
+
ensure
|
179
|
+
@process.controller.stop
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
def must_terminate?
|
185
|
+
@creatures_mutex.synchronize { @must_terminate }
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
def log severity, message = nil
|
190
|
+
@logger.__send__(severity, "#{self.class}##{caller.first.match(/`(.*)'/)[1]} -- #{block_given? ? yield : message}" )
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
# Copyright 2009 Stanislav Senotrusov <senotrusov@gmail.com>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
|
17
|
+
# NOTE:
|
18
|
+
# It is not a quarantied delivery queues.
|
19
|
+
# It rely on normal process startup/shutdown sequence.
|
20
|
+
# So, if you get sigfault all data will be lost.
|
21
|
+
# On the other hand, queues tends to be quite quickly, since all done in memory
|
22
|
+
|
23
|
+
class DaemonicThreads::Queues
|
24
|
+
|
25
|
+
DEFAULT_STORAGE_DIR = Rails.root + 'tmp' + 'queues'
|
26
|
+
|
27
|
+
def initialize(process)
|
28
|
+
@queues = {}
|
29
|
+
@storage_dir = DEFAULT_STORAGE_DIR
|
30
|
+
@queue_names = process.config.queue_names
|
31
|
+
|
32
|
+
raise("Queues storage directory #{@storage_dir} is not available!") unless storage_available?
|
33
|
+
end
|
34
|
+
|
35
|
+
def restore
|
36
|
+
@queue_names.each do |name|
|
37
|
+
if File.exists?(filename = "#{@storage_dir}/#{name}")
|
38
|
+
@queues[name] = SmartQueue.new(File.read(filename))
|
39
|
+
File.unlink(filename)
|
40
|
+
else
|
41
|
+
@queues[name] = SmartQueue.new
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def store
|
47
|
+
@queues.each do |name, queue|
|
48
|
+
File.write("#{@storage_dir}/#{name}", queue.to_storage)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def [] name
|
53
|
+
@queues[name]
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def storage_available?
|
59
|
+
if File.directory?(@storage_dir) && File.writable?(@storage_dir)
|
60
|
+
return true
|
61
|
+
else
|
62
|
+
begin
|
63
|
+
Dir.mkdir(@storage_dir)
|
64
|
+
return true
|
65
|
+
rescue SystemCallError
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,134 @@
|
|
1
|
+
|
2
|
+
# Copyright 2009 Stanislav Senotrusov <senotrusov@gmail.com>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
|
17
|
+
class DaemonicThreads::Runner
|
18
|
+
RESTART_DELAY = 15
|
19
|
+
|
20
|
+
def initialize(name, config, process)
|
21
|
+
@name = name
|
22
|
+
@config = config
|
23
|
+
@process = process
|
24
|
+
@logger = Rails.logger
|
25
|
+
|
26
|
+
@mutex = Mutex.new
|
27
|
+
@delay = ConditionVariable.new
|
28
|
+
@must_terminate = false
|
29
|
+
@restarting = false
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :name, :config, :process
|
33
|
+
|
34
|
+
def start
|
35
|
+
@watchdog_thread = Thread.new_with_exception_handling(lambda { @process.controller.stop }, @logger, :fatal, "#{self.class}#watchdog @name:`#{@name}'") { watchdog }
|
36
|
+
end
|
37
|
+
|
38
|
+
def join
|
39
|
+
@watchdog_thread.join if @watchdog_thread
|
40
|
+
end
|
41
|
+
|
42
|
+
def stop
|
43
|
+
restart_process_on_exception do
|
44
|
+
@mutex.synchronize do
|
45
|
+
@must_terminate = true
|
46
|
+
@delay.signal
|
47
|
+
|
48
|
+
return if @restarting
|
49
|
+
|
50
|
+
@daemon.stop if @daemon
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Нельзя вызывать restart_daemon(), из initialize(), потому что перестартовывать ещё нечего.
|
57
|
+
# Такой вызов может произойти, если в initialize() запускаются треды, которые при ошибке вызывают restart_daemon.
|
58
|
+
# Запускайте новые треды в методе initialize_daemon(). Он выполняется после завершения initialize()
|
59
|
+
#
|
60
|
+
# Нельзя вызывать restart_daemon() после того, как отработал join().
|
61
|
+
# Таким действием можно непреднамеренно перезапустить новое, только что созданное приложение.
|
62
|
+
# Пауза между завершением старого и запуском нового приложения (RESTART_DELAY), несколько снижает вероятность такой ошибки.
|
63
|
+
# Польностью ошибку исключить можно, только если в join() делать join для всех запущенных тредов.
|
64
|
+
# Это делается автоматически, если использовать методы spawn_daemon и spawn_thread.
|
65
|
+
#
|
66
|
+
def restart_daemon
|
67
|
+
restart_process_on_exception do
|
68
|
+
@mutex.synchronize do
|
69
|
+
return if @must_terminate || @restarting
|
70
|
+
|
71
|
+
@restarting = true
|
72
|
+
|
73
|
+
unless @daemon
|
74
|
+
raise "#{self.class}#restart_daemon @name:`#{@name} -- Called restart_daemon(), but @daemon does not exists! This may occur when you somehow call restart_daemon() from initialize() and then raises then exception from initialize(). Or you spawn threads in initialize() instead of in spawn_threads() and one thread ends with exception before initialize() fully completes. Or it may be both cases."
|
75
|
+
end
|
76
|
+
|
77
|
+
@daemon.stop
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def watchdog
|
85
|
+
loop do
|
86
|
+
@mutex.synchronize do
|
87
|
+
return if @must_terminate
|
88
|
+
@restarting = false
|
89
|
+
|
90
|
+
@logger.info "#{self.class}#watchdog @name:`#{@name}' -- Starting..."
|
91
|
+
@daemon = @config["class-constantized"].new(@name, self)
|
92
|
+
end
|
93
|
+
|
94
|
+
begin
|
95
|
+
@daemon.perform_initialize_daemon
|
96
|
+
rescue *(@config["class-constantized"]::RESTART_ON) => exception
|
97
|
+
exception.log! @logger, :warn, "#{self.class}#watchdog @name:`#{@name}' -- Restarting daemon because of exception", (@process.controller.env == "production" ? :inspect : :inspect_with_backtrace)
|
98
|
+
restart_daemon
|
99
|
+
end
|
100
|
+
|
101
|
+
@daemon.join
|
102
|
+
|
103
|
+
delay
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def delay
|
108
|
+
@mutex.synchronize do
|
109
|
+
unless @must_terminate
|
110
|
+
@logger.info "#{self.class}#delay @name:`#{@name}' -- Catch termination, restarting in #{RESTART_DELAY} seconds..."
|
111
|
+
|
112
|
+
Thread.new_with_exception_handling(lambda { @process.controller.stop }, @logger, :fatal, "#{self.class}#delay @name:`#{@name}'") do
|
113
|
+
# Если кто-то пошлёт сигнал во время сна, ожидающий получит два сигнала по пробуждении. В этом конкретном случае это не ппроблема, потому как этот кто-то - это стоп, и после него получать сигнал будет некому
|
114
|
+
sleep RESTART_DELAY
|
115
|
+
@delay.signal
|
116
|
+
end
|
117
|
+
|
118
|
+
@delay.wait(@mutex)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def restart_process_on_exception
|
124
|
+
yield
|
125
|
+
rescue Exception => exception
|
126
|
+
begin
|
127
|
+
exception.log!(@logger, :fatal, "#{self.class}##{caller.first.match(/`(.*)'/)[1]} @name:`#{@name}' -- Stopping process because of exception")
|
128
|
+
ensure
|
129
|
+
@process.controller.stop
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|