good_job 2.3.0 → 2.4.2

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: c2a09a86e822d7dfc09b5788c5790fe79fd418c5749e7ff716e79e35496d8574
4
- data.tar.gz: d588ac1a06ea6b013834922c23de7157099f4a261b2e52422081d305f2e9e468
3
+ metadata.gz: f06b525450f56cf74ff31e06e11cbf3bf2ee7f20d753973e70ddb53d67eb48d1
4
+ data.tar.gz: d25d2037ae03eaea0ff8bd4ca1dfa52ba6a57d3420e53b4be265dc7c0be580cb
5
5
  SHA512:
6
- metadata.gz: 8e21e8a4ded224874ac111aae669dcf2315dc63663b7942888ef22e8da9367f8b6fa973bfda86941564f6e35fff7ef22f7f403ae9f7414a48a3be2ae2cd7f2a3
7
- data.tar.gz: 3ddcf93091ba3a221aadaf85790b8ff7edca2a317c9e23df45a5d69d07ebdcaf9143ada9bf5f157a9909aca4deb5f7ecc51c1abb80b98e90e51241e5fce0f450
6
+ metadata.gz: 2f2be221d400f0dc7a1e7c2d262b717ecd9c88a5192771df2bd343d61e878cb777af7e067814e3e7c751aa28716c2bd487b4389ef4dbbea17cc2a6a0f23d0c9a
7
+ data.tar.gz: abfd020a2203bb573c5e801e7f278214ebe996b6e63970ec60313fcb37a3b21cb901d4cf57cfd6739b7c247ea692ff7de012df3668d7c551d7ced78cc48b79e6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,80 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.4.2](https://github.com/bensheldon/good_job/tree/v2.4.2) (2021-10-19)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.4.1...v2.4.2)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Add migration version to install/update generator templates [\#426](https://github.com/bensheldon/good_job/pull/426) ([bensheldon](https://github.com/bensheldon))
10
+
11
+ **Fixed bugs:**
12
+
13
+ - Explicitly unscope queries within block yielded to Lockable.within\_advisory\_lock [\#429](https://github.com/bensheldon/good_job/pull/429) ([bensheldon](https://github.com/bensheldon))
14
+ - Fix Demo CleanupJob args [\#427](https://github.com/bensheldon/good_job/pull/427) ([bensheldon](https://github.com/bensheldon))
15
+
16
+ **Merged pull requests:**
17
+
18
+ - Remove v1.99/v2 transitional extra advisory lock [\#428](https://github.com/bensheldon/good_job/pull/428) ([bensheldon](https://github.com/bensheldon))
19
+
20
+ ## [v2.4.1](https://github.com/bensheldon/good_job/tree/v2.4.1) (2021-10-11)
21
+
22
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.4.0...v2.4.1)
23
+
24
+ **Implemented enhancements:**
25
+
26
+ - Support Datadog APM / `dd-trace-rb` [\#323](https://github.com/bensheldon/good_job/issues/323)
27
+ - Display info about used timezone. [\#398](https://github.com/bensheldon/good_job/pull/398) ([morgoth](https://github.com/morgoth))
28
+ - Display cron schedules args in dashboard [\#396](https://github.com/bensheldon/good_job/pull/396) ([aried3r](https://github.com/aried3r))
29
+
30
+ **Fixed bugs:**
31
+
32
+ - Inline adapter should raise unhandled exceptions during execution [\#416](https://github.com/bensheldon/good_job/pull/416) ([bensheldon](https://github.com/bensheldon))
33
+ - Enforce english locale in UI [\#407](https://github.com/bensheldon/good_job/pull/407) ([morgoth](https://github.com/morgoth))
34
+
35
+ **Closed issues:**
36
+
37
+ - Finished jobs don't show up as finished [\#415](https://github.com/bensheldon/good_job/issues/415)
38
+ - Inline adapter should raise unhandled exceptions during execution [\#410](https://github.com/bensheldon/good_job/issues/410)
39
+ - Rewrite Scheduler "worker" thread name to be `thread` [\#406](https://github.com/bensheldon/good_job/issues/406)
40
+ - "WARNING: you don't own a lock of type ExclusiveLock" in Development [\#388](https://github.com/bensheldon/good_job/issues/388)
41
+ - Improve Readme's "Optimize queues, threads, processes" section [\#132](https://github.com/bensheldon/good_job/issues/132)
42
+
43
+ **Merged pull requests:**
44
+
45
+ - Ignore Rails HEAD Appraisal until `rails new` fixed [\#419](https://github.com/bensheldon/good_job/pull/419) ([bensheldon](https://github.com/bensheldon))
46
+ - Warn in Readme that configuration should not go into `config/initializers/*.rb` [\#418](https://github.com/bensheldon/good_job/pull/418) ([bensheldon](https://github.com/bensheldon))
47
+ - Replace worker wording [\#409](https://github.com/bensheldon/good_job/pull/409) ([Hugo-Hache](https://github.com/Hugo-Hache))
48
+ - Improve Readme's "Optimize queues, threads, processes" section [\#405](https://github.com/bensheldon/good_job/pull/405) ([Hugo-Hache](https://github.com/Hugo-Hache))
49
+ - Update GH Test Matrix with more PG versions [\#401](https://github.com/bensheldon/good_job/pull/401) ([tedhexaflow](https://github.com/tedhexaflow))
50
+ - Extract cron configuration hash into CronEntry ActiveModel objects [\#400](https://github.com/bensheldon/good_job/pull/400) ([bensheldon](https://github.com/bensheldon))
51
+ - Remove errant copy-paste from app.json [\#397](https://github.com/bensheldon/good_job/pull/397) ([morgoth](https://github.com/morgoth))
52
+
53
+ ## [v2.4.0](https://github.com/bensheldon/good_job/tree/v2.4.0) (2021-10-02)
54
+
55
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.3.1...v2.4.0)
56
+
57
+ **Implemented enhancements:**
58
+
59
+ - Display schedule time relative to now. [\#394](https://github.com/bensheldon/good_job/pull/394) ([morgoth](https://github.com/morgoth))
60
+ - Display cron schedules properties in dashboard [\#391](https://github.com/bensheldon/good_job/pull/391) ([aried3r](https://github.com/aried3r))
61
+
62
+ **Fixed bugs:**
63
+
64
+ - Correct icon for alert flash [\#395](https://github.com/bensheldon/good_job/pull/395) ([morgoth](https://github.com/morgoth))
65
+
66
+ ## [v2.3.1](https://github.com/bensheldon/good_job/tree/v2.3.1) (2021-09-30)
67
+
68
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.3.0...v2.3.1)
69
+
70
+ **Fixed bugs:**
71
+
72
+ - Wrap Scheduler task execution with Rails `reloader` instead of `executor` to avoid database connection changing during code reload [\#389](https://github.com/bensheldon/good_job/pull/389) ([bensheldon](https://github.com/bensheldon))
73
+
74
+ **Merged pull requests:**
75
+
76
+ - Log Cleanup thread tests, introduce "Slow" ExampleJob type, refactor ExampleJob types, run cron and log Postgres warnings in GoodJob Development harness [\#390](https://github.com/bensheldon/good_job/pull/390) ([bensheldon](https://github.com/bensheldon))
77
+
3
78
  ## [v2.3.0](https://github.com/bensheldon/good_job/tree/v2.3.0) (2021-09-25)
4
79
 
5
80
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.2.0...v2.3.0)
data/README.md CHANGED
@@ -212,7 +212,11 @@ to delete old records and preserve space in your database.
212
212
 
213
213
  To use GoodJob, you can set `config.active_job.queue_adapter` to a `:good_job`.
214
214
 
215
- Additional configuration can be provided via `config.good_job.OPTION = ...` for example:
215
+ Additional configuration can be provided via `config.good_job.OPTION = ...`.
216
+
217
+ _Configuration **must** be placed into `config/application.rb` or `config/environments/{RAILS_ENV}.rb`; configuration may not work correctly if placed into `config/initializers/*.rb` because application initializers run _after_ gem initialization (see [Rails#36650](https://github.com/rails/rails/issues/36650) and [GoodJob#380](https://github.com/bensheldon/good_job/issues/380))._
218
+
219
+ Configuration examples:
216
220
 
217
221
  ```ruby
218
222
  # config/application.rb
@@ -576,43 +580,62 @@ end
576
580
 
577
581
  By default, GoodJob creates a single thread execution pool that will execute jobs from any queue. Depending on your application's workload, job types, and service level objectives, you may wish to optimize execution resources. For example, providing dedicated execution resources for transactional emails so they are not delayed by long-running batch jobs. Some options:
578
582
 
579
- - Multiple execution pools within a single process:
583
+ - Multiple isolated execution pools within a single process:
584
+
585
+ For moderate workloads, multiple isolated thread execution pools offers a good balance between congestion management and economy.
586
+
587
+ A pool is configured with the following syntax `<participating_queues>:<thread_count>`:
588
+
589
+ - `<participating_queues>`: either `queue1,queue2` (only those queues), `*` (all) or `-queue1,queue2` (all except those queues).
590
+ - `<thread_count>`: a count overriding for this specific pool the global `max-threads`.
591
+
592
+ Pool configurations are separated with a semicolon (;) in the `queues` configuration
580
593
 
581
594
  ```bash
582
- $ bundle exec good_job --queues="transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;*" --max-threads=5
595
+ $ bundle exec good_job \
596
+ --queues="transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;*" \
597
+ --max-threads=5
583
598
  ```
584
599
 
585
- This configuration will result in a single process with 4 isolated thread execution pools. Isolated execution pools are separated with a semicolon (`;`) and queue names and thread counts with a colon (`:`)
600
+ This configuration will result in a single process with 4 isolated thread execution pools.
586
601
 
587
- - `transactional_messages:2`: execute jobs enqueued on `transactional_messages` with up to 2 threads.
588
- - `batch_processing:1` execute jobs enqueued on `batch_processing` with a single thread.
589
- - `-transactional_messages,batch_processing`: execute jobs enqueued on _any_ queue _excluding_ `transactional_messages` or `batch_processing` with up to 2 threads.
590
- - `*`: execute jobs on any queue on up to 5 threads, as configured by `--max-threads=5`
591
-
592
- For moderate workloads, multiple isolated thread execution pools offers a good balance between congestion management and economy.
602
+ - `transactional_messages:2`: execute jobs enqueued on `transactional_messages`, with up to 2 threads.
603
+ - `batch_processing:1` execute jobs enqueued on `batch_processing`, with a single thread.
604
+ - `-transactional_messages,batch_processing`: execute jobs enqueued on _any_ queue _excluding_ `transactional_messages` or `batch_processing`, with up to 2 threads.
605
+ - `*`: execute jobs on any queue, with up to 5 threads (as configured by `--max-threads=5`).
593
606
 
594
607
  Configuration can be injected by environment variables too:
595
608
 
596
609
  ```bash
597
- $ GOOD_JOB_QUEUES="transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;*" GOOD_JOB_MAX_THREADS=5 bundle exec good_job
610
+ $ GOOD_JOB_QUEUES="transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;*" \
611
+ GOOD_JOB_MAX_THREADS=5 \
612
+ bundle exec good_job
598
613
  ```
599
614
 
600
- - Multiple processes; for example, on Heroku:
615
+ - Multiple processes:
616
+
617
+ While multiple isolated thread execution pools offer a way to provide dedicated execution resources, those resources are bound to a single machine. To scale them independently, define several processes.
618
+
619
+ For example, this configuration on Heroku allows to customize the dyno count (instances), or type (CPU/RAM), per process type:
601
620
 
602
621
  ```procfile
603
622
  # Procfile
604
623
 
605
- # Separate dyno types
624
+ # Separate process types
606
625
  worker: bundle exec good_job --max-threads=5
607
626
  transactional_worker: bundle exec good_job --queues="transactional_messages" --max-threads=2
608
627
  batch_worker: bundle exec good_job --queues="batch_processing" --max-threads=1
628
+ ```
629
+
630
+ To optimize for CPU performance at the expense of greater memory and system resource usage, while keeping a single process type (and thus a single dyno), combine several processes and wait for them:
609
631
 
610
- # Combined multi-process dyno
632
+ ```procfile
633
+ # Procfile
634
+
635
+ # Combined multi-process
611
636
  combined_worker: bundle exec good_job --max-threads=5 & bundle exec good_job --queues="transactional_messages" --max-threads=2 & bundle exec good_job --queues="batch_processing" --max-threads=1 & wait -n
612
637
  ```
613
638
 
614
- Running multiple processes can optimize for CPU performance at the expense of greater memory and system resource usage.
615
-
616
639
  Keep in mind, queue operations and management is an advanced discipline. This stuff is complex, especially for heavy workloads and unique processing requirements. Good job 👍
617
640
 
618
641
  ### Database connections
@@ -2,5 +2,13 @@
2
2
  module GoodJob
3
3
  class BaseController < ActionController::Base # rubocop:disable Rails/ApplicationController
4
4
  protect_from_forgery with: :exception
5
+
6
+ around_action :switch_locale
7
+
8
+ private
9
+
10
+ def switch_locale(&action)
11
+ I18n.with_locale(:en, &action)
12
+ end
5
13
  end
6
14
  end
@@ -3,7 +3,7 @@ module GoodJob
3
3
  class CronSchedulesController < GoodJob::BaseController
4
4
  def index
5
5
  configuration = GoodJob::Configuration.new({})
6
- @cron_schedules = configuration.cron
6
+ @cron_entries = configuration.cron_entries
7
7
  end
8
8
  end
9
9
  end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  module ApplicationHelper
4
+ def relative_time(timestamp)
5
+ text = timestamp.future? ? "in #{time_ago_in_words(timestamp)}" : "#{time_ago_in_words(timestamp)} ago"
6
+ tag.time(text, datetime: timestamp, title: timestamp)
7
+ end
4
8
  end
5
9
  end
@@ -1,22 +1,66 @@
1
- <% if @cron_schedules.present? %>
1
+ <% if @cron_entries.present? %>
2
2
  <div class="card my-3">
3
3
  <div class="table-responsive">
4
4
  <table class="table card-table table-bordered table-hover table-sm mb-0">
5
5
  <thead>
6
6
  <th>Cron Job Name</th>
7
7
  <th>Configuration</th>
8
+ <th>
9
+ Set&nbsp;
10
+ <%= tag.button "Toggle", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
11
+ data: { bs_toggle: "collapse", bs_target: ".job-properties" },
12
+ aria: { expanded: false, controls: @cron_entries.map { |cron_entry| dom_id(cron_entry, 'properties') }.join(" ") }
13
+ %>
14
+ </th>
15
+ <th>
16
+ Args&nbsp;
17
+ <%= tag.button "Toggle", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
18
+ data: { bs_toggle: "collapse", bs_target: ".job-args" },
19
+ aria: { expanded: false, controls: @cron_entries.map { |cron_entry| dom_id(cron_entry, 'args') }.join(" ") }
20
+ %>
21
+ </th>
8
22
  <th>Class</th>
9
23
  <th>Description</th>
10
24
  <th>Next scheduled</th>
11
25
  </thead>
12
26
  <tbody>
13
- <% @cron_schedules.each do |job_key, job| %>
27
+ <% @cron_entries.each do |cron_entry| %>
14
28
  <tr>
15
- <td class="font-monospace"><%= job_key %></td>
16
- <td class="font-monospace"><%= job[:cron] %></td>
17
- <td class="font-monospace"><%= job[:class] %></td>
18
- <td><%= job[:description] %></td>
19
- <td><%= Fugit.parse_cron(job[:cron]).next_time.to_local_time %></td>
29
+ <td class="font-monospace"><%= cron_entry.key %></td>
30
+ <td class="font-monospace"><%= cron_entry.cron %></td>
31
+ <td>
32
+ <%=
33
+ case cron_entry.set
34
+ when NilClass
35
+ "None"
36
+ when Proc
37
+ "Lambda/Callable"
38
+ when Hash
39
+ tag.button("Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
40
+ data: { bs_toggle: "collapse", bs_target: "##{dom_id(cron_entry, 'properties')}" },
41
+ aria: { expanded: false, controls: dom_id(cron_entry, 'properties') }) +
42
+ tag.pre(JSON.pretty_generate(cron_entry.set), id: dom_id(cron_entry, 'properties'), class: "collapse job-properties")
43
+ end
44
+ %>
45
+ </td>
46
+ <td>
47
+ <%=
48
+ case cron_entry.args
49
+ when NilClass
50
+ "None"
51
+ when Proc
52
+ "Lambda/Callable"
53
+ when Hash
54
+ tag.button("Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
55
+ data: { bs_toggle: "collapse", bs_target: "##{dom_id(cron_entry, 'args')}" },
56
+ aria: { expanded: false, controls: dom_id(cron_entry, 'args') }) +
57
+ tag.pre(JSON.pretty_generate(cron_entry.args), id: dom_id(cron_entry, 'args'), class: "collapse job-args")
58
+ end
59
+ %>
60
+ </td>
61
+ <td class="font-monospace"><%= cron_entry.job_class %></td>
62
+ <td><%= cron_entry.description %></td>
63
+ <td><%= cron_entry.next_at.to_local_time %></td>
20
64
  </tr>
21
65
  <% end %>
22
66
  </tbody>
@@ -34,7 +34,7 @@
34
34
  </td>
35
35
  <td><%= execution.serialized_params['job_class'] %></td>
36
36
  <td><%= execution.queue_name %></td>
37
- <td><%= execution.scheduled_at || execution.created_at %></td>
37
+ <td><%= relative_time(execution.scheduled_at || execution.created_at) %></td>
38
38
  <td class="text-break"><%= truncate(execution.error, length: 1_000) %></td>
39
39
  <td>
40
40
  <%= tag.button "Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
@@ -33,7 +33,7 @@
33
33
  </td>
34
34
  <td><%= job.job_class %></td>
35
35
  <td><%= job.queue_name %></td>
36
- <td><%= job.scheduled_at || job.created_at %></td>
36
+ <td><%= relative_time(job.scheduled_at || job.created_at) %></td>
37
37
  <td><%= job.executions_count %></td>
38
38
  <td class="text-break"><%= truncate(job.recent_error, length: 1_000) %></td>
39
39
  <td>
@@ -49,6 +49,7 @@
49
49
  </li>
50
50
  -->
51
51
  </ul>
52
+ <div class="text-muted" title="Now is <%= Time.current %>">Times are displayed in <%= Time.current.zone %> timezone</div>
52
53
  </div>
53
54
  </div>
54
55
  </nav>
@@ -68,7 +69,7 @@
68
69
  </div>
69
70
  <% elsif alert %>
70
71
  <div class="alert alert-warning alert-dismissible fade show d-flex align-items-center offset-md-3 col-6" role="alert">
71
- <%= render "good_job/shared/icons/check", class: "flex-shrink-0 me-2" %>
72
+ <%= render "good_job/shared/icons/exclamation", class: "flex-shrink-0 me-2" %>
72
73
  <div><%= alert %></div>
73
74
  <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
74
75
  </div>
@@ -19,5 +19,11 @@ module GoodJob
19
19
  def create_migration_file
20
20
  migration_template 'migrations/create_good_jobs.rb.erb', File.join(db_migrate_path, "create_good_jobs.rb")
21
21
  end
22
+
23
+ private
24
+
25
+ def migration_version
26
+ "[#{ActiveRecord::VERSION::STRING.to_f}]"
27
+ end
22
28
  end
23
29
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- class CreateGoodJobs < ActiveRecord::Migration[5.2]
2
+ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
3
3
  def change
4
4
  enable_extension 'pgcrypto'
5
5
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- class CreateGoodJobs < ActiveRecord::Migration[5.2]
2
+ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
3
3
  def change
4
4
  enable_extension 'pgcrypto'
5
5
 
@@ -24,5 +24,11 @@ module GoodJob
24
24
  migration_template "migrations/#{template_file}", File.join(db_migrate_path, destination_file), skip: true
25
25
  end
26
26
  end
27
+
28
+ private
29
+
30
+ def migration_version
31
+ "[#{ActiveRecord::VERSION::STRING.to_f}]"
32
+ end
27
33
  end
28
34
  end
@@ -32,10 +32,9 @@ module GoodJob
32
32
 
33
33
  GoodJob::Execution.new.with_advisory_lock(key: key, function: "pg_advisory_lock") do
34
34
  enqueue_concurrency = if enqueue_limit
35
- # TODO: Why is `unscoped` necessary? Nested scope is bleeding into subsequent query?
36
- GoodJob::Execution.unscoped.where(concurrency_key: key).unfinished.advisory_unlocked.count
35
+ GoodJob::Execution.where(concurrency_key: key).unfinished.advisory_unlocked.count
37
36
  else
38
- GoodJob::Execution.unscoped.where(concurrency_key: key).unfinished.count
37
+ GoodJob::Execution.where(concurrency_key: key).unfinished.count
39
38
  end
40
39
 
41
40
  # The job has not yet been enqueued, so check if adding it will go over the limit
@@ -63,7 +62,7 @@ module GoodJob
63
62
  next if key.blank?
64
63
 
65
64
  GoodJob::Execution.new.with_advisory_lock(key: key, function: "pg_advisory_lock") do
66
- allowed_active_job_ids = GoodJob::Execution.unscoped.where(concurrency_key: key).advisory_locked.order(Arel.sql("COALESCE(performed_at, scheduled_at, created_at) ASC")).limit(perform_limit).pluck(:active_job_id)
65
+ allowed_active_job_ids = GoodJob::Execution.where(concurrency_key: key).advisory_locked.order(Arel.sql("COALESCE(performed_at, scheduled_at, created_at) ASC")).limit(perform_limit).pluck(:active_job_id)
67
66
  # The current job has already been locked and will appear in the previous query
68
67
  raise GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError unless allowed_active_job_ids.include? job.job_id
69
68
  end
@@ -38,7 +38,7 @@ module GoodJob
38
38
  @notifier.recipients << [@scheduler, :create_thread]
39
39
  @poller.recipients << [@scheduler, :create_thread]
40
40
 
41
- @cron_manager = GoodJob::CronManager.new(@configuration.cron, start_on_initialize: Rails.application.initialized?) if @configuration.enable_cron?
41
+ @cron_manager = GoodJob::CronManager.new(@configuration.cron_entries, start_on_initialize: Rails.application.initialized?) if @configuration.enable_cron?
42
42
  end
43
43
  end
44
44
 
@@ -64,10 +64,12 @@ module GoodJob
64
64
 
65
65
  if execute_inline?
66
66
  begin
67
- execution.perform
67
+ result = execution.perform
68
68
  ensure
69
69
  execution.advisory_unlock
70
70
  end
71
+
72
+ raise result.unhandled_error if result.unhandled_error
71
73
  else
72
74
  job_state = { queue_name: execution.queue_name }
73
75
  job_state[:scheduled_at] = execution.scheduled_at if execution.scheduled_at
data/lib/good_job/cli.rb CHANGED
@@ -91,7 +91,9 @@ module GoodJob
91
91
  scheduler = GoodJob::Scheduler.from_configuration(configuration, warm_cache_on_initialize: true)
92
92
  notifier.recipients << [scheduler, :create_thread]
93
93
  poller.recipients << [scheduler, :create_thread]
94
- cron_manager = GoodJob::CronManager.new(configuration.cron, start_on_initialize: true) if configuration.enable_cron?
94
+
95
+ cron_manager = GoodJob::CronManager.new(configuration.cron_entries, start_on_initialize: true) if configuration.enable_cron?
96
+
95
97
  @stop_good_job_executable = false
96
98
  %w[INT TERM].each do |signal|
97
99
  trap(signal) { @stop_good_job_executable = true }
@@ -165,6 +165,10 @@ module GoodJob
165
165
  {}
166
166
  end
167
167
 
168
+ def cron_entries
169
+ cron.map { |cron_key, params| GoodJob::CronEntry.new(params.merge(key: cron_key)) }
170
+ end
171
+
168
172
  # Number of seconds to preserve jobs when using the +good_job cleanup_preserved_jobs+ CLI command.
169
173
  # This configuration is only used when {GoodJob.preserve_job_records} is +true+.
170
174
  # @return [Integer]
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ require "concurrent/hash"
3
+ require "concurrent/scheduled_task"
4
+ require "fugit"
5
+
6
+ module GoodJob # :nodoc:
7
+ #
8
+ # A CronEntry represents a single scheduled item's properties.
9
+ #
10
+ class CronEntry
11
+ include ActiveModel::Model
12
+
13
+ attr_reader :params
14
+
15
+ def initialize(params = {})
16
+ @params = params.with_indifferent_access
17
+ end
18
+
19
+ def key
20
+ params.fetch(:key)
21
+ end
22
+ alias id key
23
+
24
+ def job_class
25
+ params.fetch(:class)
26
+ end
27
+
28
+ def cron
29
+ params.fetch(:cron)
30
+ end
31
+
32
+ def set
33
+ params[:set]
34
+ end
35
+
36
+ def args
37
+ params[:args]
38
+ end
39
+
40
+ def description
41
+ params[:description]
42
+ end
43
+
44
+ def next_at
45
+ fugit = Fugit::Cron.parse(cron)
46
+ fugit.next_time
47
+ end
48
+
49
+ def enqueue
50
+ job_class.constantize.set(set_value).perform_later(*args_value)
51
+ end
52
+
53
+ private
54
+
55
+ def set_value
56
+ value = set || {}
57
+ value.respond_to?(:call) ? value.call : value
58
+ end
59
+
60
+ def args_value
61
+ value = args || []
62
+ value.respond_to?(:call) ? value.call : value
63
+ end
64
+ end
65
+ end
@@ -11,7 +11,7 @@ module GoodJob # :nodoc:
11
11
  # @!attribute [r] instances
12
12
  # @!scope class
13
13
  # List of all instantiated CronManagers in the current process.
14
- # @return [Array<GoodJob::CronManagers>, nil]
14
+ # @return [Array<GoodJob::CronManager>, nil]
15
15
  cattr_reader :instances, default: [], instance_reader: false
16
16
 
17
17
  # Task observer for cron task
@@ -26,13 +26,13 @@ module GoodJob # :nodoc:
26
26
 
27
27
  # Execution configuration to be scheduled
28
28
  # @return [Hash]
29
- attr_reader :schedules
29
+ attr_reader :cron_entries
30
30
 
31
- # @param schedules [Hash]
31
+ # @param cron_entries [Array<CronEntry>]
32
32
  # @param start_on_initialize [Boolean]
33
- def initialize(schedules = {}, start_on_initialize: false)
33
+ def initialize(cron_entries = [], start_on_initialize: false)
34
34
  @running = false
35
- @schedules = schedules
35
+ @cron_entries = cron_entries
36
36
  @tasks = Concurrent::Hash.new
37
37
 
38
38
  self.class.instances << self
@@ -42,9 +42,11 @@ module GoodJob # :nodoc:
42
42
 
43
43
  # Schedule tasks that will enqueue jobs based on their schedule
44
44
  def start
45
- ActiveSupport::Notifications.instrument("cron_manager_start.good_job", cron_jobs: @schedules) do
45
+ ActiveSupport::Notifications.instrument("cron_manager_start.good_job", cron_entries: cron_entries) do
46
46
  @running = true
47
- schedules.each_key { |cron_key| create_task(cron_key) }
47
+ cron_entries.each do |cron_entry|
48
+ create_task(cron_entry)
49
+ end
48
50
  end
49
51
  end
50
52
 
@@ -78,36 +80,22 @@ module GoodJob # :nodoc:
78
80
  end
79
81
 
80
82
  # Enqueues a scheduled task
81
- # @param cron_key [Symbol, String] the key within the schedule to use
82
- def create_task(cron_key)
83
- schedule = @schedules[cron_key]
84
- return false if schedule.blank?
85
-
86
- fugit = Fugit::Cron.parse(schedule.fetch(:cron))
87
- delay = [(fugit.next_time - Time.current).to_f, 0].max
88
-
89
- future = Concurrent::ScheduledTask.new(delay, args: [self, cron_key]) do |thr_scheduler, thr_cron_key|
83
+ # @param cron_entry [CronEntry] the CronEntry object to schedule
84
+ def create_task(cron_entry)
85
+ delay = [(cron_entry.next_at - Time.current).to_f, 0].max
86
+ future = Concurrent::ScheduledTask.new(delay, args: [self, cron_entry]) do |thr_scheduler, thr_cron_entry|
90
87
  # Re-schedule the next cron task before executing the current task
91
- thr_scheduler.create_task(thr_cron_key)
92
-
93
- CurrentThread.reset
94
- CurrentThread.cron_key = thr_cron_key
88
+ thr_scheduler.create_task(thr_cron_entry)
95
89
 
96
90
  Rails.application.executor.wrap do
97
- schedule = thr_scheduler.schedules.fetch(thr_cron_key).with_indifferent_access
98
- job_class = schedule.fetch(:class).constantize
99
-
100
- job_set_value = schedule.fetch(:set, {})
101
- job_set = job_set_value.respond_to?(:call) ? job_set_value.call : job_set_value
102
-
103
- job_args_value = schedule.fetch(:args, [])
104
- job_args = job_args_value.respond_to?(:call) ? job_args_value.call : job_args_value
91
+ CurrentThread.reset
92
+ CurrentThread.cron_key = thr_cron_entry.key
105
93
 
106
- job_class.set(job_set).perform_later(*job_args)
94
+ cron_entry.enqueue
107
95
  end
108
96
  end
109
97
 
110
- @tasks[cron_key] = future
98
+ @tasks[cron_entry.key] = future
111
99
  future.add_observer(self.class, :task_observer)
112
100
  future.execute
113
101
  end
@@ -174,13 +174,7 @@ module GoodJob
174
174
  break if execution.blank?
175
175
  break :unlocked unless execution&.executable?
176
176
 
177
- begin
178
- execution.with_advisory_lock(key: "good_jobs-#{execution.active_job_id}") do
179
- execution.perform
180
- end
181
- rescue RecordAlreadyAdvisoryLockedError => e
182
- ExecutionResult.new(value: nil, handled_error: e)
183
- end
177
+ execution.perform
184
178
  end
185
179
  end
186
180
 
@@ -149,7 +149,7 @@ module GoodJob
149
149
 
150
150
  records = advisory_lock(column: column, function: function).to_a
151
151
  begin
152
- yield(records)
152
+ unscoped { yield(records) }
153
153
  ensure
154
154
  if unlock_session
155
155
  advisory_unlock_session
@@ -59,11 +59,11 @@ module GoodJob
59
59
 
60
60
  # @!macro notification_responder
61
61
  def cron_manager_start(event)
62
- cron_jobs = event.payload[:cron_jobs]
63
- cron_jobs_count = cron_jobs.size
62
+ cron_entries = event.payload[:cron_entries]
63
+ cron_jobs_count = cron_entries.size
64
64
 
65
65
  info do
66
- "GoodJob started cron with #{cron_jobs_count} #{'jobs'.pluralize(cron_jobs_count)}."
66
+ "GoodJob started cron with #{cron_jobs_count} #{'job'.pluralize(cron_jobs_count)}."
67
67
  end
68
68
  end
69
69
 
@@ -230,7 +230,8 @@ module GoodJob # :nodoc:
230
230
  # @return [void]
231
231
  def create_task(delay = 0)
232
232
  future = Concurrent::ScheduledTask.new(delay, args: [performer], executor: executor, timer_set: timer_set) do |thr_performer|
233
- Rails.application.executor.wrap do
233
+ Thread.current.name = Thread.current.name.sub("-worker-", "-thread-") if Thread.current.name
234
+ Rails.application.reloader.wrap do
234
235
  thr_performer.next
235
236
  end
236
237
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '2.3.0'
4
+ VERSION = '2.4.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-25 00:00:00.000000000 Z
11
+ date: 2021-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -379,7 +379,7 @@ files:
379
379
  - lib/active_job/queue_adapters/good_job_adapter.rb
380
380
  - lib/generators/good_job/install_generator.rb
381
381
  - lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb
382
- - lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb
382
+ - lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb
383
383
  - lib/generators/good_job/update_generator.rb
384
384
  - lib/good_job.rb
385
385
  - lib/good_job/active_job_extensions.rb
@@ -387,6 +387,7 @@ files:
387
387
  - lib/good_job/adapter.rb
388
388
  - lib/good_job/cli.rb
389
389
  - lib/good_job/configuration.rb
390
+ - lib/good_job/cron_entry.rb
390
391
  - lib/good_job/cron_manager.rb
391
392
  - lib/good_job/current_thread.rb
392
393
  - lib/good_job/daemon.rb