exekutor 0.1.0 → 0.1.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/exe/exekutor +2 -2
  4. data/lib/active_job/queue_adapters/exekutor_adapter.rb +2 -1
  5. data/lib/exekutor/asynchronous.rb +143 -75
  6. data/lib/exekutor/cleanup.rb +27 -28
  7. data/lib/exekutor/configuration.rb +48 -25
  8. data/lib/exekutor/hook.rb +15 -11
  9. data/lib/exekutor/info/worker.rb +3 -3
  10. data/lib/exekutor/internal/base_record.rb +2 -1
  11. data/lib/exekutor/internal/callbacks.rb +55 -35
  12. data/lib/exekutor/internal/cli/app.rb +31 -23
  13. data/lib/exekutor/internal/cli/application_loader.rb +17 -6
  14. data/lib/exekutor/internal/cli/cleanup.rb +54 -40
  15. data/lib/exekutor/internal/cli/daemon.rb +9 -11
  16. data/lib/exekutor/internal/cli/default_option_value.rb +3 -1
  17. data/lib/exekutor/internal/cli/info.rb +117 -84
  18. data/lib/exekutor/internal/cli/manager.rb +190 -123
  19. data/lib/exekutor/internal/configuration_builder.rb +40 -27
  20. data/lib/exekutor/internal/database_connection.rb +6 -0
  21. data/lib/exekutor/internal/executable.rb +12 -7
  22. data/lib/exekutor/internal/executor.rb +50 -21
  23. data/lib/exekutor/internal/hooks.rb +11 -8
  24. data/lib/exekutor/internal/listener.rb +66 -39
  25. data/lib/exekutor/internal/logger.rb +28 -10
  26. data/lib/exekutor/internal/provider.rb +93 -74
  27. data/lib/exekutor/internal/reserver.rb +27 -12
  28. data/lib/exekutor/internal/status_server.rb +81 -49
  29. data/lib/exekutor/job.rb +1 -1
  30. data/lib/exekutor/job_error.rb +1 -1
  31. data/lib/exekutor/job_options.rb +22 -13
  32. data/lib/exekutor/plugins/appsignal.rb +7 -5
  33. data/lib/exekutor/plugins.rb +8 -4
  34. data/lib/exekutor/queue.rb +40 -22
  35. data/lib/exekutor/version.rb +1 -1
  36. data/lib/exekutor/worker.rb +88 -47
  37. data/lib/exekutor.rb +2 -2
  38. data/lib/generators/exekutor/configuration_generator.rb +9 -5
  39. data/lib/generators/exekutor/install_generator.rb +26 -15
  40. data/lib/generators/exekutor/templates/install/migrations/create_exekutor_schema.rb.erb +11 -10
  41. data.tar.gz.sig +0 -0
  42. metadata +63 -19
  43. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bc7c7a8f6af32b1f764124d3905673240c167abd19b93e492016433414078a9
4
- data.tar.gz: 37f8fb516c32a2a71595b2d608b2610ba78a5396e7f1b1e6bc7c2e90da8d770d
3
+ metadata.gz: 94b3158b3e0df0ca892836df74beb23ccfd8bddf5a1377a46b5ebd881cb5bee8
4
+ data.tar.gz: a24e415df40ba9bf69d6972da2fb6461f60c655976d81f87ff36f2e365da42a6
5
5
  SHA512:
6
- metadata.gz: 8f36b7e5a5968009ba7f33657a0444053a72a5e7b91ebb8e5605c435d80a6861eec4a2d39af81c71ec6e88564a57f7d91fdaca43182ca9e5faf8c86cdd5ab7ff
7
- data.tar.gz: ab262caf9827f3eccd5995309b8f0debc0da125af459b8a8321fbd43c6a25d9bcd42dc6fa65f5a98bbb341be07fca4251a8ab73860d01c2ce43efd388499a145
6
+ metadata.gz: 8de1e356a03b80f1e7dd82802956902531f76111cbea7210ee61eeeb34dbccc22120e5127f24ec9112328bfcb0f175f593f7ab4a9e6f347165b168503c4dbf49
7
+ data.tar.gz: 33c945b0e29d611d2e25d98985e75df68494bb3463d014efda5a60c7402acb711541a465b6e4db10afcbb8f2db4b86d9d2c2fd3d6b23a140bf324eefbeb8cf51
checksums.yaml.gz.sig CHANGED
Binary file
data/exe/exekutor CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
- require "exekutor/internal/cli/app"
4
3
 
