riverqueue 0.12.0 → 0.14.0

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.
data/lib/driver.rb CHANGED
@@ -50,4 +50,5 @@ end
50
50
 
51
51
  require_relative "driver/postgres_capabilities"
52
52
  require_relative "driver/job_row_decoder"
53
+ require_relative "driver/notification_listener"
53
54
  require_relative "driver/runtime"
data/lib/event.rb CHANGED
@@ -12,7 +12,14 @@ module River
12
12
  # Event emitted by a Client and delivered through a Subscription.
13
13
  Event = Data.define(:kind, :job, :queue, :stats)
14
14
 
15
- # Timing statistics attached to job lifecycle events.
15
+ # Timing statistics attached to job lifecycle events, in seconds.
16
+ # run_duration measures worker execution including work hooks;
17
+ # complete_duration measures finalization after execution finishes.
18
+ # queue_wait_duration uses the schedule at the start of execution, before
19
+ # retries or snoozes change it.
20
+ # Execution and completion durations use a monotonic clock. Externally
21
+ # executed jobs have zero run_duration because this runtime does not observe
22
+ # their execution; their queue wait ends at the row's attempted_at timestamp.
16
23
  JobStatistics = Data.define(:complete_duration, :queue_wait_duration, :run_duration)
17
24
 
18
25
  # Bounded stream of selected Client lifecycle events.
data/lib/insert_opts.rb CHANGED
@@ -7,6 +7,8 @@ module River
7
7
  class InsertOpts
8
8
  # The maximum number of total attempts (including both the original run and
9
9
  # all retries) before a job is abandoned and set as discarded.
10
+ # Must be between 1 and MAX_ATTEMPTS_LIMIT (32,767).
11
+ # Defaults to MAX_ATTEMPTS_DEFAULT.
10
12
  attr_accessor :max_attempts
11
13
 
12
14
  # Arbitrary metadata to merge into the persisted job. River and River Pro
@@ -23,6 +25,7 @@ module River
23
25
  attr_accessor :priority
24
26
 
25
27
  # The name of the job queue in which to insert the job, as a symbol or string.
28
+ # Must contain 1 to 127 ASCII letters, digits, underscores, hyphens, colons, or periods.
26
29
  #
27
30
  # Defaults to QUEUE_DEFAULT.
28
31
  attr_accessor :queue
@@ -38,7 +41,8 @@ module River
38
41
  attr_accessor :scheduled_at
39
42
 
40
43
  # Initial state. Primarily intended for extensions such as workflows and
41
- # sequences, which insert blocked jobs as `:pending`. Accepts symbols or strings.
44
+ # sequences, which insert blocked jobs as `:pending`. Accepts only
45
+ # `available`, `pending`, or `scheduled`, as symbols or strings.
42
46
  attr_accessor :state
43
47
 
44
48
  # An arbitrary list of keywords to add to the job. They have no functional
@@ -90,7 +94,7 @@ module River
90
94
  # be inserted as a new job.
91
95
  class UniqueOpts
92
96
  # Empty options don't enable uniqueness. Zero periods and empty state lists
93
- # have the same meaning as their unset equivalents in the Go client.
97
+ # have the same meaning as their unset equivalents.
94
98
  def empty?
95
99
  !by_args && !by_queue && !exclude_kind && (!by_period || by_period == 0) && (!by_state || by_state.empty?)
96
100
  end
@@ -109,7 +113,8 @@ module River
109
113
  # in UTC. Equivalent times in different zones produce the same unique key.
110
114
  #
111
115
  # The period should be specified in seconds. So a job that's unique every 15
112
- # minute period would have a value of 900.
116
+ # minute period would have a value of 900. Nonzero periods must be at least
117
+ # one second; zero disables period-based uniqueness.
113
118
  #
114
119
  # Default is no unique period, meaning that as long as any other unique
115
120
  # property is enabled, uniqueness will be enforced across all jobs of the
data/lib/job.rb CHANGED
@@ -59,7 +59,7 @@ module River
59
59
 
60
60
  # The set of worker IDs that have worked this job. A worker ID differs
