exceptional_synchrony 1.0.1 → 1.3.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/.dependabot/config.yml +10 -0
- data/.gitignore +2 -0
- data/.jenkins/Jenkinsfile +58 -0
- data/.jenkins/ruby_build_pod.yml +19 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +29 -0
- data/Gemfile +11 -1
- data/Gemfile.lock +116 -104
- data/Rakefile +11 -0
- data/exceptional_synchrony.gemspec +20 -22
- data/lib/exceptional_synchrony.rb +0 -1
- data/lib/exceptional_synchrony/callback_exceptions.rb +36 -4
- data/lib/exceptional_synchrony/event_machine_proxy.rb +71 -7
- data/lib/exceptional_synchrony/limited_work_queue.rb +29 -5
- data/lib/exceptional_synchrony/parallel_sync.rb +1 -0
- data/lib/exceptional_synchrony/version.rb +1 -1
- data/test/test_helper.rb +12 -0
- data/test/unit/callback_exceptions_test.rb +67 -15
- data/test/unit/event_machine_proxy_test.rb +132 -19
- data/test/unit/limited_work_queue_test.rb +221 -23
- data/test/unit/parallel_sync_test.rb +6 -6
- metadata +39 -90
- data/Thorfile +0 -42
@@ -21,13 +21,13 @@ module ExceptionalSynchrony
|
|
21
21
|
result
|
22
22
|
end
|
23
23
|
when :failed
|
24
|
-
if result.respond_to?(:error)
|
25
|
-
|
24
|
+
if result.respond_to?(:error)
|
25
|
+
handle_result_error(result)
|
26
26
|
else
|
27
|
-
|
27
|
+
raise_failure_for_result(result)
|
28
28
|
end
|
29
29
|
else
|
30
|
-
raise ArgumentError, "No deferred status set yet: #{deferred_status.inspect} #{result
|
30
|
+
raise ArgumentError, "No deferred status set yet: #{deferred_status.inspect} #{truncated_inspect(result)}"
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -38,6 +38,38 @@ module ExceptionalSynchrony
|
|
38
38
|
ex
|
39
39
|
end
|
40
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def handle_result_error(result)
|
45
|
+
error = result.error
|
46
|
+
if error_is_a_timeout?(error)
|
47
|
+
raise Timeout::Error
|
48
|
+
else
|
49
|
+
raise_failure_for_result(result, error: error)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def raise_failure_for_result(result, error: nil)
|
54
|
+
result_string = truncated_inspect(result)
|
55
|
+
error_string = if error
|
56
|
+
"ERROR = #{truncated_inspect(error)}; "
|
57
|
+
end
|
58
|
+
raise Failure, "#{error_string}RESULT = #{result_string}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def truncated_inspect(obj)
|
62
|
+
inspection = obj.inspect[0, 101]
|
63
|
+
if inspection.length > 100
|
64
|
+
inspection[0, 92] + '...TRUNC'
|
65
|
+
else
|
66
|
+
inspection
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def error_is_a_timeout?(error)
|
71
|
+
error =~ /timeout/i || error == Errno::ETIMEDOUT
|
72
|
+
end
|
41
73
|
end
|
42
74
|
end
|
43
75
|
end
|
@@ -5,6 +5,10 @@ require 'em-http'
|
|
5
5
|
require 'em-synchrony/em-http'
|
6
6
|
|
7
7
|
module ExceptionalSynchrony
|
8
|
+
# It is important for this exception to be inherited from Exception so that
|
9
|
+
# when thrown it does not get caught by the EventMachine.error_handler.
|
10
|
+
class FatalRunError < Exception; end
|
11
|
+
|
8
12
|
class EventMachineProxy
|
9
13
|
|
10
14
|
attr_reader :connection
|
@@ -41,6 +45,12 @@ module ExceptionalSynchrony
|
|
41
45
|
@synchrony.sleep(seconds)
|
42
46
|
end
|
43
47
|
|
48
|
+
def yield_to_reactor
|
49
|
+
if reactor_running?
|
50
|
+
@synchrony.sleep(0)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
44
54
|
def next_tick(&block)
|
45
55
|
@synchrony.next_tick do
|
46
56
|
ensure_completely_safe("next_tick") do
|
@@ -54,17 +64,39 @@ module ExceptionalSynchrony
|
|
54
64
|
@proxy_class.next_tick { } #Fake out EventMachine's epoll mechanism so we don't block until timers fire
|
55
65
|
end
|
56
66
|
|
67
|
+
def defers_finished?
|
68
|
+
@proxy_class.defers_finished?
|
69
|
+
end
|
70
|
+
|
57
71
|
def connect(server, port = nil, handler = nil, *args, &block)
|
58
72
|
@proxy_class.connect(server, port, handler, *args, &block)
|
59
73
|
end
|
60
74
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
75
|
+
# The on_error option has these possible values:
|
76
|
+
# :log - log any rescued StandardError exceptions and continue
|
77
|
+
# :raise - raise FatalRunError for any rescued StandardError exceptions
|
78
|
+
def run(on_error: :log, &block)
|
79
|
+
case on_error
|
80
|
+
when :log then run_with_error_logging(&block)
|
81
|
+
when :raise then run_with_error_raising(&block)
|
82
|
+
else raise ArgumentError, "Invalid on_error: #{on_error.inspect}, must be :log or :raise"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# This method will execute the block on the background thread pool
|
87
|
+
# By default, it will block the caller until the background thread has finished, so that the result can be returned
|
88
|
+
# :wait_for_result - setting this to false will prevent the caller from being blocked by this deferred work
|
89
|
+
def defer(context, wait_for_result: true, &block)
|
90
|
+
if wait_for_result
|
91
|
+
deferrable = EventMachine::DefaultDeferrable.new
|
92
|
+
callback = -> (result) { deferrable.succeed(result) }
|
93
|
+
|
94
|
+
EventMachine.defer(nil, callback) { CallbackExceptions.return_exception(&block) }
|
95
|
+
EventMachine::Synchrony.sync(deferrable)
|
96
|
+
CallbackExceptions.map_deferred_result(deferrable)
|
97
|
+
else
|
98
|
+
EventMachine.defer { ExceptionHandling.ensure_completely_safe("defer", &block) }
|
99
|
+
nil
|
68
100
|
end
|
69
101
|
end
|
70
102
|
|
@@ -90,6 +122,38 @@ module ExceptionalSynchrony
|
|
90
122
|
yield
|
91
123
|
end
|
92
124
|
end
|
125
|
+
|
126
|
+
def rescue_exceptions_and_ensure_exit(context)
|
127
|
+
yield
|
128
|
+
rescue StandardError => ex
|
129
|
+
# Raise a non-StandardError so that not caught by EM.error_handler.
|
130
|
+
# Expecting rescued exception to be stored in this new exception's cause.
|
131
|
+
raise FatalRunError, "Fatal EventMachine #{context} error\n#{ex.class.name}: #{ex.message}"
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def run_with_error_logging(&block)
|
137
|
+
ensure_completely_safe("run_with_error_logging") do
|
138
|
+
if @proxy_class.respond_to?(:synchrony)
|
139
|
+
@proxy_class.synchrony(&block)
|
140
|
+
else
|
141
|
+
@proxy_class.run(&block)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def run_with_error_raising(&block)
|
147
|
+
run_block = -> { rescue_exceptions_and_ensure_exit("run_with_error_raising", &block) }
|
148
|
+
|
149
|
+
rescue_exceptions_and_ensure_exit("run_with_error_raising") do
|
150
|
+
if @proxy_class.respond_to?(:synchrony)
|
151
|
+
@proxy_class.synchrony(&run_block)
|
152
|
+
else
|
153
|
+
@proxy_class.run(&run_block)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
93
157
|
end
|
94
158
|
|
95
159
|
EMP = EventMachineProxy.new(EventMachine, EventMachine::HttpRequest)
|
@@ -1,15 +1,17 @@
|
|
1
1
|
module ExceptionalSynchrony
|
2
2
|
class LimitedWorkQueue
|
3
|
+
|
3
4
|
def initialize(em, limit)
|
4
5
|
@em = em
|
5
6
|
limit > 0 or raise ArgumentError, "limit must be positive"
|
6
7
|
@limit = limit
|
7
8
|
@worker_count = 0
|
8
9
|
@job_procs = []
|
10
|
+
@paused = false
|
9
11
|
end
|
10
12
|
|
11
13
|
# Adds a job_proc to work.
|
12
|
-
def add(proc = nil, &block)
|
14
|
+
def add!(proc = nil, &block)
|
13
15
|
job = proc || block
|
14
16
|
job.respond_to?(:call) or raise "Must respond_to?(:call)! #{job.inspect}"
|
15
17
|
if @job_procs.any? && job.respond_to?(:merge) && (merged_queue = job.merge(@job_procs))
|
@@ -17,8 +19,9 @@ module ExceptionalSynchrony
|
|
17
19
|
else
|
18
20
|
@job_procs << job
|
19
21
|
end
|
20
|
-
work!
|
22
|
+
work! unless paused?
|
21
23
|
end
|
24
|
+
alias_method :add, :add!
|
22
25
|
|
23
26
|
def workers_empty?
|
24
27
|
@worker_count.zero?
|
@@ -32,18 +35,39 @@ module ExceptionalSynchrony
|
|
32
35
|
@job_procs.empty?
|
33
36
|
end
|
34
37
|
|
35
|
-
|
38
|
+
def paused?
|
39
|
+
@paused
|
40
|
+
end
|
41
|
+
|
42
|
+
def pause!
|
43
|
+
@paused = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def unpause!
|
47
|
+
@paused = false
|
48
|
+
end
|
49
|
+
|
50
|
+
def items
|
51
|
+
@job_procs
|
52
|
+
end
|
53
|
+
|
36
54
|
def work!
|
37
55
|
until queue_empty? || workers_full?
|
38
56
|
job_proc = @job_procs.shift
|
39
57
|
@worker_count += 1
|
40
58
|
Fiber.new do
|
41
|
-
|
42
|
-
|
59
|
+
begin
|
60
|
+
job_proc.call
|
61
|
+
rescue => ex
|
62
|
+
ExceptionHandling.log_error(ex, "LimitedWorkQueue encountered an exception")
|
63
|
+
ensure
|
64
|
+
worker_done
|
65
|
+
end
|
43
66
|
end.resume
|
44
67
|
end
|
45
68
|
end
|
46
69
|
|
70
|
+
private
|
47
71
|
def worker_done
|
48
72
|
@worker_count -= 1
|
49
73
|
work!
|
data/test/test_helper.rb
CHANGED
@@ -1,11 +1,23 @@
|
|
1
|
+
ENV['RACK_ENV'] = 'test'
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
|
1
6
|
require_relative '../lib/exceptional_synchrony.rb'
|
2
7
|
|
3
8
|
require 'minitest/autorun' or raise "Already loaded minitest?"
|
4
9
|
require 'minitest/pride'
|
10
|
+
require 'minitest/reporters'
|
11
|
+
Minitest::Reporters.use! [
|
12
|
+
Minitest::Reporters::DefaultReporter.new,
|
13
|
+
Minitest::Reporters::JUnitReporter.new
|
14
|
+
]
|
5
15
|
require 'webmock'
|
6
16
|
require 'webmock/minitest'
|
7
17
|
require 'rr'
|
8
18
|
|
19
|
+
ActiveSupport::TestCase.test_order = :sorted
|
20
|
+
|
9
21
|
module TestHelper
|
10
22
|
@@constant_overrides = []
|
11
23
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative '../test_helper'
|
2
|
+
require 'pry'
|
2
3
|
|
3
4
|
describe ExceptionalSynchrony::CallbackExceptions do
|
4
5
|
describe "ensure_callback" do
|
@@ -60,14 +61,14 @@ describe ExceptionalSynchrony::CallbackExceptions do
|
|
60
61
|
deferrable = EM::DefaultDeferrable.new
|
61
62
|
deferrable.succeed(12)
|
62
63
|
result = ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
63
|
-
result.must_equal
|
64
|
+
expect(result).must_equal(12)
|
64
65
|
end
|
65
66
|
|
66
67
|
it "should map success values to an array" do
|
67
68
|
deferrable = EM::DefaultDeferrable.new
|
68
69
|
deferrable.succeed(12, 13, 14)
|
69
70
|
result = ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
70
|
-
result.must_equal
|
71
|
+
expect(result).must_equal([12, 13, 14])
|
71
72
|
end
|
72
73
|
|
73
74
|
it "should map success exception values to raise" do
|
@@ -77,7 +78,7 @@ describe ExceptionalSynchrony::CallbackExceptions do
|
|
77
78
|
result = assert_raises(ArgumentError) do
|
78
79
|
ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
79
80
|
end
|
80
|
-
result.must_equal
|
81
|
+
expect(result).must_equal(exception)
|
81
82
|
end
|
82
83
|
end
|
83
84
|
|
@@ -87,8 +88,18 @@ describe ExceptionalSynchrony::CallbackExceptions do
|
|
87
88
|
deferrable.fail(first: "a", last: "b")
|
88
89
|
result = assert_raises(ExceptionalSynchrony::CallbackExceptions::Failure) do
|
89
90
|
ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
90
|
-
end
|
91
|
-
result.
|
91
|
+
end.message
|
92
|
+
expect(result).must_equal("RESULT = {:first=>\"a\", :last=>\"b\"}")
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should truncate long failures" do
|
96
|
+
deferrable = EM::DefaultDeferrable.new
|
97
|
+
deferrable.fail('a'*75 + 'b'*75)
|
98
|
+
result = assert_raises(ExceptionalSynchrony::CallbackExceptions::Failure) do
|
99
|
+
ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
100
|
+
end.message
|
101
|
+
expected_message = "RESULT = \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb...TRUNC"
|
102
|
+
expect(result).must_equal(expected_message)
|
92
103
|
end
|
93
104
|
|
94
105
|
it "should map failure exceptions to raise" do
|
@@ -97,9 +108,9 @@ describe ExceptionalSynchrony::CallbackExceptions do
|
|
97
108
|
deferrable.fail(exception)
|
98
109
|
result = assert_raises(ExceptionalSynchrony::CallbackExceptions::Failure) do
|
99
110
|
ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
100
|
-
end
|
101
|
-
result.
|
102
|
-
result.
|
111
|
+
end.message
|
112
|
+
expect(result).must_match(/ArgumentError/)
|
113
|
+
expect(result).must_match(/Wrong argument!/)
|
103
114
|
end
|
104
115
|
|
105
116
|
it "should map timeout failure to raise TimeoutError" do
|
@@ -114,6 +125,47 @@ describe ExceptionalSynchrony::CallbackExceptions do
|
|
114
125
|
ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
115
126
|
end
|
116
127
|
end
|
128
|
+
|
129
|
+
it "should map Errno::ETIMEDOUT to TimeoutError" do
|
130
|
+
deferrable = EM::DefaultDeferrable.new
|
131
|
+
|
132
|
+
def deferrable.error
|
133
|
+
Errno::ETIMEDOUT
|
134
|
+
end
|
135
|
+
|
136
|
+
deferrable.fail(deferrable)
|
137
|
+
assert_raises(Timeout::Error) do
|
138
|
+
ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should map other SystemCallError exceptions to Failures with the error in the message" do
|
143
|
+
deferrable = EM::DefaultDeferrable.new
|
144
|
+
|
145
|
+
def deferrable.error
|
146
|
+
Errno::ECONNREFUSED
|
147
|
+
end
|
148
|
+
|
149
|
+
deferrable.fail(deferrable)
|
150
|
+
result = assert_raises(ExceptionalSynchrony::CallbackExceptions::Failure) do
|
151
|
+
ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
152
|
+
end
|
153
|
+
expect(result.message).must_match(/\AERROR = Errno::ECONNREFUSED; RESULT = #<EventMachine::DefaultDeferrable/)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should map any other errors to Failure with the error in the message" do
|
157
|
+
deferrable = EM::DefaultDeferrable.new
|
158
|
+
|
159
|
+
def deferrable.error
|
160
|
+
ArgumentError.new("Some errror")
|
161
|
+
end
|
162
|
+
|
163
|
+
deferrable.fail(deferrable)
|
164
|
+
result = assert_raises(ExceptionalSynchrony::CallbackExceptions::Failure) do
|
165
|
+
ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
166
|
+
end
|
167
|
+
expect(result.message).must_match(/\AERROR = #<ArgumentError: Some errror>; RESULT = #<EventMachine::DefaultDeferrable/)
|
168
|
+
end
|
117
169
|
end
|
118
170
|
|
119
171
|
describe "no status" do
|
@@ -122,29 +174,29 @@ describe ExceptionalSynchrony::CallbackExceptions do
|
|
122
174
|
result = assert_raises(ArgumentError) do
|
123
175
|
ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
|
124
176
|
end
|
125
|
-
result.message.must_match
|
177
|
+
expect(result.message).must_match(/no deferred status set yet/i)
|
126
178
|
end
|
127
179
|
end
|
128
180
|
end
|
129
181
|
|
130
182
|
describe "return_exception" do
|
131
183
|
it "should return the value if no exception" do
|
132
|
-
ExceptionalSynchrony::CallbackExceptions.return_exception do
|
184
|
+
expect(ExceptionalSynchrony::CallbackExceptions.return_exception do
|
133
185
|
14
|
134
|
-
end.must_equal
|
186
|
+
end).must_equal(14)
|
135
187
|
end
|
136
188
|
|
137
189
|
it "should yield its args" do
|
138
|
-
ExceptionalSynchrony::CallbackExceptions.return_exception(0, 1) do |a, b|
|
190
|
+
expect(ExceptionalSynchrony::CallbackExceptions.return_exception(0, 1) do |a, b|
|
139
191
|
assert_equal [0, 1], [a, b]
|
140
192
|
14
|
141
|
-
end.must_equal
|
193
|
+
end).must_equal(14)
|
142
194
|
end
|
143
195
|
|
144
196
|
it "should rescue any exception that was raised and return it" do
|
145
|
-
ExceptionalSynchrony::CallbackExceptions.return_exception do
|
197
|
+
expect(ExceptionalSynchrony::CallbackExceptions.return_exception do
|
146
198
|
raise ArgumentError, "An argument error occurred"
|
147
|
-
end.inspect.must_equal
|
199
|
+
end.inspect).must_equal(ArgumentError.new("An argument error occurred").inspect)
|
148
200
|
end
|
149
201
|
end
|
150
202
|
end
|
@@ -3,10 +3,39 @@ require_relative '../test_helper'
|
|
3
3
|
describe ExceptionalSynchrony::EventMachineProxy do
|
4
4
|
include TestHelper
|
5
5
|
|
6
|
+
class RunProxyMock
|
7
|
+
class << self
|
8
|
+
def run(&block)
|
9
|
+
block.call
|
10
|
+
:run
|
11
|
+
end
|
12
|
+
|
13
|
+
def error_handler
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class SynchronyProxyMock < RunProxyMock
|
19
|
+
class << self
|
20
|
+
def synchrony(&block)
|
21
|
+
block.call
|
22
|
+
:synchrony
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop_em_after_defers_finish!(em)
|
28
|
+
check_finished_counter = 0
|
29
|
+
em.add_periodic_timer(0.1) do
|
30
|
+
(check_finished_counter += 1) > 20 and raise "defer never finished!"
|
31
|
+
em.defers_finished? and em.stop
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
6
35
|
before do
|
7
36
|
@em = ExceptionalSynchrony::EventMachineProxy.new(EventMachine, nil)
|
8
37
|
@yielded_value = nil
|
9
|
-
@block =
|
38
|
+
@block = -> (value) { @yielded_value = value }
|
10
39
|
end
|
11
40
|
|
12
41
|
it "should proxy add_timer" do
|
@@ -39,7 +68,68 @@ describe ExceptionalSynchrony::EventMachineProxy do
|
|
39
68
|
ServerClass = Class.new
|
40
69
|
mock(EventMachine).connect(ServerClass, 8080, :handler, :extra_arg).yields(:called)
|
41
70
|
@em.connect(ServerClass, 8080, :handler, :extra_arg, &@block)
|
42
|
-
@yielded_value.must_equal
|
71
|
+
expect(@yielded_value).must_equal(:called)
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#yield_to_reactor" do
|
75
|
+
it "should give control to other threads when the reactor is running" do
|
76
|
+
mock(@em).reactor_running? { true }
|
77
|
+
mock(EventMachine::Synchrony).sleep(0)
|
78
|
+
@em.yield_to_reactor
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be a no-op if the reactor is not running" do
|
82
|
+
mock(@em).reactor_running? { false }
|
83
|
+
stub(EventMachine::Synchrony).sleep(0) { raise "Should not sleep!" }
|
84
|
+
@em.yield_to_reactor
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#defer" do
|
89
|
+
before do
|
90
|
+
logger = Logger.new(STDERR)
|
91
|
+
logger.extend ContextualLogger::LoggerMixin
|
92
|
+
ExceptionHandling.logger = logger
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should output its block's output when it doesn't raise an error, by default" do
|
96
|
+
@em.run do
|
97
|
+
assert_equal 12, @em.defer("#defer success") { 12 }
|
98
|
+
@em.stop
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should not wait for its block to run if option is passed" do
|
103
|
+
@block_ran = false
|
104
|
+
|
105
|
+
@em.run do
|
106
|
+
assert_nil @em.defer("#defer success", wait_for_result: false) { @block_ran = true; 12 }
|
107
|
+
refute @block_ran
|
108
|
+
stop_em_after_defers_finish!(@em)
|
109
|
+
end
|
110
|
+
|
111
|
+
assert @block_ran
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should handle exceptions when not waiting for its block to run" do
|
115
|
+
mock(ExceptionHandling).log_error(is_a(RuntimeError), "defer", {})
|
116
|
+
|
117
|
+
@em.run do
|
118
|
+
assert_nil @em.defer("#defer success", wait_for_result: false) { raise RuntimeError, "error in defer" }
|
119
|
+
stop_em_after_defers_finish!(@em)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should raise an error when its block raises an error" do
|
124
|
+
@em.run do
|
125
|
+
ex = assert_raises(ArgumentError) do
|
126
|
+
@em.defer("#defer raising an error") { raise ArgumentError, "!!!" }
|
127
|
+
end
|
128
|
+
|
129
|
+
assert_equal "!!!", ex.message
|
130
|
+
@em.stop
|
131
|
+
end
|
132
|
+
end
|
43
133
|
end
|
44
134
|
|
45
135
|
EXCEPTION = ArgumentError.new('in block')
|
@@ -50,45 +140,68 @@ describe ExceptionalSynchrony::EventMachineProxy do
|
|
50
140
|
end
|
51
141
|
|
52
142
|
it "add_timer" do
|
53
|
-
mock(ExceptionHandling).log_error(EXCEPTION, "add_timer")
|
143
|
+
mock(ExceptionHandling).log_error(EXCEPTION, "add_timer", {})
|
54
144
|
mock(EventMachine::Synchrony).add_timer(10) { |duration, *args| args.first.call }
|
55
145
|
@em.add_timer(10) { raise EXCEPTION }
|
56
146
|
end
|
57
147
|
|
58
148
|
it "add_periodic_timer" do
|
59
|
-
mock(ExceptionHandling).log_error(EXCEPTION, "add_periodic_timer")
|
149
|
+
mock(ExceptionHandling).log_error(EXCEPTION, "add_periodic_timer", {})
|
60
150
|
mock(EventMachine::Synchrony).add_periodic_timer(10) { |duration, *args| args.first.call }
|
61
151
|
@em.add_periodic_timer(10) { raise EXCEPTION }
|
62
152
|
end
|
63
153
|
|
64
154
|
it "next_tick" do
|
65
|
-
mock(ExceptionHandling).log_error(EXCEPTION, "next_tick")
|
155
|
+
mock(ExceptionHandling).log_error(EXCEPTION, "next_tick", {})
|
66
156
|
mock(EventMachine::Synchrony).next_tick { |*args| args.first.call }
|
67
157
|
@em.next_tick { raise EXCEPTION }
|
68
158
|
end
|
69
159
|
end
|
70
160
|
|
71
|
-
|
72
|
-
describe "
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
161
|
+
{ synchrony: SynchronyProxyMock, run: RunProxyMock }.each do |method, proxy_mock|
|
162
|
+
describe "run" do
|
163
|
+
before do
|
164
|
+
@proxy = ExceptionalSynchrony::EventMachineProxy.new(proxy_mock, nil)
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should raise ArgumentError if on_error has invalid value" do
|
168
|
+
assert_raises(ArgumentError, "Invalid on_error: :ignore, must be :log or :raise") do
|
169
|
+
@proxy.run(on_error: :ignore)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe "without error" do
|
174
|
+
[:log, :raise].each do |on_error|
|
175
|
+
describe "when using #{method} and on_error = #{on_error}" do
|
176
|
+
it "should dispatch to the proxy's synchrony method instead of run iff synchrony" do
|
177
|
+
dispatched = false
|
178
|
+
assert_equal method, (@proxy.run(on_error: on_error) { dispatched = true })
|
179
|
+
assert_equal true, dispatched
|
78
180
|
end
|
79
181
|
end
|
182
|
+
end
|
183
|
+
end
|
80
184
|
|
81
|
-
|
82
|
-
|
83
|
-
|
185
|
+
describe "with error" do
|
186
|
+
before do
|
187
|
+
set_test_const('ExceptionalSynchrony::EventMachineProxy::WRAP_WITH_ENSURE_COMPLETELY_SAFE', true)
|
84
188
|
end
|
85
189
|
|
86
|
-
|
190
|
+
describe "when using #{method} and on_error = :log" do
|
191
|
+
it "should rescue any exceptions and log them" do
|
192
|
+
mock(ExceptionHandling).log_error(EXCEPTION, "run_with_error_logging", {})
|
87
193
|
|
88
|
-
|
194
|
+
@proxy.run(on_error: :log) { raise EXCEPTION }
|
195
|
+
end
|
196
|
+
end
|
89
197
|
|
90
|
-
|
91
|
-
|
198
|
+
describe "when using #{method} and on_error = :raise" do
|
199
|
+
it "should rescue any exceptions and raise FatalRunError" do
|
200
|
+
assert_raises(ExceptionalSynchrony::FatalRunError, "Fatal EventMachine run error") do
|
201
|
+
@proxy.run(on_error: :raise) { raise EXCEPTION }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
92
205
|
end
|
93
206
|
end
|
94
207
|
end
|