backburner 1.1.0 → 1.2.0.pre

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
  SHA1:
3
- metadata.gz: 9f2543596fd5e337e0b4514ca94f8b32959c3901
4
- data.tar.gz: eb4133d3c373307e9399636ce8c5c4df7630e74a
3
+ metadata.gz: 47ef7389bf1b31ea5b36b286dbcde88b8df5ef6b
4
+ data.tar.gz: d218e32791da8764998ad6ca58a7bc1de0d35ae6
5
5
  SHA512:
6
- metadata.gz: 58857e61ce26ff416fa6b3732839adedc2b9230886cff2fafa812c444598b264525f141a48e9eb2294ff3d8b55bd105fb00c28a89fdf1eb7250ca74022a41567
7
- data.tar.gz: dcbde0b50cc908dbb6052769a546c72589585a86b9d50ec5a4ceea1c6bdc9977430cfa45321593de52bb0e2f9048dd78b151f41de524be1b730b94bc64765606
6
+ metadata.gz: 1f8d54cfe41edbc49995342663ee6a5a23975cca9717e72d2cc8855a2634764a635f8a766bb511044c2ea0fee196c6adb817cafd2773ef945fd78c041f23bb15
7
+ data.tar.gz: 0fd38fbe520aa7efc312eb468f4dda7c452c53e1f6936667e1896abf4f1b9b212775c825d2bb9b40c6a7868a586ee886720f36abc359d8c64bd5d322911457fa
@@ -1,5 +1,11 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## Version 1.2.0.pre (October 24 2015)
4
+
5
+ * FIX Replace static Beaneater connection with individual connections per worker instance/thread (@contentfree)
6
+ * FIX Beaneater connections try really hard to repair themselves if disconnected accidentally (@contentfree)
7
+ * NEW Event hook for workers: on_reconnect (@contentfree)
8
+
3
9
  ## Version 1.1.0 (September 14 2015)
4
10
 
5
11
  * NEW Ability to configure namespace separator (@bfolkens)
data/HOOKS.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Backburner Hooks
2
2
 
3
- You can customize Backburner or write plugins using its hook API.
3
+ You can customize Backburner or write plugins using its hook API.
4
4
  In many cases you can use a hook rather than mess around with Backburner's internals.
5
5
 
6
- ## Job Hooks
6
+ ## Job Hooks
7
7
 
