switchman-inst-jobs 4.0.0 → 4.0.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/db/migrate/20210809145804_add_n_strand_index.rb +12 -0
 - data/db/migrate/20210812210128_add_singleton_column.rb +200 -0
 - data/db/migrate/20210917232626_add_delete_conflicting_singletons_before_unlock_trigger.rb +27 -0
 - data/db/migrate/20210928174754_fix_singleton_condition_in_before_insert.rb +56 -0
 - data/db/migrate/20210929204903_update_conflicting_singleton_function_to_use_index.rb +27 -0
 - data/db/migrate/20211101190934_update_after_delete_trigger_for_singleton_index.rb +137 -0
 - data/db/migrate/20211207094200_update_after_delete_trigger_for_singleton_transition_cases.rb +171 -0
 - data/lib/switchman_inst_jobs/delayed/backend/base.rb +1 -1
 - data/lib/switchman_inst_jobs/jobs_migrator.rb +16 -14
 - data/lib/switchman_inst_jobs/version.rb +1 -1
 - data/lib/switchman_inst_jobs.rb +0 -4
 - metadata +53 -16
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: e6f9f61d51da46ec92185649cc434d9168740c491ade56179d13537d9552fa8b
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 5c53dbe8087c254444f995ee738cb4164db03fc8a91ae823938a13b372e3eb0d
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 44c3483d276b435c93abc3547b458cde60739b9a4843465541065ae5d71eb8dcee9d2c4ebebf8b1fe4fc31fd6c7951f983d8af76c44a340e7e29ce811466778f
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 0acdee9429002d28e9368bf29fce1b64fa492132f43b8ff884bca36f5b5aded965343a621c2e61b07de7bd18dcc059b5e11d1a8571a8319a0582d9dc8e1fa206
         
     | 
| 
         @@ -0,0 +1,12 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class AddNStrandIndex < ActiveRecord::Migration[5.2]
         
     | 
| 
      
 4 
     | 
    
         
            +
              disable_ddl_transaction!
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def change
         
     | 
| 
      
 7 
     | 
    
         
            +
                add_index :delayed_jobs, %i[strand next_in_strand id],
         
     | 
| 
      
 8 
     | 
    
         
            +
                          name: 'n_strand_index',
         
     | 
| 
      
 9 
     | 
    
         
            +
                          where: 'strand IS NOT NULL',
         
     | 
| 
      
 10 
     | 
    
         
            +
                          algorithm: :concurrently
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,200 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class AddSingletonColumn < ActiveRecord::Migration[5.2]
         
     | 
| 
      
 4 
     | 
    
         
            +
              disable_ddl_transaction!
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def change
         
     | 
| 
      
 7 
     | 
    
         
            +
                add_column :delayed_jobs, :singleton, :string, if_not_exists: true
         
     | 
| 
      
 8 
     | 
    
         
            +
                add_column :failed_jobs, :singleton, :string, if_not_exists: true
         
     | 
| 
      
 9 
     | 
    
         
            +
                # only one job can be queued in a singleton
         
     | 
| 
      
 10 
     | 
    
         
            +
                add_index :delayed_jobs,
         
     | 
| 
      
 11 
     | 
    
         
            +
                          :singleton,
         
     | 
| 
      
 12 
     | 
    
         
            +
                          where: 'singleton IS NOT NULL AND locked_by IS NULL',
         
     | 
| 
      
 13 
     | 
    
         
            +
                          unique: true,
         
     | 
| 
      
 14 
     | 
    
         
            +
                          name: 'index_delayed_jobs_on_singleton_not_running',
         
     | 
| 
      
 15 
     | 
    
         
            +
                          algorithm: :concurrently
         
     | 
| 
      
 16 
     | 
    
         
            +
                # only one job can be running for a singleton
         
     | 
| 
      
 17 
     | 
    
         
            +
                add_index :delayed_jobs,
         
     | 
| 
      
 18 
     | 
    
         
            +
                          :singleton,
         
     | 
| 
      
 19 
     | 
    
         
            +
                          where: 'singleton IS NOT NULL AND locked_by IS NOT NULL',
         
     | 
| 
      
 20 
     | 
    
         
            +
                          unique: true,
         
     | 
| 
      
 21 
     | 
    
         
            +
                          name: 'index_delayed_jobs_on_singleton_running',
         
     | 
| 
      
 22 
     | 
    
         
            +
                          algorithm: :concurrently
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                reversible do |direction|
         
     | 
| 
      
 25 
     | 
    
         
            +
                  direction.up do
         
     | 
| 
      
 26 
     | 
    
         
            +
                    execute(<<~SQL)
         
     | 
| 
      
 27 
     | 
    
         
            +
                      CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_after_delete_row_tr_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 28 
     | 
    
         
            +
                      DECLARE
         
     | 
| 
      
 29 
     | 
    
         
            +
                        running_count integer;
         
     | 
| 
      
 30 
     | 
    
         
            +
                        should_lock boolean;
         
     | 
| 
      
 31 
     | 
    
         
            +
                        should_be_precise boolean;
         
     | 
| 
      
 32 
     | 
    
         
            +
                        update_query varchar;
         
     | 
| 
      
 33 
     | 
    
         
            +
                        skip_locked varchar;
         
     | 
| 
      
 34 
     | 
    
         
            +
                      BEGIN
         
     | 
| 
      
 35 
     | 
    
         
            +
                        IF OLD.strand IS NOT NULL THEN
         
     | 
| 
      
 36 
     | 
    
         
            +
                          should_lock := true;
         
     | 
| 
      
 37 
     | 
    
         
            +
                          should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                          IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
         
     | 
| 
      
 40 
     | 
    
         
            +
                            running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 41 
     | 
    
         
            +
                              SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 42 
     | 
    
         
            +
                            ) subquery_for_count);
         
     | 
| 
      
 43 
     | 
    
         
            +
                            should_lock := running_count < OLD.max_concurrent;
         
     | 
| 
      
 44 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                          IF should_lock THEN
         
     | 
| 
      
 47 
     | 
    
         
            +
                            PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
         
     | 
| 
      
 48 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                          -- note that we don't really care if the row we're deleting has a singleton, or if it even
         
     | 
| 
      
 51 
     | 
    
         
            +
                          -- matches the row(s) we're going to update. we just need to make sure that whatever
         
     | 
| 
      
 52 
     | 
    
         
            +
                          -- singleton we grab isn't already running (which is a simple existence check, since
         
     | 
| 
      
 53 
     | 
    
         
            +
                          -- the unique indexes ensure there is at most one singleton running, and one queued)
         
     | 
| 
      
 54 
     | 
    
         
            +
                          update_query := 'UPDATE delayed_jobs SET next_in_strand=true WHERE id IN (
         
     | 
| 
      
 55 
     | 
    
         
            +
                            SELECT id FROM delayed_jobs j2
         
     | 
| 
      
 56 
     | 
    
         
            +
                              WHERE next_in_strand=false AND
         
     | 
| 
      
 57 
     | 
    
         
            +
                                j2.strand=$1.strand AND
         
     | 
| 
      
 58 
     | 
    
         
            +
                                (j2.singleton IS NULL OR NOT EXISTS (SELECT 1 FROM delayed_jobs j3 WHERE j3.singleton=j2.singleton AND j3.id<>j2.id))
         
     | 
| 
      
 59 
     | 
    
         
            +
                              ORDER BY j2.strand_order_override ASC, j2.id ASC
         
     | 
| 
      
 60 
     | 
    
         
            +
                              LIMIT ';
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                          IF should_be_precise THEN
         
     | 
| 
      
 63 
     | 
    
         
            +
                            running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 64 
     | 
    
         
            +
                              SELECT 1 FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 65 
     | 
    
         
            +
                            ) s);
         
     | 
| 
      
 66 
     | 
    
         
            +
                            IF running_count < OLD.max_concurrent THEN
         
     | 
| 
      
 67 
     | 
    
         
            +
                              update_query := update_query || '($1.max_concurrent - $2)';
         
     | 
| 
      
 68 
     | 
    
         
            +
                            ELSE
         
     | 
| 
      
 69 
     | 
    
         
            +
                              -- we have too many running already; just bail
         
     | 
| 
      
 70 
     | 
    
         
            +
                              RETURN OLD;
         
     | 
| 
      
 71 
     | 
    
         
            +
                            END IF;
         
     | 
| 
      
 72 
     | 
    
         
            +
                          ELSE
         
     | 
| 
      
 73 
     | 
    
         
            +
                            update_query := update_query || '1';
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                            -- n-strands don't require precise ordering; we can make this query more performant
         
     | 
| 
      
 76 
     | 
    
         
            +
                            IF OLD.max_concurrent > 1 THEN
         
     | 
| 
      
 77 
     | 
    
         
            +
                              skip_locked := ' SKIP LOCKED';
         
     | 
| 
      
 78 
     | 
    
         
            +
                            END IF;
         
     | 
