inst-jobs 0.15.19 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/db/migrate/20101216224513_create_delayed_jobs.rb +2 -0
  3. data/db/migrate/20110208031356_add_delayed_jobs_tag.rb +2 -0
  4. data/db/migrate/20110426161613_add_delayed_jobs_max_attempts.rb +2 -0
  5. data/db/migrate/20110516225834_add_delayed_jobs_strand.rb +2 -0
  6. data/db/migrate/20110531144916_cleanup_delayed_jobs_indexes.rb +2 -0
  7. data/db/migrate/20110610213249_optimize_delayed_jobs.rb +2 -0
  8. data/db/migrate/20110831210257_add_delayed_jobs_next_in_strand.rb +2 -0
  9. data/db/migrate/20120510004759_delayed_jobs_delete_trigger_lock_for_update.rb +2 -0
  10. data/db/migrate/20120531150712_drop_psql_jobs_pop_fn.rb +2 -0
  11. data/db/migrate/20120607164022_delayed_jobs_use_advisory_locks.rb +2 -0
  12. data/db/migrate/20120607181141_index_jobs_on_locked_by.rb +2 -0
  13. data/db/migrate/20120608191051_add_jobs_run_at_index.rb +2 -0
  14. data/db/migrate/20120927184213_change_delayed_jobs_handler_to_text.rb +2 -0
  15. data/db/migrate/20140505215131_add_failed_jobs_original_job_id.rb +2 -0
  16. data/db/migrate/20140505215510_copy_failed_jobs_original_id.rb +2 -0
  17. data/db/migrate/20140505223637_drop_failed_jobs_original_id.rb +2 -0
  18. data/db/migrate/20140512213941_add_source_to_jobs.rb +2 -0
  19. data/db/migrate/20150807133223_add_max_concurrent_to_jobs.rb +2 -0
  20. data/db/migrate/20151123210429_add_expires_at_to_jobs.rb +2 -0
  21. data/db/migrate/20151210162949_improve_max_concurrent.rb +2 -0
  22. data/db/migrate/20161206323555_add_back_default_string_limits_jobs.rb +2 -0
  23. data/db/migrate/20181217155351_speed_up_max_concurrent_triggers.rb +2 -0
  24. data/db/migrate/20190726154743_make_critical_columns_not_null.rb +2 -0
  25. data/db/migrate/20200330230722_add_id_to_get_delayed_jobs_index.rb +2 -0
  26. data/db/migrate/20200824222232_speed_up_max_concurrent_delete_trigger.rb +97 -0
  27. data/db/migrate/20200825011002_add_strand_order_override.rb +128 -0
  28. data/lib/delayed/backend/active_record.rb +19 -7
  29. data/lib/delayed/backend/base.rb +34 -20
  30. data/lib/delayed/backend/redis/functions.rb +2 -0
  31. data/lib/delayed/backend/redis/job.rb +2 -0
  32. data/lib/delayed/batch.rb +5 -3
  33. data/lib/delayed/cli.rb +2 -0
  34. data/lib/delayed/daemon.rb +2 -0
  35. data/lib/delayed/engine.rb +2 -0
  36. data/lib/delayed/job_tracking.rb +2 -0
  37. data/lib/delayed/lifecycle.rb +2 -0
  38. data/lib/delayed/log_tailer.rb +2 -0
  39. data/lib/delayed/logging.rb +2 -0
  40. data/lib/delayed/message_sending.rb +48 -111
  41. data/lib/delayed/performable_method.rb +15 -6
  42. data/lib/delayed/periodic.rb +6 -1
  43. data/lib/delayed/plugin.rb +2 -0
  44. data/lib/delayed/pool.rb +2 -0
  45. data/lib/delayed/server.rb +2 -0
  46. data/lib/delayed/server/helpers.rb +2 -0
  47. data/lib/delayed/settings.rb +7 -1
  48. data/lib/delayed/testing.rb +2 -0
  49. data/lib/delayed/version.rb +3 -1
  50. data/lib/delayed/work_queue/in_process.rb +2 -0
  51. data/lib/delayed/work_queue/parent_process.rb +2 -0
  52. data/lib/delayed/work_queue/parent_process/client.rb +2 -0
  53. data/lib/delayed/work_queue/parent_process/server.rb +3 -1
  54. data/lib/delayed/worker.rb +3 -1
  55. data/lib/delayed/worker/consul_health_check.rb +2 -0
  56. data/lib/delayed/worker/health_check.rb +2 -0
  57. data/lib/delayed/worker/null_health_check.rb +2 -0
  58. data/lib/delayed/worker/process_helper.rb +2 -0
  59. data/lib/delayed/yaml_extensions.rb +2 -0
  60. data/lib/delayed_job.rb +2 -0
  61. data/lib/inst-jobs.rb +2 -0
  62. data/spec/active_record_job_spec.rb +4 -2
  63. data/spec/delayed/cli_spec.rb +2 -0
  64. data/spec/delayed/daemon_spec.rb +2 -0
  65. data/spec/delayed/server_spec.rb +2 -0
  66. data/spec/delayed/settings_spec.rb +2 -0
  67. data/spec/delayed/work_queue/in_process_spec.rb +2 -0
  68. data/spec/delayed/work_queue/parent_process/client_spec.rb +2 -0
  69. data/spec/delayed/work_queue/parent_process/server_spec.rb +2 -0
  70. data/spec/delayed/work_queue/parent_process_spec.rb +2 -0
  71. data/spec/delayed/worker/consul_health_check_spec.rb +2 -0
  72. data/spec/delayed/worker/health_check_spec.rb +2 -0
  73. data/spec/delayed/worker_spec.rb +29 -0
  74. data/spec/gemfiles/51.gemfile.lock +7 -7
  75. data/spec/gemfiles/52.gemfile.lock +17 -15
  76. data/spec/gemfiles/60.gemfile.lock +91 -89
  77. data/spec/migrate/20140924140513_add_story_table.rb +2 -0
  78. data/spec/redis_job_spec.rb +10 -8
  79. data/spec/sample_jobs.rb +2 -0
  80. data/spec/shared/delayed_batch.rb +17 -15
  81. data/spec/shared/delayed_method.rb +49 -204
  82. data/spec/shared/performable_method.rb +11 -9
  83. data/spec/shared/shared_backend.rb +27 -25
  84. data/spec/shared/testing.rb +7 -5
  85. data/spec/shared/worker.rb +15 -13
  86. data/spec/shared_jobs_specs.rb +2 -0
  87. data/spec/spec_helper.rb +3 -1
  88. metadata +9 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 988fd063fdecd8a94025cf8b9d1db853e3397ab3842942928cbdc99e241d0d80
