inst-jobs 0.15.21 → 1.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.
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 +7 -1
  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 +3 -1
  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 +29 -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: d8bab4958615bf7295c579533cfb1353e1581b8b4309ea18f266d738b8eb2a0f
4
- data.tar.gz: 0b1f7c4864954f603dc3f0f49d8d07ad8019dc86a4ddf51842fd6c26043c40da
3
+ metadata.gz: 6d65c567b52e2baf26955d266c9fededc001deab65d871e6e60a42a905d552e8
4
+ data.tar.gz: b4d487088aba583188c90650a42a20726f84230e346c321b4b94ddc5c25fe36e
5
5
  SHA512:
6
- metadata.gz: fb55981330a060dce72fa0b5f82028ed9e1a2f647228c957826b9fbad34a0243bdebf6ae69b844e7b589693cda06180033e2218916a20fad21366274b5c1fa89
7
- data.tar.gz: d0c1ee9ecf0b1371a4adafac9cff748a58afd53dfa0eb7986dec50858e2419c55a47811cb072599cb6dece55f2d17b067e00a88bae4df2bec607f168ef38927d
6
+ metadata.gz: 9ade674610c5a1a4a04e4a6078b45c38358e7c7af79a417d5e6941d968e1565bc5e217405ad1ca427b373ee0d89bdbc28772f8ed52047394e8760fc09a79b60a
7
+ data.tar.gz: 216706e128397e84ba87d495e4b09d30eb58f35ab4821e8ef6f484d5f6226a959cab04a8bfdfa22916f19dad53e7ee01bbe1128bd61fb402aad29885ac81839f
@@ -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