| 
      
 79 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                          update_query := update_query || ' FOR UPDATE' || COALESCE(skip_locked, '') || ')';
         
     | 
| 
      
 82 
     | 
    
         
            +
                          EXECUTE update_query USING OLD, running_count;
         
     | 
| 
      
 83 
     | 
    
         
            +
                        ELSIF OLD.singleton IS NOT NULL THEN
         
     | 
| 
      
 84 
     | 
    
         
            +
                          UPDATE delayed_jobs SET next_in_strand = 't' WHERE singleton=OLD.singleton AND next_in_strand=false;
         
     | 
| 
      
 85 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 86 
     | 
    
         
            +
                        RETURN OLD;
         
     | 
| 
      
 87 
     | 
    
         
            +
                      END;
         
     | 
| 
      
 88 
     | 
    
         
            +
                      $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 89 
     | 
    
         
            +
                    SQL
         
     | 
| 
      
 90 
     | 
    
         
            +
                    execute(<<~SQL)
         
     | 
| 
      
 91 
     | 
    
         
            +
                      CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_before_insert_row_tr_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 92 
     | 
    
         
            +
                      BEGIN
         
     | 
| 
      
 93 
     | 
    
         
            +
                        IF NEW.strand IS NOT NULL THEN
         
     | 
| 
      
 94 
     | 
    
         
            +
                          PERFORM pg_advisory_xact_lock(half_md5_as_bigint(NEW.strand));
         
     | 
| 
      
 95 
     | 
    
         
            +
                          IF (SELECT COUNT(*) FROM (
         
     | 
| 
      
 96 
     | 
    
         
            +
                              SELECT 1 FROM delayed_jobs WHERE strand = NEW.strand AND next_in_strand=true LIMIT NEW.max_concurrent
         
     | 
| 
      
 97 
     | 
    
         
            +
                            ) s) = NEW.max_concurrent THEN
         
     | 
| 
      
 98 
     | 
    
         
            +
                            NEW.next_in_strand := false;
         
     | 
| 
      
 99 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 100 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 101 
     | 
    
         
            +
                        IF NEW.singleton IS NOT NULL THEN
         
     | 
| 
      
 102 
     | 
    
         
            +
                          PERFORM 1 FROM delayed_jobs WHERE singleton = NEW.singleton;
         
     | 
| 
      
 103 
     | 
    
         
            +
                          IF FOUND THEN
         
     | 
| 
      
 104 
     | 
    
         
            +
                            NEW.next_in_strand := false;
         
     | 
| 
      
 105 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 106 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 107 
     | 
    
         
            +
                        RETURN NEW;
         
     | 
| 
      
 108 
     | 
    
         
            +
                      END;
         
     | 
| 
      
 109 
     | 
    
         
            +
                      $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 110 
     | 
    
         
            +
                    SQL
         
     | 
| 
      
 111 
     | 
    
         
            +
                  end
         
     | 
| 
      
 112 
     | 
    
         
            +
                  direction.down do
         
     | 
| 
      
 113 
     | 
    
         
            +
                    execute(<<~SQL)
         
     | 
| 
      
 114 
     | 
    
         
            +
                      CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_after_delete_row_tr_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 115 
     | 
    
         
            +
                      DECLARE
         
     | 
| 
      
 116 
     | 
    
         
            +
                        running_count integer;
         
     | 
| 
      
 117 
     | 
    
         
            +
                        should_lock boolean;
         
     | 
| 
      
 118 
     | 
    
         
            +
                        should_be_precise boolean;
         
     | 
| 
      
 119 
     | 
    
         
            +
                      BEGIN
         
     | 
| 
      
 120 
     | 
    
         
            +
                        IF OLD.strand IS NOT NULL THEN
         
     | 
| 
      
 121 
     | 
    
         
            +
                          should_lock := true;
         
     | 
| 
      
 122 
     | 
    
         
            +
                          should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                          IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
         
     | 
| 
      
 125 
     | 
    
         
            +
                            running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 126 
     | 
    
         
            +
                              SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 127 
     | 
    
         
            +
                            ) subquery_for_count);
         
     | 
| 
      
 128 
     | 
    
         
            +
                            should_lock := running_count < OLD.max_concurrent;
         
     | 
| 
      
 129 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                          IF should_lock THEN
         
     | 
| 
      
 132 
     | 
    
         
            +
                            PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
         
     | 
| 
      
 133 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                          IF should_be_precise THEN
         
     | 
| 
      
 136 
     | 
    
         
            +
                            running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 137 
     | 
    
         
            +
                              SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 138 
     | 
    
         
            +
                            ) subquery_for_count);
         
     | 
| 
      
 139 
     | 
    
         
            +
                            IF running_count < OLD.max_concurrent THEN
         
     | 
| 
      
 140 
     | 
    
         
            +
                              UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
         
     | 
| 
      
 141 
     | 
    
         
            +
                                SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
         
     | 
| 
      
 142 
     | 
    
         
            +
                                j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
         
     | 
| 
      
 143 
     | 
    
         
            +
                              );
         
     | 
| 
      
 144 
     | 
    
         
            +
                            END IF;
         
     | 
| 
      
 145 
     | 
    
         
            +
                          ELSE
         
     | 
| 
      
 146 
     | 
    
         
            +
                            -- n-strands don't require precise ordering; we can make this query more performant
         
     | 
| 
      
 147 
     | 
    
         
            +
                            IF OLD.max_concurrent > 1 THEN
         
     | 
| 
      
 148 
     | 
    
         
            +
                              UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
         
     | 
