semian 0.6.2 → 0.7.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
- data/ext/semian/extconf.rb +1 -1
- data/ext/semian/resource.c +134 -17
- data/ext/semian/resource.h +56 -3
- data/ext/semian/semian.c +6 -1
- data/ext/semian/sysv_semaphores.c +148 -43
- data/ext/semian/sysv_semaphores.h +49 -5
- data/ext/semian/tickets.c +64 -54
- data/ext/semian/tickets.h +2 -6
- data/ext/semian/types.h +4 -9
- data/lib/semian.rb +111 -17
- data/lib/semian/adapter.rb +5 -0
- data/lib/semian/circuit_breaker.rb +9 -7
- data/lib/semian/mysql2.rb +2 -0
- data/lib/semian/protected_resource.rb +37 -14
- data/lib/semian/redis.rb +8 -6
- data/lib/semian/resource.rb +27 -3
- data/lib/semian/simple_integer.rb +15 -0
- data/lib/semian/simple_sliding_window.rb +35 -10
- data/lib/semian/simple_state.rb +7 -0
- data/lib/semian/unprotected_resource.rb +12 -0
- data/lib/semian/version.rb +1 -1
- metadata +3 -2
@@ -4,6 +4,8 @@ module Semian
|
|
4
4
|
|
5
5
|
def_delegators :@state, :closed?, :open?, :half_open?
|
6
6
|
|
7
|
+
attr_reader :name
|
8
|
+
|
7
9
|
def initialize(name, exceptions:, success_threshold:, error_threshold:, error_timeout:, implementation:)
|
8
10
|
@name = name.to_sym
|
9
11
|
@success_count_threshold = success_threshold
|
@@ -41,7 +43,7 @@ module Semian
|
|
41
43
|
end
|
42
44
|
|
43
45
|
def mark_failed(_error)
|
44
|
-
push_time(@errors
|
46
|
+
push_time(@errors)
|
45
47
|
if closed?
|
46
48
|
open if error_threshold_reached?
|
47
49
|
elsif half_open?
|
@@ -95,14 +97,14 @@ module Semian
|
|
95
97
|
end
|
96
98
|
|
97
99
|
def error_timeout_expired?
|
98
|
-
|
99
|
-
|
100
|
+
last_error_time = @errors.last
|
101
|
+
return false unless last_error_time
|
102
|
+
Time.at(last_error_time) + @error_timeout < Time.now
|
100
103
|
end
|
101
104
|
|
102
|
-
def push_time(window,
|
103
|
-
|
104
|
-
window
|
105
|
-
window << (time.to_f * 1000).to_i
|
105
|
+
def push_time(window, time: Time.now)
|
106
|
+
window.reject! { |err_time| err_time + @error_timeout < time.to_i }
|
107
|
+
window << time.to_i
|
106
108
|
end
|
107
109
|
|
108
110
|
def log_state_transition(new_state)
|
data/lib/semian/mysql2.rb
CHANGED
@@ -2,35 +2,58 @@ module Semian
|
|
2
2
|
class ProtectedResource
|
3
3
|
extend Forwardable
|
4
4
|
|
5
|
-
def_delegators :@
|
5
|
+
def_delegators :@bulkhead, :destroy, :count, :semid, :tickets, :registered_workers
|
6
6
|
def_delegators :@circuit_breaker, :reset, :mark_failed, :mark_success, :request_allowed?,
|
7
7
|
:open?, :closed?, :half_open?
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
attr_reader :bulkhead, :circuit_breaker, :name
|
10
|
+
|
11
|
+
def initialize(name, bulkhead, circuit_breaker)
|
12
|
+
@name = name
|
13
|
+
@bulkhead = bulkhead
|
11
14
|
@circuit_breaker = circuit_breaker
|
12
15
|
end
|
13
16
|
|
14
17
|
def destroy
|
15
|
-
@
|
16
|
-
@circuit_breaker.destroy
|
18
|
+
@bulkhead.destroy unless @bulkhead.nil?
|
19
|
+
@circuit_breaker.destroy unless @circuit_breaker.nil?
|
17
20
|
end
|
18
21
|
|
19
22
|
def acquire(timeout: nil, scope: nil, adapter: nil)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
23
|
+
acquire_circuit_breaker(scope, adapter) do
|
24
|
+
acquire_bulkhead(timeout, scope, adapter) do
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def acquire_circuit_breaker(scope, adapter)
|
33
|
+
if @circuit_breaker.nil?
|
34
|
+
yield self
|
35
|
+
else
|
36
|
+
@circuit_breaker.acquire do
|
37
|
+
yield self
|
29
38
|
end
|
30
39
|
end
|
31
40
|
rescue ::Semian::OpenCircuitError
|
32
41
|
Semian.notify(:circuit_open, self, scope, adapter)
|
33
42
|
raise
|
34
43
|
end
|
44
|
+
|
45
|
+
def acquire_bulkhead(timeout, scope, adapter)
|
46
|
+
if @bulkhead.nil?
|
47
|
+
yield self
|
48
|
+
else
|
49
|
+
@bulkhead.acquire(timeout: timeout) do
|
50
|
+
Semian.notify(:success, self, scope, adapter)
|
51
|
+
yield self
|
52
|
+
end
|
53
|
+
end
|
54
|
+
rescue ::Semian::TimeoutError
|
55
|
+
Semian.notify(:busy, self, scope, adapter)
|
56
|
+
raise
|
57
|
+
end
|
35
58
|
end
|
36
59
|
end
|
data/lib/semian/redis.rb
CHANGED
@@ -15,17 +15,19 @@ class Redis
|
|
15
15
|
ResourceBusyError = Class.new(SemianError)
|
16
16
|
CircuitOpenError = Class.new(SemianError)
|
17
17
|
|
18
|
-
attr_reader :semian_resource
|
19
|
-
|
20
18
|
alias_method :_original_initialize, :initialize
|
21
19
|
|
22
20
|
def initialize(*args, &block)
|
23
21
|
_original_initialize(*args, &block)
|
24
22
|
|
25
|
-
# This
|
26
|
-
#
|
27
|
-
#
|
28
|
-
@
|
23
|
+
# This reference is necessary because during a `pipelined` block the client
|
24
|
+
# is replaced by an instance of `Redis::Pipeline` and there is no way to
|
25
|
+
# access the original client which references the Semian resource.
|
26
|
+
@original_client = client
|
27
|
+
end
|
28
|
+
|
29
|
+
def semian_resource
|
30
|
+
@original_client.semian_resource
|
29
31
|
end
|
30
32
|
|
31
33
|
def semian_identifier
|
data/lib/semian/resource.rb
CHANGED
@@ -2,19 +2,31 @@ module Semian
|
|
2
2
|
class Resource #:nodoc:
|
3
3
|
attr_reader :tickets, :name
|
4
4
|
|
5
|
-
|
5
|
+
class << Semian::Resource
|
6
|
+
# Ensure that there can only be one resource of a given type
|
7
|
+
def instance(*args)
|
8
|
+
Semian.resources[args.first] ||= new(*args)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(name, tickets: nil, quota: nil, permissions: 0660, timeout: 0)
|
6
13
|
if Semian.semaphores_enabled?
|
7
|
-
initialize_semaphore(name, tickets, permissions, timeout) if respond_to?(:initialize_semaphore)
|
14
|
+
initialize_semaphore(name, tickets, quota, permissions, timeout) if respond_to?(:initialize_semaphore)
|
8
15
|
else
|
9
16
|
Semian.issue_disabled_semaphores_warning
|
10
17
|
end
|
11
18
|
@name = name
|
12
|
-
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset_registered_workers!
|
13
22
|
end
|
14
23
|
|
15
24
|
def destroy
|
16
25
|
end
|
17
26
|
|
27
|
+
def unregister_worker
|
28
|
+
end
|
29
|
+
|
18
30
|
def acquire(*)
|
19
31
|
yield self
|
20
32
|
end
|
@@ -23,8 +35,20 @@ module Semian
|
|
23
35
|
0
|
24
36
|
end
|
25
37
|
|
38
|
+
def tickets
|
39
|
+
0
|
40
|
+
end
|
41
|
+
|
42
|
+
def registered_workers
|
43
|
+
0
|
44
|
+
end
|
45
|
+
|
26
46
|
def semid
|
27
47
|
0
|
28
48
|
end
|
49
|
+
|
50
|
+
def key
|
51
|
+
'0x00000000'
|
52
|
+
end
|
29
53
|
end
|
30
54
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
1
3
|
module Semian
|
2
4
|
module Simple
|
3
5
|
class Integer #:nodoc:
|
@@ -20,4 +22,17 @@ module Semian
|
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
25
|
+
|
26
|
+
module ThreadSafe
|
27
|
+
class Integer < Simple::Integer
|
28
|
+
def initialize(*)
|
29
|
+
super
|
30
|
+
@lock = Mutex.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def increment(*)
|
34
|
+
@lock.synchronize { super }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
23
38
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
1
3
|
module Semian
|
2
4
|
module Simple
|
3
5
|
class SlidingWindow #:nodoc:
|
4
6
|
extend Forwardable
|
5
7
|
|
6
|
-
def_delegators :@window, :size, :
|
8
|
+
def_delegators :@window, :size, :last
|
7
9
|
attr_reader :max_size
|
8
10
|
|
9
11
|
# A sliding window is a structure that stores the most @max_size recent timestamps
|
@@ -15,28 +17,51 @@ module Semian
|
|
15
17
|
@window = []
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
19
|
-
|
20
|
-
@max_size = size
|
21
|
-
@window.shift while @window.size > @max_size
|
22
|
-
self
|
20
|
+
def reject!(&block)
|
21
|
+
@window.reject!(&block)
|
23
22
|
end
|
24
23
|
|
25
24
|
def push(value)
|
26
|
-
@
|
25
|
+
resize_to(@max_size - 1) # make room
|
27
26
|
@window << value
|
28
27
|
self
|
29
28
|
end
|
30
|
-
|
31
29
|
alias_method :<<, :push
|
32
30
|
|
33
31
|
def clear
|
34
32
|
@window.clear
|
35
33
|
self
|
36
34
|
end
|
35
|
+
alias_method :destroy, :clear
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def resize_to(size)
|
40
|
+
@window = @window.last(size) if @window.size >= size
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module ThreadSafe
|
46
|
+
class SlidingWindow < Simple::SlidingWindow
|
47
|
+
def initialize(*)
|
48
|
+
super
|
49
|
+
@lock = Mutex.new
|
50
|
+
end
|
51
|
+
|
52
|
+
# #size, #last, and #clear are not wrapped in a mutex. For the first two,
|
53
|
+
# the worst-case is a thread-switch at a timing where they'd receive an
|
54
|
+
# out-of-date value--which could happen with a mutex as well.
|
55
|
+
#
|
56
|
+
# As for clear, it's an all or nothing operation. Doesn't matter if we
|
57
|
+
# have the lock or not.
|
58
|
+
|
59
|
+
def reject!(*)
|
60
|
+
@lock.synchronize { super }
|
61
|
+
end
|
37
62
|
|
38
|
-
def
|
39
|
-
|
63
|
+
def push(*)
|
64
|
+
@lock.synchronize { super }
|
40
65
|
end
|
41
66
|
end
|
42
67
|
end
|
data/lib/semian/simple_state.rb
CHANGED
@@ -8,6 +8,10 @@ module Semian
|
|
8
8
|
@name = name
|
9
9
|
end
|
10
10
|
|
11
|
+
def registered_workers
|
12
|
+
0
|
13
|
+
end
|
14
|
+
|
11
15
|
def tickets
|
12
16
|
-1
|
13
17
|
end
|
@@ -51,5 +55,13 @@ module Semian
|
|
51
55
|
|
52
56
|
def mark_success
|
53
57
|
end
|
58
|
+
|
59
|
+
def bulkhead
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def circuit_breaker
|
64
|
+
nil
|
65
|
+
end
|
54
66
|
end
|
55
67
|
end
|
data/lib/semian/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: semian
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Francis
|
8
8
|
- Simon Eskildsen
|
9
|
+
- Dale Hamel
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2017-
|
13
|
+
date: 2017-06-19 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: rake-compiler
|