semian 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/semian/resource.c +257 -0
- data/ext/semian/resource.h +75 -0
- data/ext/semian/semian.c +42 -303
- data/ext/semian/semian.h +67 -0
- data/ext/semian/types.h +45 -0
- data/lib/semian/version.rb +1 -1
- metadata +7 -29
- data/.gitignore +0 -8
- data/.rubocop.yml +0 -113
- data/.ruby-version +0 -1
- data/.travis.yml +0 -15
- data/CHANGELOG.md +0 -11
- data/Gemfile +0 -10
- data/LICENSE.md +0 -21
- data/README.md +0 -576
- data/Rakefile +0 -56
- data/repodb.yml +0 -1
- data/scripts/install_toxiproxy.sh +0 -25
- data/semian.gemspec +0 -29
- data/test/circuit_breaker_test.rb +0 -133
- data/test/fixtures/binary.sql +0 -1
- data/test/helpers/background_helper.rb +0 -25
- data/test/instrumentation_test.rb +0 -61
- data/test/mysql2_test.rb +0 -296
- data/test/net_http_test.rb +0 -515
- data/test/redis_test.rb +0 -237
- data/test/resource_test.rb +0 -322
- data/test/semian_test.rb +0 -32
- data/test/simple_integer_test.rb +0 -49
- data/test/simple_sliding_window_test.rb +0 -65
- data/test/simple_state_test.rb +0 -45
- data/test/test_helper.rb +0 -33
- data/test/unprotected_resource_test.rb +0 -60
data/test/redis_test.rb
DELETED
@@ -1,237 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class TestRedis < Minitest::Test
|
4
|
-
ERROR_TIMEOUT = 5
|
5
|
-
ERROR_THRESHOLD = 1
|
6
|
-
SEMIAN_OPTIONS = {
|
7
|
-
name: :testing,
|
8
|
-
tickets: 1,
|
9
|
-
timeout: 0,
|
10
|
-
error_threshold: ERROR_THRESHOLD,
|
11
|
-
success_threshold: 2,
|
12
|
-
error_timeout: ERROR_TIMEOUT,
|
13
|
-
}
|
14
|
-
|
15
|
-
attr_writer :threads
|
16
|
-
def setup
|
17
|
-
@proxy = Toxiproxy[:semian_test_redis]
|
18
|
-
Semian.destroy(:redis_testing)
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_semian_identifier
|
22
|
-
assert_equal :redis_foo, new_redis(semian: {name: 'foo'}).client.semian_identifier
|
23
|
-
assert_equal :'redis_127.0.0.1:16379/1', new_redis(semian: {name: nil}).client.semian_identifier
|
24
|
-
assert_equal :'redis_example.com:42/1', new_redis(host: 'example.com', port: 42, semian: {name: nil}).client.semian_identifier
|
25
|
-
end
|
26
|
-
|
27
|
-
def test_client_alias
|
28
|
-
redis = connect_to_redis!
|
29
|
-
assert_equal redis.client.semian_resource, redis.semian_resource
|
30
|
-
assert_equal redis.client.semian_identifier, redis.semian_identifier
|
31
|
-
end
|
32
|
-
|
33
|
-
def test_semian_can_be_disabled
|
34
|
-
resource = Redis.new(semian: false).client.semian_resource
|
35
|
-
assert_instance_of Semian::UnprotectedResource, resource
|
36
|
-
end
|
37
|
-
|
38
|
-
def test_semian_resource_in_pipeline
|
39
|
-
redis = connect_to_redis!
|
40
|
-
redis.pipelined do
|
41
|
-
assert_instance_of Semian::ProtectedResource, redis.semian_resource
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def test_connection_errors_open_the_circuit
|
46
|
-
client = connect_to_redis!
|
47
|
-
|
48
|
-
@proxy.downstream(:latency, latency: 600).apply do
|
49
|
-
ERROR_THRESHOLD.times do
|
50
|
-
assert_raises ::Redis::TimeoutError do
|
51
|
-
client.get('foo')
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
assert_raises ::Redis::CircuitOpenError do
|
56
|
-
client.get('foo')
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_command_errors_does_not_open_the_circuit
|
62
|
-
client = connect_to_redis!
|
63
|
-
client.hset('my_hash', 'foo', 'bar')
|
64
|
-
(ERROR_THRESHOLD * 2).times do
|
65
|
-
assert_raises Redis::CommandError do
|
66
|
-
client.get('my_hash')
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def test_connect_instrumentation
|
72
|
-
notified = false
|
73
|
-
subscriber = Semian.subscribe do |event, resource, scope, adapter|
|
74
|
-
notified = true
|
75
|
-
assert_equal :success, event
|
76
|
-
assert_equal Semian[:redis_testing], resource
|
77
|
-
assert_equal :connection, scope
|
78
|
-
assert_equal :redis, adapter
|
79
|
-
end
|
80
|
-
|
81
|
-
connect_to_redis!
|
82
|
-
|
83
|
-
assert notified, 'No notifications has been emitted'
|
84
|
-
ensure
|
85
|
-
Semian.unsubscribe(subscriber)
|
86
|
-
end
|
87
|
-
|
88
|
-
def test_resource_acquisition_for_connect
|
89
|
-
connect_to_redis!
|
90
|
-
|
91
|
-
Semian[:redis_testing].acquire do
|
92
|
-
error = assert_raises Redis::ResourceBusyError do
|
93
|
-
connect_to_redis!
|
94
|
-
end
|
95
|
-
assert_equal :redis_testing, error.semian_identifier
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def test_redis_connection_errors_are_tagged_with_the_resource_identifier
|
100
|
-
@proxy.downstream(:latency, latency: 600).apply do
|
101
|
-
error = assert_raises ::Redis::TimeoutError do
|
102
|
-
connect_to_redis!
|
103
|
-
end
|
104
|
-
assert_equal :redis_testing, error.semian_identifier
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def test_other_redis_errors_are_not_tagged_with_the_resource_identifier
|
109
|
-
client = connect_to_redis!
|
110
|
-
client.set('foo', 'bar')
|
111
|
-
error = assert_raises ::Redis::CommandError do
|
112
|
-
client.hget('foo', 'bar')
|
113
|
-
end
|
114
|
-
refute error.respond_to?(:semian_identifier)
|
115
|
-
end
|
116
|
-
|
117
|
-
def test_resource_timeout_on_connect
|
118
|
-
@proxy.downstream(:latency, latency: 500).apply do
|
119
|
-
background { connect_to_redis! }
|
120
|
-
|
121
|
-
assert_raises Redis::ResourceBusyError do
|
122
|
-
connect_to_redis!
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def test_circuit_breaker_on_connect
|
128
|
-
@proxy.downstream(:latency, latency: 500).apply do
|
129
|
-
background { connect_to_redis! }
|
130
|
-
|
131
|
-
ERROR_THRESHOLD.times do
|
132
|
-
assert_raises Redis::ResourceBusyError do
|
133
|
-
connect_to_redis!
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
yield_to_background
|
139
|
-
|
140
|
-
assert_raises Redis::CircuitOpenError do
|
141
|
-
connect_to_redis!
|
142
|
-
end
|
143
|
-
|
144
|
-
Timecop.travel(ERROR_TIMEOUT + 1) do
|
145
|
-
connect_to_redis!
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def test_query_instrumentation
|
150
|
-
client = connect_to_redis!
|
151
|
-
|
152
|
-
notified = false
|
153
|
-
subscriber = Semian.subscribe do |event, resource, scope, adapter|
|
154
|
-
notified = true
|
155
|
-
assert_equal :success, event
|
156
|
-
assert_equal Semian[:redis_testing], resource
|
157
|
-
assert_equal :query, scope
|
158
|
-
assert_equal :redis, adapter
|
159
|
-
end
|
160
|
-
|
161
|
-
client.get('foo')
|
162
|
-
|
163
|
-
assert notified, 'No notifications has been emitted'
|
164
|
-
ensure
|
165
|
-
Semian.unsubscribe(subscriber)
|
166
|
-
end
|
167
|
-
|
168
|
-
def test_resource_acquisition_for_query
|
169
|
-
client = connect_to_redis!
|
170
|
-
|
171
|
-
Semian[:redis_testing].acquire do
|
172
|
-
assert_raises Redis::ResourceBusyError do
|
173
|
-
client.get('foo')
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
def test_resource_timeout_on_query
|
179
|
-
client = connect_to_redis!
|
180
|
-
client2 = connect_to_redis!
|
181
|
-
|
182
|
-
@proxy.downstream(:latency, latency: 500).apply do
|
183
|
-
background { client2.get('foo') }
|
184
|
-
|
185
|
-
assert_raises Redis::ResourceBusyError do
|
186
|
-
client.get('foo')
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def test_circuit_breaker_on_query
|
192
|
-
client = connect_to_redis!
|
193
|
-
client2 = connect_to_redis!
|
194
|
-
|
195
|
-
client.set('foo', 2)
|
196
|
-
|
197
|
-
@proxy.downstream(:latency, latency: 1000).apply do
|
198
|
-
background { client2.get('foo') }
|
199
|
-
|
200
|
-
ERROR_THRESHOLD.times do
|
201
|
-
assert_raises Redis::ResourceBusyError do
|
202
|
-
client.get('foo')
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
yield_to_background
|
208
|
-
|
209
|
-
assert_raises Redis::CircuitOpenError do
|
210
|
-
client.get('foo')
|
211
|
-
end
|
212
|
-
|
213
|
-
Timecop.travel(ERROR_TIMEOUT + 1) do
|
214
|
-
assert_equal '2', client.get('foo')
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
private
|
219
|
-
|
220
|
-
def new_redis(options = {})
|
221
|
-
semian_options = SEMIAN_OPTIONS.merge(options.delete(:semian) || {})
|
222
|
-
Redis.new({
|
223
|
-
host: '127.0.0.1',
|
224
|
-
port: 16_379,
|
225
|
-
reconnect_attempts: 0,
|
226
|
-
db: 1,
|
227
|
-
timeout: 0.5,
|
228
|
-
semian: semian_options,
|
229
|
-
}.merge(options))
|
230
|
-
end
|
231
|
-
|
232
|
-
def connect_to_redis!(semian_options = {})
|
233
|
-
redis = new_redis(semian: semian_options)
|
234
|
-
redis.client.connect
|
235
|
-
redis
|
236
|
-
end
|
237
|
-
end
|
data/test/resource_test.rb
DELETED
@@ -1,322 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class TestResource < Minitest::Test
|
4
|
-
def setup
|
5
|
-
Semian.destroy(:testing)
|
6
|
-
rescue
|
7
|
-
nil
|
8
|
-
end
|
9
|
-
|
10
|
-
def teardown
|
11
|
-
destroy_resources
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_initialize_invalid_args
|
15
|
-
assert_raises TypeError do
|
16
|
-
create_resource 123, tickets: 2
|
17
|
-
end
|
18
|
-
assert_raises ArgumentError do
|
19
|
-
create_resource :testing, tickets: -1
|
20
|
-
end
|
21
|
-
assert_raises ArgumentError do
|
22
|
-
create_resource :testing, tickets: 1_000_000
|
23
|
-
end
|
24
|
-
assert_raises TypeError do
|
25
|
-
create_resource :testing, tickets: 2, permissions: 'test'
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_initialize_with_float
|
30
|
-
resource = create_resource :testing, tickets: 1.0
|
31
|
-
assert resource
|
32
|
-
assert_equal 1, resource.tickets
|
33
|
-
end
|
34
|
-
|
35
|
-
def test_max_tickets
|
36
|
-
assert Semian::MAX_TICKETS > 0
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_register
|
40
|
-
create_resource :testing, tickets: 2
|
41
|
-
end
|
42
|
-
|
43
|
-
def test_register_with_no_tickets_raises
|
44
|
-
assert_raises Semian::SyscallError do
|
45
|
-
create_resource :testing, tickets: 0
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def test_acquire
|
50
|
-
acquired = false
|
51
|
-
resource = create_resource :testing, tickets: 1
|
52
|
-
resource.acquire { acquired = true }
|
53
|
-
assert acquired
|
54
|
-
end
|
55
|
-
|
56
|
-
def test_acquire_return_val
|
57
|
-
resource = create_resource :testing, tickets: 1
|
58
|
-
val = resource.acquire { 1234 }
|
59
|
-
assert_equal 1234, val
|
60
|
-
end
|
61
|
-
|
62
|
-
def test_acquire_timeout
|
63
|
-
resource = create_resource :testing, tickets: 1, timeout: 0.05
|
64
|
-
|
65
|
-
acquired = false
|
66
|
-
m = Monitor.new
|
67
|
-
cond = m.new_cond
|
68
|
-
|
69
|
-
t = Thread.start do
|
70
|
-
m.synchronize do
|
71
|
-
cond.wait_until { acquired }
|
72
|
-
assert_raises Semian::TimeoutError do
|
73
|
-
resource.acquire { refute true }
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
resource.acquire do
|
79
|
-
acquired = true
|
80
|
-
m.synchronize { cond.signal }
|
81
|
-
sleep 0.2
|
82
|
-
end
|
83
|
-
|
84
|
-
t.join
|
85
|
-
|
86
|
-
assert acquired
|
87
|
-
end
|
88
|
-
|
89
|
-
def test_acquire_timeout_override
|
90
|
-
resource = create_resource :testing, tickets: 1, timeout: 0.01
|
91
|
-
|
92
|
-
acquired = false
|
93
|
-
thread_acquired = false
|
94
|
-
m = Monitor.new
|
95
|
-
cond = m.new_cond
|
96
|
-
|
97
|
-
t = Thread.start do
|
98
|
-
m.synchronize do
|
99
|
-
cond.wait_until { acquired }
|
100
|
-
resource.acquire(timeout: 1) { thread_acquired = true }
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
resource.acquire do
|
105
|
-
acquired = true
|
106
|
-
m.synchronize { cond.signal }
|
107
|
-
sleep 0.2
|
108
|
-
end
|
109
|
-
|
110
|
-
t.join
|
111
|
-
|
112
|
-
assert acquired
|
113
|
-
assert thread_acquired
|
114
|
-
end
|
115
|
-
|
116
|
-
def test_acquire_with_fork
|
117
|
-
resource = create_resource :testing, tickets: 2, timeout: 0.5
|
118
|
-
|
119
|
-
resource.acquire do
|
120
|
-
fork do
|
121
|
-
resource.acquire do
|
122
|
-
assert_raises Semian::TimeoutError do
|
123
|
-
resource.acquire {}
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
Process.wait
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def test_acquire_releases_on_kill
|
133
|
-
resource = create_resource :testing, tickets: 1, timeout: 0.1
|
134
|
-
acquired = false
|
135
|
-
|
136
|
-
# Ghetto process synchronization
|
137
|
-
file = Tempfile.new('semian')
|
138
|
-
path = file.path
|
139
|
-
file.close!
|
140
|
-
|
141
|
-
pid = fork do
|
142
|
-
resource.acquire do
|
143
|
-
FileUtils.touch(path)
|
144
|
-
sleep 1000
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
sleep 0.1 until File.exist?(path)
|
149
|
-
assert_raises Semian::TimeoutError do
|
150
|
-
resource.acquire {}
|
151
|
-
end
|
152
|
-
|
153
|
-
Process.kill("KILL", pid)
|
154
|
-
resource.acquire { acquired = true }
|
155
|
-
assert acquired
|
156
|
-
|
157
|
-
Process.wait
|
158
|
-
ensure
|
159
|
-
FileUtils.rm_f(path) if path
|
160
|
-
end
|
161
|
-
|
162
|
-
def test_count
|
163
|
-
resource = create_resource :testing, tickets: 2
|
164
|
-
acquired = false
|
165
|
-
|
166
|
-
resource.acquire do
|
167
|
-
acquired = true
|
168
|
-
assert_equal 1, resource.count
|
169
|
-
assert_equal 2, resource.tickets
|
170
|
-
end
|
171
|
-
|
172
|
-
assert acquired
|
173
|
-
end
|
174
|
-
|
175
|
-
def test_sem_undo
|
176
|
-
resource = create_resource :testing, tickets: 1
|
177
|
-
|
178
|
-
# Ensure we don't hit ERANGE errors caused by lack of SEM_UNDO on semop* calls
|
179
|
-
# by doing an acquire > SEMVMX (32767) times:
|
180
|
-
#
|
181
|
-
# See: http://lxr.free-electrons.com/source/ipc/sem.c?v=3.8#L419
|
182
|
-
(1 << 16).times do # do an acquire 64k times
|
183
|
-
resource.acquire do
|
184
|
-
1
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def test_destroy
|
190
|
-
resource = create_resource :testing, tickets: 1
|
191
|
-
resource.destroy
|
192
|
-
assert_raises Semian::SyscallError do
|
193
|
-
resource.acquire {}
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
def test_permissions
|
198
|
-
resource = create_resource :testing, permissions: 0600, tickets: 1
|
199
|
-
semid = resource.semid
|
200
|
-
`ipcs -s `.lines.each do |line|
|
201
|
-
if /\s#{semid}\s/.match(line)
|
202
|
-
assert_equal '600', line.split[3]
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
resource = create_resource :testing, permissions: 0660, tickets: 1
|
207
|
-
semid = resource.semid
|
208
|
-
`ipcs -s `.lines.each do |line|
|
209
|
-
if /\s#{semid}\s/.match(line)
|
210
|
-
assert_equal '660', line.split[3]
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def test_resize_tickets_increase
|
216
|
-
resource = create_resource :testing, tickets: 1
|
217
|
-
|
218
|
-
acquired = false
|
219
|
-
m = Monitor.new
|
220
|
-
cond = m.new_cond
|
221
|
-
|
222
|
-
t = Thread.start do
|
223
|
-
m.synchronize do
|
224
|
-
cond.wait_until { acquired }
|
225
|
-
|
226
|
-
resource = create_resource :testing, tickets: 5
|
227
|
-
assert_equal 4, resource.count
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
assert_equal 1, resource.count
|
232
|
-
|
233
|
-
resource.acquire do
|
234
|
-
acquired = true
|
235
|
-
m.synchronize { cond.signal }
|
236
|
-
sleep 0.2
|
237
|
-
end
|
238
|
-
|
239
|
-
t.join
|
240
|
-
|
241
|
-
assert_equal 5, resource.count
|
242
|
-
end
|
243
|
-
|
244
|
-
def test_resize_tickets_decrease
|
245
|
-
resource = create_resource :testing, tickets: 5
|
246
|
-
|
247
|
-
acquired = false
|
248
|
-
m = Monitor.new
|
249
|
-
cond = m.new_cond
|
250
|
-
|
251
|
-
t = Thread.start do
|
252
|
-
m.synchronize do
|
253
|
-
cond.wait_until { acquired }
|
254
|
-
|
255
|
-
resource = create_resource :testing, tickets: 1
|
256
|
-
assert_equal 0, resource.count
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
assert_equal 5, resource.count
|
261
|
-
|
262
|
-
resource.acquire do
|
263
|
-
acquired = true
|
264
|
-
m.synchronize { cond.signal }
|
265
|
-
sleep 0.2
|
266
|
-
end
|
267
|
-
|
268
|
-
t.join
|
269
|
-
|
270
|
-
assert_equal 1, resource.count
|
271
|
-
end
|
272
|
-
|
273
|
-
def test_multiple_register_with_fork
|
274
|
-
f = Tempfile.new('semian_test')
|
275
|
-
|
276
|
-
begin
|
277
|
-
f.flock(File::LOCK_EX)
|
278
|
-
|
279
|
-
children = []
|
280
|
-
5.times do
|
281
|
-
children << fork do
|
282
|
-
acquired = false
|
283
|
-
|
284
|
-
f.flock(File::LOCK_SH)
|
285
|
-
create_resource(:testing, tickets: 5).acquire do |resource|
|
286
|
-
assert resource.count < 5
|
287
|
-
acquired = true
|
288
|
-
end
|
289
|
-
assert acquired
|
290
|
-
end
|
291
|
-
end
|
292
|
-
children.compact!
|
293
|
-
|
294
|
-
f.flock(File::LOCK_UN)
|
295
|
-
|
296
|
-
children.delete(Process.wait) while children.any?
|
297
|
-
|
298
|
-
assert_equal 5, create_resource(:testing, tickets: 0).count
|
299
|
-
ensure
|
300
|
-
f.close!
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
def create_resource(*args)
|
305
|
-
@resources ||= []
|
306
|
-
resource = Semian::Resource.new(*args)
|
307
|
-
@resources << resource
|
308
|
-
resource
|
309
|
-
end
|
310
|
-
|
311
|
-
def destroy_resources
|
312
|
-
return unless @resources
|
313
|
-
@resources.each do |resource|
|
314
|
-
begin
|
315
|
-
resource.destroy
|
316
|
-
rescue
|
317
|
-
nil
|
318
|
-
end
|
319
|
-
end
|
320
|
-
@resources = []
|
321
|
-
end
|
322
|
-
end
|