4
- data.tar.gz: c20258789ad05909efe3125e70a34bceb2a124398e43dbdf7d4b3c3d6dadf8ab
3
+ metadata.gz: 8c754ed71778d5b3f08641e133be947fae204a56dcee71aef08a7780a7d6afa5
4
+ data.tar.gz: 10949318e78e8e9d56b92ee7f2d14bfd647da20ff70578d894e4d1e85441d2fe
5
5
  SHA512:
6
- metadata.gz: e5f688d3413484f51c93270aea753178915ed38cdd3081dba5816621b070a99cea9c5bd518757db43ced1607a651241f8cd62f5160f5ea53cf113a7df3cab8c7
7
- data.tar.gz: fb563b85cc6b7caaef52e63342f00ce06ad0d30c75a3adaa47b7f8e81565a77610e4c3fc7aea7b3af1c703ea9e5f5e80c8801cf7ec4dc0cd10569cae57ebfcde
6
+ metadata.gz: 1a014e191bc0d6ccd083340b6d5c4bca0b7e03439aca459f79a5a704d93d16c42dd9e4855ca5985f7b1e4fb1fe62eead160cb6cb356c73b00ac297dc30112d8a
7
+ data.tar.gz: 53997c66017cf6db561104054379cb9e7e934fcc56c54d552b7629ff29eeb58de37c8674088661ba683e7e4d8b19234f26021819753ac06a6c738dd435c3f169
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class CreateDelayedJobs < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddDelayedJobsTag < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddDelayedJobsMaxAttempts < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddDelayedJobsStrand < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class CleanupDelayedJobsIndexes < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class OptimizeDelayedJobs < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddDelayedJobsNextInStrand < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DelayedJobsDeleteTriggerLockForUpdate < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DropPsqlJobsPopFn < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DelayedJobsUseAdvisoryLocks < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class IndexJobsOnLockedBy < ActiveRecord::Migration[4.2]
2
4
  disable_ddl_transaction! if respond_to?(:disable_ddl_transaction!)
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddJobsRunAtIndex < ActiveRecord::Migration[4.2]
2
4
  disable_ddl_transaction! if respond_to?(:disable_ddl_transaction!)
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ChangeDelayedJobsHandlerToText < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddFailedJobsOriginalJobId < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class CopyFailedJobsOriginalId < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DropFailedJobsOriginalId < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Backend::ActiveRecord::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddSourceToJobs < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddMaxConcurrentToJobs < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddExpiresAtToJobs < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImproveMaxConcurrent < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddBackDefaultStringLimitsJobs < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class SpeedUpMaxConcurrentTriggers < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class MakeCriticalColumnsNotNull < ActiveRecord::Migration[4.2]
2
4
  def connection
