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/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
|
data/semian.gemspec
DELETED
@@ -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
|
data/test/fixtures/binary.sql
DELETED
@@ -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
|
data/test/mysql2_test.rb
DELETED
@@ -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
|