5
- Process.setproctitle "Exekutor worker (Initializing…) [#{$PROGRAM_NAME}]"
4
+ require "exekutor/version"
5
+ require "exekutor/internal/cli/app"
6
6
 
7
7
  exit Exekutor::Internal::CLI::App.run(ARGV)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "active_job"
3
4
  require "active_job/queue_adapters"
4
5
 
@@ -11,4 +12,4 @@ module ActiveJob
11
12
  alias enqueue_at schedule_at
12
13
  end
13
14
  end
14
- end
15
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Exekutor
2
4
  # Mixin to let methods be executed asynchronously by active job
3
5
  #
@@ -21,10 +23,10 @@ module Exekutor
21
23
  included do
22
24
  mattr_reader :__async_class_methods, instance_accessor: false, default: {}
23
25
  mattr_reader :__async_instance_methods, instance_accessor: false, default: {}
24
- private_class_method :perform_asynchronously
26
+ private_class_method :perform_asynchronously, :async_delegate_and_definitions, :redefine_method
25
27
  end
26
28
 
27
- class_methods do
29
+ class_methods do # rubocop:disable Metrics/BlockLength
28
30
  # Changes a method to be executed asynchronously.
29
31
  # Be aware that you can no longer use the return value for
30
32
  # asynchronous methods, because the actual method will be performed by a worker at a later time. The new
@@ -36,46 +38,62 @@ module Exekutor
36
38
  # @param class_method [Boolean] whether the method is a class method.
37
39
  # @raise [Error] if the method could not be replaced with the asynchronous version
38
40
  def perform_asynchronously(method_name, alias_to: "__immediately_#{method_name}", class_method: false)
39
- raise ArgumentError, "method_name must be a Symbol (actual: #{method_name.class.name})" unless method_name.is_a? Symbol
40
- raise ArgumentError, "alias_to must be present" unless alias_to.present?
41
+ unless method_name.is_a? Symbol
42
+ raise ArgumentError, "method_name must be a Symbol (actual: #{method_name.class.name})"
43
+ end
44
+ raise ArgumentError, "alias_to must be present" if alias_to.blank?
45
+
46
+ delegate, definitions = async_delegate_and_definitions(method_name, class_method: class_method)
47
+ raise Error, "##{method_name} was already marked as asynchronous" if definitions.include? method_name
48
+
49
+ redefine_method_as_async(delegate, method_name, alias_to)
50
+ definitions[method_name] = alias_to
51
+ nil
52
+ end
53
+
54
+ # Gets the object to define aliased method on, and the definitions that are already defined
55
+ # @return [Array(Any, Hash<Symbol=>Symbol>)] The delegate and the existing definitions
56
+ def async_delegate_and_definitions(method_name, class_method:)
41
57
  if class_method
42
58
  raise ArgumentError, "##{method_name} does not exist" unless respond_to? method_name, true
43
59
 
44
- delegate = singleton_class
45
- definitions = __async_class_methods
60
+ [singleton_class, __async_class_methods]
46
61
  else
47
62
  unless method_defined?(method_name, true) || private_method_defined?(method_name, true)
48
63
  raise ArgumentError, "##{method_name} does not exist"
49
64
  end
50
65
 
51
- delegate = self
52
- definitions = __async_instance_methods
53
- end
54
- if definitions.include? method_name
55
- raise Error, "##{method_name} was already marked as asynchronous"
66
+ [self, __async_instance_methods]
56
67
  end
68
+ end
69
+
70
+ # Aliases the indicated method and redefines the method to call the original method asynchronously.
71
+ # @param delegate [Any] the delegate to redefine the method on
72
+ # @param method_name [Symbol] the name of the method to redefine
73
+ # @param alias_to [String] the name to alias the original method to
74
+ # @return [Void]
75
+ def redefine_method_as_async(delegate, method_name, alias_to)
76
+ visibility = if delegate.private_method_defined?(method_name)
77
+ :private
78
+ elsif delegate.protected_method_defined?(method_name)
79
+ :protected
80
+ else
81
+ :public
82
+ end
57
83
 