3
5
  Delayed::Job.connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddIdToGetDelayedJobsIndex < ActiveRecord::Migration[4.2]
2
4
  disable_ddl_transaction! if respond_to?(:disable_ddl_transaction!)
3
5
 
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SpeedUpMaxConcurrentDeleteTrigger < ActiveRecord::Migration[4.2]
4
+ def connection
5
+ Delayed::Job.connection
6
+ end
7
+
8
+ def up
9
+ if connection.adapter_name == 'PostgreSQL'
10
+ # tl;dr sacrifice some responsiveness to max_concurrent changes for faster performance
11
+ # don't get the count every single time - it's usually safe to just set the next one in line
12
+ # since the max_concurrent doesn't change all that often for a strand
13
+ execute(<<-SQL)
14
+ CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
15
+ DECLARE
16
+ running_count integer;
17
+ should_lock boolean;
18
+ should_be_precise boolean;
19
+ BEGIN
20
+ IF OLD.strand IS NOT NULL THEN
21
+ should_lock := true;
22
+ should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
23
+
24
+ IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
25
+ running_count := (SELECT COUNT(*) FROM (
26
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
27
+ ) subquery_for_count);
28
+ should_lock := running_count < OLD.max_concurrent;
29
+ END IF;
30
+
31
+ IF should_lock THEN
32
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
33
+ END IF;
34
+
35
+ IF should_be_precise THEN
36
+ running_count := (SELECT COUNT(*) FROM (
37
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
38
+ ) subquery_for_count);
39
+ IF running_count < OLD.max_concurrent THEN
40
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
41
+ SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
42
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
43
+ );
44
+ END IF;
45
+ ELSE
46
+ -- n-strands don't require precise ordering; we can make this query more performant
47
+ IF OLD.max_concurrent > 1 THEN
48
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
49
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
50
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
51
+ ELSE
52
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
53
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
54
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE);
55
+ END IF;
56
+ END IF;
57
+ END IF;
58
+ RETURN OLD;
59
+ END;
60
+ $$ LANGUAGE plpgsql;
61
+ SQL
62
+ end
63
+ end
64
+
65
+ def down
66
+ if connection.adapter_name == 'PostgreSQL'
67
+ execute(<<-SQL)
68
+ CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
69
+ DECLARE
70
+ running_count integer;
71
+ BEGIN
72
+ IF OLD.strand IS NOT NULL THEN
73
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
74
+ IF OLD.id % 20 = 0 THEN
75
+ running_count := (SELECT COUNT(*) FROM (
76
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
77
+ ) subquery_for_count);
78
+ IF running_count < OLD.max_concurrent THEN
79
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
80
+ SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
81
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
82
+ );
83
+ END IF;
84
+ ELSE
85
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
86
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
87
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE);
88
+ END IF;
89
+ END IF;
90
+ RETURN OLD;
91
+ END;
92
+ $$ LANGUAGE plpgsql;
93
+ SQL
94
+ end
95
+ end
96
+ end
97
+
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddStrandOrderOverride < ActiveRecord::Migration[4.2]
4
+ disable_ddl_transaction! if respond_to?(:disable_ddl_transaction!)
5
+
6
+ def connection
7
+ Delayed::Job.connection
8
+ end
9
+
10
+ def up
11
+ add_column :delayed_jobs, :strand_order_override, :integer, default: 0, null: false
12
+ add_column :failed_jobs, :strand_order_override, :integer, default: 0, null: false
13
+ add_index :delayed_jobs, [:strand, :strand_order_override, :id],
14
+ algorithm: :concurrently,
15
+ where: "strand IS NOT NULL",
16
+ name: "next_in_strand_index"
17
+
18
+ if connection.adapter_name == 'PostgreSQL'
19
+ # Use the strand_order_override as the primary sorting mechanism (useful when moving between jobs queues without preserving ID ordering)
20
+ execute(<<-SQL)
21
+ CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
22
+ DECLARE
23
+ running_count integer;
24
+ should_lock boolean;
25
+ should_be_precise boolean;
26
+ BEGIN
27
+ IF OLD.strand IS NOT NULL THEN
28
+ should_lock := true;
29
+ should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
30
+
31
+ IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
32
+ running_count := (SELECT COUNT(*) FROM (
33
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
34
+ ) subquery_for_count);
35
+ should_lock := running_count < OLD.max_concurrent;
36
+ END IF;
37
+
38
+ IF should_lock THEN
39
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
40
+ END IF;
41
+
42
+ IF should_be_precise THEN
43
+ running_count := (SELECT COUNT(*) FROM (
44
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
45
+ ) subquery_for_count);
46
+ IF running_count < OLD.max_concurrent THEN
47
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
48
+ SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
49
+ j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
50
+ );
51
+ END IF;
52
+ ELSE
53
+ -- n-strands don't require precise ordering; we can make this query more performant
54
+ IF OLD.max_concurrent > 1 THEN
55
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
56
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
57
+ j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
58
+ ELSE
59
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
60
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
61
+ j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT 1 FOR UPDATE);
62
+ END IF;
63
+ END IF;
64
+ END IF;
65
+ RETURN OLD;
66
+ END;
67
+ $$ LANGUAGE plpgsql;
68
+ SQL
69
+ end
70
+ end
71
+
72
+ def down
73
+ remove_column :delayed_jobs, :strand_order_override, :integer
74
+ remove_column :failed_jobs, :strand_order_override, :integer
75
+
76
+ if connection.adapter_name == 'PostgreSQL'
77
+ execute(<<-SQL)
78
+ CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
79
+ DECLARE
80
+ running_count integer;
81
+ should_lock boolean;
82
+ should_be_precise boolean;
83
+ BEGIN
84
+ IF OLD.strand IS NOT NULL THEN
85
+ should_lock := true;
86
+ should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
87
+
88
+ IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
89
+ running_count := (SELECT COUNT(*) FROM (
90
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
91
+ ) subquery_for_count);
92
+ should_lock := running_count < OLD.max_concurrent;
93
+ END IF;
94
+
95
+ IF should_lock THEN
96
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
97
+ END IF;
98
+
99
+ IF should_be_precise THEN
100
+ running_count := (SELECT COUNT(*) FROM (
101
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
102
+ ) subquery_for_count);
103
+ IF running_count < OLD.max_concurrent THEN
104
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
105
+ SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
106
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
107
+ );
108
+ END IF;
109
+ ELSE
110
+ -- n-strands don't require precise ordering; we can make this query more performant
111
+ IF OLD.max_concurrent > 1 THEN
112
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
113
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
114
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
115
+ ELSE
116
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
117
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
118
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE);
119
+ END IF;
120
+ END IF;
121
+ END IF;
122
+ RETURN OLD;
123
+ END;
124
+ $$ LANGUAGE plpgsql;
125
+ SQL
126
+ end
127
+ end
128
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ActiveRecord::Base
2
4
  def self.load_for_delayed_job(id)
