| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7e36d7b commit bbbbfc0
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 7 | 7 | ||
| 8 | 8 | ## [Unreleased] | |
| 9 | 9 | ||
| 10 | + ### Changed | ||
| 11 | + | ||
| 12 | + - Tag format is now checked on insert. Tags should be no more than 255 characters and match the regex `/\A[\w][\w\-]+[\w]\z/`. [PR #22](https://github.com/riverqueue/riverqueue-ruby/pull/22). | ||
| 13 | + - Returned jobs now have a `metadata` property. [PR #21](https://github.com/riverqueue/riverqueue-ruby/pull/22). | ||
| 14 | + | ||
| 10 | 15 | ## [0.4.0] - 2024-04-28 | |
| 11 | 16 | ||
| 12 | 17 | ### Changed | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -86,7 +86,7 @@ insert_res.unique_skipped_as_duplicated | |||
| 86 | 86 | ||
| 87 | 87 | ### Custom advisory lock prefix | |
| 88 | 88 | ||
| 89 | - Unique job insertion takes a Postgres advisory lock to make sure that it's uniqueness check still works even if two conflicting insert operations are occurring in parallel. Postgres advisory locks share a global 64-bit namespace, which is a large enough space that it's unlikely for two advisory locks to ever conflict, but to _guarantee_ that River's advisory locks never interfere with an application's, River can be configured with a 32-bit advisory lock prefix which it will use for all its locks: | ||
| 89 | + Unique job insertion takes a Postgres advisory lock to make sure that its uniqueness check still works even if two conflicting insert operations are occurring in parallel. Postgres advisory locks share a global 64-bit namespace, which is a large enough space that it's unlikely for two advisory locks to ever conflict, but to _guarantee_ that River's advisory locks never interfere with an application's, River can be configured with a 32-bit advisory lock prefix which it will use for all its locks: | ||
| 90 | 90 | ||
| 91 | 91 | ```ruby | |
| 92 | 92 | client = River::Client.new(mock_driver, advisory_lock_prefix: 123456) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -100,6 +100,7 @@ def transaction(&) | |||
| 100 | 100 | finalized_at: river_job.finalized_at, | |
| 101 | 101 | kind: river_job.kind, | |
| 102 | 102 | max_attempts: river_job.max_attempts, | |
| 103 | + metadata: river_job.metadata, | ||
| 103 | 104 | priority: river_job.priority, | |
| 104 | 105 | queue: river_job.queue, | |
| 105 | 106 | scheduled_at: river_job.scheduled_at, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -86,6 +86,7 @@ def transaction(&) | |||
| 86 | 86 | finalized_at: river_job.finalized_at, | |
| 87 | 87 | kind: river_job.kind, | |
| 88 | 88 | max_attempts: river_job.max_attempts, | |
| 89 | + metadata: river_job.metadata, | ||
| 89 | 90 | priority: river_job.priority, | |
| 90 | 91 | queue: river_job.queue, | |
| 91 | 92 | scheduled_at: river_job.scheduled_at, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,8 +2,13 @@ | |||
| 2 | 2 | require "time" | |
| 3 | 3 | ||
| 4 | 4 | module River | |
| 5 | + # Default number of maximum attempts for a job. | ||
| 5 | 6 | MAX_ATTEMPTS_DEFAULT = 25 | |
| 7 | + | ||
| 8 | + # Default priority for a job. | ||
| 6 | 9 | PRIORITY_DEFAULT = 1 | |
| 10 | + | ||
| 11 | + # Default queue for a job. | ||
| 7 | 12 | QUEUE_DEFAULT = "default" | |
| 8 | 13 | ||
| 9 | 14 | # Provides a client for River that inserts jobs. Unlike the Go version of the | |
@@ -241,7 +246,7 @@ def insert_many(args) | |||
| 241 | 246 | queue: insert_opts.queue || args_insert_opts.queue || QUEUE_DEFAULT, | |
| 242 | 247 | scheduled_at: scheduled_at&.utc, # database defaults to now | |
| 243 | 248 | state: scheduled_at ? JOB_STATE_SCHEDULED : JOB_STATE_AVAILABLE, | |
| 244 | - tags: insert_opts.tags || args_insert_opts.tags | ||
| 249 | + tags: validate_tags(insert_opts.tags || args_insert_opts.tags) | ||
| 245 | 250 | ), | |
| 246 | 251 | unique_opts | |
| 247 | 252 | ] | |
@@ -260,6 +265,16 @@ def insert_many(args) | |||
| 260 | 265 | private def uint64_to_int64(int) | |
| 261 | 266 | [int].pack("Q").unpack1("q") #: Integer # rubocop:disable Layout/LeadingCommentSpace | |
| 262 | 267 | end | |
| 268 | + | ||
| 269 | + TAG_RE = /\A[\w][\w\-]+[\w]\z/ | ||
| 270 | + private_constant :TAG_RE | ||
| 271 | + | ||
| 272 | + private def validate_tags(tags) | ||
| 273 | + tags&.each do |tag| | ||
| 274 | + raise ArgumentError, "tags should be 255 characters or less" if tag.length > 255 | ||
| 275 | + raise ArgumentError, "tag should match regex #{TAG_RE.inspect}" unless TAG_RE.match(tag) | ||
| 276 | + end | ||
| 277 | + end | ||
| 263 | 278 | end | |
| 264 | 279 | ||
| 265 | 280 | # A single job to insert that's part of an #insert_many batch insert. Unlike | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -55,15 +55,15 @@ class JobRow | |||
| 55 | 55 | # The set of worker IDs that have worked this job. A worker ID differs | |
| 56 | 56 | # between different programs, but is shared by all executors within any | |
| 57 | 57 | # given one. (i.e. Different Go processes have different IDs, but IDs are | |
| 58 | - # shared within any given process.) A process generates a new ULID (an | ||
| 59 | - # ordered UUID) worker ID when it starts up. | ||
| 58 | + # shared within any given process.) A process generates a new ID based on | ||
| 59 | + # host and current time when it starts up. | ||
| 60 | 60 | attr_accessor :attempted_by | |
| 61 | 61 | ||
| 62 | 62 | # When the job record was created. | |
| 63 | 63 | attr_accessor :created_at | |
| 64 | 64 | ||
| 65 | 65 | # A set of errors that occurred when the job was worked, one for each | |
| 66 | - # attempt. Ordered from earliest error to the latest error. | ||
| 66 | + # attempt. Ordered from earliest error to the latest error. | ||
| 67 | 67 | attr_accessor :errors | |
| 68 | 68 | ||
| 69 | 69 | # The time at which the job was "finalized", meaning it was either completed | |
@@ -79,6 +79,9 @@ class JobRow | |||
| 79 | 79 | # for the last time and will no longer be worked. | |
| 80 | 80 | attr_accessor :max_attempts | |
| 81 | 81 | ||
| 82 | + # Arbitrary metadata associated with the job. | ||
| 83 | + attr_accessor :metadata | ||
| 84 | + | ||
| 82 | 85 | # The priority of the job, with 1 being the highest priority and 4 being the | |
| 83 | 86 | # lowest. When fetching available jobs to work, the highest priority jobs | |
| 84 | 87 | # will always be fetched before any lower priority jobs are fetched. Note | |
@@ -112,6 +115,7 @@ def initialize( | |||
| 112 | 115 | created_at:, | |
| 113 | 116 | kind:, | |
| 114 | 117 | max_attempts:, | |
| 118 | + metadata:, | ||
| 115 | 119 | priority:, | |
| 116 | 120 | queue:, | |
| 117 | 121 | scheduled_at:, | |
@@ -134,6 +138,7 @@ def initialize( | |||
| 134 | 138 | self.finalized_at = finalized_at | |
| 135 | 139 | self.kind = kind | |
| 136 | 140 | self.max_attempts = max_attempts | |
| 141 | + self.metadata = metadata | ||
| 137 | 142 | self.priority = priority | |
| 138 | 143 | self.queue = queue | |
| 139 | 144 | self.scheduled_at = scheduled_at | |
@@ -157,7 +162,7 @@ class AttemptError | |||
| 157 | 162 | attr_accessor :error | |
| 158 | 163 | ||
| 159 | 164 | # Contains a stack trace from a job that panicked. The trace is produced by | |
| 160 | - # invoking `debug.Trace()`. | ||
| 165 | + # invoking `debug.Trace()` in Go. | ||
| 161 | 166 | attr_accessor :trace | |
| 162 | 167 | ||
| 163 | 168 | def initialize( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,9 +16,13 @@ module River | |||
| 16 | 16 | def insert_many: (Array[jobArgs | InsertManyParams]) -> Integer | |
| 17 | 17 | ||
| 18 | 18 | private def check_unique_job: (Driver::JobInsertParams, UniqueOpts?) { () -> InsertResult } -> InsertResult | |
| 19 | - private def uint64_to_int64: (Integer) -> Integer | ||
| 20 | 19 | private def make_insert_params: (jobArgs, InsertOpts, ?is_insert_many: bool) -> [Driver::JobInsertParams, UniqueOpts?] | |
| 21 | 20 | private def truncate_time: (Time, Integer) -> Time | |
| 21 | + private def uint64_to_int64: (Integer) -> Integer | ||
| 22 | + | ||
| 23 | + TAG_RE: Regexp | ||
| 24 | + | ||
| 25 | + private def validate_tags: (Array[String]?) -> Array[String]? | ||
| 22 | 26 | end | |
| 23 | 27 | ||
| 24 | 28 | class InsertManyParams | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -45,13 +45,14 @@ module River | |||
| 45 | 45 | attr_accessor finalized_at: Time? | |
| 46 | 46 | attr_accessor kind: String | |
| 47 | 47 | attr_accessor max_attempts: Integer | |
| 48 | + attr_accessor metadata: Hash[String, untyped] | ||
| 48 | 49 | attr_accessor priority: Integer | |
| 49 | 50 | attr_accessor queue: String | |
| 50 | 51 | attr_accessor scheduled_at: Time | |
| 51 | 52 | attr_accessor state: jobStateAll | |
| 52 | 53 | attr_accessor tags: Array[String]? | |
| 53 | 54 | ||
| 54 | - def initialize: (id: Integer, args: Hash[String, untyped], attempt: Integer, ?attempted_at: Time?, ?attempted_by: String?, created_at: Time, ?errors: Array[AttemptError]?, ?finalized_at: Time?, kind: String, max_attempts: Integer, priority: Integer, queue: String, scheduled_at: Time, state: jobStateAll, ?tags: Array[String]?) -> void | ||
| 55 | + def initialize: (id: Integer, args: Hash[String, untyped], attempt: Integer, ?attempted_at: Time?, ?attempted_by: String?, created_at: Time, ?errors: Array[AttemptError]?, ?finalized_at: Time?, kind: String, max_attempts: Integer, metadata: Hash[String, untyped], priority: Integer, queue: String, scheduled_at: Time, state: jobStateAll, ?tags: Array[String]?) -> void | ||
| 55 | 56 | end | |
| 56 | 57 | ||
| 57 | 58 | class AttemptError | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -51,6 +51,7 @@ def transaction(&) | |||
| 51 | 51 | finalized_at: nil, | |
| 52 | 52 | kind: insert_params.kind, | |
| 53 | 53 | max_attempts: insert_params.max_attempts, | |
| 54 | + metadata: nil, | ||
| 54 | 55 | priority: insert_params.priority, | |
| 55 | 56 | queue: insert_params.queue, | |
| 56 | 57 | scheduled_at: insert_params.scheduled_at || Time.now, # normally defaults from DB | |
@@ -194,6 +195,22 @@ def to_json = nil | |||
| 194 | 195 | end.to raise_error(RuntimeError, "args should return non-nil from `#to_json`") | |
| 195 | 196 | end | |
| 196 | 197 | ||
| 198 | + it "raises error if tags are too long" do | ||
| 199 | + expect do | ||
| 200 | + client.insert(SimpleArgs.new(job_num: 1), insert_opts: River::InsertOpts.new( | ||
| 201 | + tags: ["a" * 256] | ||
| 202 | + )) | ||
| 203 | + end.to raise_error(ArgumentError, "tags should be 255 characters or less") | ||
| 204 | + end | ||
| 205 | + | ||
| 206 | + it "raises error if tags are misformatted" do | ||
| 207 | + expect do | ||
| 208 | + client.insert(SimpleArgs.new(job_num: 1), insert_opts: River::InsertOpts.new( | ||
| 209 | + tags: ["no,commas,allowed"] | ||
| 210 | + )) | ||
| 211 | + end.to raise_error(ArgumentError, 'tag should match regex /\A[\w][\w\-]+[\w]\z/') | ||
| 212 | + end | ||
| 213 | + | ||
| 197 | 214 | def check_bigint_bounds(int) | |
| 198 | 215 | raise "lock key shouldn't be larger than Postgres bigint max (9223372036854775807); was: #{int}" if int > 9223372036854775807 | |
| 199 | 216 | raise "lock key shouldn't be smaller than Postgres bigint min (-9223372036854775808); was: #{int}" if int < -9223372036854775808 | |
| Back | FazBrowse Home | New Git URL |
0 commit comments