61
61
  # between different programs, but is shared by all executors within any
62
- # given one. (i.e. Different Go processes have different IDs, but IDs are
62
+ # given one. (i.e. Different processes have different IDs, but IDs are
63
63
  # shared within any given process.) A process generates a new ID based on
64
64
  # host and current time when it starts up.
65
65
  attr_accessor :attempted_by
@@ -173,7 +173,7 @@ module River
173
173
  # that occurred.
174
174
  class AttemptError
175
175
  # The time at which the error occurred. Unreadable historical timestamps
176
- # become Time.utc(1), matching Go's zero time.
176
+ # become Time.utc(1), the zero time 0001-01-01T00:00:00Z.
177
177
  attr_accessor :at
178
178
 
179
179
  # The attempt number on which the error occurred (maps to #attempt on a job
@@ -184,8 +184,8 @@ module River
184
184
  # value in case of a panic.
185
185
  attr_accessor :error
186
186
 
187
- # Contains a stack trace from a job that panicked. The trace is produced by
188
- # invoking `debug.Trace()` in Go.
187
+ # Contains a stack trace from a job that raised or panicked. Ruby records
188
+ # the exception backtrace; other River clients record their own format.
189
189
  attr_accessor :trace
190
190
 
191
191
  def initialize(
@@ -202,7 +202,7 @@ module River
202
202
 
203
203
  # Returns the database-compatible representation of this attempt error.
204
204
  def to_h
205
- {at: at.utc.iso8601(6), attempt: attempt, error: error, trace: trace}
205
+ {at: at.getutc.iso8601(6), attempt: attempt, error: error, trace: trace}
206
206
  end
207
207
  end
208
208
  end
@@ -5,7 +5,8 @@ require "logger"
5
5
  module River
6
6
  module JobPersistedLogging
7
7
  # Captures job.logger output and persists it with the attempt's state change,
8
- # including failed attempts. Uses Go riverlog's metadata format for River UI.
8
+ # including failed attempts. Uses the shared `river:log` metadata format
9
+ # that River UI displays.
9
10
  # Register through Config#plugins, preferably before other work middleware.
10
11
  class Plugin
11
12
  # Limits are positive byte counts. History is capped at 64 MiB, dropping
@@ -84,7 +85,7 @@ module River
84
85
  @mutex.synchronize do
85
86
  @closed = true
86
87
  # Drop invalid/incomplete UTF-8 sequences (including a character cut
87
- # at the byte limit) and NULs, which PostgreSQL JSONB cannot represent.
88
+ # at the byte limit) and NULs, which Postgres JSONB cannot represent.
88
89
  [@data.dup.force_encoding(Encoding::UTF_8).scrub("").delete("\0"), @truncated]
89
90
  end
90
91
  end
data/lib/migrator.rb CHANGED
@@ -9,7 +9,7 @@ module River
9
9
 
10
10
  attr_reader :migrations
11
11
 
12
- # Creates a migrator for an installed River driver. PostgreSQL schemas must
12
+ # Creates a migrator for an installed River driver. Postgres schemas must
13
13
  # already exist and use simple SQL identifiers; SQLite uses its main schema.
14
14
  # The optional migrations_path is a root containing backend/line/*.sql.
15
15
  def initialize(driver, line: "main", migrations_path: File.expand_path("../migration", __dir__.to_s), schema: nil)
data/lib/periodic_cron.rb CHANGED
@@ -4,21 +4,57 @@ module River
4
4
  # A cron schedule for PeriodicJob. Add the optional +fugit+ gem to use it.
5
5
  class PeriodicCron
6
6
  # Parses a cron expression once. +timezone+ defaults to UTC; use an IANA
7
- # name such as America/New_York for local calendar schedules. Specify the
8
- # timezone here, not inside +expression+. Fugit is loaded only on construction.
7
+ # name or a fixed offset for local schedules. A CRON_TZ= or TZ= prefix takes
8
+ # precedence. Supports @every durations such as `1h30m` or `500ms`, rounded
9
+ # down to whole seconds with a one-second minimum. Fugit is loaded only on
10
+ # construction.
9
11
  def initialize(expression, timezone: "UTC")
10
12
  require "fugit"
11
13
 
12
14
  raise ArgumentError, "cron expression must be a String" unless expression.is_a?(String)
13
15
  raise ArgumentError, "timezone must be a nonempty name without whitespace" unless timezone.is_a?(String) && /\A\S+\z/.match?(timezone)
14
16
 
15
- @cron = Object.const_get(:Fugit).const_get(:Cron).do_parse("#{expression} #{timezone}")
17
+ expression = expression.strip
18
+ if (prefix = /\A(?:CRON_TZ|TZ)=(\S+)\s+(.+)\z/.match(expression))
19
+ timezone = prefix[1].to_s
20
+ expression = prefix[2].to_s
21
+ end
22
+ # Validate the zone even for interval schedules, which don't use it.
23
+ cron_class = Object.const_get(:Fugit).const_get(:Cron)
24
+ cron_class.do_parse("* * * * * #{timezone}")
25
+ @interval = nil
26
+ if expression.start_with?("@every ")
27
+ @interval = parse_interval(expression.delete_prefix("@every "))
28
+ @cron = nil
29
+ else
30
+ @cron = cron_class.do_parse("#{expression.tr("?", "*")} #{timezone}")
31
+ end
16
32
  end
17
33
 
18
- # Returns a UTC Time strictly after +time+. Calendar and daylight-saving
19
- # rules are provided by Fugit; this helper does not enqueue or persist jobs.
34
+ # Returns a UTC Time strictly after +time+. Interval schedules align to
35
+ # whole seconds. Calendar and daylight-saving rules use Fugit.
20
36
  def next(time)
21
- @cron.next_time(time).to_t.getutc
37
+ interval = @interval
38
+ return Time.at(time.to_i + interval).utc if interval
39
+
40
+ # Keep the reference in UTC so Fugit does not skip a repeated local wall
41
+ # time when daylight saving ends. The schedule owns the calendar zone.
42
+ @cron.next_time(time.getutc).to_t.getutc
43
+ end
44
+
45
+ private def parse_interval(duration)
46
+ # Go's time.ParseDuration syntax, deliberately excluding Fugit's extra
47
+ # units (days/months) and natural-language interval expressions.
48
+ sign = duration.start_with?("-") ? -1 : 1
49
+ duration = duration.sub(/\A[+-]/, "")
50
+ parts = duration.scan(/((?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+))(ns|us|µs|μs|ms|s|m|h)/) #: Array[[String, String]] # rubocop:disable Layout/LeadingCommentSpace
51
+ valid = duration == "0" || (!parts.empty? && parts.map(&:join).join == duration)
52
+ raise ArgumentError, "invalid @every duration" unless valid
53
+ units = {"ns" => 1, "us" => 1_000, "µs" => 1_000, "μs" => 1_000, "ms" => 1_000_000, "s" => 1_000_000_000, "m" => 60_000_000_000, "h" => 3_600_000_000_000}
54
+ nanos = parts.sum { |number, unit| (number.to_r * units.fetch(unit)).to_i } * sign
55
+ raise ArgumentError, "@every duration must fit in signed 64-bit nanoseconds" unless (-(1 << 63)...(1 << 63)).cover?(nanos)
56
+
57
+ [nanos.div(1_000_000_000), 1].max
22
58
  end
23
59
  end
24
60
  end
data/lib/periodic_job.rb CHANGED
@@ -56,7 +56,8 @@ module River
56
56
 
57
57
  # Thread-safe collection used to change a client's periodic jobs at runtime.
58
58
  class PeriodicJobBundle
59
- def initialize(jobs, wake:)
59
+ def initialize(jobs, wake:, enabled: true)
60
+ @enabled = enabled
60
61
  @jobs = {}
61
62
  @mutex = Mutex.new
62
63
  @next_handle = 0
@@ -72,8 +73,13 @@ module River
72
73
  # Adds periodic jobs atomically and returns their handles in input order.
73
74
  # Invalid schedules or duplicate IDs leave the registry unchanged.
74
75
  def add_many(jobs)
76
+ raise ArgumentError, "periodic jobs require leader election" if !@enabled && !jobs.empty?
77
+
75
78
  now = Time.now.utc
76
- entries = jobs.map { |job| {job: job, next_at: job.run_on_start ? now : job.next_at(now)} }
79
+ entries = jobs.map do |job|
80
+ run_on_start = job.run_on_start
81
+ {job: job, next_at: run_on_start ? now : job.next_at(now), run_on_start: run_on_start}
82
+ end
77
83
  handles = @mutex.synchronize do
78
84
  ids = @jobs.values.to_h { |entry| [entry[:job].id, true] }
79
85
  jobs.each do |job|
@@ -97,7 +103,9 @@ module River
97
103
  self
98
104
  end
99
105
 
100
- def due(now)
106
+ # Internal scheduler entry point that retains each claimed occurrence's
107
+ # time even after advancing its registration.
108
+ def __due_with_times(now)
101
109
  # Application schedules may reenter the registry, so evaluate outside its
102
110
  # lock and only advance registrations that haven't been removed or claimed.
103
111
  entries = @mutex.synchronize { @jobs.map { |handle, entry| [handle, entry, entry[:next_at]] } }
@@ -107,8 +115,10 @@ module River
107
115
  following = entry[:job].next_at(now)
108
116
  @mutex.synchronize do
109
117
  next unless @jobs[handle].equal?(entry) && entry[:next_at] == next_at
118
+ scheduled_at = entry[:run_on_start] ? now : next_at
119
+ entry[:run_on_start] = false
110
120
  entry[:next_at] = following
111
- entry[:job]
121
+ [entry[:job], scheduled_at]
112
122
  end
113
123
  rescue => error
114
124
  raise unless block_given?
@@ -117,6 +127,15 @@ module River
117
127
  end
118
128
  end
119
129
 
130
+ def due(now, &block)
131
+ __due_with_times(now, &block).map(&:first)
132
+ end
133
+
134
+ # Returns true when the bundle has no registered periodic jobs.
135
+ def empty?
136
+ @mutex.synchronize { @jobs.empty? }
137
+ end
138
+
120
139
  # Removes the job associated with +handle+, returning the PeriodicJob or
121
140
  # nil when no such handle exists.
122
141
  def remove(handle)
data/lib/resumable.rb CHANGED
@@ -11,6 +11,7 @@ module River
11
11
  attr_accessor :completed_step
12
12
  attr_reader :cursors
13
13
  attr_accessor :cursors_dirty
14
+ attr_reader :pending_cursor_steps
14
15
  attr_accessor :resume_matched
15
16
  attr_reader :resume_step
16
17
  attr_accessor :step_name
@@ -24,6 +25,8 @@ module River
24
25
  @step_name = nil
25
26
 
26
27
  @resume_matched = @resume_step.to_s.empty?
28
+ # Step markers may accompany stale cursors from older clients.
29
+ @pending_cursor_steps = @resume_matched ? @cursors.keys.sort : []
27
30
  end
28
31
 
29
32
  def register(name)
@@ -37,6 +40,9 @@ module River
37
40
  # Internal runtime boundary: attach progress only to attempts that did not
38
41
  # complete, matching River Go's persisted metadata format.
39
42
  def __capture_resumable_metadata!
43
+ # Keep missing cursor steps reachable when the worker restores them.
44
+ return unless @resumable_state.pending_cursor_steps.empty?
45
+
40
46
  if @resumable_state.cursors_dirty
41
47
  @metadata_updates[RESUMABLE_CURSOR_METADATA_KEY] = @resumable_state.cursors.empty? ? nil : @resumable_state.cursors.dup
42
48
  end
@@ -49,6 +55,9 @@ module River
49
55
  # Internal runtime boundary: validate that the recorded resume point still
50
56
  # exists in the worker after it has returned.
51
57
  def __finish_resumable_work!
58
+ if (pending = @resumable_state.pending_cursor_steps.first)
59
+ raise Error, "resumable cursor step #{pending.inspect} not found in worker"
60
+ end
52
61
  if @resumable_state.resume_step && !@resumable_state.resume_matched
53
62
  raise Error, "resumable step #{@resumable_state.resume_step.inspect} not found in worker"
54
63
  end
@@ -56,12 +65,22 @@ module River
56
65
 
57
66
  # Immediately checkpoints the current step and cursor progress. Wrap the
58
67
  # call in Driver#transaction alongside application writes when an atomic
59
- # checkpoint is needed.
68
+ # checkpoint is needed. When resuming cursor-only progress, reach its saved
69
+ # cursor step before checkpointing another step.
70
+ #
71
+ # Commit the transaction before the step block returns
72
+ # successfully, and let transaction and commit errors propagate out of it.
73
+ # A successful block marks the step complete even if an enclosing transaction
74
+ # later rolls back.
60
75
  def resumable_checkpoint(cursor: RESUMABLE_CURSOR_UNSET)
61
76
  step_name = @resumable_state.step_name
62
77
  raise Error, "resumable step can only be persisted inside a resumable step" unless step_name
63
78
  raise Error, "job must be running" unless row.state == JOB_STATE_RUNNING
64
79
 
80
+ if (pending = @resumable_state.pending_cursor_steps.first)
81
+ raise Error, "resumable checkpoint cannot precede saved cursor step #{pending.inspect}"
82
+ end
83
+
65
84
  cursors = @resumable_state.cursors.dup
66
85
  cursors[step_name] = JSON.parse(JSON.generate(cursor)) unless cursor.equal?(RESUMABLE_CURSOR_UNSET)
67
86
  updates = {
@@ -117,6 +136,9 @@ module River
117
136
  private def run_resumable_step(name, cursor:, default:)
118
137
  raise Error, "resumable steps cannot be nested" if @resumable_state.step_name
119
138
  raise ArgumentError, "resumable step name must be non-empty" if name.empty?
139
+ if !cursor && @resumable_state.pending_cursor_steps.include?(name)
140
+ raise Error, "resumable cursor step #{name.inspect} requires a cursor step"
141
+ end
120
142
  @resumable_state.register(name)
121
143
 
122
144
  unless @resumable_state.resume_matched
@@ -132,6 +154,7 @@ module River
132
154
  @resumable_state.step_name = name
133
155
  begin
134
156
  value = (cursor && @resumable_state.cursors.key?(name)) ? @resumable_state.cursors[name] : default
157
+ @resumable_state.pending_cursor_steps.delete(name) if cursor
135
158
  result = cursor ? yield(value) : yield
136
159
  @resumable_state.completed_step = name
137
160
  if cursor && @resumable_state.cursors.key?(name)
data/lib/worker.rb CHANGED
@@ -12,8 +12,18 @@ module River
12
12
  #
13
13
  # With one argument, the kind is read from +worker.kind+ or
14
14
  # +worker.class.kind+. Pass a kind and worker separately to override it.
15
- # Kinds and aliases accept symbols or strings.
16
- def add(kind_or_worker, worker = nil, aliases: [])
15
+ # Alternatively, pass a kind and a block that receives a River::Job on each
16
+ # attempt. Block workers use the client's retry and timeout policies.
17
+ # Supply a worker or a block, not both. Kinds and aliases accept symbols or
18
+ # strings.
19
+ def add(kind_or_worker, worker = nil, aliases: [], &block)
20
+ if block
21
+ raise ArgumentError, "use a worker or a block, not both" unless worker.nil?
22
+ raise ArgumentError, "block workers require a string or symbol kind" unless kind_or_worker.is_a?(String) || kind_or_worker.is_a?(Symbol)
23
+
24
+ worker = BlockWorker.new(block)
25
+ end
26
+
17
27
  if worker
18
28
  kind = kind_or_worker.to_s
19
29
  else
@@ -54,11 +64,29 @@ module River
54
64
  def kinds
55
65
  @workers.keys.freeze
56
66
  end
67
+
68
+ # Adapts a registered block to the runtime's worker interface.
69
+ class BlockWorker
70
+ def initialize(work_func)
71
+ @work_func = work_func
72
+ end
73
+
74
+ def work(job)
75
+ @work_func.call(job)
76
+ end
77
+ end
78
+ private_constant :BlockWorker
57
79
  end
58
80
 
59
81
  # A job being worked, with access to its persisted row and attempt-local
60
82
  # metadata changes.
61
83
  class Job
84
+ # Internal runtime hint; persisted state determines whether completion committed.
85
+ attr_accessor :__completion_attempted
86
+
87
+ # Internal snapshot retained only after transactional completion commits.
88
+ attr_accessor :__completion_snapshot
89
+
62
90
  # Client working this job.
63
91
  attr_reader :client
64
92
 
@@ -67,6 +95,8 @@ module River
67
95
 
68
96
  def initialize(client, row)
69
97
  @client = client
98
+ @__completion_attempted = false
99
+ @__completion_snapshot = nil
70
100
  @logger = nil
71
101
  @metadata_updates = {}
72
102
  @row = row
data/migration/README.md CHANGED
@@ -5,7 +5,9 @@ https://github.com/riverqueue/river, distributed under the upstream Mozilla
5
5
  Public License 2.0 (see LICENSE). The Ruby implementation remains licensed
6
6
  under the repository's MPL-2.0 license.
7
7
 
8
- The bundle matches River v0.48.0 through main migration 008.
9
- manifest.json records the source commit and SHA-256 of every SQL file.
8
+ The bundle includes main migrations through version 008.
9
+ manifest.json records the SHA-256 of every SQL file in the repository's
10
+ riverdriver/ directory.
11
+ Verification compares against this checkout, not a pinned upstream revision.
10
12
  Update or verify these files with scripts/sync_migrations.rb; do not edit SQL
11
13
  here independently of upstream. Private Pro migrations are not included here.
@@ -12,10 +12,10 @@
12
12
  "postgresql/main/005_migration_unique_client.up.sql": "b760f487152c7d92102869d46b8a64dc1e2094d5675e690ffbe52a747eee8431",
13
13
  "postgresql/main/006_bulk_unique.down.sql": "726483f6e5aa7dd02cdd974cd7bf716973a8d0a97ba6dbc5dc5304aaf54c7ad7",
14
14
  "postgresql/main/006_bulk_unique.up.sql": "3b133f7ce4662d3dc8bd4a57628e0e116a300b2635a79315557aa1369849f0fb",
15
- "postgresql/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.down.sql": "9131aae235187dbdaaa822dab2a475a884e917d9af05e3c98fb95c152eaa769a",
16
- "postgresql/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.up.sql": "47ec8031b88e69004de2def5bc3109d969f71ee4c33a1e7dac2fb8c9dd19182d",
17
- "postgresql/main/008_job_id_autoincrement.down.sql": "0c3750a947d6494db07d56f5d3735a5e49a2cbfa1f7a09771227c31c8147bf70",
18
- "postgresql/main/008_job_id_autoincrement.up.sql": "0c3750a947d6494db07d56f5d3735a5e49a2cbfa1f7a09771227c31c8147bf70",
15
+ "postgresql/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.down.sql": "2771765becc5baa0a8af4406cb4d58ea6d3fb0ebdbf72a0c23ccc72da623caeb",
16
+ "postgresql/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.up.sql": "af01339fcb1687de1f768a570b6ad1e51d23300082aed60440f6a662a0e39d85",
17
+ "postgresql/main/008_job_id_autoincrement.down.sql": "678b3b02deb2f798c3fda3a73c974fc1fe48c6b88688b94f0f7700132b30626f",
18
+ "postgresql/main/008_job_id_autoincrement.up.sql": "678b3b02deb2f798c3fda3a73c974fc1fe48c6b88688b94f0f7700132b30626f",
19
19
  "sqlite/main/001_create_river_migration.down.sql": "34c87dc594bf7520bc3ae69f6f0da8d2d9a472616ab38b37e63d4e3838da06d2",
20
20
  "sqlite/main/001_create_river_migration.up.sql": "d15597cb0bb884fb0727d2a29ad8313842708b55fd561a5fe62e37aad5f34298",
21
21
  "sqlite/main/002_initial_schema.down.sql": "900508ba08d0ca3c8451eb2854cd9ab837166ef55736393524253b6228438470",
@@ -31,8 +31,7 @@
31
31
  "sqlite/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.down.sql": "55bffeb528b40dffc0cbec2f22d1463aef37a8b3d7977f83729cc505bb77c745",
32
32
  "sqlite/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.up.sql": "441a05e1d9aa4f151877ccf725b0b0a86f27013442297d4b621c05676270d0b0",
33
33
  "sqlite/main/008_job_id_autoincrement.down.sql": "04871283fe5d4cab4ac70da28d8509aa7ba765994d528c2dcc7ebe0ee596710c",
34
- "sqlite/main/008_job_id_autoincrement.up.sql": "049c9bf615f24a326bcc31ddc87b46f76e3de11e3eea3b2b9e1358131b3f3bed"
34
+ "sqlite/main/008_job_id_autoincrement.up.sql": "3ce6d941f4c434adbd87e69089fa5e99b6047e5b51e611c9845e1d0d3c080e0f"
35
35
  },
36
- "repository": "river",
37
- "revision": "c2c380490d14df8efc28b86fbe7be1cf93930405"
36
+ "source": "riverdriver/"
38
37
  }