58
84
  delegate.alias_method alias_to, method_name
59
85
  delegate.define_method method_name do |*args, **kwargs|
60
86
  error = Asynchronous.validate_args(self, alias_to, *args, **kwargs)
61
87
  raise error if error
62
88
  raise ArgumentError, "Cannot asynchronously execute with a block argument" if block_given?
89
+
63
90
  AsyncMethodJob.perform_later self, method_name, [args, kwargs.presence]
64
91
  end
65
92
 
66
- definitions[method_name] = alias_to
67
- if delegate.public_method_defined?(alias_to)
68
- delegate.send :public, method_name
69
- elsif delegate.protected_method_defined?(alias_to)
70
- delegate.send :protected, method_name
71
- else
72
- delegate.send :private, method_name
73
- end
93
+ delegate.send(visibility, method_name)
74
94
  end
75
95
  end
76
96
 
77
- private
78
-
79
97
  # Validates whether the given arguments match the expected parameters for +method+
80
98
  # @param delegate [Object] the object the +method+ will be called on
81
99
  # @param method [Symbol] the method that will be called on +delegate+
@@ -83,60 +101,16 @@ module Exekutor
83
101
  # @param kwargs [Hash] the keyword arguments that will be given to the method
84
102
  # @return [ArgumentError,nil] nil if the keywords are valid; an ArgumentError otherwise
85
103
  def self.validate_args(delegate, method, *args, **kwargs)
86
- obj_method = delegate.method(method)
87
- min_arg_length = 0
88
- max_arg_length = 0
89
- accepts_keywords = false
90
- missing_keywords = []
91
- unknown_keywords = kwargs.keys
92
- obj_method.parameters.each do |type, name|
93
- if type == :req
94
- min_arg_length += 1
95
- max_arg_length += 1 if max_arg_length
96
- elsif type == :opt
97
- max_arg_length += 1 if max_arg_length
98
- elsif type == :rest
99
- max_arg_length = nil
100
- elsif type == :keyreq
101
- accepts_keywords = true
102
- missing_keywords << name if kwargs.exclude?(name)
103
- unknown_keywords.delete(name)
104
- elsif type == :key
105
- accepts_keywords = true
106
- unknown_keywords.delete(name)
107
- elsif type == :keyrest
108
- accepts_keywords = true
109
- unknown_keywords = []
110
- end
111
- end
112
- if missing_keywords.present?
113
- return ArgumentError.new "missing keyword#{"s" if missing_keywords.many?}: #{missing_keywords.map(&:inspect).join(", ")}"
114
- end
115
- if accepts_keywords
116
- if unknown_keywords.present?
117
- return ArgumentError.new "unknown keyword#{"s" if unknown_keywords.many?}: #{unknown_keywords.map(&:inspect).join(", ")}"
118
- end
119
- elsif kwargs.present?
120
- args += [kwargs]
121
- end
104
+ error = ArgumentValidator.new(delegate, method).validate(args, kwargs)
105
+ return nil unless error
122
106
 
123
- args_len = args.length
124
- if min_arg_length > args_len || (max_arg_length.present? && max_arg_length < args_len)
125
- expected = min_arg_length.to_s
126
- if max_arg_length.nil?
127
- expected += "+"
128
- elsif max_arg_length > min_arg_length
129
- expected += "..#{max_arg_length}"
130
- end
131
- return ArgumentError.new "wrong number of arguments (given #{args_len}, expected #{expected})"
132
- end
107
+ ArgumentError.new(error)
133
108
  end
134
109
 
135
110
  # The internal job used for {Exekutor::Asynchronous}. Only works for methods that are marked as asynchronous to
136
111
  # prevent remote code execution. Include the {Exekutor::Asynchronous} and call
137
- # {Exekutor::Asynchronous#perform_asynchronously} to mark a method as asynchronous.
138
- class AsyncMethodJob < ActiveJob::Base
139
-
112
+ # +perform_asynchronously+ to mark a method as asynchronous.
113
+ class AsyncMethodJob < ActiveJob::Base # rubocop:disable Rails/ApplicationJob
140
114
  # Calls the original, synchronous method
141
115
  # @!visibility private
