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.
data/Rakefile DELETED
@@ -1,56 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- begin
3
- require 'rubocop/rake_task'
4
- RuboCop::RakeTask.new
5
- rescue LoadError
6
- end
7
-
8
- # ==========================================================
9
- # Packaging
10
- # ==========================================================
11
-
12
- GEMSPEC = eval(File.read('semian.gemspec'))
13
-
14
- require 'rubygems/package_task'
15
- Gem::PackageTask.new(GEMSPEC) do |_pkg|
16
- end
17
-
18
- # ==========================================================
19
- # Ruby Extension
20
- # ==========================================================
21
-
22
- $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
23
- require 'semian/platform'
24
- if Semian.sysv_semaphores_supported?
25
- require 'rake/extensiontask'
26
- Rake::ExtensionTask.new('semian', GEMSPEC) do |ext|
27
- ext.ext_dir = 'ext/semian'
28
- ext.lib_dir = 'lib/semian'
29
- end
30
- task build: :compile
31
- else
32
- task :build do
33
- end
34
- end
35
-
36
- # ==========================================================
37
- # Testing
38
- # ==========================================================
39
-
40
- require 'rake/testtask'
41
- Rake::TestTask.new 'test' do |t|
42
- t.libs = %w(lib test)
43
- t.pattern = "test/*_test.rb"
44
- end
45
- task test: :build
46
-
47
- # ==========================================================
48
- # Documentation
49
- # ==========================================================
50
- require 'rdoc/task'
51
- RDoc::Task.new do |rdoc|
52
- rdoc.rdoc_files.include("lib/*.rb", "ext/semian/*.c")
53
- end
54
-
55
- task default: :test
56
- task default: :rubocop
data/repodb.yml DELETED
@@ -1 +0,0 @@
1
- classification: library
@@ -1,25 +0,0 @@
1
- set -e
2
-
3
- if which toxiproxy > /dev/null; then
4
- echo "Toxiproxy is already installed."
5
- exit 0
6
- fi
7
-
8
- if which apt-get > /dev/null; then
9
- echo "Installing toxiproxy"
10
- wget -O /tmp/toxiproxy.deb https://github.com/Shopify/toxiproxy/releases/download/v2.0.0/toxiproxy_2.0.0_amd64.deb
11
- sudo dpkg -i /tmp/toxiproxy.deb
12
- sudo service toxiproxy start
13
- exit 0
14
- fi
15
-
16
- if which brew > /dev/null; then
17
- echo "Installing toxiproxy from homebrew."
18
- brew tap shopify/shopify
19
- brew install toxiproxy
20
- brew info toxiproxy
21
- exit 0
22
- fi
23
-
24
- echo "Sorry, there is no toxiproxy package available for your system. You might need to build it from source."
25
- exit 1
@@ -1,29 +0,0 @@
1
- $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
-
3
- require 'semian/version'
4
- require 'semian/platform'
5
-
6
- Gem::Specification.new do |s|
7
- s.name = 'semian'
8
- s.version = Semian::VERSION
9
- s.summary = 'Bulkheading for Ruby with SysV semaphores'
10
- s.description = <<-DOC
11
- A Ruby C extention that is used to control access to shared resources
12
- across process boundaries with SysV semaphores.
13
- DOC
14
- s.homepage = 'https://github.com/shopify/semian'
15
- s.authors = ['Scott Francis', 'Simon Eskildsen']
16
- s.email = 'scott.francis@shopify.com'
17
- s.license = 'MIT'
18
-
19
- s.files = `git ls-files`.split("\n")
20
- s.extensions = ['ext/semian/extconf.rb']
21
- s.add_development_dependency 'rake-compiler', '~> 0.9'
22
- s.add_development_dependency 'rake', '< 11.0'
23
- s.add_development_dependency 'timecop'
24
- s.add_development_dependency 'minitest'
25
- s.add_development_dependency 'mysql2'
26
- s.add_development_dependency 'redis'
27
- s.add_development_dependency 'thin', '~> 1.6.4'
28
- s.add_development_dependency 'toxiproxy', '~> 1.0.0'
29
- end
@@ -1,133 +0,0 @@
1
- require 'test_helper'
2
-
3
- class TestCircuitBreaker < Minitest::Test
4
- SomeError = Class.new(StandardError)
5
-
6
- def setup
7
- begin
8
- Semian.destroy(:testing)
9
- rescue
10
- nil
11
- end
12
- Semian.register(:testing, tickets: 1, exceptions: [SomeError], error_threshold: 2, error_timeout: 5, success_threshold: 1)
13
- @resource = Semian[:testing]
14
- end
15
-
16
- def test_acquire_yield_when_the_circuit_is_closed
17
- block_called = false
18
- @resource.acquire { block_called = true }
19
- assert_equal true, block_called
20
- end
21
-
22
- def test_acquire_raises_circuit_open_error_when_the_circuit_is_open
23
- open_circuit!
24
- assert_raises Semian::OpenCircuitError do
25
- @resource.acquire { 1 + 1 }
26
- end
27
- end
28
-
29
- def test_after_error_threshold_the_circuit_is_open
30
- open_circuit!
31
- assert_circuit_opened
32
- end
33
-
34
- def test_after_error_timeout_is_elapsed_requests_are_attempted_again
35
- half_open_cicuit!
36
- assert_circuit_closed
37
- end
38
-
39
- def test_until_success_threshold_is_reached_a_single_error_will_reopen_the_circuit
40
- half_open_cicuit!
41
- trigger_error!
42
- assert_circuit_opened
43
- end
44
-
45
- def test_once_success_threshold_is_reached_only_error_threshold_will_open_the_circuit_again
46
- half_open_cicuit!
47
- assert_circuit_closed
48
- trigger_error!
49
- assert_circuit_closed
50
- trigger_error!
51
- assert_circuit_opened
52
- end
53
-
54
- def test_reset_allow_to_close_the_circuit_and_forget_errors
55
- open_circuit!
56
- @resource.reset
57
- assert_circuit_closed
58
- end
59
-
60
- def test_errors_more_than_duration_apart_doesnt_open_circuit
61
- Timecop.travel(Time.now - 6) do
62
- trigger_error!
63
- assert_circuit_closed
64
- end
65
-
66
- trigger_error!
67
- assert_circuit_closed
68
- end
69
-
70
- def test_sparse_errors_dont_open_circuit
71
- resource = Semian.register(:three, tickets: 1, exceptions: [SomeError], error_threshold: 3, error_timeout: 5, success_threshold: 1)
72
-
73
- Timecop.travel(-6) do
74
- trigger_error!(resource)
75
- assert_circuit_closed(resource)
76
- end
77
-
78
- Timecop.travel(-1) do
79
- trigger_error!(resource)
80
- assert_circuit_closed(resource)
81
- end
82
-
83
- trigger_error!(resource)
84
- assert_circuit_closed(resource)
85
- ensure
86
- Semian.destroy(:three)
87
- end
88
-
89
- def test_request_allowed_query_doesnt_trigger_transitions
90
- Timecop.travel(Time.now - 6) do
91
- open_circuit!
92
-
93
- refute_predicate @resource, :request_allowed?
94
- assert_predicate @resource, :open?
95
- end
96
-
97
- assert_predicate @resource, :request_allowed?
98
- assert_predicate @resource, :open?
99
- end
100
-
101
- private
102
-
103
- def open_circuit!(resource = @resource)
104
- 2.times { trigger_error!(resource) }
105
- end
106
-
107
- def half_open_cicuit!(resource = @resource)
108
- Timecop.travel(Time.now - 10) do
109
- open_circuit!(resource)
110
- end
111
- end
112
-
113
- def trigger_error!(resource = @resource)
114
- resource.acquire { raise SomeError }
115
- rescue SomeError
116
- end
117
-
118
- def assert_circuit_closed(resource = @resource)
119
- block_called = false
120
- resource.acquire { block_called = true }
121
- assert block_called, 'Expected the circuit to be closed, but it was open'
122
- end
123
-
124
- def assert_circuit_opened(resource = @resource)
125
- open = false
126
- begin
127
- resource.acquire {}
128
- rescue Semian::OpenCircuitError
129
- open = true
130
- end
131
- assert open, 'Expected the circuit to be open, but it was closed'
132
- end
133
- end
@@ -1 +0,0 @@
1
- INSERT IGNORE INTO `theme_template_bodies` (`cityhash`, `body`, `created_at`) VALUES ('716374049952273167', '��{\"current\":{\"bg_color\":\"#ff0000\"},\"presets\":{\"sandbox>,\0\07, grey_bg\":6M\0\06! blueJ!\0lff!redJ \0$ff0000\"}}}', '2015-11-06 19:08:03.498432')
@@ -1,25 +0,0 @@
1
- module BackgroundHelper
2
- attr_writer :threads
3
-
4
- def teardown
5
- threads.each(&:kill)
6
- self.threads = []
7
- end
8
-
9
- private
10
-
11
- def background(&block)
12
- thread = Thread.new(&block)
13
- threads << thread
14
- thread.join(0.1)
15
- thread
16
- end
17
-
18
- def threads
19
- @threads ||= []
20
- end
21
-
22
- def yield_to_background
23
- threads.each(&:join)
24
- end
25
- end
@@ -1,61 +0,0 @@
1
- require 'test_helper'
2
-
3
- class TestInstrumentation < Minitest::Test
4
- def setup
5
- Semian.destroy(:testing) if Semian[:testing]
6
- Semian.register(:testing, tickets: 1, error_threshold: 1, error_timeout: 5, success_threshold: 1)
7
- end
8
-
9
- def test_busy_instrumentation
10
- assert_notify(:success, :busy) do
11
- Semian[:testing].acquire do
12
- assert_raises Semian::TimeoutError do
13
- Semian[:testing].acquire {}
14
- end
15
- end
16
- end
17
- end
18
-
19
- def test_circuit_open_instrumentation
20
- assert_notify(:success, :busy) do
21
- Semian[:testing].acquire do
22
- assert_raises Semian::TimeoutError do
23
- Semian[:testing].acquire {}
24
- end
25
- end
26
- end
27
-
28
- assert_notify(:circuit_open) do
29
- assert_raises Semian::OpenCircuitError do
30
- Semian[:testing].acquire {}
31
- end
32
- end
33
- end
34
-
35
- def test_success_instrumentation
36
- assert_notify(:success) do
37
- Semian[:testing].acquire {}
38
- end
39
- end
40
-
41
- def test_success_instrumentation_when_unknown_exceptions_occur
42
- assert_notify(:success) do
43
- assert_raises RuntimeError do
44
- Semian[:testing].acquire { raise "Some error" }
45
- end
46
- end
47
- end
48
-
49
- private
50
-
51
- def assert_notify(*expected_events)
52
- events = []
53
- subscription = Semian.subscribe do |event, _resource|
54
- events << event
55
- end
56
- yield
57
- assert_equal expected_events, events, "The timeline of events was not as expected"
58
- ensure
59
- Semian.unsubscribe(subscription)
60
- end
61
- end
@@ -1,296 +0,0 @@
1
- require 'test_helper'
2
-
3
- class TestMysql2 < 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
- def setup
16
- @proxy = Toxiproxy[:semian_test_mysql]
17
- Semian.destroy(:mysql_testing)
18
- end
19
-
20
- def test_semian_identifier
21
- assert_equal :mysql_foo, FakeMysql.new(semian: {name: 'foo'}).semian_identifier
22
- assert_equal :'mysql_localhost:3306', FakeMysql.new.semian_identifier
23
- assert_equal :'mysql_127.0.0.1:3306', FakeMysql.new(host: '127.0.0.1').semian_identifier
24
- assert_equal :'mysql_example.com:42', FakeMysql.new(host: 'example.com', port: 42).semian_identifier
25
- end
26
-
27
- def test_semian_can_be_disabled
28
- resource = Mysql2::Client.new(semian: false).semian_resource
29
- assert_instance_of Semian::UnprotectedResource, resource
30
- end
31
-
32
- def test_connection_errors_open_the_circuit
33
- @proxy.downstream(:latency, latency: 1200).apply do
34
- ERROR_THRESHOLD.times do
35
- assert_raises ::Mysql2::Error do
36
- connect_to_mysql!
37
- end
38
- end
39
-
40
- assert_raises ::Mysql2::CircuitOpenError do
41
- connect_to_mysql!
42
- end
43
- end
44
- end
45
-
46
- def test_query_errors_does_not_open_the_circuit
47
- client = connect_to_mysql!
48
- (ERROR_THRESHOLD * 2).times do
49
- assert_raises ::Mysql2::Error do
50
- client.query('ERROR!')
51
- end
52
- end
53
- end
54
-
55
- def test_connect_instrumentation
56
- notified = false
57
- subscriber = Semian.subscribe do |event, resource, scope, adapter|
58
- notified = true
59
- assert_equal :success, event
60
- assert_equal Semian[:mysql_testing], resource
61
- assert_equal :connection, scope
62
- assert_equal :mysql, adapter
63
- end
64
-
65
- connect_to_mysql!
66
-
67
- assert notified, 'No notifications has been emitted'
68
- ensure
69
- Semian.unsubscribe(subscriber)
70
- end
71
-
72
- def test_resource_acquisition_for_connect
73
- connect_to_mysql!
74
-
75
- Semian[:mysql_testing].acquire do
76
- error = assert_raises Mysql2::ResourceBusyError do
77
- connect_to_mysql!
78
- end
79
- assert_equal :mysql_testing, error.semian_identifier
80
- end
81
- end
82
-
83
- def test_network_errors_are_tagged_with_the_resource_identifier
84
- client = connect_to_mysql!
85
- @proxy.down do
86
- error = assert_raises ::Mysql2::Error do
87
- client.query('SELECT 1 + 1;')
88
- end
89
- assert_equal client.semian_identifier, error.semian_identifier
90
- end
91
- end
92
-
93
- def test_other_mysql_errors_are_not_tagged_with_the_resource_identifier
94
- client = connect_to_mysql!
95
-
96
- error = assert_raises Mysql2::Error do
97
- client.query('SYNTAX ERROR!')
98
- end
99
- assert_nil error.semian_identifier
100
- end
101
-
102
- def test_resource_timeout_on_connect
103
- @proxy.downstream(:latency, latency: 500).apply do
104
- background { connect_to_mysql! }
105
-
106
- assert_raises Mysql2::ResourceBusyError do
107
- connect_to_mysql!
108
- end
109
- end
110
- end
111
-
112
- def test_circuit_breaker_on_connect
113
- @proxy.downstream(:latency, latency: 500).apply do
114
- background { connect_to_mysql! }
115
-
116
- ERROR_THRESHOLD.times do
117
- assert_raises Mysql2::ResourceBusyError do
118
- connect_to_mysql!
119
- end
120
- end
121
- end
122
-
123
- yield_to_background
124
-
125
- assert_raises Mysql2::CircuitOpenError do
126
- connect_to_mysql!
127
- end
128
-
129
- Timecop.travel(ERROR_TIMEOUT + 1) do
130
- connect_to_mysql!
131
- end
132
- end
133
-
134
- def test_query_instrumentation
135
- client = connect_to_mysql!
136
-
137
- notified = false
138
- subscriber = Semian.subscribe do |event, resource, scope, adapter|
139
- notified = true
140
- assert_equal :success, event
141
- assert_equal Semian[:mysql_testing], resource
142
- assert_equal :query, scope
143
- assert_equal :mysql, adapter
144
- end
145
-
146
- client.query('SELECT 1 + 1;')
147
-
148
- assert notified, 'No notifications has been emitted'
149
- ensure
150
- Semian.unsubscribe(subscriber)
151
- end
152
-
153
- def test_resource_acquisition_for_query
154
- client = connect_to_mysql!
155
-
156
- Semian[:mysql_testing].acquire do
157
- assert_raises Mysql2::ResourceBusyError do
158
- client.query('SELECT 1 + 1;')
159
- end
160
- end
161
- end
162
-
163
- def test_semian_allows_rollback
164
- client = connect_to_mysql!
165
-
166
- client.query('START TRANSACTION;')
167
-
168
- Semian[:mysql_testing].acquire do
169
- client.query('ROLLBACK;')
170
- end
171
- end
172
-
173
- def test_semian_allows_commit
174
- client = connect_to_mysql!
175
-
176
- client.query('START TRANSACTION;')
177
-
178
- Semian[:mysql_testing].acquire do
179
- client.query('COMMIT;')
180
- end
181
- end
182
-
183
- def test_query_whitelisted_returns_false_for_binary_sql
184
- binary_query = File.read(File.expand_path('../fixtures/binary.sql', __FILE__))
185
- client = connect_to_mysql!
186
- refute client.send(:query_whitelisted?, binary_query)
187
- end
188
-
189
- def test_semian_allows_rollback_to_safepoint
190
- client = connect_to_mysql!
191
-
192
- client.query('START TRANSACTION;')
193
- client.query('SAVEPOINT foobar;')
194
-
195
- Semian[:mysql_testing].acquire do
196
- client.query('ROLLBACK TO foobar;')
197
- end
198
-
199
- client.query('ROLLBACK;')
200
- end
201
-
202
- def test_semian_allows_release_savepoint
203
- client = connect_to_mysql!
204
-
205
- client.query('START TRANSACTION;')
206
- client.query('SAVEPOINT foobar;')
207
-
208
- Semian[:mysql_testing].acquire do
209
- client.query('RELEASE SAVEPOINT foobar;')
210
- end
211
-
212
- client.query('ROLLBACK;')
213
- end
214
-
215
- def test_resource_timeout_on_query
216
- client = connect_to_mysql!
217
- client2 = connect_to_mysql!
218
-
219
- @proxy.downstream(:latency, latency: 500).apply do
220
- background { client2.query('SELECT 1 + 1;') }
221
-
222
- assert_raises Mysql2::ResourceBusyError do
223
- client.query('SELECT 1 + 1;')
224
- end
225
- end
226
- end
227
-
228
- def test_circuit_breaker_on_query
229
- client = connect_to_mysql!
230
- client2 = connect_to_mysql!
231
-
232
- @proxy.downstream(:latency, latency: 1000).apply do
233
- background { client2.query('SELECT 1 + 1;') }
234
-
235
- ERROR_THRESHOLD.times do
236
- assert_raises Mysql2::ResourceBusyError do
237
- client.query('SELECT 1 + 1;')
238
- end
239
- end
240
- end
241
-
242
- yield_to_background
243
-
244
- assert_raises Mysql2::CircuitOpenError do
245
- client.query('SELECT 1 + 1;')
246
- end
247
-
248
- Timecop.travel(ERROR_TIMEOUT + 1) do
249
- assert_equal 2, client.query('SELECT 1 + 1 as sum;').to_a.first['sum']
250
- end
251
- end
252
-
253
- def test_unconfigured
254
- client = Mysql2::Client.new(host: '127.0.0.1', port: '13306')
255
- assert_equal 2, client.query('SELECT 1 + 1 as sum;').to_a.first['sum']
256
- end
257
-
258
- def test_pings_are_circuit_broken
259
- client = connect_to_mysql!
260
-
261
- def client.raw_ping
262
- @real_pings ||= 0
263
- @real_pings += 1
264
- super
265
- end
266
-
267
- @proxy.downstream(:latency, latency: 1200).apply do
268
- ERROR_THRESHOLD.times do
269
- client.ping
270
- end
271
-
272
- assert_equal false, client.ping
273
- end
274
-
275
- assert_equal ERROR_THRESHOLD, client.instance_variable_get(:@real_pings)
276
- end
277
-
278
- private
279
-
280
- def connect_to_mysql!(semian_options = {})
281
- Mysql2::Client.new(
282
- connect_timeout: 1,
283
- read_timeout: 1,
284
- host: '127.0.0.1',
285
- port: '13306',
286
- semian: SEMIAN_OPTIONS.merge(semian_options),
287
- )
288
- end
289
-
290
- class FakeMysql < Mysql2::Client
291
- private
292
-
293
- def connect(*)
294
- end
295
- end
296
- end