@@ -47,7 +47,7 @@ ALTER TABLE /* TEMPLATE: schema */river_queue
47
47
  --
48
48
  -- SQLite JSONB conversion rollback.
49
49
  --
50
- -- No-op. PostgreSQL already stores River JSON columns as jsonb.
50
+ -- No-op. Postgres already stores River JSON columns as jsonb.
51
51
 
52
52
  --
53
53
  -- Notification outbox rollback.
@@ -16,7 +16,7 @@ CREATE INDEX river_notification_topic_id_idx ON /* TEMPLATE: schema */river_noti
16
16
  --
17
17
  -- SQLite JSONB conversion.
18
18
  --
19
- -- No-op. PostgreSQL already stores River JSON columns as jsonb.
19
+ -- No-op. Postgres already stores River JSON columns as jsonb.
20
20
 
21
21
  --
22
22
  -- SQL cleanup.
@@ -1,3 +1,3 @@
1
- -- No-op. PostgreSQL sequences already prevent automatically generated job IDs
1
+ -- No-op. Postgres sequences already prevent automatically generated job IDs
2
2
  -- from being reused.
3
3
  SELECT 1;
@@ -1,3 +1,3 @@
1
- -- No-op. PostgreSQL sequences already prevent automatically generated job IDs
1
+ -- No-op. Postgres sequences already prevent automatically generated job IDs
2
2
  -- from being reused.
