crono_trigger 0.5.4 → 0.6.0

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: 414c7d5d5bc3a58298ef4cefaaf5dd23e12e6b2af43b2f269b77ddfa4460e3fa
4
- data.tar.gz: 8004dcf5504db3f36c6e0548197842eaf3665cb1957ed8400c39338fc94b5539
3
+ metadata.gz: 3a9b3bad7ab74a886398bcffaa3f2d935ec35dcd83819abe6b24c0453771a4d1
4
+ data.tar.gz: 4afa776cb8305b9a95b92c180a3c6da86f0ed205e32a93f3887084b3f498e980
5
5
  SHA512:
6
- metadata.gz: 7de0e41900387c9c2f66e1df342645080e98d1058a39a7d4553dc9bd38625c640d2f48ccb360052bc31d614022a0c93bd1a08976d4b766f70c6fbf1de3d03133
7
- data.tar.gz: e79b4255892a31cfe73bc673705605549b6ab687712e754d0aa790d45d4e09bc98006b86a52fac6901ad4e433951c15ae5f15ba71b88da60b6297884ee8db224
6
+ metadata.gz: 7e42ef78268d05a3add743f0616fbaa0ea69dba395328a1304fd1a7b18450777ce9c97ccd7be58f59a47d5f5b7c0854098ca74f5756d1b07470b91d42305ba29
7
+ data.tar.gz: 1ae81221b5251c7ede8d204385910930162bebba7d74ed743fd5978461c9a9d008b9ede5e3c8890ab02b2867030d088032e5776441e073f26a080b8b53410b5b
data/README.md CHANGED
@@ -111,7 +111,7 @@ class MailNotification < ActiveRecord::Base
111
111
  send_mail
112
112
 
113
113
  throw :retry # break execution and retry task
114
- throw :abort # break execution and raise AbortExecution. AbortExecution is not retried
114
+ throw :abort # break execution
115
115
  throw :ok # break execution and handle task as success
116
116
  throw :ok_without_reset # break execution and handle task as success but without schedule reseting and unlocking
117
117
  end
@@ -160,19 +160,20 @@ Usage: crono_trigger [options] MODEL [MODEL..]
160
160
 
161
161
  ### Columns
162
162
 
163
- |name |type |required|rename|description |
164
- |-----------------|--------|--------|------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
165
- |cron |string |no |no |Recurring schedule formatted by cron style |
166
- |next_execute_at |datetime|yes |yes |Timestamp of next execution. Worker executes task if this column <= now |
167
- |last_executed_at |datetime|no |yes |Timestamp of last execution |
168
- |timezone |datetime|no |yes |Timezone name (Parsed by tzinfo) |
169
- |execute_lock |integer |yes |yes |Timestamp of fetching record in order to hide record from other transaction during execute lock timeout. <br> when execution complete this column is reset to 0|
170
- |started_at |datetime|no |yes |Timestamp of schedule activated |
171
- |finished_at |datetime|no |yes |Timestamp of schedule deactivated |
172
- |last_error_name |string |no |no |Class name of last error |
173
- |last_error_reason|string |no |no |Error message of last error |
174
- |last_error_time |datetime|no |no |Timestamp of last error occured |
175
- |retry_count |integer |no |no |Retry count. <br> If execution succeed retry_count is reset to 0 |
163
+ | name | type | required | rename | description |
164
+ | ----------------- | -------- | -------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
165
+ | cron | string | no | no | Recurring schedule formatted by cron style |
166
+ | next_execute_at | datetime | yes | yes | Timestamp of next execution. Worker executes task if this column <= now |
167
+ | last_executed_at | datetime | no | yes | Timestamp of last execution |
168
+ | timezone | datetime | no | yes | Timezone name (Parsed by tzinfo) |
169
+ | execute_lock | integer | yes | yes | Timestamp of fetching record in order to hide record from other transaction during execute lock timeout. <br> when execution complete this column is reset to 0 |
170
+ | started_at | datetime | no | yes | Timestamp of schedule activated |
171
+ | finished_at | datetime | no | yes | Timestamp of schedule deactivated |
172
+ | last_error_name | string | no | no | Class name of last error |
173
+ | last_error_reason | string | no | no | Error message of last error |
174
+ | last_error_time | datetime | no | no | Timestamp of last error occured |
175
+ | retry_count | integer | no | no | Retry count. <br> If execution succeed retry_count is reset to 0 |
176
+ | current_cycle_id | string | no | yes | UUID that is updated when the schedule is resetted successfully |
176
177
 