| 
      
 149 
     | 
    
         
            +
                              (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
         
     | 
| 
      
 150 
     | 
    
         
            +
                                j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
         
     | 
| 
      
 151 
     | 
    
         
            +
                            ELSE
         
     | 
| 
      
 152 
     | 
    
         
            +
                              UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
         
     | 
| 
      
 153 
     | 
    
         
            +
                                (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
         
     | 
| 
      
 154 
     | 
    
         
            +
                                  j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT 1 FOR UPDATE);
         
     | 
| 
      
 155 
     | 
    
         
            +
                            END IF;
         
     | 
| 
      
 156 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 157 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 158 
     | 
    
         
            +
                        RETURN OLD;
         
     | 
| 
      
 159 
     | 
    
         
            +
                      END;
         
     | 
| 
      
 160 
     | 
    
         
            +
                      $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 161 
     | 
    
         
            +
                    SQL
         
     | 
| 
      
 162 
     | 
    
         
            +
                    execute(<<~SQL)
         
     | 
| 
      
 163 
     | 
    
         
            +
                      CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_before_insert_row_tr_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 164 
     | 
    
         
            +
                      BEGIN
         
     | 
| 
      
 165 
     | 
    
         
            +
                        IF NEW.strand IS NOT NULL THEN
         
     | 
| 
      
 166 
     | 
    
         
            +
                          PERFORM pg_advisory_xact_lock(half_md5_as_bigint(NEW.strand));
         
     | 
| 
      
 167 
     | 
    
         
            +
                          IF (SELECT COUNT(*) FROM (
         
     | 
| 
      
 168 
     | 
    
         
            +
                              SELECT 1 AS one FROM delayed_jobs WHERE strand = NEW.strand LIMIT NEW.max_concurrent
         
     | 
| 
      
 169 
     | 
    
         
            +
                            ) subquery_for_count) = NEW.max_concurrent THEN
         
     | 
| 
      
 170 
     | 
    
         
            +
                            NEW.next_in_strand := 'f';
         
     | 
| 
      
 171 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 172 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 173 
     | 
    
         
            +
                        RETURN NEW;
         
     | 
| 
      
 174 
     | 
    
         
            +
                      END;
         
     | 
| 
      
 175 
     | 
    
         
            +
                      $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 176 
     | 
    
         
            +
                    SQL
         
     | 
| 
      
 177 
     | 
    
         
            +
                  end
         
     | 
| 
      
 178 
     | 
    
         
            +
                end
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                connection.transaction do
         
     | 
| 
      
 181 
     | 
    
         
            +
                  reversible do |direction|
         
     | 
| 
      
 182 
     | 
    
         
            +
                    direction.up do
         
     | 
| 
      
 183 
     | 
    
         
            +
                      drop_triggers
         
     | 
| 
      
 184 
     | 
    
         
            +
                      execute("CREATE TRIGGER delayed_jobs_before_insert_row_tr BEFORE INSERT ON #{::Delayed::Job.quoted_table_name} FOR EACH ROW WHEN (NEW.strand IS NOT NULL OR NEW.singleton IS NOT NULL) EXECUTE PROCEDURE #{connection.quote_table_name('delayed_jobs_before_insert_row_tr_fn')}()")
         
     | 
| 
      
 185 
     | 
    
         
            +
                      execute("CREATE TRIGGER delayed_jobs_after_delete_row_tr AFTER DELETE ON #{::Delayed::Job.quoted_table_name} FOR EACH ROW WHEN ((OLD.strand IS NOT NULL OR OLD.singleton IS NOT NULL) AND OLD.next_in_strand=true) EXECUTE PROCEDURE #{connection.quote_table_name('delayed_jobs_after_delete_row_tr_fn')}()")
         
     | 
| 
      
 186 
     | 
    
         
            +
                    end
         
     | 
| 
      
 187 
     | 
    
         
            +
                    direction.down do
         
     | 
| 
      
 188 
     | 
    
         
            +
                      drop_triggers
         
     | 
| 
      
 189 
     | 
    
         
            +
                      execute("CREATE TRIGGER delayed_jobs_before_insert_row_tr BEFORE INSERT ON #{::Delayed::Job.quoted_table_name} FOR EACH ROW WHEN (NEW.strand IS NOT NULL) EXECUTE PROCEDURE #{connection.quote_table_name('delayed_jobs_before_insert_row_tr_fn')}()")
         
     | 
| 
      
 190 
     | 
    
         
            +
                      execute("CREATE TRIGGER delayed_jobs_after_delete_row_tr AFTER DELETE ON #{::Delayed::Job.quoted_table_name} FOR EACH ROW WHEN (OLD.strand IS NOT NULL AND OLD.next_in_strand = 't') EXECUTE PROCEDURE #{connection.quote_table_name('delayed_jobs_after_delete_row_tr_fn()')}")
         
     | 
| 
      
 191 
     | 
    
         
            +
                    end
         
     | 
| 
      
 192 
     | 
    
         
            +
                  end
         
     | 
| 
      
 193 
     | 
    
         
            +
                end
         
     | 
| 
      
 194 
     | 
    
         
            +
              end
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
              def drop_triggers
         
     | 
| 
      
 197 
     | 
    
         
            +
                execute("DROP TRIGGER delayed_jobs_before_insert_row_tr ON #{::Delayed::Job.quoted_table_name}")
         
     | 
| 
      
 198 
     | 
    
         
            +
                execute("DROP TRIGGER delayed_jobs_after_delete_row_tr ON #{::Delayed::Job.quoted_table_name}")
         
     | 
| 
      
 199 
     | 
    
         
            +
              end
         
     | 
| 
      
 200 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class AddDeleteConflictingSingletonsBeforeUnlockTrigger < ActiveRecord::Migration[5.2]
         
     | 
| 
      
 4 
     | 
    
         
            +
              def up
         
     | 
| 
      
 5 
     | 
    
         
            +
                execute(<<~SQL)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  CREATE FUNCTION #{connection.quote_table_name('delayed_jobs_before_unlock_delete_conflicting_singletons_row_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 7 
     | 
    
         
            +
                  BEGIN
         
     | 
| 
      
 8 
     | 
    
         
            +
                    IF EXISTS (SELECT 1 FROM delayed_jobs j2 WHERE j2.singleton=OLD.singleton) THEN
         
     | 
| 
      
 9 
     | 
    
         
            +
                      DELETE FROM delayed_jobs WHERE id<>OLD.id AND singleton=OLD.singleton;
         
     | 
| 
      
 10 
     | 
    
         
            +
                    END IF;
         
     | 
| 
      
 11 
     | 
    
         
            +
                    RETURN NEW;
         
     | 
| 
      
 12 
     | 
    
         
            +
                  END;
         
     | 
| 
      
 13 
     | 
    
         
            +
                  $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 14 
     | 
    
         
            +
                SQL
         
     | 
| 
      
 15 
     | 
    
         
            +
                execute(<<~SQL)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  CREATE TRIGGER delayed_jobs_before_unlock_delete_conflicting_singletons_row_tr BEFORE UPDATE ON #{::Delayed::Job.quoted_table_name} FOR EACH ROW WHEN (
         
     | 
| 
      
 17 
     | 
    
         
            +
                    OLD.singleton IS NOT NULL AND
         
     | 
| 
      
 18 
     | 
    
         
            +
                    OLD.singleton=NEW.singleton AND
         
     | 
| 
      
 19 
     | 
    
         
            +
                    OLD.locked_by IS NOT NULL AND
         
     | 
| 
      
 20 
     | 
    
         
            +
                    NEW.locked_by IS NULL) EXECUTE PROCEDURE #{connection.quote_table_name('delayed_jobs_before_unlock_delete_conflicting_singletons_row_fn')}();
         
     | 
| 
      
 21 
     | 
    
         
            +
                SQL
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              def down
         
     | 
| 
      
 25 
     | 
    
         
            +
                execute("DROP FUNCTION #{connection.quote_table_name('delayed_jobs_before_unlock_delete_conflicting_singletons_row_tr_fn')}() CASCADE")
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,56 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class FixSingletonConditionInBeforeInsert < ActiveRecord::Migration[5.2]
         
     | 
| 
      
 4 
     | 
    
         
            +
              def change
         
     | 
| 
      
 5 
     | 
    
         
            +
                reversible do |direction|
         
     | 
| 
      
 6 
     | 
    
         
            +
                  direction.up do
         
     | 
| 
      
 7 
     | 
    
         
            +
                    execute(<<~SQL)
         
     | 
| 
      
 8 
     | 
    
         
            +
                      CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_before_insert_row_tr_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 9 
     | 
    
         
            +
                      BEGIN
         
     | 
| 
      
 10 
     | 
    
         
            +
                        IF NEW.strand IS NOT NULL THEN
         
     | 
| 
      
 11 
     | 
    
         
            +
                          PERFORM pg_advisory_xact_lock(half_md5_as_bigint(NEW.strand));
         
     | 
| 
      
 12 
     | 
    
         
            +
                          IF (SELECT COUNT(*) FROM (
         
     | 
| 
      
 13 
     | 
    
         
            +
                              SELECT 1 FROM delayed_jobs WHERE strand = NEW.strand AND next_in_strand=true LIMIT NEW.max_concurrent
         
     | 
| 
      
 14 
     | 
    
         
            +
                            ) s) = NEW.max_concurrent THEN
         
     | 
| 
      
 15 
     | 
    
         
            +
                            NEW.next_in_strand := false;
         
     | 
| 
      
 16 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 17 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 18 
     | 
    
         
            +
                        IF NEW.singleton IS NOT NULL THEN
         
     | 
| 
      
 19 
     | 
    
         
            +
                          -- this condition seems silly, but it forces postgres to use the two partial indexes on singleton,
         
     | 
| 
      
 20 
     | 
    
         
            +
                          -- rather than doing a seq scan
         
     | 
| 
      
 21 
     | 
    
         
            +
                          PERFORM 1 FROM delayed_jobs WHERE singleton = NEW.singleton AND (locked_by IS NULL OR locked_by IS NOT NULL);
         
     | 
| 
      
 22 
     | 
    
         
            +
                          IF FOUND THEN
         
     | 
| 
      
 23 
     | 
    
         
            +
                            NEW.next_in_strand := false;
         
     | 
| 
      
 24 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 25 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 26 
     | 
    
         
            +
                        RETURN NEW;
         
     | 
| 
      
 27 
     | 
    
         
            +
                      END;
         
     | 
| 
      
 28 
     | 
    
         
            +
                      $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 29 
     | 
    
         
            +
                    SQL
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
                  direction.down do
         
     | 
| 
      
 32 
     | 
    
         
            +
                    execute(<<~SQL)
         
     | 
| 
      
 33 
     | 
    
         
            +
                      CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_before_insert_row_tr_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 34 
     | 
    
         
            +
                      BEGIN
         
     | 
| 
      
 35 
     | 
    
         
            +
                        IF NEW.strand IS NOT NULL THEN
         
     | 
| 
      
 36 
     | 
    
         
            +
                          PERFORM pg_advisory_xact_lock(half_md5_as_bigint(NEW.strand));
         
     | 
| 
      
 37 
     | 
    
         
            +
                          IF (SELECT COUNT(*) FROM (
         
     | 
| 
      
 38 
     | 
    
         
            +
                              SELECT 1 FROM delayed_jobs WHERE strand = NEW.strand AND next_in_strand=true LIMIT NEW.max_concurrent
         
     | 
| 
      
 39 
     | 
    
         
            +
                            ) s) = NEW.max_concurrent THEN
         
     | 
| 
      
 40 
     | 
    
         
            +
                            NEW.next_in_strand := false;
         
     | 
| 
      
 41 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 42 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 43 
     | 
    
         
            +
                        IF NEW.singleton IS NOT NULL THEN
         
     | 
| 
      
 44 
     | 
    
         
            +
                          PERFORM 1 FROM delayed_jobs WHERE singleton = NEW.singleton;
         
     | 
| 
      
 45 
     | 
    
         
            +
                          IF FOUND THEN
         
     | 
| 
      
 46 
     | 
    
         
            +
                            NEW.next_in_strand := false;
         
     | 
