exekutor 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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