177
178
  You can rename some columns.
178
179
  ex. `crono_trigger_options[:next_execute_at_column_name] = "next_time"`
@@ -19,6 +19,7 @@ module CronoTrigger
19
19
  executor_thread: 25,
20
20
  model_names: nil,
21
21
  error_handlers: [],
22
+ global_error_handlers: [],
22
23
  )
23
24
 
24
25
  def self.config
@@ -4,12 +4,25 @@ module CronoTrigger
4
4
  @schedulable = schedulable
5
5
  end
6
6
 
7
+ def self.track(schedulable, &pr)
8
+ new(schedulable).track(&pr)
9
+ end
10
+
7
11
  def track(&pr)
8
12
  if @schedulable.track_execution
9
13
  begin
10
14
  execution = @schedulable.crono_trigger_executions.create_with_timestamp!
11
- pr.call
12
- execution.complete!
15
+ result = pr.call
16
+ case result
17
+ when :ok
18
+ execution.complete!
19
+ when :retry
20
+ execution.retrying!
21
+ when :abort
22
+ execution.aborted!
23
+ else
24
+ execution.complete!
25
+ end
13
26
  rescue => e
14
27
  execution.error!(e)
15
28
  raise
@@ -0,0 +1,29 @@
1
+ module CronoTrigger
2
+ class GlobalExceptionHandler
3
+ def self.handle_global_exception(ex)
4
+ new.handle_global_exception(ex)
5
+ end
6
+
7
+ def handle_global_exception(ex)
8
+ handlers = CronoTrigger.config.global_error_handlers
9
+ handlers.each do |callable|
10
+ callable, arity = ensure_callable(callable)
11
+
12
+ args = [ex]
13
+ args = arity < 0 ? args : args.take(arity)
14
+ callable.call(*args)
15
+ end
16
+ rescue Exception => e
17
+ ActiveRecord::Base.logger.error("CronoTrigger error handler raises error")
18
+ ActiveRecord::Base.logger.error(e)
19
+ end
20
+
21
+ private
22
+
23
+ def ensure_callable(callable)
24
+ if callable.respond_to?(:call)
25
+ return callable, callable.arity
26
+ end
27
+ end
28
+ end
29
+ end
@@ -11,6 +11,8 @@ module CronoTrigger
11
11
  executing: "executing",
12
12
  completed: "completed",
13
13
  failed: "failed",
14
+ retrying: "retrying",
15
+ aborted: "aborted",
14
16
  }
15
17
 
16
18
  def self.create_with_timestamp!
@@ -22,8 +22,9 @@ module CronoTrigger
22
22
  poll(model)
23
23
  rescue ThreadError => e
24
24
  @logger.error(e) unless e.message == "queue empty"
25
- rescue => e
26
- @logger.error(e)
25
+ rescue => ex
26
+ @logger.error(ex)
27
+ CronoTrigger::GlobalExceptionHandler.handle_global_exception(ex)
27
28
  ensure
28
29
  @model_queue << model_name if model_name
29
30
  end
@@ -63,14 +64,7 @@ module CronoTrigger
63
64
  @executor.post do
64
65
  @execution_counter.increment
65
66
  begin
66
- model.connection_pool.with_connection do
67
- @logger.info "(executor-thread-#{Thread.current.object_id}) Execute #{record.class}-#{record.id}"
68
- begin
69
- record.do_execute
70
- rescue Exception => e
71
- @logger.error(e)
72
- end
73
- end
67
+ process_record(record)
74
68
  ensure
75
69
  @execution_counter.decrement
76
70
  end
@@ -83,7 +77,19 @@ module CronoTrigger
83
77
  end while overflowed_record_ids.empty? && records.any?
84
78
  end
85
79
 
86
- private def unlock_overflowed_records(model, overflowed_record_ids)
80
+ private
81
+
82
+ def process_record(record)
83
+ record.class.connection_pool.with_connection do
84
+ @logger.info "(executor-thread-#{Thread.current.object_id}) Execute #{record.class}-#{record.id}"
85
+ record.do_execute
86
+ end
87
+ rescue Exception => ex
88
+ @logger.error(ex)
89
+ CronoTrigger::GlobalExceptionHandler.handle_global_exception(ex)
90
+ end
91
+
92
+ def unlock_overflowed_records(model, overflowed_record_ids)
87
93
  model.connection_pool.with_connection do