3
3
  SELECT 1;
@@ -1,5 +1,5 @@
1
1
  -- Rebuild river_job so automatically generated IDs are never reused after the
2
- -- job holding the largest ID is deleted. Unlike PostgreSQL sequences, SQLite's
2
+ -- job holding the largest ID is deleted. Unlike Postgres sequences, SQLite's
3
3
  -- default ROWID allocator may otherwise reuse that deleted ID.
4
4
 
5
5
  -- Rebuilding river_job would discard schema installed by River Pro. Check
data/sig/client.rbs CHANGED
@@ -1,5 +1,6 @@
1
1
  module River
2
2
  MAX_ATTEMPTS_DEFAULT: Integer
3
+ MAX_ATTEMPTS_LIMIT: Integer
3
4
  PRIORITY_DEFAULT: Integer
4
5
  QUEUE_DEFAULT: String
5
6
 
@@ -14,15 +15,17 @@ module River
14
15
 
15
16
  def initialize: (_Driver driver, ?config: Config?) -> void
16
17
  def __finish_claimed_job: (JobRow, ?Exception?) -> untyped
18
+ def __insert_with_default_scheduled_at: (jobArgs, InsertOpts, Time?) -> JobInsertResult
17
19
  def __perform_job: (Integer, ?allow_scheduled: bool) -> untyped