| 
      
 47 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 48 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 49 
     | 
    
         
            +
                        RETURN NEW;
         
     | 
| 
      
 50 
     | 
    
         
            +
                      END;
         
     | 
| 
      
 51 
     | 
    
         
            +
                      $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 52 
     | 
    
         
            +
                    SQL
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
              end
         
     | 
| 
      
 56 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class UpdateConflictingSingletonFunctionToUseIndex < ActiveRecord::Migration[5.2]
         
     | 
| 
      
 4 
     | 
    
         
            +
              def up
         
     | 
| 
      
 5 
     | 
    
         
            +
                execute(<<~SQL)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_before_unlock_delete_conflicting_singletons_row_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 7 
     | 
    
         
            +
                  BEGIN
         
     | 
| 
      
 8 
     | 
    
         
            +
                    DELETE FROM delayed_jobs WHERE id<>OLD.id AND singleton=OLD.singleton AND locked_by IS NULL;
         
     | 
| 
      
 9 
     | 
    
         
            +
                    RETURN NEW;
         
     | 
| 
      
 10 
     | 
    
         
            +
                  END;
         
     | 
| 
      
 11 
     | 
    
         
            +
                  $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 12 
     | 
    
         
            +
                SQL
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def down
         
     | 
| 
      
 16 
     | 
    
         
            +
                execute(<<~SQL)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_before_unlock_delete_conflicting_singletons_row_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 18 
     | 
    
         
            +
                  BEGIN
         
     | 
| 
      
 19 
     | 
    
         
            +
                    IF EXISTS (SELECT 1 FROM delayed_jobs j2 WHERE j2.singleton=OLD.singleton) THEN
         
     | 
| 
      
 20 
     | 
    
         
            +
                      DELETE FROM delayed_jobs WHERE id<>OLD.id AND singleton=OLD.singleton;
         
     | 
| 
      
 21 
     | 
    
         
            +
                    END IF;
         
     | 
| 
      
 22 
     | 
    
         
            +
                    RETURN NEW;
         
     | 
| 
      
 23 
     | 
    
         
            +
                  END;
         
     | 
| 
      
 24 
     | 
    
         
            +
                  $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 25 
     | 
    
         
            +
                SQL
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,137 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class UpdateAfterDeleteTriggerForSingletonIndex < ActiveRecord::Migration[5.2]
         
     | 
| 
      
 4 
     | 
    
         
            +
              def up
         
     | 
| 
      
 5 
     | 
    
         
            +
                execute(<<~SQL)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_after_delete_row_tr_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 7 
     | 
    
         
            +
                  DECLARE
         
     | 
| 
      
 8 
     | 
    
         
            +
                    running_count integer;
         
     | 
| 
      
 9 
     | 
    
         
            +
                    should_lock boolean;
         
     | 
| 
      
 10 
     | 
    
         
            +
                    should_be_precise boolean;
         
     | 
| 
      
 11 
     | 
    
         
            +
                    update_query varchar;
         
     | 
| 
      
 12 
     | 
    
         
            +
                    skip_locked varchar;
         
     | 
| 
      
 13 
     | 
    
         
            +
                  BEGIN
         
     | 
| 
      
 14 
     | 
    
         
            +
                    IF OLD.strand IS NOT NULL THEN
         
     | 
| 
      
 15 
     | 
    
         
            +
                      should_lock := true;
         
     | 
| 
      
 16 
     | 
    
         
            +
                      should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                      IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
         
     | 
| 
      
 19 
     | 
    
         
            +
                        running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 20 
     | 
    
         
            +
                          SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 21 
     | 
    
         
            +
                        ) subquery_for_count);
         
     | 
| 
      
 22 
     | 
    
         
            +
                        should_lock := running_count < OLD.max_concurrent;
         
     | 
| 
      
 23 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                      IF should_lock THEN
         
     | 
| 
      
 26 
     | 
    
         
            +
                        PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
         
     | 
| 
      
 27 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                      -- note that we don't really care if the row we're deleting has a singleton, or if it even
         
     | 
| 
      
 30 
     | 
    
         
            +
                      -- matches the row(s) we're going to update. we just need to make sure that whatever
         
     | 
| 
      
 31 
     | 
    
         
            +
                      -- singleton we grab isn't already running (which is a simple existence check, since
         
     | 
| 
      
 32 
     | 
    
         
            +
                      -- the unique indexes ensure there is at most one singleton running, and one queued)
         
     | 
| 
      
 33 
     | 
    
         
            +
                      update_query := 'UPDATE delayed_jobs SET next_in_strand=true WHERE id IN (
         
     | 
| 
      
 34 
     | 
    
         
            +
                        SELECT id FROM delayed_jobs j2
         
     | 
| 
      
 35 
     | 
    
         
            +
                          WHERE next_in_strand=false AND
         
     | 
| 
      
 36 
     | 
    
         
            +
                            j2.strand=$1.strand AND
         
     | 
| 
      
 37 
     | 
    
         
            +
                            (j2.singleton IS NULL OR NOT EXISTS (SELECT 1 FROM delayed_jobs j3 WHERE j3.singleton=j2.singleton AND j3.id<>j2.id AND (j3.locked_by IS NULL OR j3.locked_by IS NOT NULL)))
         
     | 
| 
      
 38 
     | 
    
         
            +
                          ORDER BY j2.strand_order_override ASC, j2.id ASC
         
     | 
| 
      
 39 
     | 
    
         
            +
                          LIMIT ';
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                      IF should_be_precise THEN
         
     | 
| 
      
 42 
     | 
    
         
            +
                        running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 43 
     | 
    
         
            +
                          SELECT 1 FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 44 
     | 
    
         
            +
                        ) s);
         
     | 
| 
      
 45 
     | 
    
         
            +
                        IF running_count < OLD.max_concurrent THEN
         
     | 
| 
      
 46 
     | 
    
         
            +
                          update_query := update_query || '($1.max_concurrent - $2)';
         
     | 
| 
      
 47 
     | 
    
         
            +
                        ELSE
         
     | 
| 
      
 48 
     | 
    
         
            +
                          -- we have too many running already; just bail
         
     | 
| 
      
 49 
     | 
    
         
            +
                          RETURN OLD;
         
     | 
| 
      
 50 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 51 
     | 
    
         
            +
                      ELSE
         
     | 
| 
      
 52 
     | 
    
         
            +
                        update_query := update_query || '1';
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                        -- n-strands don't require precise ordering; we can make this query more performant
         
     | 
| 
      
 55 
     | 
    
         
            +
                        IF OLD.max_concurrent > 1 THEN
         
     | 
| 
      
 56 
     | 
    
         
            +
                          skip_locked := ' SKIP LOCKED';
         
     | 
| 
      
 57 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 58 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                      update_query := update_query || ' FOR UPDATE' || COALESCE(skip_locked, '') || ')';
         
     | 
| 
      
 61 
     | 
    
         
            +
                      EXECUTE update_query USING OLD, running_count;
         
     | 
| 
      
 62 
     | 
    
         
            +
                    ELSIF OLD.singleton IS NOT NULL THEN
         
     | 
| 
      
 63 
     | 
    
         
            +
                      UPDATE delayed_jobs SET next_in_strand = 't' WHERE singleton=OLD.singleton AND next_in_strand=false AND locked_by IS NULL;
         
     | 
| 
      
 64 
     | 
    
         
            +
                    END IF;
         
     | 
| 
      
 65 
     | 
    
         
            +
                    RETURN OLD;
         
     | 
| 
      
 66 
     | 
    
         
            +
                  END;
         
     | 
| 
      
 67 
     | 
    
         
            +
                  $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 68 
     | 
    
         
            +
                SQL
         
     | 
| 
      
 69 
     | 
    
         
            +
              end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
              def down
         
     | 
| 
      
 72 
     | 
    
         
            +
                execute(<<~SQL)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_after_delete_row_tr_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 74 
     | 
    
         
            +
                  DECLARE
         
     | 
| 
      
 75 
     | 
    
         
            +
                    running_count integer;
         
     | 
| 
      
 76 
     | 
    
         
            +
                    should_lock boolean;
         
     | 
| 
      
 77 
     | 
    
         
            +
                    should_be_precise boolean;
         
     | 
| 
      
 78 
     | 
    
         
            +
                    update_query varchar;
         
     | 
| 
      
 79 
     | 
    
         
            +
                    skip_locked varchar;
         
     | 
| 
      
 80 
     | 
    
         
            +
                  BEGIN
         
     | 
| 
      
 81 
     | 
    
         
            +
                    IF OLD.strand IS NOT NULL THEN
         
     | 
| 
      
 82 
     | 
    
         
            +
                      should_lock := true;
         
     | 
| 
      
 83 
     | 
    
         
            +
                      should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                      IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
         
     | 
| 
      
 86 
     | 
    
         
            +
                        running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 87 
     | 
    
         
            +
                          SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 88 
     | 
    
         
            +
                        ) subquery_for_count);
         
     | 