88
94
  unless overflowed_record_ids.empty?
89
95
  model.where(id: overflowed_record_ids).crono_trigger_unlock_all!
@@ -2,12 +2,15 @@ require 'rollbar'
2
2
 
3
3
  module Rollbar
4
4
  class CronoTrigger
5
- def self.handle_exception(ex, record)
5
+ def self.handle_exception(ex, record = nil)
6
6
  scope = {
7
7
  framework: "CronoTrigger: #{::CronoTrigger::VERSION}",
8
- context: "#{record.class}/#{record.id}"
9
8
  }
10
9
 
10
+ if record
11
+ scope.merge!({context: "#{record.class}/#{record.id}"})
12
+ end
13
+
11
14
  Rollbar.scope(scope).error(ex, use_exception_level_filters: true)
12
15
  end
13
16
  end
@@ -18,8 +21,11 @@ Rollbar.plugins.define('crono_trigger') do
18
21
 
19
22
  execute! do
20
23
  CronoTrigger.config.error_handlers << proc do |ex, record|
21
- Rollbar.reset_notifier!
22
24
  Rollbar::CronoTrigger.handle_exception(ex, record)
23
25
  end
26
+
27
+ CronoTrigger.config.global_error_handlers << proc do |ex|
28
+ Rollbar::CronoTrigger.handle_exception(ex)
29
+ end
24
30
  end
25
31
  end
@@ -20,9 +20,6 @@ module CronoTrigger
20
20
  @included_by
21
21
  end
22
22
 
23
- class AbortExecution < StandardError; end
24
- class RetryExecution < StandardError; end
25
-
26
23
  extend ActiveSupport::Concern
27
24
  include ActiveSupport::Callbacks
28
25
 
@@ -59,6 +56,7 @@ module CronoTrigger
59
56
  rel
60
57
  end
61
58
 
59
+ before_create :set_current_cyctle_id
62
60
  before_update :update_next_execute_at_if_update_cron
63
61
 
64
62
  validate :validate_cron_format
@@ -112,37 +110,34 @@ module CronoTrigger
112
110
  end
113
111
 
114
112
  def do_execute
115
- execution_tracker = ExecutionTracker.new(self)
116
- run_callbacks :execute do
117
- execution_tracker.track do
118
- catch(:ok_without_reset) do
119
- catch(:ok) do
120
- catch(:retry) do
121
- catch(:abort) do
122
- execute
123
- throw :ok
124
- end
125
- raise AbortExecution
126
- end
127
- retry!
128
- raise RetryExecution
129
- end
130
- reset!
131
- end
132
- end
113
+ ExecutionTracker.track(self) do
114
+ do_execute_with_catch
133
115
  end
134
- rescue AbortExecution => ex
135
- save_last_error_info(ex)
136
- reset!(false)
137
-
138
- raise
139
- rescue RetryExecution => ex
140
- save_last_error_info(ex)
141
116
  rescue Exception => ex
117
+ logger.error(ex) if logger
142
118
  save_last_error_info(ex)
143
119
  retry_or_reset!(ex)
120
+ end
144
121
 
145
- raise
122
+ private def do_execute_with_catch
123
+ catch(:ok_without_reset) do
124
+ catch(:ok) do
125
+ catch(:retry) do
126
+ catch(:abort) do
127
+ run_callbacks :execute do
128
+ execute
129
+ end
130
+ throw :ok
131
+ end
132
+ abort_execution!
133
+ return :abort
134
+ end
135
+ retry!
136
+ return :retry
137
+ end
138
+ reset!
139
+ return :ok
140
+ end
146
141
  end
147
142
 
148
143
  def activate_schedule!(at: Time.current)
@@ -213,10 +208,18 @@ module CronoTrigger
213
208
  attributes.merge!(retry_count: 0)
214
209
  end
215
210
 
211
+ if self.class.column_names.include?(crono_trigger_column_name(:current_cycle_id))
212
+ attributes.merge!(crono_trigger_column_name(:current_cycle_id) => SecureRandom.uuid)
213
+ end
214
+
216
215
  merge_updated_at_for_crono_trigger!(attributes, now)