142
116
  def perform(object, method, args)
@@ -172,16 +146,110 @@ module Exekutor
172
146
  class_name = object.class.name
173
147
  definitions = object.class.__async_instance_methods
174
148
  end
175
- unless object.respond_to? method, true
176
- raise Error, "#{class_name} does not respond to #{method}"
149
+ raise Error, "#{class_name} does not respond to #{method}" unless object.respond_to? method, true
150
+ raise Error, "#{class_name}##{method} is not marked as asynchronous" unless definitions.include? method.to_sym
151
+
152
+ definitions[method.to_sym]
153
+ end
154
+ end
155
+
156
+ # Validates whether a set of arguments is valid for a particular method
157
+ class ArgumentValidator
158
+ def initialize(delegate, method)
159
+ @required_keywords = []
160
+ @optional_keywords = []
161
+ @accepts_keyrest = false
162
+ parse_method_params delegate.method(method)
163
+ end
164
+
165
+ def parse_method_params(method)
166
+ arguments = ArgumentCounter.new
167
+ method.parameters.each do |type, name|
168
+ case type
169
+ when :req, :opt
170
+ arguments.increment(type)
171
+ when :rest
172
+ arguments.clear_max
173
+ when :keyreq
174
+ @required_keywords << name
175
+ when :key
176
+ @optional_keywords << name
177
+ when :keyrest
178
+ @accepts_keyrest = true
179
+ else
180
+ Exekutor.say "Unsupported parameter type: #{type.inspect}"
181
+ end
182
+ end
183
+ @arg_length = arguments.to_range
184
+ end
185
+
186
+ def accepts_keywords?
187
+ @accepts_keyrest || @required_keywords.present? || @optional_keywords.present?
188
+ end
189
+
190
+ def fixed_keywords?
191
+ !@accepts_keyrest && (@required_keywords.present? || @optional_keywords.present?)
192
+ end
193
+
194
+ def validate(args, kwargs)
195
+ args += [kwargs] unless kwargs.empty? || accepts_keywords?
196
+
197
+ return argument_length_error(args.length) unless @arg_length.cover? args.length
198
+
199
+ missing_keywords = @required_keywords - kwargs.keys
200
+ return missing_keywords_error(missing_keywords) if missing_keywords.present?
201
+
202
+ unknown_keywords = (kwargs.keys - @required_keywords - @optional_keywords if fixed_keywords?)
203
+ return unknown_keywords_error(unknown_keywords) if unknown_keywords.present?
204
+
205
+ nil
206
+ end
207
+
208
+ private
209
+
210
+ def unknown_keywords_error(unknown_keywords)
211
+ "unknown keyword#{"s" if unknown_keywords.many?}: #{
212
+ unknown_keywords.map(&:inspect).join(", ")}"
213
+ end
214
+
215
+ def missing_keywords_error(missing_keywords)
216
+ "missing keyword#{"s" if missing_keywords.many?}: #{
217
+ missing_keywords.map(&:inspect).join(", ")}"
218
+ end
219
+
220
+ def argument_length_error(given_length)
221
+ expected = @arg_length.begin.to_s
222
+ if @arg_length.end.nil?
223
+ expected += "+"
224
+ elsif @arg_length.end > @arg_length.begin
225
+ expected += "..#{@arg_length.end}"
177
226
  end
178
- unless definitions.include? method.to_sym
179
- raise Error, "#{class_name}##{method} is not marked as asynchronous"
227
+ "wrong number of arguments (given #{given_length}, expected #{expected})"
228
+ end
229
+
230
+ # Keeps track of the minimum and maximum number of allowed arguments
231
+ class ArgumentCounter
232
+ def initialize
233
+ @min = @max = 0
234
+ end
235
+
236
+ def increment(type)
237
+ @min += 1 if type == :req
238
+ @max += 1 if @max
239
+ end
240
+
241
+ def clear_max
242
+ @max = nil
243
+ end
244
+
245
+ def to_range
246
+ @min..@max
180
247
  end
181
- definitions[method.to_sym]
182
248
  end
183
249
  end
184
250
 
251
+ private_constant :ArgumentValidator
252
+
185
253
  # Raised when an error occurs while configuring or executing asynchronous methods