3
5
  if id
@@ -17,6 +19,8 @@ module Delayed
17
19
  include Delayed::Backend::Base
18
20
  self.table_name = :delayed_jobs
19
21
 
22
+ scope :next_in_strand_order, -> { order(:strand_order_override, :id) }
23
+
20
24
  def self.reconnect!
21
25
  clear_all_connections!
22
26
  end
@@ -103,7 +107,7 @@ module Delayed
103
107
  # so rather than changing the strand and balancing at queue time,
104
108
  # this keeps the strand intact and uses triggers to limit the number running
105
109
  def self.n_strand_options(strand_name, num_strands)
106
- {:strand => strand_name, :max_concurrent => num_strands}
110
+ { strand: strand_name, max_concurrent: num_strands }
107
111
  end
108
112
 
109
113
  def self.current
@@ -123,8 +127,8 @@ module Delayed
123
127
  end
124
128
 
125
129
  # a nice stress test:
126
- # 10_000.times { |i| Kernel.send_later_enqueue_args(:system, { :strand => 's1', :run_at => (24.hours.ago + (rand(24.hours.to_i))) }, "echo #{i} >> test1.txt") }
127
- # 500.times { |i| "ohai".send_later_enqueue_args(:reverse, { :run_at => (12.hours.ago + (rand(24.hours.to_i))) }) }
130
+ # 10_000.times { |i| Kernel.delay(strand: 's1', run_at: (24.hours.ago + (rand(24.hours.to_i))).system("echo #{i} >> test1.txt") }
131
+ # 500.times { |i| "ohai".delay(run_at: (12.hours.ago + (rand(24.hours.to_i))).reverse }
128
132
  # then fire up your workers
