inst-jobs 0.15.22 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) 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 +9 -5
  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/core_ext/kernel.rb +9 -0
  35. data/lib/delayed/daemon.rb +2 -0
  36. data/lib/delayed/engine.rb +2 -0
  37. data/lib/delayed/job_tracking.rb +2 -0
  38. data/lib/delayed/lifecycle.rb +2 -0
  39. data/lib/delayed/log_tailer.rb +2 -0
  40. data/lib/delayed/logging.rb +2 -0
  41. data/lib/delayed/message_sending.rb +90 -106
  42. data/lib/delayed/performable_method.rb +34 -6
  43. data/lib/delayed/periodic.rb +6 -4
  44. data/lib/delayed/plugin.rb +2 -0
  45. data/lib/delayed/pool.rb +2 -0
  46. data/lib/delayed/server.rb +2 -0
  47. data/lib/delayed/server/helpers.rb +2 -0
  48. data/lib/delayed/settings.rb +2 -0
  49. data/lib/delayed/testing.rb +2 -0
  50. data/lib/delayed/version.rb +3 -1
  51. data/lib/delayed/work_queue/in_process.rb +2 -0
  52. data/lib/delayed/work_queue/parent_process.rb +2 -0
  53. data/lib/delayed/work_queue/parent_process/client.rb +2 -0
  54. data/lib/delayed/work_queue/parent_process/server.rb +2 -0
  55. data/lib/delayed/worker.rb +2 -0
  56. data/lib/delayed/worker/consul_health_check.rb +3 -1
  57. data/lib/delayed/worker/health_check.rb +2 -0
  58. data/lib/delayed/worker/null_health_check.rb +2 -0
  59. data/lib/delayed/worker/process_helper.rb +2 -0
  60. data/lib/delayed/yaml_extensions.rb +2 -0
  61. data/lib/delayed_job.rb +4 -0
  62. data/lib/inst-jobs.rb +2 -0
  63. data/spec/active_record_job_spec.rb +4 -6
  64. data/spec/delayed/cli_spec.rb +2 -0
  65. data/spec/delayed/daemon_spec.rb +2 -0
  66. data/spec/delayed/message_sending_spec.rb +101 -0
  67. data/spec/delayed/server_spec.rb +2 -4
  68. data/spec/delayed/settings_spec.rb +2 -0
  69. data/spec/delayed/work_queue/in_process_spec.rb +2 -4
  70. data/spec/delayed/work_queue/parent_process/client_spec.rb +2 -4
  71. data/spec/delayed/work_queue/parent_process/server_spec.rb +2 -1
  72. data/spec/delayed/work_queue/parent_process_spec.rb +2 -1
  73. data/spec/delayed/worker/consul_health_check_spec.rb +3 -1
  74. data/spec/delayed/worker/health_check_spec.rb +2 -0
  75. data/spec/delayed/worker_spec.rb +2 -0
  76. data/spec/gemfiles/42.gemfile.lock +192 -0
  77. data/spec/gemfiles/50.gemfile.lock +197 -0
  78. data/spec/gemfiles/51.gemfile.lock +198 -0
  79. data/spec/gemfiles/52.gemfile.lock +206 -0
  80. data/spec/gemfiles/60.gemfile.lock +224 -0
  81. data/spec/migrate/20140924140513_add_story_table.rb +2 -0
  82. data/spec/redis_job_spec.rb +10 -12
  83. data/spec/sample_jobs.rb +2 -0
  84. data/spec/shared/delayed_batch.rb +17 -15
  85. data/spec/shared/delayed_method.rb +49 -204
  86. data/spec/shared/performable_method.rb +11 -9
  87. data/spec/shared/shared_backend.rb +27 -25
  88. data/spec/shared/testing.rb +7 -5
  89. data/spec/shared/worker.rb +15 -13
  90. data/spec/shared_jobs_specs.rb +2 -0
  91. data/spec/spec_helper.rb +12 -1
  92. metadata +36 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33b7f135ba67d0b2b056851cec2e95672ac03766fa48d614ef53fa0e25a04255
4
- data.tar.gz: 29878f6688d376811085f7615c701ae2f7cf31cc3bf6462440823f4e91ccb271
3
+ metadata.gz: 78a0627c587b4c10c24df960b67b4a6427fe5b9c8e9561f0464864203a43c5e9
4
+ data.tar.gz: 0deba8f386d28954fe5b196aa86551d3bc50d6b98d261ffc3468406645a464d5
5
5
  SHA512:
6
- metadata.gz: 8695ff4bd7e9c10a5b1a393adc56d0be6cb1b45c668a8ba413bf91a59a8b1eb76633f0f8e3429ff15c6a0f03a9404e3e4f0d3bacd6f2cbd0ef3984ac5b0e0ad7
7
- data.tar.gz: 63f911fce5ddf75e7565095c134ffa0a3808fc5bf2b8e86f62466092b32a127afa449cbb44007774c771838fd30222a957246cbb44e9b8b125b01c2a7e8f2fe0
6
+ metadata.gz: 86e29718ae2772221fe62c2ce6e482cbecdb18cefe320e8f0cf408f6bb6826e1b90ea78c704e72818016e77c4f6b8537620043df2f00532d69c2ba505a2c52c7
7
+ data.tar.gz: 30d839adc80bdcb4043644d162a7a368080a0eaf3bc6fb82c26b994e61e0580991a6b2622188fa8dd6c07cdf78b3198deadb8cc22533743400fecd38dcbc392f
@@ -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
@@ -398,7 +402,7 @@ module Delayed
398
402
  strand = options[:strand]
399
403
  on_conflict = options.delete(:on_conflict) || :use_earliest
400
404
  transaction_for_singleton(strand, on_conflict) do
401
- 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
402
406
  new_job = new(options)
403
407
  if job
404
408
  new_job.initialize_defaults