186
254
  class Error < Exekutor::DiscardJob; end
187
255
  end
@@ -1,23 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Exekutor
2
4
  # Helper class to clean up finished jobs and stale workers.
3
5
  class Cleanup
4
-
5
6
  # Purges all workers where the last heartbeat is over the +timeout+ ago.
6
7
  # @param timeout [ActiveSupport::Duration,Numeric,Time] the timeout. Default: 4 hours
7
8
  # @return [Array<Exekutor::Info::Worker>] the purged workers
8
9
  def cleanup_workers(timeout: 4.hours)
9
- destroy_before = case timeout
10
- when ActiveSupport::Duration
11
- timeout.ago
12
- when Numeric
13
- timeout.hours.ago
14
- when Date, Time
15
- timeout
16
- else
17
- raise ArgumentError, "Unsupported value for timeout: #{timeout.class}"
18
- end
19
- # TODO PG-NOTIFY each worker with an EXIT command
20
- Exekutor::Info::Worker.where(%{"last_heartbeat_at"<?}, destroy_before).destroy_all
10
+ destroy_before = parse_timeout_arg :timeout, timeout
11
+ # TODO: PG-NOTIFY each worker with an EXIT command
12
+ Exekutor::Info::Worker.where(%("last_heartbeat_at"<?), destroy_before).destroy_all
21
13
  end
22
14
 
23
15
  # Purges all jobs where scheduled at is before +before+. Only purges jobs with the given status, if no status is
@@ -26,24 +18,13 @@ module Exekutor
26
18
  # @param status [Array<String,Symbol>,String,Symbol] the statuses to purge. Default: All except +:pending+
27
19
  # @return [Integer] the number of purged jobs
28
20
  def cleanup_jobs(before: 48.hours.ago, status: nil)
29
- destroy_before = case before
30
- when ActiveSupport::Duration
31
- before.ago
32
- when Numeric
33
- before.hours.ago
34
- when Date, Time
35
- before
36
- else
37
- raise ArgumentError, "Unsupported value for before: #{before.class}"
38
- end
39
- unless [Array, String, Symbol, NilClass].any?(&status.method(:is_a?))
21
+ destroy_before = parse_timeout_arg :before, before
22
+ unless [Array, String, Symbol, NilClass].any? { |c| status.is_a? c }
40
23
  raise ArgumentError, "Unsupported value for status: #{status.class}"
41
24
  end
42
25
 
43
26
  jobs = Exekutor::Job.all
44
- unless before.nil?
45
- jobs.where!(%{"scheduled_at"<?}, destroy_before)
46
- end
27
+ jobs.where!(%("scheduled_at"<?), destroy_before) unless before.nil?
47
28
  if status
48
29
  jobs.where! status: status
49
30
  else
@@ -52,5 +33,23 @@ module Exekutor
52
33
  jobs.delete_all
53
34
  end
54
35
 
36
+ private
37
+
38
+ # Converts timout argument to a Time
39
+ # @param name [Symbol,String] the name of the argument
40
+ # @param value [ActiveSupport::Duration,Numeric,Date,Time] the argument to parse
41
+ # @return [Date,Time] The point in time
42
+ def parse_timeout_arg(name, value)
43
+ case value
44
+ when ActiveSupport::Duration
45
+ value.ago
46
+ when Numeric
47
+ value.hours.ago
48
+ when Date, Time
49
+ value
50
+ else
51
+ raise ArgumentError, "Unsupported value for #{name}: #{value.class}"
52
+ end
53
+ end
55
54
  end
56
- end
55
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "internal/configuration_builder"
4
4
 
5
+ # The Exekutor namespace
5
6
  module Exekutor
6
7
  # Configuration for the Exekutor library
7
8
  class Configuration
@@ -9,7 +10,7 @@ module Exekutor
9
10
 
10
11
  # @private
11
12
  DEFAULT_BASE_RECORD_CLASS = "ActiveRecord::Base"
12
- private_constant "DEFAULT_BASE_RECORD_CLASS"
13
+ private_constant :DEFAULT_BASE_RECORD_CLASS
13
14
 
14
15
  # @!macro
15
16
  # @!method $1
@@ -73,8 +74,7 @@ module Exekutor
73
74
  # @param value [String,Symbol,Proc,Object] the serializer