8
8
  Hooks are transparently adapted from [Resque](https://github.com/defunkt/resque/blob/master/docs/HOOKS.md), so
9
9
  if you are familiar with their hook API, now you can use nearly the same ones with beanstalkd and backburner!
@@ -26,7 +26,7 @@ There are a variety of hooks available that are triggered during the lifecycle o
26
26
  to perform the job (but is not required to do so). It may handle exceptions
27
27
  thrown by perform, but uncaught exceptions will be treated like regular job exceptions.
28
28
 
29
- * `on_retry`: Called with the retry count, the delay and the job args whenever a job is retried.
29
+ * `on_retry`: Called with the retry count, the delay and the job args whenever a job is retried.
30
30
 
31
31
  * `on_bury`: Called with the job args when the job is buried.
32
32
 
@@ -49,7 +49,7 @@ class SomeJob
49
49
  def self.perform(*args)
50
50
  # ...
51
51
  end
52
-
52
+
53
53
  def self.logger
54
54
  @_logger ||= Logger.new(STDOUT)
55
55
  end
@@ -60,5 +60,16 @@ You can also setup modules to create compose-able and reusable hooks for your jo
60
60
 
61
61
  ## Worker Hooks
62
62
 
63
- Coming soon. What do you need here? Just let me know!
63
+ Currently, there is just one hook:
64
+
65
+ * `on_reconnect`: Called on the worker whose connection has been reset. The connection
66
+ is given as the argument
67
+
68
+ An example:
64
69
 
70
+ ```ruby
71
+ class MyWorker < Backburner::Worker
72
+ def on_reconnect(conn)
73
+ prepare
74
+ end
75
+ end
data/README.md CHANGED
@@ -113,10 +113,12 @@ The key options available are:
113
113
  | `tube_namespace` | Prefix used for all tubes related to this backburner queue. |
114
114
  | `namespace_separator` | Separator used for namespace and queue name |
115
115
  | `on_error` | Lambda invoked with the error whenever any job in the system fails. |
116
- | `default_worker` | Worker class that will be used if no other worker is specified. |
117
116
  | `max_job_retries` | Integer defines how many times to retry a job before burying. |
118
117
  | `retry_delay` | Integer defines the base time to wait (in secs) between job retries. |
119
118
  | `retry_delay_proc` | Lambda calculates the delay used, allowing for exponential back-off. |
119
+ | `default_priority` | Integer The default priority of jobs |
120
+ | `respond_timeout` | Integer defines how long a job has to complete its task |
121
+ | `default_worker` | Worker class that will be used if no other worker is specified. |
120
122
  | `logger` | Logger recorded to when backburner wants to report info or errors. |
121
123
  | `primary_queue` | Primary queue used for a job when an alternate queue is not given. |
122
124
  | `priority_labels` | Hash of named priority definitions for your app. |
@@ -158,7 +160,7 @@ class NewsletterJob
158
160
 
159
161
  # optional, defaults to respond_timeout
160
162
  def self.queue_respond_timeout
161
- 300 # number of seconds before job times out, 0 to avoid timeout
163
+ 300 # number of seconds before job times out, 0 to avoid timeout. NB: A timeout of 1 second will likely lead to race conditions between Backburner and beanstalkd and should be avoided
162
164
  end
163
165
  end
164
166
  ```
@@ -238,7 +240,7 @@ User.async(:queue => lambda { |user_klass| ["queue1","queue2"].sample(1).first }
238
240
 
239
241
  ### Using Async Asynchronously ###
240
242
 
241
- It's often useful to be able to configure your app in production such that every invocation of a method is asynchronous by default as seen in [delayed_job](https://github.com/collectiveidea/delayed_job#queuing-jobs). To accomplish this, the `Backburner::Performable` module exposes two `handle_asynchronously` convenience methods
243
+ It's often useful to be able to configure your app in production such that every invocation of a method is asynchronous by default as seen in [delayed_job](https://github.com/collectiveidea/delayed_job#queuing-jobs). To accomplish this, the `Backburner::Performable` module exposes two `handle_asynchronously` convenience methods
242
244
  which accept the same options as the `async` method:
243
245
 
244
246
  ```ruby
@@ -248,14 +250,14 @@ class User
248
250
  def send_welcome_email
249
251
  # ...
250
252
  end
251
-
253
+
252
254
  # ---> For instance methods
253
255
  handle_asynchronously :send_welcome_email, queue: 'send-mail', pri: 5000, ttr: 60
254
256
 
255
257
  def self.update_recent_visitors
256
258
  # ...
257
259
  end
258
-
260
+
259
261
  # ---> For class methods
260
262
  handle_static_asynchronously :update_recent_visitors, queue: 'long-tasks', ttr: 300
261
263
  end
@@ -471,7 +473,7 @@ end
471
473
  Note the default `max_job_retries` is 0, meaning that by default **jobs are not retried**.
472
474
 
473
475
  As jobs are retried, a progressively-increasing delay is added to give time for transient
474
- problems to resolve themselves. This may be configured using `retry_delay_proc`. It expects
476
+ problems to resolve themselves. This may be configured using `retry_delay_proc`. It expects
475
477
  an object that responds to `#call` and receives the value of `retry_delay` and the number
476
478
  of times the job has been retried already. The default is a cubic back-off, eg:
477
479
 
@@ -38,7 +38,7 @@ module Tester
38
38
  end
39
39
  end
40
40
 
41
- # connection = Backburner::Connection.new("beanstalk://localhost")
41
+ # connection = Backburner::Connection.new("beanstalk://127.0.0.1")
42
42
 
43
43
  Backburner.configure do |config|
44
44
  config.beanstalk_url = "beanstalk://127.0.0.1"
@@ -57,4 +57,4 @@ Tester::UserModel.async.foo("bar", "baz")
57
57
  Backburner.default_queues.concat([Tester::TestJob.queue, Tester::UserModel.queue])
58
58
  Backburner.work
59
59
  # Backburner.work("test.job")
60
- # Backburner.work("tester/user-model")
60
+ # Backburner.work("tester/user-model")
@@ -19,7 +19,7 @@ module Backburner
19
19
  attr_accessor :reserve_timeout # duration to wait to reserve on a single server
20
20
 
21
21
  def initialize
22
- @beanstalk_url = "beanstalk://localhost"
22
+ @beanstalk_url = "beanstalk://127.0.0.1"
23
23
  @tube_namespace = "backburner.worker.queue"
24
24
  @namespace_separator = "."
25
25
  @default_priority = 65536
@@ -6,32 +6,126 @@ module Backburner
6
6
 
7
7
  attr_accessor :url, :beanstalk
8
8
 
9
+ # If a proc is provided, it will be called (and given this connection as an
10
+ # argument) whenever the connection is reconnected.
11
+ # @example
12
+ # connection.on_reconnect = lambda { |conn| puts 'reconnected!' }
13
+ attr_accessor :on_reconnect
14
+
9
15
  # Constructs a backburner connection
10
- # `url` can be a string i.e 'localhost:3001' or an array of addresses.
11
- def initialize(url)
16
+ # `url` can be a string i.e '127.0.0.1:3001' or an array of
17
+ # addresses (however, only the first element in the array will
18
+ # be used)
19
+ def initialize(url, &on_reconnect)
12
20
  @url = url
13
21
  @beanstalk = nil
22
+ @on_reconnect = on_reconnect
14
23
  connect!
15
24
  end
16
25
 
17
- # Sets the delegator object to the underlying beaneater pool
18
- # self.put(...)
19
- def __getobj__
26
+ # Close the connection, if it exists
27
+ def close
28
+ @beanstalk.close if @beanstalk
29
+ @beanstalk = nil
20
30
  __setobj__(@beanstalk)
21
- super
31
+ end
32
+
33
+ # Determines if the connection to Beanstalk is currently open
34
+ def connected?
35
+ begin
36
+ !!(@beanstalk && @beanstalk.connection && @beanstalk.connection.connection && !@beanstalk.connection.connection.closed?) # Would be nice if beaneater provided a connected? method
37
+ rescue => e
38
+ false
39
+ end
40
+ end
41
+
42
+ # Attempt to reconnect to Beanstalk. Note: the connection will not be watching
43
+ # or using the tubes it was before it was reconnected (as it's actually a
44
+ # completely new connection)
45
+ # @raise [Beaneater::NotConnected] If beanstalk fails to connect
46
+ def reconnect!
47
+ close
48
+ connect!
49
+ @on_reconnect.call(self) if @on_reconnect.respond_to?(:call)
50
+ end
51
+
52
+ # Yield to a block that will be retried several times if the connection to
53
+ # beanstalk goes down and is able to be re-established.
54
+ #
55
+ # @param options Hash Options. Valid options are:
56
+ # :max_retries Integer The maximum number of times the block will be yielded to.
57
+ # Defaults to 4
58
+ # :on_retry Proc An optional proc that will be called for each retry. Will be
59
+ # called after the connection is re-established and :retry_delay
60
+ # has passed but before the block is yielded to again
61
+ # :retry_delay Float The amount to sleep before retrying. Defaults to 1.0
62
+ # @raise Beaneater::NotConnected If a connection is unable to be re-established
63
+ def retryable(options = {}, &block)
64
+ options = {:max_retries => 4, :on_retry => nil, :retry_delay => 1.0}.merge!(options)
65
+ retry_count = options[:max_retries]
66
+
67
+ begin
68
+ yield
69
+
70
+ rescue Beaneater::NotConnected
71
+ if retry_count > 0
72
+ reconnect!
73
+ retry_count -= 1
74
+ sleep options[:retry_delay]
75
+ options[:on_retry].call if options[:on_retry].respond_to?(:call)
76
+ retry
77
+ else # stop retrying
78
+ raise e
79
+ end
80
+ end
22
81
  end
23
82
 
24
83
  protected
25
84
 
85
+ # Attempt to ensure we're connected to Beanstalk if the missing method is
86
+ # present in the delegate and we haven't shut down the connection on purpose
87
+ # @raise [Beaneater::NotConnected] If beanstalk fails to connect after multiple attempts.
88
+ def method_missing(m, *args, &block)
89
+ ensure_connected! if respond_to_missing?(m, false)
90
+ super
91
+ end
92
+
26
93
  # Connects to a beanstalk queue
94
+ # @raise Beaneater::NotConnected if the connection cannot be established
27
95
  def connect!
28
- @beanstalk ||= Beaneater.new(beanstalk_addresses)
96
+ @beanstalk = Beaneater.new(beanstalk_addresses)
97
+ __setobj__(@beanstalk)
98
+ @beanstalk
99
+ end
100
+
101
+ # Attempts to ensure a connection to beanstalk is established but only if
102
+ # we're not connected already
103
+ # @param max_retries Integer The maximum number of times to attempt connecting. Defaults to 4
104
+ # @param retry_delay Float The time to wait between retrying to connect. Defaults to 1.0
105
+ # @raise [Beaneater::NotConnected] If beanstalk fails to connect after multiple attempts.
106
+ # @return Connection This Connection is returned if the connection to beanstalk is open or was able to be reconnected
107
+ def ensure_connected!(max_retries = 4, retry_delay = 1.0)
108
+ return self if connected?
109
+
110
+ begin
111
+ reconnect!
112
+ return self
113
+
114
+ rescue Beaneater::NotConnected => e
115
+ if max_retries > 0
116
+ max_retries -= 1
117
+ sleep retry_delay
118
+ retry
119
+ else # stop retrying
120
+ raise e
121
+ end
122
+ end
29
123
  end
30
124
 
31
125
  # Returns the beanstalk queue addresses
32
126
  #
33
127
  # @example
34
- # beanstalk_addresses => ["localhost:11300"]
128
+ # beanstalk_addresses => ["127.0.0.1:11300"]
35
129
  #
36
130
  def beanstalk_addresses
37
131
  uri = self.url.is_a?(Array) ? self.url.first : self.url
@@ -41,11 +135,11 @@ module Backburner
41
135
  # Returns a host and port based on the uri_string given
42
136
  #
43
137
  # @example
44
- # beanstalk_host_and_port("beanstalk://localhost") => "localhost:11300"
138
+ # beanstalk_host_and_port("beanstalk://127.0.0.1") => "127.0.0.1:11300"
45
139
  #
46
140
  def beanstalk_host_and_port(uri_string)
47
141
  uri = URI.parse(uri_string)
48
- raise(BadURL, uri_string) if uri.scheme != 'beanstalk'
142
+ raise(BadURL, uri_string) if uri.scheme != 'beanstalk'.freeze
49
143
  "#{uri.host}:#{uri.port || 11300}"
50
144
  end
51
145
  end # Connection
@@ -4,11 +4,11 @@ module Backburner
4
4
  # Triggers all method hooks that match the given event type with specified arguments.
5
5
  #
6
6
  # @example
7
- # invoke_hook_events(:before_enqueue, 'some', 'args')
8
- # invoke_hook_events(:after_perform, 5)
7
+ # invoke_hook_events(hookable, :before_enqueue, 'some', 'args')
8
+ # invoke_hook_events(hookable, :after_perform, 5)
9
9
  #
10
- def invoke_hook_events(job, event, *args)
11
- res = find_hook_events(job, event).map { |e| job.send(e, *args) }
10
+ def invoke_hook_events(hookable, event, *args)
11
+ res = find_hook_events(hookable, event).map { |e| hookable.send(e, *args) }
12
12
  return false if res.any? { |result| result == false }
13
13
  res
14
14
  end
@@ -20,16 +20,16 @@ module Backburner
20
20
  # original task after calling all other around blocks.
21
21
  #
22
22
  # @example
23
- # around_hook_events(:around_perform) { job.perform }
23
+ # around_hook_events(hookable, :around_perform) { hookable.perform }
24
24
  #
25
- def around_hook_events(job, event, *args, &block)
25
+ def around_hook_events(hookable, event, *args, &block)
26
26
  raise "Please pass a block to hook events!" unless block_given?
27
- around_hooks = find_hook_events(job, event).reverse
27
+ around_hooks = find_hook_events(hookable, event).reverse
28
28
  aggregate_filter = Proc.new { |&blk| blk.call }
29
29
  around_hooks.each do |ah|
30
30
  prior_around_filter = aggregate_filter
31
31
  aggregate_filter = Proc.new do |&blk|
32
- job.method(ah).call(*args) do
32
+ hookable.method(ah).call(*args) do
33
33
  prior_around_filter.call(&blk)
34
34
  end
35
35
  end
@@ -45,9 +45,9 @@ module Backburner
45
45
  # find_hook_events(:before_enqueue)
46
46
  # # => ['before_enqueue_foo', 'before_enqueue_bar']
47
47
  #
48
- def find_hook_events(job, event)
49
- (job.methods - Object.methods).grep(/^#{event}/).sort
48
+ def find_hook_events(hookable, event)
49
+ (hookable.methods - Object.methods).grep(/^#{event}/).sort
50
50
  end
51
51
  end
52
52
  end # Hooks
53
- end # Backburner
53
+ end # Backburner
@@ -46,7 +46,12 @@ module Backburner
46
46
  return false unless res
47
47
  # Execute the job
48
48
  @hooks.around_hook_events(job_class, :around_perform, *args) do
49
- timeout_job_after(task.ttr) { job_class.perform(*args) }
49
+ # We subtract one to ensure we timeout before beanstalkd does, except if:
50
+ # a) ttr == 0, to support never timing out
51
+ # b) ttr == 1, so that we don't accidentally set it to never time out
52
+ # NB: A ttr of 1 will likely result in race conditions between
53
+ # Backburner and beanstalkd and should probably be avoided
54
+ timeout_job_after(task.ttr > 1 ? task.ttr - 1 : task.ttr) { job_class.perform(*args) }
50
55
  end
51
56
  task.delete
52
57
  # Invoke after perform hook
@@ -1,3 +1,3 @@
1
1
  module Backburner
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0.pre"
3
3
  end
@@ -27,15 +27,24 @@ module Backburner
27
27
  pri = resolve_priority(opts[:pri] || job_class)
28
28
  delay = [0, opts[:delay].to_i].max
29
29
  ttr = resolve_respond_timeout(opts[:ttr] || job_class)
30
- res = Backburner::Hooks.invoke_hook_events(job_class, :before_enqueue, *args)
30
+ res = Backburner::Hooks.invoke_hook_events(job_class, :before_enqueue, *args)
31
+
31
32
  return false unless res # stop if hook is false
33
+
32
34
  data = { :class => job_class.name, :args => args }
33
- retryable_command do
34
- queue = opts[:queue] && (Proc === opts[:queue] ? opts[:queue].call(job_class) : opts[:queue])
35
- tube = connection.tubes[expand_tube_name(queue || job_class)]
36
- tube.put(data.to_json, :pri => pri, :delay => delay, :ttr => ttr)
35
+ queue = opts[:queue] && (Proc === opts[:queue] ? opts[:queue].call(job_class) : opts[:queue])
36
+
37
+ begin
38
+ connection = Backburner::Connection.new(Backburner.configuration.beanstalk_url)
39
+ connection.retryable do
40
+ tube = connection.tubes[expand_tube_name(queue || job_class)]
41
+ tube.put(data.to_json, :pri => pri, :delay => delay, :ttr => ttr)
42
+ end
43
+ Backburner::Hooks.invoke_hook_events(job_class, :after_enqueue, *args)
44
+ ensure
45
+ connection.close if connection
37
46
  end
38
- Backburner::Hooks.invoke_hook_events(job_class, :after_enqueue, *args)
47
+
39
48
  return true
40
49
  end
41
50
 
@@ -52,21 +61,15 @@ module Backburner
52
61
  end
53
62
  end
54
63
 
55
- # Returns the worker connection.
56
- # @example
57
- # Backburner::Worker.connection # => <Beaneater::Connection>
58
- def self.connection
59
- @connection ||= Connection.new(Backburner.configuration.beanstalk_url)
60
- end
61
-
62
64
  # List of tube names to be watched and processed
63
- attr_accessor :tube_names
65
+ attr_accessor :tube_names, :connection
64
66
 
65
67
  # Constructs a new worker for processing jobs within specified tubes.
66
68
  #
67
69
  # @example
68
70
  # Worker.new(['test.job'])
69
71
  def initialize(tube_names=nil)
72
+ @connection = new_connection
70
73
  @tube_names = self.process_tube_names(tube_names)
71
74
  register_signal_handlers!
72
75
  end
@@ -116,27 +119,34 @@ module Backburner
116
119
  compact_tube_names(tube_names)
117
120
  end
118
121
 
119
- # Reserves one job within the specified queues.
120
- # Pops the job off and serializes the job to JSON.
121
- # Each job is performed by invoking `perform` on the job class.
122
+ # Performs a job by reserving a job from beanstalk and processing it
122
123
  #
123
124
  # @example
124
125
  # @worker.work_one_job
125
- #
126
- def work_one_job(conn = nil)
127
- conn ||= self.connection
126
+ # @raise [Beaneater::NotConnected] If beanstalk fails to connect multiple times.
127
+ def work_one_job(conn = connection)
128
128
  begin
129
- job = Backburner::Job.new(conn.tubes.reserve(Backburner.configuration.reserve_timeout))
129
+ job = reserve_job(conn)
130
130
  rescue Beaneater::TimedOutError => e
131
131
  return
132
132
  end
133
+
133
134
  self.log_job_begin(job.name, job.args)
134
135
  job.process
135
136
  self.log_job_end(job.name)
137
+
136
138
  rescue Backburner::Job::JobFormatInvalid => e
137
139
  self.log_error self.exception_message(e)
138
140
  rescue => e # Error occurred processing job
139
141
  self.log_error self.exception_message(e)
142
+
143
+ unless job
144
+ self.log_error "Error occurred before we were able to assign a job. Giving up without retrying!"
145
+ return
146
+ end
147
+
148
+ # NB: There's a slight chance here that the connection to beanstalkd has
149
+ # gone down between the time we reserved / processed the job and here.
140
150
  num_retries = job.stats.releases
141
151
  retry_status = "failed: attempt #{num_retries+1} of #{queue_config.max_job_retries+1}"
142
152
  if num_retries < queue_config.max_job_retries # retry again
@@ -147,46 +157,23 @@ module Backburner
147
157
  job.bury
148
158
  self.log_job_end(job.name, "#{retry_status}, burying") if job_started_at
149
159
  end
150
- handle_error(e, job.name, job.args, job)
151
- end
152
160
 
153
- # Retries the given command specified in the block several times if there is a connection error
154
- # Used to execute beanstalkd commands in a retryable way
155
- #
156
- # @example
157
- # retryable_command { ... }
158
- # @raise [Beaneater::NotConnected] If beanstalk fails to connect multiple times.
159
- #
160
- def self.retryable_command(max_tries=8, &block)
161
- begin
162
- yield
163
- rescue Beaneater::NotConnected
164
- retry_connection!(max_tries)
165
- yield
166
- end
161
+ handle_error(e, job.name, job.args, job)
167
162
  end
168
163
 
169
- # Retries to make a connection to beanstalkd if that connection failed.
170
- # @raise [Beaneater::NotConnected] If beanstalk fails to connect multiple times.
171
- #
172
- def self.retry_connection!(max_tries=8)
173
- retry_count = 0
174
- begin
175
- @connection = nil
176
- self.connection.stats
177
- rescue Beaneater::NotConnected => e
178
- if retry_count < max_tries
179
- retry_count += 1
180
- sleep 1
181
- retry
182
- else # stop retrying
183
- raise e
184
- end
185
- end
186
- end # retry_connection!
187
164
 
188
165
  protected
189
166
 
167
+ # Return a new connection instance
168
+ def new_connection
169
+ Connection.new(Backburner.configuration.beanstalk_url) { |conn| Backburner::Hooks.invoke_hook_events(self, :on_reconnect, conn) }
170
+ end
171
+
172
+ # Reserve a job from the watched queues
173
+ def reserve_job(conn, reserve_timeout = Backburner.configuration.reserve_timeout)
174
+ Backburner::Job.new(conn.tubes.reserve(reserve_timeout))
175
+ end
176
+
190
177
  # Returns a list of all tubes known within the system
191
178
  # Filtered for tubes that match the known prefix
192
179
  def all_existing_queues
@@ -195,10 +182,6 @@ module Backburner
195
182
  existing_tubes + known_queues + [queue_config.primary_queue]
196
183
  end
197
184
 
198
- # Returns a reference to the beanstalk connection
199
- def connection
200
- self.class.connection
201
- end
202
185
 
203
186
  # Handles an error according to custom definition
204
187
  # Used when processing a job that errors out