crono_trigger 0.5.4 → 0.6.0

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.
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