business_flow 0.18.1 → 0.19.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/Gemfile.lock +36 -36
- data/business_flow.gemspec +2 -2
- data/lib/business_flow/cluster_lock.rb +193 -0
- data/lib/business_flow/compat.rb +1 -1
- data/lib/business_flow/dsl.rb +3 -1
- data/lib/business_flow/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 358d1c9f044cb27c403f3f09dca2ff62306a43a86b89cbdbd0a5bd2f1052384b
|
|
4
|
+
data.tar.gz: 9ace3fc7d47f1f383dbe5a5dcee90891b790bc5dfe602530c3230f5ae2b11db9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12300927b4b0ed87c5961d86709d030059fb3734c54680b17c450a1e3f64d6e28fe2c22c158e9578f24ce137c0a24952bc3c14a501c24332fb7baf292ffedff8
|
|
7
|
+
data.tar.gz: b53b551074f8edb4fddc50791e1f6a64682205f49af85b1cbf6915f57677b9ab6f762c4c1e100eee658477f78b9c9abfc7199b5594490524144bf0653e5825a5
|
data/Gemfile.lock
CHANGED
|
@@ -1,53 +1,52 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
business_flow (0.
|
|
4
|
+
business_flow (0.19.0)
|
|
5
5
|
activemodel (>= 4.2, < 8)
|
|
6
6
|
activesupport (>= 4.2, < 8)
|
|
7
7
|
|
|
8
8
|
GEM
|
|
9
9
|
remote: https://rubygems.org/
|
|
10
10
|
specs:
|
|
11
|
-
activemodel (7.0.
|
|
12
|
-
activesupport (= 7.0.
|
|
13
|
-
activesupport (7.0.
|
|
11
|
+
activemodel (7.0.4)
|
|
12
|
+
activesupport (= 7.0.4)
|
|
13
|
+
activesupport (7.0.4)
|
|
14
14
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
15
15
|
i18n (>= 1.6, < 2)
|
|
16
16
|
minitest (>= 5.1)
|
|
17
17
|
tzinfo (~> 2.0)
|
|
18
18
|
ast (2.4.2)
|
|
19
|
-
|
|
20
|
-
concurrent-ruby (1.1.9)
|
|
19
|
+
concurrent-ruby (1.1.10)
|
|
21
20
|
diff-lcs (1.5.0)
|
|
22
|
-
docile (1.
|
|
23
|
-
i18n (1.
|
|
21
|
+
docile (1.4.0)
|
|
22
|
+
i18n (1.12.0)
|
|
24
23
|
concurrent-ruby (~> 1.0)
|
|
25
24
|
jaro_winkler (1.5.4)
|
|
26
|
-
|
|
27
|
-
minitest (5.
|
|
28
|
-
parallel (1.
|
|
29
|
-
parser (2.
|
|
30
|
-
ast (~> 2.4.
|
|
25
|
+
kwalify (0.7.2)
|
|
26
|
+
minitest (5.16.3)
|
|
27
|
+
parallel (1.22.1)
|
|
28
|
+
parser (3.2.2.1)
|
|
29
|
+
ast (~> 2.4.1)
|
|
31
30
|
rainbow (3.1.1)
|
|
32
31
|
rake (10.5.0)
|
|
33
|
-
reek (
|
|
34
|
-
|
|
35
|
-
parser (
|
|
32
|
+
reek (6.1.4)
|
|
33
|
+
kwalify (~> 0.7.0)
|
|
34
|
+
parser (~> 3.2.0)
|
|
36
35
|
rainbow (>= 2.0, < 4.0)
|
|
37
36
|
retryable (3.0.5)
|
|
38
|
-
rspec (3.
|
|
39
|
-
rspec-core (~> 3.
|
|
40
|
-
rspec-expectations (~> 3.
|
|
41
|
-
rspec-mocks (~> 3.
|
|
42
|
-
rspec-core (3.
|
|
43
|
-
rspec-support (~> 3.
|
|
44
|
-
rspec-expectations (3.
|
|
37
|
+
rspec (3.12.0)
|
|
38
|
+
rspec-core (~> 3.12.0)
|
|
39
|
+
rspec-expectations (~> 3.12.0)
|
|
40
|
+
rspec-mocks (~> 3.12.0)
|
|
41
|
+
rspec-core (3.12.0)
|
|
42
|
+
rspec-support (~> 3.12.0)
|
|
43
|
+
rspec-expectations (3.12.0)
|
|
45
44
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
46
|
-
rspec-support (~> 3.
|
|
47
|
-
rspec-mocks (3.
|
|
45
|
+
rspec-support (~> 3.12.0)
|
|
46
|
+
rspec-mocks (3.12.0)
|
|
48
47
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
49
|
-
rspec-support (~> 3.
|
|
50
|
-
rspec-support (3.
|
|
48
|
+
rspec-support (~> 3.12.0)
|
|
49
|
+
rspec-support (3.12.0)
|
|
51
50
|
rubocop (0.68.1)
|
|
52
51
|
jaro_winkler (~> 1.5.1)
|
|
53
52
|
parallel (~> 1.10)
|
|
@@ -58,13 +57,14 @@ GEM
|
|
|
58
57
|
rubocop-rspec (1.24.0)
|
|
59
58
|
rubocop (>= 0.53.0)
|
|
60
59
|
ruby-progressbar (1.11.0)
|
|
61
|
-
simplecov (0.
|
|
62
|
-
docile (~> 1.1
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
simplecov-html (0.
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
simplecov (0.22.0)
|
|
61
|
+
docile (~> 1.1)
|
|
62
|
+
simplecov-html (~> 0.11)
|
|
63
|
+
simplecov_json_formatter (~> 0.1)
|
|
64
|
+
simplecov-html (0.12.3)
|
|
65
|
+
simplecov_json_formatter (0.1.4)
|
|
66
|
+
timecop (0.9.6)
|
|
67
|
+
tzinfo (2.0.5)
|
|
68
68
|
concurrent-ruby (~> 1.0)
|
|
69
69
|
unicode-display_width (1.5.0)
|
|
70
70
|
|
|
@@ -74,12 +74,12 @@ PLATFORMS
|
|
|
74
74
|
DEPENDENCIES
|
|
75
75
|
business_flow!
|
|
76
76
|
rake (~> 10.0)
|
|
77
|
-
reek (~>
|
|
77
|
+
reek (~> 6.1)
|
|
78
78
|
retryable (~> 3.0.4)
|
|
79
79
|
rspec (~> 3.0)
|
|
80
80
|
rubocop (~> 0.53)
|
|
81
81
|
rubocop-rspec (~> 1.24.0)
|
|
82
|
-
simplecov (~> 0.
|
|
82
|
+
simplecov (~> 0.22.0)
|
|
83
83
|
timecop (~> 0.9.1)
|
|
84
84
|
|
|
85
85
|
BUNDLED WITH
|
data/business_flow.gemspec
CHANGED
|
@@ -24,11 +24,11 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.add_dependency 'activesupport', '>= 4.2', '< 8'
|
|
25
25
|
|
|
26
26
|
spec.add_development_dependency 'rake', '~> 10.0'
|
|
27
|
-
spec.add_development_dependency 'reek', '~>
|
|
27
|
+
spec.add_development_dependency 'reek', '~> 6.1'
|
|
28
28
|
spec.add_development_dependency 'retryable', '~> 3.0.4'
|
|
29
29
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
30
30
|
spec.add_development_dependency 'rubocop', '~> 0.53'
|
|
31
31
|
spec.add_development_dependency 'rubocop-rspec', '~> 1.24.0'
|
|
32
|
-
spec.add_development_dependency 'simplecov', '~> 0.
|
|
32
|
+
spec.add_development_dependency 'simplecov', '~> 0.22.0'
|
|
33
33
|
spec.add_development_dependency 'timecop', '~> 0.9.1'
|
|
34
34
|
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BusinessFlow
|
|
4
|
+
# Mixin for business flow to acquire and retain a cluster lock.
|
|
5
|
+
module ClusterLock
|
|
6
|
+
def self.included(klass)
|
|
7
|
+
klass.extend(ClassMethods)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.disable!
|
|
11
|
+
@disabled = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.enable!
|
|
15
|
+
@disabled = false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.disabled?
|
|
19
|
+
!!@disabled
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.default_servers=(servers)
|
|
23
|
+
if servers.is_a?(String)
|
|
24
|
+
@default_servers = proc { servers }
|
|
25
|
+
elsif servers
|
|
26
|
+
@default_servers = Callable.new(servers)
|
|
27
|
+
else
|
|
28
|
+
@default_servers = nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.default_servers
|
|
33
|
+
@default_servers ||= proc { nil }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def assert_cluster_lock!
|
|
37
|
+
@_business_flow_cluster_lock.assert! if !BusinessFlow::ClusterLock.disabled?
|
|
38
|
+
rescue ZK::Exceptions::ZKError => exc
|
|
39
|
+
errors.add(:cluster_lock, :assert_failed, message: exc.message)
|
|
40
|
+
raise
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# DSL Methods
|
|
44
|
+
module ClassMethods
|
|
45
|
+
# Error raised when there is an internal issue with acquiring a lock.
|
|
46
|
+
class LockFailure < StandardError
|
|
47
|
+
attr_reader :error_type
|
|
48
|
+
def initialize(error_type, message)
|
|
49
|
+
@error_type = error_type
|
|
50
|
+
super(message)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def add_to(flow)
|
|
54
|
+
errors = flow.errors
|
|
55
|
+
errors.add(:cluster_lock, error_type, message: message) if !errors.key?(:cluster_lock)
|
|
56
|
+
flow
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Holder for information about the lock
|
|
61
|
+
class LockInfo
|
|
62
|
+
attr_reader :lock_name, :zookeeper_servers
|
|
63
|
+
|
|
64
|
+
def initialize(lock_name, zookeeper_servers)
|
|
65
|
+
@lock_name = lock_name
|
|
66
|
+
@zookeeper_servers = zookeeper_servers
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def with_cluster_lock(lock_name = nil, opts = {}, &blk)
|
|
71
|
+
if lock_name.is_a?(String)
|
|
72
|
+
@lock_name = Step.new(Callable.new(proc { lock_name }), {})
|
|
73
|
+
elsif lock_name || blk
|
|
74
|
+
@lock_name = Step.new(Callable.new(lock_name || blk),
|
|
75
|
+
{ default_output: :lock_name }.merge(opts))
|
|
76
|
+
else
|
|
77
|
+
@lock_name ||= Step.new(Callable.new(default_lock_name), opts)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def with_zookeeper_servers(servers = nil, opts = {}, &blk)
|
|
82
|
+
if servers.is_a?(String)
|
|
83
|
+
@zookeeper_servers = Step.new(Callable.new(proc { servers }), {})
|
|
84
|
+
elsif servers || blk
|
|
85
|
+
@zookeeper_servers = Step.new(Callable.new(servers || blk),
|
|
86
|
+
{ default_output: :zookeeper_servers }.merge(opts))
|
|
87
|
+
else
|
|
88
|
+
@zookeeper_servers || Step.new(BusinessFlow::ClusterLock.default_servers, opts)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def default_lock_name
|
|
93
|
+
proc { self.class.name }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def build(parameter_object)
|
|
97
|
+
add_cluster_luck_info_to_result_class
|
|
98
|
+
super(parameter_object)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def execute(flow)
|
|
102
|
+
lock_info = LockInfo.new(
|
|
103
|
+
ClassMethods.lock_name(flow),
|
|
104
|
+
ClassMethods.zookeeper_server_list(flow)
|
|
105
|
+
)
|
|
106
|
+
ClassMethods.with_lock(flow, lock_info) do
|
|
107
|
+
super(flow)._business_flow_cluster_lock_finalize(lock_info)
|
|
108
|
+
end
|
|
109
|
+
rescue LockFailure => exc
|
|
110
|
+
return result_from(exc.add_to(flow))._business_flow_cluster_lock_finalize(lock_info)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
RESULT_FINALIZE = proc do |cluster_lock_info|
|
|
114
|
+
@cluster_lock_info = cluster_lock_info
|
|
115
|
+
self
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def add_cluster_luck_info_to_result_class
|
|
119
|
+
return if @cluster_lock_info_added
|
|
120
|
+
result_class = const_get(:Result)
|
|
121
|
+
DSL::PublicField.new(:cluster_lock_info).add_to(result_class)
|
|
122
|
+
result_class.send(:define_method, :_business_flow_cluster_lock_finalize,
|
|
123
|
+
RESULT_FINALIZE)
|
|
124
|
+
@cluster_lock_info_added = true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# :reek:NilCheck
|
|
128
|
+
def self.zookeeper_server_list(flow)
|
|
129
|
+
servers = catch(:halt_step) { flow.class.with_zookeeper_servers.call(flow)&.merge_into(flow)&.to_s }
|
|
130
|
+
if servers.nil? || servers.length == 0
|
|
131
|
+
raise LockFailure.new(:no_servers, 'no zookeeper servers provided')
|
|
132
|
+
end
|
|
133
|
+
servers
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# :reek:NilCheck
|
|
137
|
+
def self.lock_name(flow)
|
|
138
|
+
lock_name = catch(:halt_step) { flow.class.with_cluster_lock.call(flow)&.merge_into(flow)&.to_s }
|
|
139
|
+
if lock_name.nil? || lock_name.length == 0
|
|
140
|
+
raise LockFailure.new(:no_lock_name, 'no lock name provided')
|
|
141
|
+
end
|
|
142
|
+
lock_name
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def self.instrumented_acquire_lock(flow, lock_info)
|
|
146
|
+
flow.class.instrument(:cluster_lock_setup, flow) do |payload|
|
|
147
|
+
payload[:lock_name] = lock_info.lock_name if payload
|
|
148
|
+
acquire_lock(flow, lock_info, payload)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def self.acquire_lock(flow, lock_info, payload)
|
|
153
|
+
zk_connection = ZK::Client::Threaded.new(lock_info.zookeeper_servers)
|
|
154
|
+
lock = flow.instance_variable_set(
|
|
155
|
+
:@_business_flow_cluster_lock,
|
|
156
|
+
ZK::Locker::ExclusiveLocker.new(zk_connection, lock_info.lock_name)
|
|
157
|
+
)
|
|
158
|
+
inner_acquire_lock(zk_connection, lock, payload)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def self.inner_acquire_lock(zk_connection, lock, payload)
|
|
162
|
+
lock_held = lock.lock(wait: false)
|
|
163
|
+
payload[:lock_acquired] = lock_held if payload
|
|
164
|
+
if !lock_held
|
|
165
|
+
zk_connection.close!
|
|
166
|
+
raise LockFailure.new(:lock_unavailable, 'the lock was not available')
|
|
167
|
+
end
|
|
168
|
+
[zk_connection, lock]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def self.cleanup(lock, zk_connection)
|
|
172
|
+
lock.unlock if lock
|
|
173
|
+
zk_connection.close! if zk_connection
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def self.with_lock(flow, lock_info, &blk)
|
|
177
|
+
zk_connection, lock =
|
|
178
|
+
if !BusinessFlow::ClusterLock.disabled?
|
|
179
|
+
instrumented_acquire_lock(flow, lock_info)
|
|
180
|
+
end
|
|
181
|
+
yield lock_info
|
|
182
|
+
rescue ZK::Exceptions::LockAssertionFailedError => exc
|
|
183
|
+
# This would occur if we asserted a cluster lock while executing the flow.
|
|
184
|
+
# This will have set an error on the flow, so we can carry on.
|
|
185
|
+
raise LockFailure.new(:assert_failed, exc.message)
|
|
186
|
+
rescue ZK::Exceptions::OperationTimeOut
|
|
187
|
+
# Sometimes this happens. Just let the ensure block take care of everything
|
|
188
|
+
ensure
|
|
189
|
+
cleanup(lock, zk_connection)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
data/lib/business_flow/compat.rb
CHANGED
|
@@ -14,7 +14,7 @@ module BusinessFlow
|
|
|
14
14
|
|
|
15
15
|
if !ActiveModel::Errors.instance_methods.include?(:merge!)
|
|
16
16
|
# ActiveModel 5 added details (which we do not use here) and #merge!
|
|
17
|
-
# :reek:
|
|
17
|
+
# :reek:MissingSafeMethod Look it's the API.
|
|
18
18
|
class ::ActiveModel::Errors
|
|
19
19
|
def merge!(other)
|
|
20
20
|
other.each do |attribute, message|
|
data/lib/business_flow/dsl.rb
CHANGED
|
@@ -103,7 +103,9 @@ module BusinessFlow
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def call(parameter_object = {})
|
|
106
|
-
|
|
106
|
+
flow = build(parameter_object)
|
|
107
|
+
return result_from(flow) if flow.errors?
|
|
108
|
+
execute(flow)
|
|
107
109
|
end
|
|
108
110
|
|
|
109
111
|
# :reek:UtilityFunction This is a function on us so that other modules
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: business_flow
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Scarborough
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-05-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activemodel
|
|
@@ -70,14 +70,14 @@ dependencies:
|
|
|
70
70
|
requirements:
|
|
71
71
|
- - "~>"
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: '
|
|
73
|
+
version: '6.1'
|
|
74
74
|
type: :development
|
|
75
75
|
prerelease: false
|
|
76
76
|
version_requirements: !ruby/object:Gem::Requirement
|
|
77
77
|
requirements:
|
|
78
78
|
- - "~>"
|
|
79
79
|
- !ruby/object:Gem::Version
|
|
80
|
-
version: '
|
|
80
|
+
version: '6.1'
|
|
81
81
|
- !ruby/object:Gem::Dependency
|
|
82
82
|
name: retryable
|
|
83
83
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -140,14 +140,14 @@ dependencies:
|
|
|
140
140
|
requirements:
|
|
141
141
|
- - "~>"
|
|
142
142
|
- !ruby/object:Gem::Version
|
|
143
|
-
version: 0.
|
|
143
|
+
version: 0.22.0
|
|
144
144
|
type: :development
|
|
145
145
|
prerelease: false
|
|
146
146
|
version_requirements: !ruby/object:Gem::Requirement
|
|
147
147
|
requirements:
|
|
148
148
|
- - "~>"
|
|
149
149
|
- !ruby/object:Gem::Version
|
|
150
|
-
version: 0.
|
|
150
|
+
version: 0.22.0
|
|
151
151
|
- !ruby/object:Gem::Dependency
|
|
152
152
|
name: timecop
|
|
153
153
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -184,6 +184,7 @@ files:
|
|
|
184
184
|
- lib/business_flow/base.rb
|
|
185
185
|
- lib/business_flow/cacheable.rb
|
|
186
186
|
- lib/business_flow/callable.rb
|
|
187
|
+
- lib/business_flow/cluster_lock.rb
|
|
187
188
|
- lib/business_flow/compat.rb
|
|
188
189
|
- lib/business_flow/default_step_executor.rb
|
|
189
190
|
- lib/business_flow/dsl.rb
|