217
216
  update_columns(attributes)
218
217
  end
219
218
 
219
+ def abort_execution!
220
+ reset!(false)
221
+ end
222
+
220
223
  def crono_trigger_lock!(**attributes)
221
224
  attributes = {
222
225
  crono_trigger_column_name(:execute_lock) => Time.current.to_i,
@@ -259,7 +262,7 @@ module CronoTrigger
259
262
  end
260
263
 
261
264
  def locking?(at: Time.now)
262
- self[crono_trigger_column_name(:execute_lock)] > 0 &&
265
+ self[crono_trigger_column_name(:execute_lock)] > 0 &&
263
266
  self[crono_trigger_column_name(:execute_lock)] >= at.to_f - self.class.execute_lock_timeout
264
267
  end
265
268
 
@@ -293,7 +296,19 @@ module CronoTrigger
293
296
  tz = self[crono_trigger_column_name(:timezone)].try { |zn| TZInfo::Timezone.get(zn) }
294
297
  base = [now, self[crono_trigger_column_name(:started_at)]].compact.max
295
298
  cron_now = tz ? base.in_time_zone(tz) : base
296
- Chrono::NextTime.new(now: cron_now, source: self[crono_trigger_column_name(:cron)]).to_time
299
+ calculated = Chrono::NextTime.new(now: cron_now, source: self[crono_trigger_column_name(:cron)]).to_time
300
+
301
+ return calculated unless self[crono_trigger_column_name(:finished_at)]
302
+ return if calculated > self[crono_trigger_column_name(:finished_at)]
303
+
304
+ calculated
305
+ end
306
+ end
307
+
308
+ def set_current_cyctle_id
309
+ if self.class.column_names.include?(crono_trigger_column_name(:current_cycle_id)) &&
310
+ self[crono_trigger_column_name(:current_cycle_id)].nil?
311
+ self[crono_trigger_column_name(:current_cycle_id)] = SecureRandom.uuid
297
312
  end
298
313
  end
299
314
 
@@ -1,3 +1,3 @@
1
1
  module CronoTrigger
2
- VERSION = "0.5.4"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -1,5 +1,7 @@
1
1
  require "active_support/core_ext/string"
2
2
 
3
+ require "crono_trigger/global_exception_handler"
4
+
3
5
  module CronoTrigger
4
6
  module Worker
5
7
  HEARTBEAT_INTERVAL = 60
@@ -100,9 +102,9 @@ module CronoTrigger
100
102
  worker_record.polling_model_names = @model_names
101
103
  worker_record.last_heartbeated_at = Time.current
102
104
  @logger.info("[worker_id:#{@crono_trigger_worker_id}] Send heartbeat to database")
103
- worker_record.save
105
+ worker_record.save!
104
106
  rescue => ex
105
- p ex
107
+ CronoTrigger::GlobalExceptionHandler.handle_global_exception(ex)
106
108
  stop
107
109
  end
108
110
  end
@@ -128,6 +130,8 @@ module CronoTrigger
128
130
  CronoTrigger::Models::Worker.connection_pool.with_connection do
129
131
  CronoTrigger::Models::Worker.find_by(worker_id: @crono_trigger_worker_id)&.destroy
130
132
  end
133
+ rescue => ex
134
+ CronoTrigger::GlobalExceptionHandler.handle_global_exception(ex)
131
135
  end
132
136
 
133
137
  def handle_signal_from_rdb
@@ -137,6 +141,8 @@ module CronoTrigger
137
141
  s.kill_me(to_supervisor: s.signal != "TSTP")
138
142
  end
139
143
  end
144
+ rescue => ex
145
+ CronoTrigger::GlobalExceptionHandler.handle_global_exception(ex)
140
146
  end
141
147
  end
142
148
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crono_trigger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - joker1007
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-23 00:00:00.000000000 Z
11
+ date: 2020-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chrono
@@ -263,6 +263,7 @@ files:
263
263
  - lib/crono_trigger/cli.rb
264
264
  - lib/crono_trigger/exception_handler.rb
265
265
  - lib/crono_trigger/execution_tracker.rb
266
+ - lib/crono_trigger/global_exception_handler.rb
266
267
  - lib/crono_trigger/models/execution.rb
267
268
  - lib/crono_trigger/models/signal.rb
268
269
  - lib/crono_trigger/models/worker.rb