74
75
  # @return [self]
75
76
  define_option :json_serializer, default: "::JSON", required: true do |value|
76
- unless value.is_a?(String) || value.is_a?(Symbol) || value.respond_to?(:call) ||
77
- (value.respond_to?(:dump) && value.respond_to?(:load))
77
+ unless value.is_a?(String) || value.is_a?(Symbol) || value.respond_to?(:call) || SerializerValidator.valid?(value)
78
78
  raise Error, "#json_serializer must either be a String, a Proc, or respond to #dump and #load"
79
79
  end
80
80
  end
@@ -83,24 +83,13 @@ module Exekutor
83
83
  # @raise [Error] when the class cannot be found, or does not respond to +#dump+ and +#load+
84
84
  # @return [Object]
85
85
  def load_json_serializer
86
- raw_value = self.json_serializer
86
+ raw_value = json_serializer
87
87
  if defined?(@json_serializer_instance) && @json_serializer_instance[0] == raw_value
88
88
  return @json_serializer_instance[1]
89
89
  end
90
90
 
91
91
  serializer = const_get :json_serializer
92
- unless serializer.respond_to?(:dump) && serializer.respond_to?(:load)
93
- serializer = serializer.call if serializer.respond_to?(:call)
94
- unless serializer.respond_to?(:dump) && serializer.respond_to?(:load)
95
- serializer = serializer.new if serializer.respond_to?(:new)
96
- end
97
- end
98
- unless serializer.respond_to?(:dump) && serializer.respond_to?(:load)
99
- raise Error, <<~MSG.squish
100
- The configured serializer (#{serializer.class}) does not respond to #dump and #load
101
- MSG
102
- end
103
-
92
+ serializer = SerializerValidator.convert! serializer unless SerializerValidator.valid? serializer
104
93
  @json_serializer_instance = [raw_value, serializer]
105
94
  serializer
106
95
  end
@@ -305,7 +294,7 @@ module Exekutor
305
294
  {
306
295
  min_threads: min_execution_threads,
307
296
  max_threads: max_execution_threads,
308
- max_thread_idletime: max_execution_thread_idletime,
297
+ max_thread_idletime: max_execution_thread_idletime
309
298
  }.tap do |opts|
310
299
  opts[:set_db_connection_name] = set_db_connection_name? unless set_db_connection_name.nil?
311
300
  %i[enable_listener delete_completed_jobs delete_discarded_jobs delete_failed_jobs].each do |option|
@@ -351,6 +340,36 @@ module Exekutor
351
340
  def error_class
352
341
  Error
353
342
  end
343
+
344
+ # Validates the value for a serializer, which must implement dump & load
345
+ class SerializerValidator
346
+ # @param serializer [Any] the value to validate
347
+ # @return [Boolean] whether the serializer has implemented dump & load
348
+ def self.valid?(serializer)
349
+ serializer.respond_to?(:dump) && serializer.respond_to?(:load)
350
+ end
351
+
352
+ # Tries to convert the specified value to a serializer, raises an error if the conversion fails.
353
+ # @param serializer [Any] the value to convert
354
+ # @return [#dump&#load]
355
+ # @raise [Error] if the serializer has not implemented dump & load
356
+ def self.convert!(serializer)
357
+ return serializer if SerializerValidator.valid? serializer
358
+
359
+ if serializer.respond_to?(:call)
360
+ serializer = serializer.call
361
+ return serializer if SerializerValidator.valid? serializer
362
+ end
363
+ if serializer.respond_to?(:new)
364
+ serializer = serializer.new
365
+ return serializer if SerializerValidator.valid? serializer
366
+ end
367
+
368
+ raise Error, <<~MSG.squish
369
+ The configured serializer (#{serializer.class}) does not respond to #dump and #load
370
+ MSG
371
+ end
372
+ end
354
373
  end
355
374
 
356
375
  def self.config
@@ -358,16 +377,20 @@ module Exekutor
358
377
  end
359
378
 
360
379
  def self.configure(opts = nil, &block)
361
- raise ArgumentError, "opts must be a Hash" unless opts.nil? || opts.is_a?(Hash)
362
- raise ArgumentError, "Either opts or a block must be given" unless opts.present? || block_given?
380
+ raise ArgumentError, "either opts or a block must be given" unless opts || block
363
381
 
364
- config.set(**opts) if opts
365
- return unless block_given?
382
+ if opts
383
+ raise ArgumentError, "opts must be a Hash" unless opts.is_a?(Hash)
366
384
 
367
- if block.arity == 1
368
- block.call config
369
- else
370
- instance_eval(&block)
385
+ config.set(**opts)
386
+ end
387
+ if block
388
+ if block.arity.zero?
389
+ instance_eval(&block)
390
+ else
391
+ yield config
392
+ end
371
393
  end
394
+ self
372
395
  end
373
396
  end
data/lib/exekutor/hook.rb CHANGED
@@ -27,12 +27,12 @@ module Exekutor
27
27
  before_enqueue around_enqueue after_enqueue before_job_execution around_job_execution after_job_execution
28
28
  on_job_failure on_fatal_error before_startup after_startup before_shutdown after_shutdown
29
29
  ].freeze
30
- private_constant "CALLBACK_NAMES"
30
+ private_constant :CALLBACK_NAMES
31
31
 
32
32
  included do
33
33
  class_attribute :__callbacks, default: Hash.new { |h, k| h[k] = [] }
34
34
 
35
- private_class_method :add_callback!
35
+ private_class_method :_add_callback
36
36
  end
37
37
 
38
38
  # Gets the registered callbacks
@@ -53,7 +53,6 @@ module Exekutor
53
53
  end
54
54
 
55
55
  class_methods do
56
-
57
56
  # @!method before_enqueue
58
57
  # Registers a callback to be called before a job is enqueued.
59
58
  # @param methods [Symbol] the method(s) to call
@@ -141,9 +140,9 @@ module Exekutor
141
140
 
142
141
  CALLBACK_NAMES.each do |name|
143
142
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
144
- def #{name}(*methods, &callback)
145
- add_callback! :#{name}, methods, callback
146
- end
143
+ def #{name}(*methods, &callback) # def callback_name(*methods, &callback
144
+ _add_callback :#{name}, methods, callback # _add_callback :callback_name, methods, callback
145
+ end # end
147
146
  RUBY
148
147
  end
149
148
 
@@ -156,16 +155,21 @@ module Exekutor
156
155
  raise Error, "Invalid callback type: #{type} (Expected one of: #{CALLBACK_NAMES.map(&:inspect).join(", ")}"
157
156
  end
158
157
 
159
- add_callback! type, methods, callback
158
+ _add_callback type, methods, callback
160
159
  true
161
160
  end
162
161
 
163
- def add_callback!(type, methods, callback)
164
- raise Error, "No method or callback block supplied" if methods.blank? && callback.nil?
162
+ # @!visibility private
163
+ def _add_callback(type, methods, callback)
165
164
  raise Error, "Either a method or a callback block must be supplied" if methods.present? && callback.present?
166
165
 
167
- methods&.each { |method| __callbacks[type] << [method, nil] }
168
- __callbacks[type] << [nil, callback] if callback.present?
166
+ if methods.present?
167
+ methods.each { |method| __callbacks[type] << [method, nil] }
168
+ elsif callback.present?
169
+ __callbacks[type] << [nil, callback]
170
+ else
171
+ raise Error, "No method or callback block supplied"
172
+ end
169
173
  end
170
174
  end
171
175
  end
@@ -12,9 +12,9 @@ module Exekutor
12
12
 
13
13
  # Registers a heartbeat for this worker, if necessary
14
14
  def heartbeat!
15
- now = Time.now.change(sec: 0)
16
- touch :last_heartbeat_at, time: now if self.last_heartbeat_at.nil? || now >= self.last_heartbeat_at + 1.minute
15
+ now = Time.current.change(sec: 0)
16
+ touch :last_heartbeat_at, time: now if last_heartbeat_at.nil? || now >= last_heartbeat_at + 1.minute
17
17
  end
18
18
  end
19
19
  end
20
- end
20
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Exekutor
3
4
  # @private
4
5
  module Internal
@@ -8,4 +9,4 @@ module Exekutor
8
9
  self.table_name_prefix = "exekutor_"
9
10
  end
10
11
  end
11
- end
12
+ end