good_job 2.3.0 → 2.4.2
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 +4 -4
- data/CHANGELOG.md +75 -0
- data/README.md +39 -16
- data/engine/app/controllers/good_job/base_controller.rb +8 -0
- data/engine/app/controllers/good_job/cron_schedules_controller.rb +1 -1
- data/engine/app/helpers/good_job/application_helper.rb +4 -0
- data/engine/app/views/good_job/cron_schedules/index.html.erb +51 -7
- data/engine/app/views/good_job/shared/_executions_table.erb +1 -1
- data/engine/app/views/good_job/shared/_jobs_table.erb +1 -1
- data/engine/app/views/layouts/good_job/base.html.erb +2 -1
- data/lib/generators/good_job/install_generator.rb +6 -0
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +1 -1
- data/lib/generators/good_job/templates/update/migrations/{01_create_good_jobs.rb → 01_create_good_jobs.rb.erb} +1 -1
- data/lib/generators/good_job/update_generator.rb +6 -0
- data/lib/good_job/active_job_extensions/concurrency.rb +3 -4
- data/lib/good_job/adapter.rb +4 -2
- data/lib/good_job/cli.rb +3 -1
- data/lib/good_job/configuration.rb +4 -0
- data/lib/good_job/cron_entry.rb +65 -0
- data/lib/good_job/cron_manager.rb +18 -30
- data/lib/good_job/execution.rb +1 -7
- data/lib/good_job/lockable.rb +1 -1
- data/lib/good_job/log_subscriber.rb +3 -3
- data/lib/good_job/scheduler.rb +2 -1
- data/lib/good_job/version.rb +1 -1
- metadata +4 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f06b525450f56cf74ff31e06e11cbf3bf2ee7f20d753973e70ddb53d67eb48d1
         | 
| 4 | 
            +
              data.tar.gz: d25d2037ae03eaea0ff8bd4ca1dfa52ba6a57d3420e53b4be265dc7c0be580cb
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 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 =  | 
| 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  | 
| 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. | 
| 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 | 
| 588 | 
            -
                - `batch_processing:1` execute jobs enqueued on `batch_processing | 
| 589 | 
            -
                - `-transactional_messages,batch_processing`: execute jobs enqueued on _any_ queue _excluding_ `transactional_messages` or `batch_processing | 
| 590 | 
            -
                - `*`: execute jobs on any queue  | 
| 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;*"  | 
| 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 | 
| 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  | 
| 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 | 
            -
                 | 
| 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
         | 
| @@ -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 @ | 
| 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 
         | 
| 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 
         | 
| 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 | 
            -
                      <% @ | 
| 27 | 
            +
                      <% @cron_entries.each do |cron_entry| %>
         | 
| 14 28 | 
             
                        <tr>
         | 
| 15 | 
            -
                          <td class="font-monospace"><%=  | 
| 16 | 
            -
                          <td class="font-monospace"><%=  | 
| 17 | 
            -
                          <td | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 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/ | 
| 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
         | 
| @@ -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 | 
            -
                                                 | 
| 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. | 
| 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. | 
| 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
         | 
    
        data/lib/good_job/adapter.rb
    CHANGED
    
    | @@ -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. | 
| 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 | 
            -
             | 
| 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:: | 
| 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 : | 
| 29 | 
            +
                attr_reader :cron_entries
         | 
| 30 30 |  | 
| 31 | 
            -
                # @param  | 
| 31 | 
            +
                # @param cron_entries [Array<CronEntry>]
         | 
| 32 32 | 
             
                # @param start_on_initialize [Boolean]
         | 
| 33 | 
            -
                def initialize( | 
| 33 | 
            +
                def initialize(cron_entries = [], start_on_initialize: false)
         | 
| 34 34 | 
             
                  @running = false
         | 
| 35 | 
            -
                  @ | 
| 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",  | 
| 45 | 
            +
                  ActiveSupport::Notifications.instrument("cron_manager_start.good_job", cron_entries: cron_entries) do
         | 
| 46 46 | 
             
                    @running = true
         | 
| 47 | 
            -
                     | 
| 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  | 
| 82 | 
            -
                def create_task( | 
| 83 | 
            -
                   | 
| 84 | 
            -
                   | 
| 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( | 
| 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 | 
            -
                       | 
| 98 | 
            -
                       | 
| 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 | 
            -
                       | 
| 94 | 
            +
                      cron_entry.enqueue
         | 
| 107 95 | 
             
                    end
         | 
| 108 96 | 
             
                  end
         | 
| 109 97 |  | 
| 110 | 
            -
                  @tasks[ | 
| 98 | 
            +
                  @tasks[cron_entry.key] = future
         | 
| 111 99 | 
             
                  future.add_observer(self.class, :task_observer)
         | 
| 112 100 | 
             
                  future.execute
         | 
| 113 101 | 
             
                end
         | 
    
        data/lib/good_job/execution.rb
    CHANGED
    
    | @@ -174,13 +174,7 @@ module GoodJob | |
| 174 174 | 
             
                    break if execution.blank?
         | 
| 175 175 | 
             
                    break :unlocked unless execution&.executable?
         | 
| 176 176 |  | 
| 177 | 
            -
                     | 
| 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 |  | 
    
        data/lib/good_job/lockable.rb
    CHANGED
    
    
| @@ -59,11 +59,11 @@ module GoodJob | |
| 59 59 |  | 
| 60 60 | 
             
                # @!macro notification_responder
         | 
| 61 61 | 
             
                def cron_manager_start(event)
         | 
| 62 | 
            -
                   | 
| 63 | 
            -
                  cron_jobs_count =  | 
| 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} #{' | 
| 66 | 
            +
                    "GoodJob started cron with #{cron_jobs_count} #{'job'.pluralize(cron_jobs_count)}."
         | 
| 67 67 | 
             
                  end
         | 
| 68 68 | 
             
                end
         | 
| 69 69 |  | 
    
        data/lib/good_job/scheduler.rb
    CHANGED
    
    | @@ -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 | 
            -
                     | 
| 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
         | 
    
        data/lib/good_job/version.rb
    CHANGED
    
    
    
        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. | 
| 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- | 
| 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
         |