business_flow 0.18.1 → 0.19.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9163d03b65bfd1a9419b31593b1e7efca00c7eb1b13838dab9a5c402597e14e1
4
- data.tar.gz: 65cb6f0168df3fb353c70a7db3614950be0fce5304f99529357e9ccdf664d1e7
3
+ metadata.gz: b3cbbc4f9a0eba68f3056378a62625b0b855957945fb45392fe3101455aa3df3
4
+ data.tar.gz: 4b47a1c7479257c75814abdaaa023e9cc58c01e169eacfd95276c26441c64203
5
5
  SHA512:
6
- metadata.gz: 156666c6666f24bd1d4bfa024d63b337c33cec5055cb7918bde71428e452609d37b45da7f8dd74d7aafd1890952006ca89b17eeaf508fe7d70f14bb408d71522
7
- data.tar.gz: a907affc0aea2953272d33cb836b42ab8b6729a4a9ce43eaf88617ea03aae510efff9dfeb30f61198dd18a16d61ae88698d873a95223404c763d717be50ad9f3
6
+ metadata.gz: 15aa3e482853c9f9022729f3e44e4f4e566695b23393339c3fb11915127c0003992de7fb04e4515725e99fbb2dddfe6bd8c1d9dcbb8dc7ee82c523a956181b3b
7
+ data.tar.gz: 947c46bdd62b88e8a5d588d3258a8df4f0820b0dd8ba408329fd6b4dc3acf16019465e242bbb1a0ebb0806af1f367813fc1c5735e66c52e15ca4a330e9b0cf49
data/Gemfile.lock CHANGED
@@ -1,53 +1,52 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- business_flow (0.18.1)
4
+ business_flow (0.19.1)
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.2)
12
- activesupport (= 7.0.2)
13
- activesupport (7.0.2)
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
- codeclimate-engine-rb (0.4.2)
20
- concurrent-ruby (1.1.9)
19
+ concurrent-ruby (1.1.10)
21
20
  diff-lcs (1.5.0)
22
- docile (1.1.5)
23
- i18n (1.9.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
- json (2.6.1)
27
- minitest (5.15.0)
28
- parallel (1.21.0)
29
- parser (2.5.3.0)
30
- ast (~> 2.4.0)
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 (4.8.2)
34
- codeclimate-engine-rb (~> 0.4.0)
35
- parser (>= 2.5.0.0, < 2.6)
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.10.0)
39
- rspec-core (~> 3.10.0)
40
- rspec-expectations (~> 3.10.0)
41
- rspec-mocks (~> 3.10.0)
42
- rspec-core (3.10.2)
43
- rspec-support (~> 3.10.0)
44
- rspec-expectations (3.10.2)
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.10.0)
47
- rspec-mocks (3.10.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.10.0)
50
- rspec-support (3.10.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.15.1)
62
- docile (~> 1.1.0)
63
- json (>= 1.8, < 3)
64
- simplecov-html (~> 0.10.0)
65
- simplecov-html (0.10.2)
66
- timecop (0.9.4)
67
- tzinfo (2.0.4)
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 (~> 4.8)
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.15.1)
82
+ simplecov (~> 0.22.0)
83
83
  timecop (~> 0.9.1)
84
84
 
85
85
  BUNDLED WITH
@@ -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', '~> 4.8'
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.15.1'
32
+ spec.add_development_dependency 'simplecov', '~> 0.22.0'
33
33
  spec.add_development_dependency 'timecop', '~> 0.9.1'
34
34
  end
@@ -66,6 +66,27 @@ module BusinessFlow
66
66
  Callable.new(:_business_flow_dsl_parameters)
67
67
  end
68
68
 
69
+ def clear_cache(parameter_object = {})
70
+ clear_cache!(parameter_object)
71
+ rescue FlowFailedException => exc
72
+ exc.flow
73
+ end
74
+
75
+ def clear_cache!(parameter_object = {})
76
+ flow = build(parameter_object)
77
+ raise FlowFailedException, result_from(flow) if flow.errors?
78
+ ClassMethods.clear_cache(flow)
79
+ end
80
+
81
+ def self.clear_cache(flow)
82
+ klass = flow.class
83
+ klass.instrument(:clear_cache, flow) do |payload|
84
+ ret = klass.cache_store.delete(flow.cache_key)
85
+ payload[:cache_clear] = ret if payload
86
+ ret
87
+ end
88
+ end
89
+
69
90
  def execute(flow)
70
91
  with_cache(flow) do
71
92
  super(flow)._business_flow_cacheable_finalize(flow.cache_key)
@@ -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
@@ -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:PrimaDonnaMethod Look it's the API.
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|
@@ -103,7 +103,9 @@ module BusinessFlow
103
103
  end
104
104
 
105
105
  def call(parameter_object = {})
106
- execute(build(parameter_object))
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BusinessFlow
4
- VERSION = '0.18.1'.freeze
4
+ VERSION = '0.19.1'.freeze
5
5
  end
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.18.1
4
+ version: 0.19.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Scarborough
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-01 00:00:00.000000000 Z
11
+ date: 2023-05-19 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: '4.8'
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: '4.8'
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.15.1
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.15.1
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