| 
      
 89 
     | 
    
         
            +
                        should_lock := running_count < OLD.max_concurrent;
         
     | 
| 
      
 90 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                      IF should_lock THEN
         
     | 
| 
      
 93 
     | 
    
         
            +
                        PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
         
     | 
| 
      
 94 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                      -- note that we don't really care if the row we're deleting has a singleton, or if it even
         
     | 
| 
      
 97 
     | 
    
         
            +
                      -- matches the row(s) we're going to update. we just need to make sure that whatever
         
     | 
| 
      
 98 
     | 
    
         
            +
                      -- singleton we grab isn't already running (which is a simple existence check, since
         
     | 
| 
      
 99 
     | 
    
         
            +
                      -- the unique indexes ensure there is at most one singleton running, and one queued)
         
     | 
| 
      
 100 
     | 
    
         
            +
                      update_query := 'UPDATE delayed_jobs SET next_in_strand=true WHERE id IN (
         
     | 
| 
      
 101 
     | 
    
         
            +
                        SELECT id FROM delayed_jobs j2
         
     | 
| 
      
 102 
     | 
    
         
            +
                          WHERE next_in_strand=false AND
         
     | 
| 
      
 103 
     | 
    
         
            +
                            j2.strand=$1.strand AND
         
     | 
| 
      
 104 
     | 
    
         
            +
                            (j2.singleton IS NULL OR NOT EXISTS (SELECT 1 FROM delayed_jobs j3 WHERE j3.singleton=j2.singleton AND j3.id<>j2.id))
         
     | 
| 
      
 105 
     | 
    
         
            +
                          ORDER BY j2.strand_order_override ASC, j2.id ASC
         
     | 
| 
      
 106 
     | 
    
         
            +
                          LIMIT ';
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                      IF should_be_precise THEN
         
     | 
| 
      
 109 
     | 
    
         
            +
                        running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 110 
     | 
    
         
            +
                          SELECT 1 FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 111 
     | 
    
         
            +
                        ) s);
         
     | 
| 
      
 112 
     | 
    
         
            +
                        IF running_count < OLD.max_concurrent THEN
         
     | 
| 
      
 113 
     | 
    
         
            +
                          update_query := update_query || '($1.max_concurrent - $2)';
         
     | 
| 
      
 114 
     | 
    
         
            +
                        ELSE
         
     | 
| 
      
 115 
     | 
    
         
            +
                          -- we have too many running already; just bail
         
     | 
| 
      
 116 
     | 
    
         
            +
                          RETURN OLD;
         
     | 
| 
      
 117 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 118 
     | 
    
         
            +
                      ELSE
         
     | 
| 
      
 119 
     | 
    
         
            +
                        update_query := update_query || '1';
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                        -- n-strands don't require precise ordering; we can make this query more performant
         
     | 
| 
      
 122 
     | 
    
         
            +
                        IF OLD.max_concurrent > 1 THEN
         
     | 
| 
      
 123 
     | 
    
         
            +
                          skip_locked := ' SKIP LOCKED';
         
     | 
| 
      
 124 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 125 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                      update_query := update_query || ' FOR UPDATE' || COALESCE(skip_locked, '') || ')';
         
     | 
| 
      
 128 
     | 
    
         
            +
                      EXECUTE update_query USING OLD, running_count;
         
     | 
| 
      
 129 
     | 
    
         
            +
                    ELSIF OLD.singleton IS NOT NULL THEN
         
     | 
| 
      
 130 
     | 
    
         
            +
                      UPDATE delayed_jobs SET next_in_strand = 't' WHERE singleton=OLD.singleton AND next_in_strand=false;
         
     | 
| 
      
 131 
     | 
    
         
            +
                    END IF;
         
     | 
| 
      
 132 
     | 
    
         
            +
                    RETURN OLD;
         
     | 
| 
      
 133 
     | 
    
         
            +
                  END;
         
     | 
| 
      
 134 
     | 
    
         
            +
                  $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 135 
     | 
    
         
            +
                SQL
         
     | 
| 
      
 136 
     | 
    
         
            +
              end
         
     | 
| 
      
 137 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,171 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class UpdateAfterDeleteTriggerForSingletonTransitionCases < ActiveRecord::Migration[5.2]
         
     | 
| 
      
 4 
     | 
    
         
            +
              def up
         
     | 
| 
      
 5 
     | 
    
         
            +
                execute(<<~SQL)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_after_delete_row_tr_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 7 
     | 
    
         
            +
                  DECLARE
         
     | 
| 
      
 8 
     | 
    
         
            +
                    next_strand varchar;
         
     | 
| 
      
 9 
     | 
    
         
            +
                    running_count integer;
         
     | 
| 
      
 10 
     | 
    
         
            +
                    should_lock boolean;
         
     | 
| 
      
 11 
     | 
    
         
            +
                    should_be_precise boolean;
         
     | 
| 
      
 12 
     | 
    
         
            +
                    update_query varchar;
         
     | 
| 
      
 13 
     | 
    
         
            +
                    skip_locked varchar;
         
     | 
| 
      
 14 
     | 
    
         
            +
                    transition boolean;
         
     | 
| 
      
 15 
     | 
    
         
            +
                  BEGIN
         
     | 
| 
      
 16 
     | 
    
         
            +
                    IF OLD.strand IS NOT NULL THEN
         
     | 
| 
      
 17 
     | 
    
         
            +
                      should_lock := true;
         
     | 
| 
      
 18 
     | 
    
         
            +
                      should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                      IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
         
     | 
| 
      
 21 
     | 
    
         
            +
                        running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 22 
     | 
    
         
            +
                          SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 23 
     | 
    
         
            +
                        ) subquery_for_count);
         
     | 
| 
      
 24 
     | 
    
         
            +
                        should_lock := running_count < OLD.max_concurrent;
         
     | 
| 
      
 25 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      IF should_lock THEN
         
     | 
| 
      
 28 
     | 
    
         
            +
                        PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
         
     | 
| 
      
 29 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                      -- note that we don't really care if the row we're deleting has a singleton, or if it even
         
     | 
| 
      
 32 
     | 
    
         
            +
                      -- matches the row(s) we're going to update. we just need to make sure that whatever
         
     | 
| 
      
 33 
     | 
    
         
            +
                      -- singleton we grab isn't already running (which is a simple existence check, since
         
     | 
| 
      
 34 
     | 
    
         
            +
                      -- the unique indexes ensure there is at most one singleton running, and one queued)
         
     | 
| 
      
 35 
     | 
    
         
            +
                      update_query := 'UPDATE delayed_jobs SET next_in_strand=true WHERE id IN (
         
     | 
| 
      
 36 
     | 
    
         
            +
                        SELECT id FROM delayed_jobs j2
         
     | 
| 
      
 37 
     | 
    
         
            +
                          WHERE next_in_strand=false AND
         
     | 
| 
      
 38 
     | 
    
         
            +
                            j2.strand=$1.strand AND
         
     | 
| 
      
 39 
     | 
    
         
            +
                            (j2.singleton IS NULL OR NOT EXISTS (SELECT 1 FROM delayed_jobs j3 WHERE j3.singleton=j2.singleton AND j3.id<>j2.id AND (j3.locked_by IS NULL OR j3.locked_by IS NOT NULL)))
         
     | 
| 
      
 40 
     | 
    
         
            +
                          ORDER BY j2.strand_order_override ASC, j2.id ASC
         
     | 
| 
      
 41 
     | 
    
         
            +
                          LIMIT ';
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                      IF should_be_precise THEN
         
     | 
| 
      
 44 
     | 
    
         
            +
                        running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 45 
     | 
    
         
            +
                          SELECT 1 FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 46 
     | 
    
         
            +
                        ) s);
         
     | 
| 
      
 47 
     | 
    
         
            +
                        IF running_count < OLD.max_concurrent THEN
         
     | 
| 
      
 48 
     | 
    
         
            +
                          update_query := update_query || '($1.max_concurrent - $2)';
         
     | 
| 
      
 49 
     | 
    
         
            +
                        ELSE
         
     | 
| 
      
 50 
     | 
    
         
            +
                          -- we have too many running already; just bail
         
     | 
| 
      
 51 
     | 
    
         
            +
                          RETURN OLD;
         
     | 
| 
      
 52 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 53 
     | 
    
         
            +
                      ELSE
         
     | 
| 
      
 54 
     | 
    
         
            +
                        update_query := update_query || '1';
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                        -- n-strands don't require precise ordering; we can make this query more performant
         
     | 
| 
      
 57 
     | 
    
         
            +
                        IF OLD.max_concurrent > 1 THEN
         
     | 
| 
      
 58 
     | 
    
         
            +
                          skip_locked := ' SKIP LOCKED';
         
     | 
