semian 0.0.8 → 0.1.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.travis.yml +4 -1
- data/README.md +1 -1
- data/Rakefile +7 -6
- data/ext/semian/semian.c +7 -13
- data/lib/semian/circuit_breaker.rb +135 -0
- data/lib/semian/instrumentable.rb +22 -0
- data/lib/semian/mysql2.rb +77 -0
- data/lib/semian/platform.rb +2 -2
- data/lib/semian/protected_resource.rb +36 -0
- data/lib/semian/resource.rb +26 -0
- data/lib/semian/version.rb +2 -2
- data/lib/semian.rb +80 -27
- data/scripts/install_toxiproxy.sh +25 -0
- data/semian.gemspec +3 -0
- data/test/fixtures/toxiproxy.json +7 -0
- data/test/test_circuit_breaker.rb +114 -0
- data/test/test_instrumentation.rb +62 -0
- data/test/test_mysql2.rb +186 -0
- data/test/{test_semian.rb → test_resource.rb} +73 -55
- data/test/test_unsupported.rb +7 -3
- data.tar.gz.sig +0 -0
- metadata +55 -4
- metadata.gz.sig +1 -2
- data/lib/semian/unsupported.rb +0 -26
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'semian'
|
3
|
+
require 'timecop'
|
4
|
+
|
5
|
+
class TestCircuitBreaker < MiniTest::Unit::TestCase
|
6
|
+
SomeError = Class.new(StandardError)
|
7
|
+
|
8
|
+
def setup
|
9
|
+
Semian.destroy(:testing) rescue nil
|
10
|
+
Semian.register(:testing, tickets: 1, exceptions: [SomeError], error_threshold: 2, error_timeout: 5, success_threshold: 1)
|
11
|
+
@resource = Semian[:testing]
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_with_fallback_value_returns_the_value
|
15
|
+
result = @resource.with_fallback(42) do
|
16
|
+
raise SomeError
|
17
|
+
end
|
18
|
+
assert_equal 42, result
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_with_fallback_block_call_the_block
|
22
|
+
result = @resource.with_fallback(-> { 42 }) do
|
23
|
+
raise SomeError
|
24
|
+
end
|
25
|
+
assert_equal 42, result
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_unknown_exceptions_are_not_rescued
|
29
|
+
assert_raises RuntimeError do
|
30
|
+
@resource.with_fallback(42) do
|
31
|
+
raise RuntimeError
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_all_semian_exceptions_are_rescued
|
37
|
+
result = @resource.with_fallback(42) do
|
38
|
+
raise Semian::BaseError
|
39
|
+
end
|
40
|
+
assert_equal 42, result
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_acquire_yield_when_the_circuit_is_closed
|
44
|
+
block_called = false
|
45
|
+
@resource.acquire { block_called = true }
|
46
|
+
assert_equal true, block_called
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_acquire_raises_circuit_open_error_when_the_circuit_is_open
|
50
|
+
open_circuit!
|
51
|
+
assert_raises Semian::OpenCircuitError do
|
52
|
+
@resource.acquire { 1 + 1 }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_after_error_threshold_the_circuit_is_open
|
57
|
+
open_circuit!
|
58
|
+
assert_circuit_opened
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_after_error_timeout_is_elapsed_requests_are_attempted_again
|
62
|
+
half_open_cicuit!
|
63
|
+
assert_circuit_closed
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_until_success_threshold_is_reached_a_single_error_will_reopen_the_circuit
|
67
|
+
half_open_cicuit!
|
68
|
+
trigger_error!
|
69
|
+
assert_circuit_opened
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_once_success_threshold_is_reached_only_error_threshold_will_open_the_circuit_again
|
73
|
+
half_open_cicuit!
|
74
|
+
assert_circuit_closed
|
75
|
+
trigger_error!
|
76
|
+
assert_circuit_closed
|
77
|
+
trigger_error!
|
78
|
+
assert_circuit_opened
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_reset_allow_to_close_the_circuit_and_forget_errors
|
82
|
+
open_circuit!
|
83
|
+
@resource.reset
|
84
|
+
assert_circuit_closed
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def open_circuit!
|
90
|
+
2.times { trigger_error! }
|
91
|
+
end
|
92
|
+
|
93
|
+
def half_open_cicuit!
|
94
|
+
Timecop.travel(Time.now - 10) do
|
95
|
+
open_circuit!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def trigger_error!
|
100
|
+
@resource.with_fallback(42) { raise SomeError }
|
101
|
+
end
|
102
|
+
|
103
|
+
def assert_circuit_closed
|
104
|
+
block_called = false
|
105
|
+
@resource.with_fallback(42) { block_called = true }
|
106
|
+
assert block_called, 'Expected the circuit to be closed, but it was open'
|
107
|
+
end
|
108
|
+
|
109
|
+
def assert_circuit_opened
|
110
|
+
block_called = false
|
111
|
+
@resource.with_fallback(42) { block_called = true }
|
112
|
+
refute block_called, 'Expected the circuit to be open, but it was closed'
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'semian'
|
3
|
+
|
4
|
+
class TestInstrumentation < MiniTest::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
Semian.destroy(:testing) if Semian[:testing]
|
7
|
+
Semian.register(:testing, tickets: 1, error_threshold: 1, error_timeout: 5, success_threshold: 1)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_occupied_instrumentation
|
11
|
+
assert_notify(:success, :occupied) do
|
12
|
+
Semian[:testing].acquire do
|
13
|
+
assert_raises Semian::TimeoutError do
|
14
|
+
Semian[:testing].acquire {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_circuit_open_instrumentation
|
21
|
+
assert_notify(:success, :occupied) do
|
22
|
+
Semian[:testing].acquire do
|
23
|
+
assert_raises Semian::TimeoutError do
|
24
|
+
Semian[:testing].acquire {}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
assert_notify(:circuit_open) do
|
30
|
+
assert_raises Semian::OpenCircuitError do
|
31
|
+
Semian[:testing].acquire {}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_success_instrumentation
|
37
|
+
assert_notify(:success) do
|
38
|
+
Semian[:testing].acquire {}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_success_instrumentation_when_unknown_exceptions_occur
|
43
|
+
assert_notify(:success) do
|
44
|
+
assert_raises RuntimeError do
|
45
|
+
Semian[:testing].acquire { raise "Some error" }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def assert_notify(*expected_events)
|
53
|
+
events = []
|
54
|
+
subscription = Semian.subscribe do |event, resource|
|
55
|
+
events << event
|
56
|
+
end
|
57
|
+
yield
|
58
|
+
assert_equal expected_events, events, "The timeline of events was not as expected"
|
59
|
+
ensure
|
60
|
+
Semian.unsubscribe(subscription)
|
61
|
+
end
|
62
|
+
end
|
data/test/test_mysql2.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'semian/mysql2'
|
3
|
+
require 'toxiproxy'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
class TestMysql2 < MiniTest::Unit::TestCase
|
7
|
+
ERROR_TIMEOUT = 5
|
8
|
+
ERROR_THRESHOLD = 1
|
9
|
+
SEMIAN_OPTIONS = {
|
10
|
+
name: :testing,
|
11
|
+
tickets: 1,
|
12
|
+
timeout: 0,
|
13
|
+
error_threshold: ERROR_THRESHOLD,
|
14
|
+
success_threshold: 2,
|
15
|
+
error_timeout: ERROR_TIMEOUT,
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_writer :threads
|
19
|
+
def setup
|
20
|
+
@proxy = Toxiproxy[:semian_test_mysql]
|
21
|
+
Semian.destroy(:mysql_testing)
|
22
|
+
end
|
23
|
+
|
24
|
+
def teardown
|
25
|
+
threads.each { |t| t.kill }
|
26
|
+
self.threads = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_semian_identifier
|
30
|
+
assert_equal :mysql_foo, FakeMysql.new(semian: {name: 'foo'}).semian_identifier
|
31
|
+
assert_equal :'mysql_localhost:3306', FakeMysql.new.semian_identifier
|
32
|
+
assert_equal :'mysql_127.0.0.1:3306', FakeMysql.new(host: '127.0.0.1').semian_identifier
|
33
|
+
assert_equal :'mysql_example.com:42', FakeMysql.new(host: 'example.com', port: 42).semian_identifier
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_connect_instrumentation
|
37
|
+
notified = false
|
38
|
+
subscriber = Semian.subscribe do |event, resource, scope|
|
39
|
+
notified = true
|
40
|
+
assert_equal :success, event
|
41
|
+
assert_equal Semian[:mysql_testing], resource
|
42
|
+
assert_equal :connect, scope
|
43
|
+
end
|
44
|
+
|
45
|
+
connect_to_mysql!
|
46
|
+
|
47
|
+
assert notified, 'No notification have been emitted'
|
48
|
+
ensure
|
49
|
+
Semian.unsubscribe(subscriber)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_resource_acquisition_for_connect
|
53
|
+
client = connect_to_mysql!
|
54
|
+
|
55
|
+
Semian[:mysql_testing].acquire do
|
56
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
57
|
+
connect_to_mysql!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_resource_timeout_on_connect
|
63
|
+
@proxy.downstream(:latency, latency: 500).apply do
|
64
|
+
background { connect_to_mysql! }
|
65
|
+
|
66
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
67
|
+
connect_to_mysql!
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_circuit_breaker_on_connect
|
73
|
+
@proxy.downstream(:latency, latency: 500).apply do
|
74
|
+
background { connect_to_mysql! }
|
75
|
+
|
76
|
+
ERROR_THRESHOLD.times do
|
77
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
78
|
+
connect_to_mysql!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
yield_to_background
|
84
|
+
|
85
|
+
assert_raises Mysql2::CircuitOpenError do
|
86
|
+
connect_to_mysql!
|
87
|
+
end
|
88
|
+
|
89
|
+
Timecop.travel(ERROR_TIMEOUT + 1) do
|
90
|
+
connect_to_mysql!
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_query_instrumentation
|
95
|
+
client = connect_to_mysql!
|
96
|
+
|
97
|
+
notified = false
|
98
|
+
subscriber = Semian.subscribe do |event, resource, scope|
|
99
|
+
notified = true
|
100
|
+
assert_equal :success, event
|
101
|
+
assert_equal Semian[:mysql_testing], resource
|
102
|
+
assert_equal :query, scope
|
103
|
+
end
|
104
|
+
|
105
|
+
client.query('SELECT 1 + 1;')
|
106
|
+
|
107
|
+
assert notified, 'No notification have been emitted'
|
108
|
+
ensure
|
109
|
+
Semian.unsubscribe(subscriber)
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_resource_acquisition_for_query
|
113
|
+
client = connect_to_mysql!
|
114
|
+
|
115
|
+
Semian[:mysql_testing].acquire do
|
116
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
117
|
+
client.query('SELECT 1 + 1;')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_resource_timeout_on_query
|
123
|
+
client = connect_to_mysql!
|
124
|
+
client2 = connect_to_mysql!
|
125
|
+
|
126
|
+
@proxy.downstream(:latency, latency: 500).apply do
|
127
|
+
background { client2.query('SELECT 1 + 1;') }
|
128
|
+
|
129
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
130
|
+
client.query('SELECT 1 + 1;')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_circuit_breaker_on_query
|
136
|
+
client = connect_to_mysql!
|
137
|
+
client2 = connect_to_mysql!
|
138
|
+
|
139
|
+
@proxy.downstream(:latency, latency: 1000).apply do
|
140
|
+
background { client2.query('SELECT 1 + 1;') }
|
141
|
+
|
142
|
+
ERROR_THRESHOLD.times do
|
143
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
144
|
+
client.query('SELECT 1 + 1;')
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
yield_to_background
|
150
|
+
|
151
|
+
assert_raises Mysql2::CircuitOpenError do
|
152
|
+
client.query('SELECT 1 + 1;')
|
153
|
+
end
|
154
|
+
|
155
|
+
Timecop.travel(ERROR_TIMEOUT + 1) do
|
156
|
+
assert_equal 2, client.query('SELECT 1 + 1 as sum;').to_a.first['sum']
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def background(&block)
|
163
|
+
thread = Thread.new(&block)
|
164
|
+
threads << thread
|
165
|
+
thread.join(0.1)
|
166
|
+
thread
|
167
|
+
end
|
168
|
+
|
169
|
+
def threads
|
170
|
+
@threads ||= []
|
171
|
+
end
|
172
|
+
|
173
|
+
def yield_to_background
|
174
|
+
threads.each(&:join)
|
175
|
+
end
|
176
|
+
|
177
|
+
def connect_to_mysql!(semian_options = {})
|
178
|
+
Mysql2::Client.new(host: '127.0.0.1', port: '13306', semian: SEMIAN_OPTIONS.merge(semian_options))
|
179
|
+
end
|
180
|
+
|
181
|
+
class FakeMysql < Mysql2::Client
|
182
|
+
private
|
183
|
+
def connect(*)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -1,25 +1,29 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'semian'
|
3
3
|
require 'tempfile'
|
4
4
|
require 'fileutils'
|
5
5
|
|
6
|
-
class
|
6
|
+
class TestResource < MiniTest::Unit::TestCase
|
7
7
|
def setup
|
8
|
-
Semian
|
8
|
+
Semian.destroy(:testing) rescue nil
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def teardown
|
12
|
+
destroy_resources
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_initialize_invalid_args
|
12
16
|
assert_raises TypeError do
|
13
|
-
|
17
|
+
create_resource 123, tickets: 2
|
14
18
|
end
|
15
19
|
assert_raises ArgumentError do
|
16
|
-
|
20
|
+
create_resource :testing, tickets: -1
|
17
21
|
end
|
18
22
|
assert_raises ArgumentError do
|
19
|
-
|
23
|
+
create_resource :testing, tickets: 1_000_000
|
20
24
|
end
|
21
25
|
assert_raises TypeError do
|
22
|
-
|
26
|
+
create_resource :testing, tickets: 2, permissions: 'test'
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
@@ -28,30 +32,30 @@ class TestSemian < Test::Unit::TestCase
|
|
28
32
|
end
|
29
33
|
|
30
34
|
def test_register
|
31
|
-
|
35
|
+
create_resource :testing, tickets: 2
|
32
36
|
end
|
33
37
|
|
34
38
|
def test_register_with_no_tickets_raises
|
35
39
|
assert_raises Semian::SyscallError do
|
36
|
-
|
40
|
+
create_resource :testing, tickets: 0
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
40
44
|
def test_acquire
|
41
45
|
acquired = false
|
42
|
-
|
43
|
-
|
46
|
+
resource = create_resource :testing, tickets: 1
|
47
|
+
resource.acquire { acquired = true }
|
44
48
|
assert acquired
|
45
49
|
end
|
46
50
|
|
47
51
|
def test_acquire_return_val
|
48
|
-
|
49
|
-
val =
|
52
|
+
resource = create_resource :testing, tickets: 1
|
53
|
+
val = resource.acquire { 1234 }
|
50
54
|
assert_equal 1234, val
|
51
55
|
end
|
52
56
|
|
53
57
|
def test_acquire_timeout
|
54
|
-
|
58
|
+
resource = create_resource :testing, tickets: 1, timeout: 0.05
|
55
59
|
|
56
60
|
acquired = false
|
57
61
|
m = Monitor.new
|
@@ -61,12 +65,12 @@ class TestSemian < Test::Unit::TestCase
|
|
61
65
|
m.synchronize do
|
62
66
|
cond.wait_until { acquired }
|
63
67
|
assert_raises Semian::TimeoutError do
|
64
|
-
|
68
|
+
resource.acquire { refute true }
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
69
|
-
|
73
|
+
resource.acquire do
|
70
74
|
acquired = true
|
71
75
|
m.synchronize { cond.signal }
|
72
76
|
sleep 0.2
|
@@ -78,7 +82,7 @@ class TestSemian < Test::Unit::TestCase
|
|
78
82
|
end
|
79
83
|
|
80
84
|
def test_acquire_timeout_override
|
81
|
-
|
85
|
+
resource = create_resource :testing, tickets: 1, timeout: 0.01
|
82
86
|
|
83
87
|
acquired = false
|
84
88
|
thread_acquired = false
|
@@ -88,11 +92,11 @@ class TestSemian < Test::Unit::TestCase
|
|
88
92
|
t = Thread.start do
|
89
93
|
m.synchronize do
|
90
94
|
cond.wait_until { acquired }
|
91
|
-
|
95
|
+
resource.acquire(timeout: 1) { thread_acquired = true }
|
92
96
|
end
|
93
97
|
end
|
94
98
|
|
95
|
-
|
99
|
+
resource.acquire do
|
96
100
|
acquired = true
|
97
101
|
m.synchronize { cond.signal }
|
98
102
|
sleep 0.2
|
@@ -105,14 +109,13 @@ class TestSemian < Test::Unit::TestCase
|
|
105
109
|
end
|
106
110
|
|
107
111
|
def test_acquire_with_fork
|
108
|
-
|
112
|
+
resource = create_resource :testing, tickets: 2, timeout: 0.5
|
109
113
|
|
110
|
-
|
114
|
+
resource.acquire do
|
111
115
|
pid = fork do
|
112
|
-
|
113
|
-
Semian[:testing].acquire do
|
116
|
+
resource.acquire do
|
114
117
|
assert_raises Semian::TimeoutError do
|
115
|
-
|
118
|
+
resource.acquire { }
|
116
119
|
end
|
117
120
|
end
|
118
121
|
end
|
@@ -123,7 +126,7 @@ class TestSemian < Test::Unit::TestCase
|
|
123
126
|
|
124
127
|
def test_acquire_releases_on_kill
|
125
128
|
begin
|
126
|
-
|
129
|
+
resource = create_resource :testing, tickets: 1, timeout: 0.1
|
127
130
|
acquired = false
|
128
131
|
|
129
132
|
# Ghetto process synchronization
|
@@ -132,7 +135,7 @@ class TestSemian < Test::Unit::TestCase
|
|
132
135
|
file.close!
|
133
136
|
|
134
137
|
pid = fork do
|
135
|
-
|
138
|
+
resource.acquire do
|
136
139
|
FileUtils.touch(path)
|
137
140
|
sleep 1000
|
138
141
|
end
|
@@ -140,11 +143,11 @@ class TestSemian < Test::Unit::TestCase
|
|
140
143
|
|
141
144
|
sleep 0.1 until File.exists?(path)
|
142
145
|
assert_raises Semian::TimeoutError do
|
143
|
-
|
146
|
+
resource.acquire {}
|
144
147
|
end
|
145
148
|
|
146
149
|
Process.kill("KILL", pid)
|
147
|
-
|
150
|
+
resource.acquire { acquired = true }
|
148
151
|
assert acquired
|
149
152
|
|
150
153
|
Process.wait
|
@@ -154,50 +157,51 @@ class TestSemian < Test::Unit::TestCase
|
|
154
157
|
end
|
155
158
|
|
156
159
|
def test_count
|
157
|
-
|
160
|
+
resource = create_resource :testing, tickets: 2
|
158
161
|
acquired = false
|
159
162
|
|
160
|
-
|
163
|
+
resource.acquire do
|
161
164
|
acquired = true
|
162
|
-
assert_equal 1,
|
165
|
+
assert_equal 1, resource.count
|
166
|
+
assert_equal 2, resource.tickets
|
163
167
|
end
|
164
168
|
|
165
169
|
assert acquired
|
166
170
|
end
|
167
171
|
|
168
172
|
def test_sem_undo
|
169
|
-
|
173
|
+
resource = create_resource :testing, tickets: 1
|
170
174
|
|
171
175
|
# Ensure we don't hit ERANGE errors caused by lack of SEM_UNDO on semop* calls
|
172
176
|
# by doing an acquire > SEMVMX (32767) times:
|
173
177
|
#
|
174
178
|
# See: http://lxr.free-electrons.com/source/ipc/sem.c?v=3.8#L419
|
175
179
|
(1 << 16).times do # do an acquire 64k times
|
176
|
-
|
180
|
+
resource.acquire do
|
177
181
|
1
|
178
182
|
end
|
179
183
|
end
|
180
184
|
end
|
181
185
|
|
182
186
|
def test_destroy
|
183
|
-
|
184
|
-
|
187
|
+
resource = create_resource :testing, tickets: 1
|
188
|
+
resource.destroy
|
185
189
|
assert_raises Semian::SyscallError do
|
186
|
-
|
190
|
+
resource.acquire { }
|
187
191
|
end
|
188
192
|
end
|
189
193
|
|
190
194
|
def test_permissions
|
191
|
-
|
192
|
-
semid =
|
195
|
+
resource = create_resource :testing, permissions: 0600, tickets: 1
|
196
|
+
semid = resource.semid
|
193
197
|
`ipcs -s `.lines.each do |line|
|
194
198
|
if /\s#{semid}\s/.match(line)
|
195
199
|
assert_equal '600', line.split[3]
|
196
200
|
end
|
197
201
|
end
|
198
202
|
|
199
|
-
|
200
|
-
semid =
|
203
|
+
resource = create_resource :testing, permissions: 0660, tickets: 1
|
204
|
+
semid = resource.semid
|
201
205
|
`ipcs -s `.lines.each do |line|
|
202
206
|
if /\s#{semid}\s/.match(line)
|
203
207
|
assert_equal '660', line.split[3]
|
@@ -206,7 +210,7 @@ class TestSemian < Test::Unit::TestCase
|
|
206
210
|
end
|
207
211
|
|
208
212
|
def test_resize_tickets_increase
|
209
|
-
|
213
|
+
resource = create_resource :testing, tickets: 1
|
210
214
|
|
211
215
|
acquired = false
|
212
216
|
m = Monitor.new
|
@@ -216,14 +220,14 @@ class TestSemian < Test::Unit::TestCase
|
|
216
220
|
m.synchronize do
|
217
221
|
cond.wait_until { acquired }
|
218
222
|
|
219
|
-
|
220
|
-
assert_equal 4,
|
223
|
+
resource = create_resource :testing, tickets: 5
|
224
|
+
assert_equal 4, resource.count
|
221
225
|
end
|
222
226
|
end
|
223
227
|
|
224
|
-
assert_equal 1,
|
228
|
+
assert_equal 1, resource.count
|
225
229
|
|
226
|
-
|
230
|
+
resource.acquire do
|
227
231
|
acquired = true
|
228
232
|
m.synchronize { cond.signal }
|
229
233
|
sleep 0.2
|
@@ -231,11 +235,11 @@ class TestSemian < Test::Unit::TestCase
|
|
231
235
|
|
232
236
|
t.join
|
233
237
|
|
234
|
-
assert_equal 5,
|
238
|
+
assert_equal 5, resource.count
|
235
239
|
end
|
236
240
|
|
237
241
|
def test_resize_tickets_decrease
|
238
|
-
|
242
|
+
resource = create_resource :testing, tickets: 5
|
239
243
|
|
240
244
|
acquired = false
|
241
245
|
m = Monitor.new
|
@@ -245,14 +249,14 @@ class TestSemian < Test::Unit::TestCase
|
|
245
249
|
m.synchronize do
|
246
250
|
cond.wait_until { acquired }
|
247
251
|
|
248
|
-
|
249
|
-
assert_equal 0,
|
252
|
+
resource = create_resource :testing, tickets: 1
|
253
|
+
assert_equal 0, resource.count
|
250
254
|
end
|
251
255
|
end
|
252
256
|
|
253
|
-
assert_equal 5,
|
257
|
+
assert_equal 5, resource.count
|
254
258
|
|
255
|
-
|
259
|
+
resource.acquire do
|
256
260
|
acquired = true
|
257
261
|
m.synchronize { cond.signal }
|
258
262
|
sleep 0.2
|
@@ -260,7 +264,7 @@ class TestSemian < Test::Unit::TestCase
|
|
260
264
|
|
261
265
|
t.join
|
262
266
|
|
263
|
-
assert_equal 1,
|
267
|
+
assert_equal 1, resource.count
|
264
268
|
end
|
265
269
|
|
266
270
|
def test_multiple_register_with_fork
|
@@ -275,7 +279,7 @@ class TestSemian < Test::Unit::TestCase
|
|
275
279
|
acquired = false
|
276
280
|
|
277
281
|
f.flock(File::LOCK_SH)
|
278
|
-
|
282
|
+
create_resource(:testing, tickets: 5).acquire do |resource|
|
279
283
|
assert resource.count < 5
|
280
284
|
acquired = true
|
281
285
|
end
|
@@ -290,10 +294,24 @@ class TestSemian < Test::Unit::TestCase
|
|
290
294
|
children.delete(Process.wait)
|
291
295
|
end
|
292
296
|
|
293
|
-
assert_equal 5,
|
297
|
+
assert_equal 5, create_resource(:testing, tickets: 0).count
|
294
298
|
ensure
|
295
299
|
f.close!
|
296
300
|
end
|
297
301
|
end
|
298
302
|
|
303
|
+
def create_resource(*args)
|
304
|
+
@resources ||= []
|
305
|
+
resource = Semian::Resource.new(*args)
|
306
|
+
@resources << resource
|
307
|
+
resource
|
308
|
+
end
|
309
|
+
|
310
|
+
def destroy_resources
|
311
|
+
return unless @resources
|
312
|
+
@resources.each do |resource|
|
313
|
+
resource.destroy rescue nil
|
314
|
+
end
|
315
|
+
@resources = []
|
316
|
+
end
|
299
317
|
end
|
data/test/test_unsupported.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'semian'
|
3
3
|
|
4
|
-
class TestSemian <
|
4
|
+
class TestSemian < MiniTest::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
Semian.destroy(:testing) rescue nil
|
7
|
+
end
|
8
|
+
|
5
9
|
def test_unsupported_acquire_yields
|
6
10
|
acquired = false
|
7
|
-
Semian.register :testing, tickets: 1
|
11
|
+
Semian.register :testing, tickets: 1, error_threshold: 1, error_timeout: 2, success_threshold: 1
|
8
12
|
Semian[:testing].acquire { acquired = true }
|
9
13
|
assert acquired
|
10
14
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|