129
133
  # you can check out strand correctness: diff test1.txt <(sort -n test1.txt)
130
134
  def self.ready_to_run(forced_latency: nil)
@@ -286,7 +290,7 @@ module Delayed
286
290
  lock(lock)
287
291
  jobs_with_row_number = all.from(target_jobs).
288
292
  select("id, ROW_NUMBER() OVER () AS row_number")
289
- updates = "locked_by = CASE row_number "
293
+ updates = +"locked_by = CASE row_number "
290
294
  effective_worker_names.each_with_index do |worker, i|
291
295
  updates << "WHEN #{i + 1} THEN #{connection.quote(worker)} "
292
296
  end
@@ -377,7 +381,15 @@ module Delayed
377
381
  def self.transaction_for_singleton(strand, on_conflict)
378
382
  return yield if on_conflict == :loose
379
383
  self.transaction do
380
- connection.execute(sanitize_sql(["SELECT pg_advisory_xact_lock(#{connection.quote_table_name('half_md5_as_bigint')}(?))", strand]))
384
+ if on_conflict == :patient
385
+ pg_function = 'pg_try_advisory_xact_lock'
386
+ execute_method = :select_value
387
+ else
388
+ pg_function = 'pg_advisory_xact_lock'
389
+ execute_method = :execute
390
+ end
391
+ result = connection.send(execute_method, sanitize_sql(["SELECT #{pg_function}(#{connection.quote_table_name('half_md5_as_bigint')}(?))", strand]))
392
+ return if result == false && on_conflict == :patient
381
393
  yield
382
394
  end
383
395
  end
@@ -390,13 +402,13 @@ module Delayed
390
402
  strand = options[:strand]
391
403
  on_conflict = options.delete(:on_conflict) || :use_earliest
392
404
  transaction_for_singleton(strand, on_conflict) do
393
- job = self.where(:strand => strand, :locked_at => nil).order(:id).first
405
+ job = self.where(:strand => strand, :locked_at => nil).next_in_strand_order.first
394
406
  new_job = new(options)
395
407
  if job
396
408
  new_job.initialize_defaults
397
409
  job.run_at =
398
410
  case on_conflict
399
- when :use_earliest
411
+ when :use_earliest, :patient
400
412
  [job.run_at, new_job.run_at].min
401
413
  when :overwrite
402
414
  new_job.run_at