| 
      
 59 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 60 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                      update_query := update_query || ' FOR UPDATE' || COALESCE(skip_locked, '') || ')';
         
     | 
| 
      
 63 
     | 
    
         
            +
                      EXECUTE update_query USING OLD, running_count;
         
     | 
| 
      
 64 
     | 
    
         
            +
                    END IF;
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    IF OLD.singleton IS NOT NULL THEN
         
     | 
| 
      
 67 
     | 
    
         
            +
                      transition := EXISTS (SELECT 1 FROM delayed_jobs AS j1 WHERE j1.singleton = OLD.singleton AND j1.strand IS DISTINCT FROM OLD.strand AND locked_by IS NULL);
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                      IF transition THEN
         
     | 
| 
      
 70 
     | 
    
         
            +
                        next_strand := (SELECT j1.strand FROM delayed_jobs AS j1 WHERE j1.singleton = OLD.singleton AND j1.strand IS DISTINCT FROM OLD.strand AND locked_by IS NULL AND j1.strand IS NOT NULL LIMIT 1);
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                        IF next_strand IS NOT NULL THEN
         
     | 
| 
      
 73 
     | 
    
         
            +
                          -- if the singleton has a new strand defined, we need to lock it to ensure we obey n_strand constraints --
         
     | 
| 
      
 74 
     | 
    
         
            +
                          IF NOT pg_try_advisory_xact_lock(half_md5_as_bigint(next_strand)) THEN
         
     | 
| 
      
 75 
     | 
    
         
            +
                            -- a failure to acquire the lock means that another process already has it and will thus handle this singleton --
         
     | 
| 
      
 76 
     | 
    
         
            +
                            RETURN OLD;
         
     | 
| 
      
 77 
     | 
    
         
            +
                          END IF;
         
     | 
| 
      
 78 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 79 
     | 
    
         
            +
                      ELSIF OLD.strand IS NOT NULL THEN
         
     | 
| 
      
 80 
     | 
    
         
            +
                        -- if there is no transition and there is a strand then we have already handled this singleton in the case above --
         
     | 
| 
      
 81 
     | 
    
         
            +
                        RETURN OLD;
         
     | 
| 
      
 82 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                      -- handles transitioning a singleton from stranded to not stranded --
         
     | 
| 
      
 85 
     | 
    
         
            +
                      -- handles transitioning a singleton from unstranded to stranded --
         
     | 
| 
      
 86 
     | 
    
         
            +
                      -- handles transitioning a singleton from strand A to strand B --
         
     | 
| 
      
 87 
     | 
    
         
            +
                      -- these transitions are a relatively rare case, so we take a shortcut and --
         
     | 
| 
      
 88 
     | 
    
         
            +
                      -- only start the next singleton if its strand does not currently have any running jobs --
         
     | 
| 
      
 89 
     | 
    
         
            +
                      -- if it does, the next stranded job that finishes will start this singleton if it can --
         
     | 
| 
      
 90 
     | 
    
         
            +
                      UPDATE delayed_jobs SET next_in_strand=true WHERE id IN (
         
     | 
| 
      
 91 
     | 
    
         
            +
                        SELECT id FROM delayed_jobs j2
         
     | 
| 
      
 92 
     | 
    
         
            +
                          WHERE next_in_strand=false AND
         
     | 
| 
      
 93 
     | 
    
         
            +
                            j2.singleton=OLD.singleton AND
         
     | 
| 
      
 94 
     | 
    
         
            +
                            j2.locked_by IS NULL AND
         
     | 
| 
      
 95 
     | 
    
         
            +
                            (j2.strand IS NULL OR NOT EXISTS (SELECT 1 FROM delayed_jobs j3 WHERE j3.strand=j2.strand AND j3.id<>j2.id))
         
     | 
| 
      
 96 
     | 
    
         
            +
                          FOR UPDATE
         
     | 
| 
      
 97 
     | 
    
         
            +
                        );
         
     | 
| 
      
 98 
     | 
    
         
            +
                    END IF;
         
     | 
| 
      
 99 
     | 
    
         
            +
                    RETURN OLD;
         
     | 
| 
      
 100 
     | 
    
         
            +
                  END;
         
     | 
| 
      
 101 
     | 
    
         
            +
                  $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 102 
     | 
    
         
            +
                SQL
         
     | 
| 
      
 103 
     | 
    
         
            +
              end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
              def down
         
     | 
| 
      
 106 
     | 
    
         
            +
                execute(<<~SQL)
         
     | 
| 
      
 107 
     | 
    
         
            +
                  CREATE OR REPLACE FUNCTION #{connection.quote_table_name('delayed_jobs_after_delete_row_tr_fn')} () RETURNS trigger AS $$
         
     | 
| 
      
 108 
     | 
    
         
            +
                  DECLARE
         
     | 
| 
      
 109 
     | 
    
         
            +
                    running_count integer;
         
     | 
| 
      
 110 
     | 
    
         
            +
                    should_lock boolean;
         
     | 
| 
      
 111 
     | 
    
         
            +
                    should_be_precise boolean;
         
     | 
| 
      
 112 
     | 
    
         
            +
                    update_query varchar;
         
     | 
| 
      
 113 
     | 
    
         
            +
                    skip_locked varchar;
         
     | 
| 
      
 114 
     | 
    
         
            +
                  BEGIN
         
     | 
| 
      
 115 
     | 
    
         
            +
                    IF OLD.strand IS NOT NULL THEN
         
     | 
| 
      
 116 
     | 
    
         
            +
                      should_lock := true;
         
     | 
| 
      
 117 
     | 
    
         
            +
                      should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                      IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
         
     | 
| 
      
 120 
     | 
    
         
            +
                        running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 121 
     | 
    
         
            +
                          SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 122 
     | 
    
         
            +
                        ) subquery_for_count);
         
     | 
| 
      
 123 
     | 
    
         
            +
                        should_lock := running_count < OLD.max_concurrent;
         
     | 
| 
      
 124 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                      IF should_lock THEN
         
     | 
| 
      
 127 
     | 
    
         
            +
                        PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
         
     | 
| 
      
 128 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                      -- note that we don't really care if the row we're deleting has a singleton, or if it even
         
     | 
| 
      
 131 
     | 
    
         
            +
                      -- matches the row(s) we're going to update. we just need to make sure that whatever
         
     | 
| 
      
 132 
     | 
    
         
            +
                      -- singleton we grab isn't already running (which is a simple existence check, since
         
     | 
| 
      
 133 
     | 
    
         
            +
                      -- the unique indexes ensure there is at most one singleton running, and one queued)
         
     | 
| 
      
 134 
     | 
    
         
            +
                      update_query := 'UPDATE delayed_jobs SET next_in_strand=true WHERE id IN (
         
     | 
| 
      
 135 
     | 
    
         
            +
                        SELECT id FROM delayed_jobs j2
         
     | 
| 
      
 136 
     | 
    
         
            +
                          WHERE next_in_strand=false AND
         
     | 
| 
      
 137 
     | 
    
         
            +
                            j2.strand=$1.strand AND
         
     | 
| 
      
 138 
     | 
    
         
            +
                            (j2.singleton IS NULL OR NOT EXISTS (SELECT 1 FROM delayed_jobs j3 WHERE j3.singleton=j2.singleton AND j3.id<>j2.id AND (j3.locked_by IS NULL OR j3.locked_by IS NOT NULL)))
         
     | 
| 
      
 139 
     | 
    
         
            +
                          ORDER BY j2.strand_order_override ASC, j2.id ASC
         
     | 
| 
      
 140 
     | 
    
         
            +
                          LIMIT ';
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                      IF should_be_precise THEN
         
     | 
| 
      
 143 
     | 
    
         
            +
                        running_count := (SELECT COUNT(*) FROM (
         
     | 
| 
      
 144 
     | 
    
         
            +
                          SELECT 1 FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
         
     | 
| 
      
 145 
     | 
    
         
            +
                        ) s);
         
     | 
| 
      
 146 
     | 
    
         
            +
                        IF running_count < OLD.max_concurrent THEN
         
     | 
| 
      
 147 
     | 
    
         
            +
                          update_query := update_query || '($1.max_concurrent - $2)';
         
     | 
| 
      
 148 
     | 
    
         
            +
                        ELSE
         
     | 
| 
      
 149 
     | 
    
         
            +
                          -- we have too many running already; just bail
         
     | 
| 
      
 150 
     | 
    
         
            +
                          RETURN OLD;
         
     | 
| 
      
 151 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 152 
     | 
    
         
            +
                      ELSE
         
     | 
| 
      
 153 
     | 
    
         
            +
                        update_query := update_query || '1';
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                        -- n-strands don't require precise ordering; we can make this query more performant
         
     | 
| 
      
 156 
     | 
    
         
            +
                        IF OLD.max_concurrent > 1 THEN
         
     | 
| 
      
 157 
     | 
    
         
            +
                          skip_locked := ' SKIP LOCKED';
         
     | 
