que 0.11.3 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/tests.yml +51 -0
  3. data/.gitignore +2 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +502 -97
  6. data/Dockerfile +20 -0
  7. data/LICENSE.txt +1 -1
  8. data/README.md +205 -59
  9. data/auto/dev +21 -0
  10. data/auto/pre-push-hook +30 -0
  11. data/auto/psql +9 -0
  12. data/auto/test +5 -0
  13. data/auto/test-postgres-14 +17 -0
  14. data/bin/que +8 -81
  15. data/docker-compose.yml +47 -0
  16. data/docs/README.md +881 -0
  17. data/lib/que/active_job/extensions.rb +114 -0
  18. data/lib/que/active_record/connection.rb +51 -0
  19. data/lib/que/active_record/model.rb +48 -0
  20. data/lib/que/command_line_interface.rb +259 -0
  21. data/lib/que/connection.rb +198 -0
  22. data/lib/que/connection_pool.rb +78 -0
  23. data/lib/que/job.rb +210 -103
  24. data/lib/que/job_buffer.rb +255 -0
  25. data/lib/que/job_methods.rb +176 -0
  26. data/lib/que/listener.rb +176 -0
  27. data/lib/que/locker.rb +507 -0
  28. data/lib/que/metajob.rb +47 -0
  29. data/lib/que/migrations/4/down.sql +48 -0
  30. data/lib/que/migrations/4/up.sql +267 -0
  31. data/lib/que/migrations/5/down.sql +73 -0
  32. data/lib/que/migrations/5/up.sql +76 -0
  33. data/lib/que/migrations/6/down.sql +8 -0
  34. data/lib/que/migrations/6/up.sql +8 -0
  35. data/lib/que/migrations/7/down.sql +5 -0
  36. data/lib/que/migrations/7/up.sql +13 -0
  37. data/lib/que/migrations.rb +37 -18
  38. data/lib/que/poller.rb +274 -0
  39. data/lib/que/rails/railtie.rb +12 -0
  40. data/lib/que/result_queue.rb +35 -0
  41. data/lib/que/sequel/model.rb +52 -0
  42. data/lib/que/utils/assertions.rb +62 -0
  43. data/lib/que/utils/constantization.rb +19 -0
  44. data/lib/que/utils/error_notification.rb +68 -0
  45. data/lib/que/utils/freeze.rb +20 -0
  46. data/lib/que/utils/introspection.rb +50 -0
  47. data/lib/que/utils/json_serialization.rb +21 -0
  48. data/lib/que/utils/logging.rb +79 -0
  49. data/lib/que/utils/middleware.rb +46 -0
  50. data/lib/que/utils/queue_management.rb +18 -0
  51. data/lib/que/utils/ruby2_keywords.rb +19 -0
  52. data/lib/que/utils/transactions.rb +34 -0
  53. data/lib/que/version.rb +5 -1
  54. data/lib/que/worker.rb +145 -149
  55. data/lib/que.rb +103 -159
  56. data/que.gemspec +17 -4
  57. data/scripts/docker-entrypoint +14 -0
  58. data/scripts/test +6 -0
  59. metadata +59 -95
  60. data/.rspec +0 -2
  61. data/.travis.yml +0 -17
  62. data/Gemfile +0 -24
  63. data/docs/advanced_setup.md +0 -106
  64. data/docs/customizing_que.md +0 -200
  65. data/docs/error_handling.md +0 -47
  66. data/docs/inspecting_the_queue.md +0 -114
  67. data/docs/logging.md +0 -50
  68. data/docs/managing_workers.md +0 -80
  69. data/docs/migrating.md +0 -30
  70. data/docs/multiple_queues.md +0 -27
  71. data/docs/shutting_down_safely.md +0 -7
  72. data/docs/using_plain_connections.md +0 -41
  73. data/docs/using_sequel.md +0 -31
  74. data/docs/writing_reliable_jobs.md +0 -117
  75. data/lib/generators/que/install_generator.rb +0 -24
  76. data/lib/generators/que/templates/add_que.rb +0 -13
  77. data/lib/que/adapters/active_record.rb +0 -54
  78. data/lib/que/adapters/base.rb +0 -127
  79. data/lib/que/adapters/connection_pool.rb +0 -16
  80. data/lib/que/adapters/pg.rb +0 -21
  81. data/lib/que/adapters/pond.rb +0 -16
  82. data/lib/que/adapters/sequel.rb +0 -20
  83. data/lib/que/railtie.rb +0 -16
  84. data/lib/que/rake_tasks.rb +0 -59
  85. data/lib/que/sql.rb +0 -152
  86. data/spec/adapters/active_record_spec.rb +0 -152
  87. data/spec/adapters/connection_pool_spec.rb +0 -22
  88. data/spec/adapters/pg_spec.rb +0 -41
  89. data/spec/adapters/pond_spec.rb +0 -22
  90. data/spec/adapters/sequel_spec.rb +0 -57
  91. data/spec/gemfiles/Gemfile1 +0 -18
  92. data/spec/gemfiles/Gemfile2 +0 -18
  93. data/spec/spec_helper.rb +0 -118
  94. data/spec/support/helpers.rb +0 -19
  95. data/spec/support/jobs.rb +0 -35
  96. data/spec/support/shared_examples/adapter.rb +0 -37
  97. data/spec/support/shared_examples/multi_threaded_adapter.rb +0 -46
  98. data/spec/travis.rb +0 -23
  99. data/spec/unit/connection_spec.rb +0 -14
  100. data/spec/unit/customization_spec.rb +0 -251
  101. data/spec/unit/enqueue_spec.rb +0 -245
  102. data/spec/unit/helper_spec.rb +0 -12
  103. data/spec/unit/logging_spec.rb +0 -101
  104. data/spec/unit/migrations_spec.rb +0 -84
  105. data/spec/unit/pool_spec.rb +0 -365
  106. data/spec/unit/run_spec.rb +0 -14
  107. data/spec/unit/states_spec.rb +0 -50
  108. data/spec/unit/stats_spec.rb +0 -46
  109. data/spec/unit/transaction_spec.rb +0 -36
  110. data/spec/unit/work_spec.rb +0 -407
  111. data/spec/unit/worker_spec.rb +0 -167
  112. data/tasks/benchmark.rb +0 -3
  113. data/tasks/rspec.rb +0 -14
  114. data/tasks/safe_shutdown.rb +0 -67
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Que
4
+ SQL[:finish_job] =
5
+ %{
6
+ UPDATE public.que_jobs
7
+ SET finished_at = now()
8
+ WHERE id = $1::bigint
9
+ }
10
+
11
+ SQL[:expire_job] =
12
+ %{
13
+ UPDATE public.que_jobs
14
+ SET error_count = error_count + 1,
15
+ expired_at = now()
16
+ WHERE id = $1::bigint
17
+ }
18
+
19
+ SQL[:destroy_job] =
20
+ %{
21
+ DELETE FROM public.que_jobs
22
+ WHERE id = $1::bigint
23
+ }
24
+
25
+ SQL[:set_error] =
26
+ %{
27
+ UPDATE public.que_jobs
28
+ SET error_count = error_count + 1,
29
+ run_at = now() + $1::float * '1 second'::interval,
30
+ last_error_message = left($2::text, 500),
31
+ last_error_backtrace = left($3::text, 10000)
32
+ WHERE id = $4::bigint
33
+ }
34
+
35
+ module JobMethods
36
+ # Note that we delegate almost all methods to the result of the que_target
37
+ # method, which could be one of a few things, depending on the circumstance.
38
+
39
+ # Run the job with error handling and cleanup logic. Optionally support
40
+ # overriding the args, because it's necessary when jobs are invoked from
41
+ # ActiveJob.
42
+ def _run(args: nil, kwargs: nil, reraise_errors: false)
43
+ if args.nil? && que_target
44
+ args = que_target.que_attrs.fetch(:args)
45
+ end
46
+
47
+ if kwargs.nil? && que_target
48
+ kwargs = que_target.que_attrs.fetch(:kwargs)
49
+ end
50
+
51
+ run(*args, **kwargs)
52
+ default_resolve_action if que_target && !que_target.que_resolved
53
+ rescue => error
54
+ raise error unless que_target
55
+
56
+ que_target.que_error = error
57
+
58
+ run_error_notifier =
59
+ begin
60
+ handle_error(error)
61
+ rescue => error_2
62
+ Que.notify_error(error_2, que_target.que_attrs)
63
+ true
64
+ end
65
+
66
+ Que.notify_error(error, que_target.que_attrs) if run_error_notifier
67
+ retry_in_default_interval unless que_target.que_resolved
68
+
69
+ raise error if reraise_errors
70
+ end
71
+
72
+ def log_level(elapsed)
73
+ :debug
74
+ end
75
+
76
+ private
77
+
78
+ # This method defines the object on which the various job helper methods are
79
+ # acting. When using Que in the default configuration this will just be
80
+ # self, but when using the Que adapter for ActiveJob it'll be the actual
81
+ # underlying job object. When running an ActiveJob::Base subclass that
82
+ # includes this module through a separate adapter this will be nil - hence,
83
+ # the defensive coding in every method that no-ops if que_target is falsy.
84
+ def que_target
85
+ raise NotImplementedError
86
+ end
87
+
88
+ def resolve_que_setting(*args)
89
+ return unless que_target
90
+
91
+ que_target.class.resolve_que_setting(*args)
92
+ end
93
+
94
+ def default_resolve_action
95
+ return unless que_target
96
+
97
+ destroy
98
+ end
99
+
100
+ def expire
101
+ return unless que_target
102
+
103
+ if id = que_target.que_attrs[:id]
104
+ Que.execute :expire_job, [id]
105
+ end
106
+
107
+ que_target.que_resolved = true
108
+ end
109
+
110
+ def finish
111
+ return unless que_target
112
+
113
+ if id = que_target.que_attrs[:id]
114
+ Que.execute :finish_job, [id]
115
+ end
116
+
117
+ que_target.que_resolved = true
118
+ end
119
+
120
+ def error_count
121
+ return 0 unless que_target
122
+
123
+ count = que_target.que_attrs.fetch(:error_count)
124
+ que_target.que_error ? count + 1 : count
125
+ end
126
+
127
+ # To be overridden in subclasses.
128
+ def handle_error(error)
129
+ return unless que_target
130
+
131
+ max = resolve_que_setting(:maximum_retry_count)
132
+
133
+ if max && error_count > max
134
+ expire
135
+ else
136
+ retry_in_default_interval
137
+ end
138
+ end
139
+
140
+ def retry_in_default_interval
141
+ return unless que_target
142
+
143
+ retry_in(resolve_que_setting(:retry_interval, error_count))
144
+ end
145
+
146
+ # Explicitly check for the job id in these helpers, because it won't exist
147
+ # if we're running synchronously.
148
+ def retry_in(period)
149
+ return unless que_target
150
+
151
+ if id = que_target.que_attrs[:id]
152
+ values = [period]
153
+
154
+ if e = que_target.que_error
155
+ values << "#{e.class}: #{e.message}".slice(0, 500) << e.backtrace.join("\n").slice(0, 10000)
156
+ else
157
+ values << nil << nil
158
+ end
159
+
160
+ Que.execute :set_error, values << id
161
+ end
162
+
163
+ que_target.que_resolved = true
164
+ end
165
+
166
+ def destroy
167
+ return unless que_target
168
+
169
+ if id = que_target.que_attrs[:id]
170
+ Que.execute :destroy_job, [id]
171
+ end
172
+
173
+ que_target.que_resolved = true
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Que
4
+ class Listener
5
+ MESSAGE_FORMATS = {}
6
+
7
+ attr_reader :connection, :channel
8
+
9
+ def initialize(connection:, channel: nil)
10
+ @connection = connection
11
+ @channel = channel || "que_listener_#{connection.backend_pid}"
12
+
13
+ Que.internal_log :listener_instantiate, self do
14
+ {
15
+ backend_pid: connection.backend_pid,
16
+ }
17
+ end
18
+ end
19
+
20
+ def listen
21
+ connection.execute "LISTEN #{channel}"
22
+ end
23
+
24
+ def wait_for_grouped_messages(timeout)
25
+ messages = wait_for_messages(timeout)
26
+
27
+ output = {}
28
+
29
+ messages.each do |message|
30
+ message_type = message.delete(:message_type)
31
+
32
+ (output[message_type.to_sym] ||= []) << message.freeze
33
+ end
34
+
35
+ output
36
+ end
37
+
38
+ def wait_for_messages(timeout)
39
+ # Make sure we never pass nil to this method, so we don't hang the thread.
40
+ Que.assert(Numeric, timeout)
41
+
42
+ Que.internal_log :listener_waiting, self do
43
+ {
44
+ backend_pid: connection.backend_pid,
45
+ channel: channel,
46
+ timeout: timeout,
47
+ }
48
+ end
49
+
50
+ accumulated_messages = []
51
+
52
+ # Notifications often come in batches (especially when a transaction that
53
+ # inserted many jobs commits), so we want to loop and pick up all the
54
+ # received notifications before continuing.
55
+ loop do
56
+ notification_received =
57
+ connection.wait_for_notify(timeout) do |channel, pid, payload|
58
+ # We've received a notification, so zero out the timeout before we
59
+ # loop again to check for another message. This ensures that we
60
+ # don't wait an additional `timeout` seconds after processing the
61
+ # final message before this method returns.
62
+ timeout = 0
63
+
64
+ Que.internal_log(:listener_received_notification, self) do
65
+ {
66
+ channel: channel,
67
+ backend_pid: connection.backend_pid,
68
+ source_pid: pid,
69
+ payload: payload,
70
+ }
71
+ end
72
+
73
+ # Be very defensive about the message we receive - it may not be
74
+ # valid JSON or have the structure we expect.
75
+ next unless message = parse_payload(payload)
76
+
77
+ case message
78
+ when Array then accumulated_messages.concat(message)
79
+ when Hash then accumulated_messages << message
80
+ else raise Error, "Unexpected parse_payload output: #{message.class}"
81
+ end
82
+ end
83
+
84
+ break unless notification_received
85
+ end
86
+
87
+ return accumulated_messages if accumulated_messages.empty?
88
+
89
+ Que.internal_log(:listener_received_messages, self) do
90
+ {
91
+ backend_pid: connection.backend_pid,
92
+ channel: channel,
93
+ messages: accumulated_messages,
94
+ }
95
+ end
96
+
97
+ accumulated_messages.keep_if do |message|
98
+ next unless message.is_a?(Hash)
99
+ next unless type = message[:message_type]
100
+ next unless type.is_a?(String)
101
+ next unless format = MESSAGE_FORMATS[type.to_sym]
102
+
103
+ if message_matches_format?(message, format)
104
+ true
105
+ else
106
+ error_message = [
107
+ "Message of type '#{type}' doesn't match format!",
108
+ # Massage message and format a bit to make these errors more readable.
109
+ "Message: #{Hash[message.reject{|k,v| k == :message_type}.sort_by{|k,v| k}].inspect}",
110
+ "Format: #{Hash[format.sort_by{|k,v| k}].inspect}",
111
+ ].join("\n")
112
+
113
+ Que.notify_error_async(Error.new(error_message))
114
+ false
115
+ end
116
+ end
117
+
118
+ Que.internal_log(:listener_filtered_messages, self) do
119
+ {
120
+ backend_pid: connection.backend_pid,
121
+ channel: channel,
122
+ messages: accumulated_messages,
123
+ }
124
+ end
125
+
126
+ accumulated_messages
127
+ end
128
+
129
+ def unlisten
130
+ # Be sure to drain all notifications so that any code that uses this
131
+ # connection later doesn't receive any nasty surprises.
132
+ connection.execute "UNLISTEN *"
133
+ connection.drain_notifications
134
+
135
+ Que.internal_log :listener_unlisten, self do
136
+ {
137
+ backend_pid: connection.backend_pid,
138
+ channel: channel,
139
+ }
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def parse_payload(payload)
146
+ Que.deserialize_json(payload)
147
+ rescue JSON::ParserError => e
148
+ Que.notify_error_async(e)
149
+ nil
150
+ end
151
+
152
+ def message_matches_format?(message, format)
153
+ message_has_all_keys?(message, format) &&
154
+ message_has_no_excess_keys?(message, format) &&
155
+ message_keys_all_valid?(message, format)
156
+ end
157
+
158
+ def message_has_all_keys?(message, format)
159
+ format.all? { |k,v| message.has_key?(k) }
160
+ end
161
+
162
+ def message_has_no_excess_keys?(message, format)
163
+ message.all? { |k,v| format.has_key?(k) || k == :message_type }
164
+ end
165
+
166
+ def message_keys_all_valid?(message, format)
167
+ message.all? do |key, value|
168
+ if type = format[key]
169
+ Que.assert?(type, value)
170
+ else
171
+ true
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end