18
20
  def __interrupt_workers: () -> untyped
19
21
  def __runtime_healthy?: () -> bool
20
22
  def id: () -> String
21
23
  def insert: (jobArgs, ?insert_opts: InsertOpts?, ?max_attempts: Integer?,
22
24
  ?metadata: Hash[String | Symbol, untyped]?, ?priority: Integer?, ?queue: (String | Symbol)?,
23
- ?scheduled_at: Time?, ?state: jobStateInput?, ?tags: Array[String]?, ?unique_opts: UniqueOpts?) -> JobInsertResult
25
+ ?scheduled_at: Time?, ?state: jobStateInitialInput?, ?tags: Array[String]?, ?unique_opts: UniqueOpts?) -> JobInsertResult
24
26
  def insert_many: (Array[jobArgs | InsertManyParams]) -> Array[JobInsertResult]
25
27
  def job_cancel: (Integer) -> JobRow
28
+ def job_complete_tx: (Job) -> JobRow
26
29
  def job_delete: (Integer) -> JobRow
27
30
  def job_delete_many: (?JobListParams?, ?after: untyped, ?after_id: untyped,
28
31
  ?ids: untyped, ?kinds: untyped, ?limit: untyped, ?metadata: untyped, ?priorities: untyped,
@@ -45,6 +48,7 @@ module River
45
48
  def queue_remove: (String | Symbol) -> self
46
49
  def queue_resume: (String | Symbol) -> bool
47
50
  def queue_update: (String | Symbol, metadata: Hash[untyped, untyped]) -> Queue
51
+ def request_resign: () -> nil
48
52
  def start: () -> self
49
53
  def started?: () -> bool
50
54
  def stop: (?wait: bool) -> self
@@ -54,15 +58,17 @@ module River
54
58
 
55
59
  DEFAULT_UNIQUE_STATES: Array[jobStateAll]
56
60
  EMPTY_INSERT_OPTS: InsertOpts
61
+ INITIAL_STATES: Array[jobStateInitial]
57
62
  REQUIRED_UNIQUE_STATES: Array[jobStateAll]
58
63
  TAG_RE: Regexp
59
64
 
60
65
  private def insert_and_check_unique_job: (Driver::JobInsertParams) -> JobInsertResult
61
- private def make_insert_params: (jobArgs, InsertOpts) -> Driver::JobInsertParams
66
+ private def make_insert_params: (jobArgs, InsertOpts, ?Time?) -> Driver::JobInsertParams
62
67
  private def make_unique_key_and_bitmask: (Driver::JobInsertParams, UniqueOpts) -> [String, String]
63
68
  private def run_insert_plugins: (Array[Driver::JobInsertParams]) { () -> Array[JobInsertResult] } -> Array[JobInsertResult]
64
69
  private def truncate_time: (Time, Integer | Rational) -> Time
65
70
  private def uint64_to_int64: (Integer) -> Integer
71
+ private def validate_insert_state: (String | Symbol) -> jobStateInitial
66
72
  private def validate_tags: (Array[String]) -> Array[String]
67
73
  private def validate_unique_states: (Array[jobStateInput]) -> Array[jobStateAll]
68
74
  end
@@ -76,7 +82,7 @@ module River
76
82
 
77
83
  def initialize: (jobArgs job, ?insert_opts: InsertOpts?, ?max_attempts: Integer?,
78
84
  ?metadata: Hash[String | Symbol, untyped]?, ?priority: Integer?, ?queue: (String | Symbol)?,
79
- ?scheduled_at: Time?, ?state: jobStateInput?, ?tags: Array[String]?, ?unique_opts: UniqueOpts?) -> void
85
+ ?scheduled_at: Time?, ?state: jobStateInitialInput?, ?tags: Array[String]?, ?unique_opts: UniqueOpts?) -> void
80
86
  def is_a?: (Class) -> bool
81
87
  end
82
88