| 
      
 158 
     | 
    
         
            +
                        END IF;
         
     | 
| 
      
 159 
     | 
    
         
            +
                      END IF;
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                      update_query := update_query || ' FOR UPDATE' || COALESCE(skip_locked, '') || ')';
         
     | 
| 
      
 162 
     | 
    
         
            +
                      EXECUTE update_query USING OLD, running_count;
         
     | 
| 
      
 163 
     | 
    
         
            +
                    ELSIF OLD.singleton IS NOT NULL THEN
         
     | 
| 
      
 164 
     | 
    
         
            +
                      UPDATE delayed_jobs SET next_in_strand = 't' WHERE singleton=OLD.singleton AND next_in_strand=false AND locked_by IS NULL;
         
     | 
| 
      
 165 
     | 
    
         
            +
                    END IF;
         
     | 
| 
      
 166 
     | 
    
         
            +
                    RETURN OLD;
         
     | 
| 
      
 167 
     | 
    
         
            +
                  END;
         
     | 
| 
      
 168 
     | 
    
         
            +
                  $$ LANGUAGE plpgsql SET search_path TO #{::Switchman::Shard.current.name};
         
     | 
| 
      
 169 
     | 
    
         
            +
                SQL
         
     | 
| 
      
 170 
     | 
    
         
            +
              end
         
     | 
| 
      
 171 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -74,7 +74,7 @@ module SwitchmanInstJobs 
     | 
|
| 
       74 
74 
     | 
    
         
             
                      self.shard_id = shard.id
         
     | 
| 
       75 
75 
     | 
    
         
             
                      self.shard_id = nil if shard.is_a?(::Switchman::DefaultShard)
         
     | 
| 
       76 
76 
     | 
    
         
             
                      # If jobs are held for a shard, enqueue new ones as held as well
         
     | 
| 
       77 
     | 
    
         
            -
                      return unless shard.jobs_held
         
     | 
| 
      
 77 
     | 
    
         
            +
                      return unless ::Switchman::Shard.columns_hash.key?('jobs_held') && shard.jobs_held
         
     | 
| 
       78 
78 
     | 
    
         | 
| 
       79 
79 
     | 
    
         
             
                      self.locked_by = ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY
         
     | 
| 
       80 
80 
     | 
    
         
             
                      self.locked_at = ::Delayed::Job.db_time_now
         
     | 
| 
         @@ -1,7 +1,3 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # Just disabling all the rubocop metrics for this file for now,
         
     | 
| 
       2 
     | 
    
         
            -
            # as it is a direct port-in of existing code
         
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
            # rubocop:disable Metrics/BlockLength, Metrics/MethodLength, Metrics/AbcSize, Metrics/ClassLength
         
     | 
| 
       5 
1 
     | 
    
         
             
            require 'set'
         
     | 
| 
       6 
2 
     | 
    
         
             
            require 'parallel'
         
     | 
| 
       7 
3 
     | 
    
         | 
| 
         @@ -76,7 +72,7 @@ module SwitchmanInstJobs 
     | 
|
| 
       76 
72 
     | 
    
         | 
| 
       77 
73 
     | 
    
         
             
                  def clear_shard_cache(debug_message = nil)
         
     | 
| 
       78 
74 
     | 
    
         
             
                    ::Switchman.cache.clear
         
     | 
| 
       79 
     | 
    
         
            -
                    Rails.logger.debug 
     | 
| 
      
 75 
     | 
    
         
            +
                    Rails.logger.debug { "Waiting for caches to clear #{debug_message}" }
         
     | 
| 
       80 
76 
     | 
    
         
             
                    # Wait a little over the 60 second in-process shard cache clearing
         
     | 
| 
       81 
77 
     | 
    
         
             
                    # threshold to ensure that all new stranded jobs are now being
         
     | 
| 
       82 
78 
     | 
    
         
             
                    # enqueued with next_in_strand: false
         
     | 
| 
         @@ -225,12 +221,14 @@ module SwitchmanInstJobs 
     | 
|
| 
       225 
221 
     | 
    
         
             
                      target_jobs = scope.limit(1000).lock('FOR UPDATE SKIP LOCKED')
         
     | 
| 
       226 
222 
     | 
    
         | 
| 
       227 
223 
     | 
    
         
             
                      query = source_shard.activate(::Delayed::Backend::ActiveRecord::AbstractJob) do
         
     | 
| 
       228 
     | 
    
         
            -
                         
     | 
| 
       229 
     | 
    
         
            -
             
     | 
| 
       230 
     | 
    
         
            -
             
     | 
| 
       231 
     | 
    
         
            -
             
     | 
| 
       232 
     | 
    
         
            -
             
     | 
| 
       233 
     | 
    
         
            -
             
     | 
| 
      
 224 
     | 
    
         
            +
                        <<~SQL
         
     | 
