semian 0.6.0 → 0.6.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.
- 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
|