| 
      
 225 
     | 
    
         
            +
                          WITH limited_jobs AS (#{target_jobs.to_sql})
         
     | 
| 
      
 226 
     | 
    
         
            +
                          UPDATE #{::Delayed::Job.quoted_table_name}
         
     | 
| 
      
 227 
     | 
    
         
            +
                          SET locked_by = #{::Delayed::Job.connection.quote(::Delayed::Backend::Base::ON_HOLD_LOCKED_BY)},
         
     | 
| 
      
 228 
     | 
    
         
            +
                          locked_at = #{::Delayed::Job.connection.quote(::Delayed::Job.db_time_now)}
         
     | 
| 
      
 229 
     | 
    
         
            +
                          FROM limited_jobs WHERE limited_jobs.id=#{::Delayed::Job.quoted_table_name}.id
         
     | 
| 
      
 230 
     | 
    
         
            +
                          RETURNING #{::Delayed::Job.quoted_table_name}.*
         
     | 
| 
      
 231 
     | 
    
         
            +
                        SQL
         
     | 
| 
       234 
232 
     | 
    
         
             
                      end
         
     | 
| 
       235 
233 
     | 
    
         | 
| 
       236 
234 
     | 
    
         
             
                      jobs = source_shard.activate(::Delayed::Backend::ActiveRecord::AbstractJob) do
         
     | 
| 
         @@ -283,7 +281,10 @@ module SwitchmanInstJobs 
     | 
|
| 
       283 
281 
     | 
    
         
             
                    connection = ::Delayed::Job.connection
         
     | 
| 
       284 
282 
     | 
    
         
             
                    quoted_keys = keys.map { |k| connection.quote_column_name(k) }.join(', ')
         
     | 
| 
       285 
283 
     | 
    
         | 
| 
       286 
     | 
    
         
            -
                    connection.execute  
     | 
| 
      
 284 
     | 
    
         
            +
                    connection.execute 'DROP TABLE IF EXISTS delayed_jobs_bulk_copy'
         
     | 
| 
      
 285 
     | 
    
         
            +
                    connection.execute "CREATE TEMPORARY TABLE delayed_jobs_bulk_copy
         
     | 
| 
      
 286 
     | 
    
         
            +
                      (LIKE #{::Delayed::Job.quoted_table_name} INCLUDING DEFAULTS)"
         
     | 
| 
      
 287 
     | 
    
         
            +
                    connection.execute "COPY delayed_jobs_bulk_copy (#{quoted_keys}) FROM STDIN"
         
     | 
| 
       287 
288 
     | 
    
         
             
                    records.map do |record|
         
     | 
| 
       288 
289 
     | 
    
         
             
                      connection.raw_connection.put_copy_data("#{keys.map { |k| quote_text(record[k]) }.join("\t")}\n")
         
     | 
| 
       289 
290 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -295,6 +296,9 @@ module SwitchmanInstJobs 
     | 
|
| 
       295 
296 
     | 
    
         
             
                    rescue StandardError => e
         
     | 
| 
       296 
297 
     | 
    
         
             
                      raise connection.send(:translate_exception, e, 'COPY FROM STDIN')
         
     | 
| 
       297 
298 
     | 
    
         
             
                    end
         
     | 
| 
      
 299 
     | 
    
         
            +
                    connection.execute "INSERT INTO #{::Delayed::Job.quoted_table_name} (#{quoted_keys})
         
     | 
| 
      
 300 
     | 
    
         
            +
                      SELECT #{quoted_keys} FROM delayed_jobs_bulk_copy
         
     | 
| 
      
 301 
     | 
    
         
            +
                      ON CONFLICT (singleton) WHERE singleton IS NOT NULL AND locked_by IS NULL DO NOTHING"
         
     | 
| 
       298 
302 
     | 
    
         
             
                    result.cmd_tuples
         
     | 
| 
       299 
303 
     | 
    
         
             
                  end
         
     | 
| 
       300 
304 
     | 
    
         | 
| 
         @@ -312,5 +316,3 @@ module SwitchmanInstJobs 
     | 
|
| 
       312 
316 
     | 
    
         
             
                end
         
     | 
| 
       313 
317 
     | 
    
         
             
              end
         
     | 
| 
       314 
318 
     | 
    
         
             
            end
         
     | 
| 
       315 
     | 
    
         
            -
             
     | 
| 
       316 
     | 
    
         
            -
            # rubocop:enable Metrics/BlockLength, Metrics/MethodLength, Metrics/AbcSize, Metrics/ClassLength
         
     | 
    
        data/lib/switchman_inst_jobs.rb
    CHANGED
    
    | 
         @@ -17,10 +17,6 @@ module SwitchmanInstJobs 
     | 
|
| 
       17 
17 
     | 
    
         
             
                ::Delayed::Backend::ActiveRecord::Job.prepend(
         
     | 
| 
       18 
18 
     | 
    
         
             
                  Delayed::Backend::Base
         
     | 
| 
       19 
19 
     | 
    
         
             
                )
         
     | 
| 
       20 
     | 
    
         
            -
                ::Delayed::Backend::Redis::Job.prepend(
         
     | 
| 
       21 
     | 
    
         
            -
                  Delayed::Backend::Base
         
     | 
| 
       22 
     | 
    
         
            -
                )
         
     | 
| 
       23 
     | 
    
         
            -
                ::Delayed::Backend::Redis::Job.column :shard_id, :integer
         
     | 
| 
       24 
20 
     | 
    
         
             
                ::Delayed::Pool.prepend Delayed::Pool
         
     | 
| 
       25 
21 
     | 
    
         
             
                ::Delayed::Worker.prepend Delayed::Worker
         
     | 
| 
       26 
22 
     | 
    
         
             
                ::Delayed::Worker::HealthCheck.prepend Delayed::Worker::HealthCheck
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,35 +1,35 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: switchman-inst-jobs
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 4.0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 4.0.2
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Bryan Petty
         
     | 
| 
       8 
     | 
    
         
            -
            autorequire:
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2021- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2021-12-09 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: inst-jobs
         
     | 
| 
       15 
15 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       16 
16 
     | 
    
         
             
                requirements:
         
     | 
| 
       17 
     | 
    
         
            -
                - - "~>"
         
     | 
| 
       18 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       19 
     | 
    
         
            -
                    version: '2.0'
         
     | 
| 
       20 
17 
     | 
    
         
             
                - - ">="
         
     | 
| 
       21 
18 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       22 
     | 
    
         
            -
                    version: 2. 
     | 
| 
      
 19 
     | 
    
         
            +
                    version: 2.4.9
         
     | 
| 
      
 20 
     | 
    
         
            +
                - - "<"
         
     | 
| 
      
 21 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 22 
     | 
    
         
            +
                    version: '4.0'
         
     | 
| 
       23 
23 
     | 
    
         
             
              type: :runtime
         
     | 
| 
       24 
24 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       25 
25 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       26 
26 
     | 
    
         
             
                requirements:
         
     | 
| 
       27 
     | 
    
         
            -
                - - "~>"
         
     | 
| 
       28 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       29 
     | 
    
         
            -
                    version: '2.0'
         
     | 
| 
       30 
27 
     | 
    
         
             
                - - ">="
         
     | 
| 
       31 
28 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       32 
     | 
    
         
            -
                    version: 2. 
     | 
| 
      
 29 
     | 
    
         
            +
                    version: 2.4.9
         
     | 
| 
      
 30 
     | 
    
         
            +
                - - "<"
         
     | 
| 
      
 31 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 32 
     | 
    
         
            +
                    version: '4.0'
         
     | 
| 
       33 
33 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       34 
34 
     | 
    
         
             
              name: parallel
         
     | 
| 
       35 
35 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -238,6 +238,34 @@ dependencies: 
     | 
|
| 
       238 
238 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       239 
239 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       240 
240 
     | 
    
         
             
                    version: '2.10'
         
     | 
| 
      
 241 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 242 
     | 
    
         
            +
              name: rubocop-rake
         
     | 
| 
      
 243 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 244 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 245 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 246 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 247 
     | 
    
         
            +
                    version: '0.6'
         
     | 
| 
      
 248 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 249 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 250 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 251 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 252 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 253 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 254 
     | 
    
         
            +
                    version: '0.6'
         
     | 
| 
      
 255 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 256 
     | 
    
         
            +
              name: rubocop-rspec
         
     | 
| 
      
 257 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 258 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 259 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 260 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 261 
     | 
    
         
            +
                    version: '2.4'
         
     | 
| 
      
 262 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 263 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 264 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 265 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 266 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 267 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 268 
     | 
    
         
            +
                    version: '2.4'
         
     | 
| 
       241 
269 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       242 
270 
     | 
    
         
             
              name: simplecov
         
     | 
| 
       243 
271 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -266,7 +294,7 @@ dependencies: 
     | 
|
| 
       266 
294 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       267 
295 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       268 
296 
     | 
    
         
             
                    version: '1.4'
         
     | 
| 
       269 
     | 
    
         
            -
            description:
         
     | 
| 
      
 297 
     | 
    
         
            +
            description: 
         
     | 
| 
       270 
298 
     | 
    
         
             
            email:
         
     | 
| 
       271 
299 
     | 
    
         
             
            - bpetty@instructure.com
         
     | 
| 
       272 
300 
     | 
    
         
             
            executables: []
         
     | 
| 
         @@ -303,6 +331,13 @@ files: 
     | 
|
| 
       303 
331 
     | 
    
         
             
            - db/migrate/20200822014259_add_block_stranded_to_switchman_shards.rb
         
     | 
| 
       304 
332 
     | 
    
         
             
            - db/migrate/20200824222232_speed_up_max_concurrent_delete_trigger.rb
         
     | 
| 
       305 
333 
     | 
    
         
             
            - db/migrate/20200825011002_add_strand_order_override.rb
         
     | 
| 
      
 334 
     | 
    
         
            +
            - db/migrate/20210809145804_add_n_strand_index.rb
         
     | 
| 
      
 335 
     | 
    
         
            +
            - db/migrate/20210812210128_add_singleton_column.rb
         
     | 
| 
      
 336 
     | 
    
         
            +
            - db/migrate/20210917232626_add_delete_conflicting_singletons_before_unlock_trigger.rb
         
     | 
| 
      
 337 
     | 
    
         
            +
            - db/migrate/20210928174754_fix_singleton_condition_in_before_insert.rb
         
     | 
| 
      
 338 
     | 
    
         
            +
            - db/migrate/20210929204903_update_conflicting_singleton_function_to_use_index.rb
         
     | 
| 
      
 339 
     | 
    
         
            +
            - db/migrate/20211101190934_update_after_delete_trigger_for_singleton_index.rb
         
     | 
| 
      
 340 
     | 
    
         
            +
            - db/migrate/20211207094200_update_after_delete_trigger_for_singleton_transition_cases.rb
         
     | 
| 
       306 
341 
     | 
    
         
             
            - lib/switchman-inst-jobs.rb
         
     | 
| 
       307 
342 
     | 
    
         
             
            - lib/switchman_inst_jobs.rb
         
     | 
| 
       308 
343 
     | 
    
         
             
            - lib/switchman_inst_jobs/active_record/connection_adapters/connection_pool.rb
         
     | 
| 
         @@ -327,8 +362,10 @@ files: 
     | 
|
| 
       327 
362 
     | 
    
         
             
            homepage: https://github.com/instructure/switchman-inst-jobs
         
     | 
| 
       328 
363 
     | 
    
         
             
            licenses:
         
     | 
| 
       329 
364 
     | 
    
         
             
            - MIT
         
     | 
| 
       330 
     | 
    
         
            -
            metadata: 
     | 
| 
       331 
     | 
    
         
            -
             
     | 
| 
      
 365 
     | 
    
         
            +
            metadata:
         
     | 
| 
      
 366 
     | 
    
         
            +
              allowed_push_host: https://rubygems.org
         
     | 
| 
      
 367 
     | 
    
         
            +
              rubygems_mfa_required: 'true'
         
     | 
| 
      
 368 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
       332 
369 
     | 
    
         
             
            rdoc_options: []
         
     | 
| 
       333 
370 
     | 
    
         
             
            require_paths:
         
     | 
| 
       334 
371 
     | 
    
         
             
            - lib
         
     | 
| 
         @@ -343,8 +380,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       343 
380 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       344 
381 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       345 
382 
     | 
    
         
             
            requirements: []
         
     | 
| 
       346 
     | 
    
         
            -
            rubygems_version: 3. 
     | 
| 
       347 
     | 
    
         
            -
            signing_key:
         
     | 
| 
      
 383 
     | 
    
         
            +
            rubygems_version: 3.1.4
         
     | 
| 
      
 384 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
       348 
385 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       349 
386 
     | 
    
         
             
            summary: Switchman and Instructure Jobs compatibility gem.
         
     | 
| 
       350 
387 
     | 
    
         
             
            test_files: []
         
     |