riverqueue 0.7.0 → 0.9.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.
- checksums.yaml +4 -4
- data/lib/client.rb +81 -111
- data/lib/driver.rb +7 -24
- data/lib/insert_opts.rb +12 -8
- data/lib/job.rb +7 -1
- data/lib/riverqueue.rb +1 -1
- data/lib/unique_bitmask.rb +41 -0
- metadata +4 -7
- data/lib/fnv.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ff63f8dce021accd460f7009b3db5eb562f787d2df47f7de69f754a1f9d1734
|
4
|
+
data.tar.gz: cd0b0f7abacdc94d5db17a96d20c597c2e6f7de341b563cde46325459c42490e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eaa89fce622ca310f58b5e1c76442e4284dd537469732deb0c9a37c14e1d93db1e6902f13f40bdb289976ddd2e68ec2f382b2a2c5b16d2684872491351d24c5a
|
7
|
+
data.tar.gz: 7fad9dc2cdfc7087e386f1dc0336b403bc94a86ddcddd90dc681f973f7cda9b1e46e0c4f454db35f007cab1f67819a33d7ce52cb9a01cead3ced069f30c646ea
|
data/lib/client.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require "digest"
|
2
|
-
require "fnv"
|
3
2
|
require "time"
|
4
3
|
|
5
4
|
module River
|
@@ -25,9 +24,8 @@ module River
|
|
25
24
|
# River drivers are found in separate gems like `riverqueue-sequel` to help
|
26
25
|
# minimize transient dependencies.
|
27
26
|
class Client
|
28
|
-
def initialize(driver
|
27
|
+
def initialize(driver)
|
29
28
|
@driver = driver
|
30
|
-
@advisory_lock_prefix = check_advisory_lock_prefix_bounds(advisory_lock_prefix)
|
31
29
|
@time_now_utc = -> { Time.now.utc } # for test time stubbing
|
32
30
|
end
|
33
31
|
|
@@ -73,11 +71,8 @@ module River
|
|
73
71
|
#
|
74
72
|
# Returns an instance of InsertResult.
|
75
73
|
def insert(args, insert_opts: EMPTY_INSERT_OPTS)
|
76
|
-
insert_params
|
77
|
-
|
78
|
-
job = @driver.job_insert(insert_params)
|
79
|
-
InsertResult.new(job)
|
80
|
-
end
|
74
|
+
insert_params = make_insert_params(args, insert_opts)
|
75
|
+
insert_and_check_unique_job(insert_params)
|
81
76
|
end
|
82
77
|
|
83
78
|
# Inserts many new jobs as part of a single batch operation for improved
|
@@ -122,30 +117,20 @@ module River
|
|
122
117
|
#
|
123
118
|
# See also JobArgsHash for an easy way to insert a job from a hash.
|
124
119
|
#
|
125
|
-
# Unique job insertion isn't supported with bulk insertion because it'd run
|
126
|
-
# the risk of major lock contention.
|
127
|
-
#
|
128
120
|
# Returns the number of jobs inserted.
|
129
121
|
def insert_many(args)
|
130
122
|
all_params = args.map do |arg|
|
131
123
|
if arg.is_a?(InsertManyParams)
|
132
|
-
make_insert_params(arg.args, arg.insert_opts || EMPTY_INSERT_OPTS
|
124
|
+
make_insert_params(arg.args, arg.insert_opts || EMPTY_INSERT_OPTS)
|
133
125
|
else # jobArgs
|
134
|
-
make_insert_params(arg, EMPTY_INSERT_OPTS
|
126
|
+
make_insert_params(arg, EMPTY_INSERT_OPTS)
|
135
127
|
end
|
136
128
|
end
|
137
129
|
|
138
130
|
@driver.job_insert_many(all_params)
|
139
|
-
|
140
|
-
|
141
|
-
private def check_advisory_lock_prefix_bounds(advisory_lock_prefix)
|
142
|
-
return nil if advisory_lock_prefix.nil?
|
143
|
-
|
144
|
-
# 2**32-1 is 0xffffffff (the largest number that's four bytes)
|
145
|
-
if advisory_lock_prefix < 0 || advisory_lock_prefix > 2**32 - 1
|
146
|
-
raise ArgumentError, "advisory lock prefix must fit inside four bytes"
|
131
|
+
.map do |job, unique_skipped_as_duplicate|
|
132
|
+
InsertResult.new(job, unique_skipped_as_duplicated: unique_skipped_as_duplicate)
|
147
133
|
end
|
148
|
-
advisory_lock_prefix
|
149
134
|
end
|
150
135
|
|
151
136
|
# Default states that are used during a unique insert. Can be overridden by
|
@@ -153,123 +138,101 @@ module River
|
|
153
138
|
DEFAULT_UNIQUE_STATES = [
|
154
139
|
JOB_STATE_AVAILABLE,
|
155
140
|
JOB_STATE_COMPLETED,
|
141
|
+
JOB_STATE_PENDING,
|
156
142
|
JOB_STATE_RETRYABLE,
|
157
143
|
JOB_STATE_RUNNING,
|
158
144
|
JOB_STATE_SCHEDULED
|
159
145
|
].freeze
|
160
146
|
private_constant :DEFAULT_UNIQUE_STATES
|
161
147
|
|
148
|
+
REQUIRED_UNIQUE_STATES = [
|
149
|
+
JOB_STATE_AVAILABLE,
|
150
|
+
JOB_STATE_PENDING,
|
151
|
+
JOB_STATE_RUNNING,
|
152
|
+
JOB_STATE_SCHEDULED
|
153
|
+
].freeze
|
154
|
+
private_constant :REQUIRED_UNIQUE_STATES
|
155
|
+
|
162
156
|
EMPTY_INSERT_OPTS = InsertOpts.new.freeze
|
163
157
|
private_constant :EMPTY_INSERT_OPTS
|
164
158
|
|
165
|
-
private def
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
get_params = Driver::JobGetByKindAndUniquePropertiesParam.new(kind: insert_params.kind)
|
170
|
-
unique_key = ""
|
159
|
+
private def insert_and_check_unique_job(insert_params)
|
160
|
+
job, unique_skipped_as_duplicate = @driver.job_insert(insert_params)
|
161
|
+
InsertResult.new(job, unique_skipped_as_duplicated: unique_skipped_as_duplicate)
|
162
|
+
end
|
171
163
|
|
172
|
-
|
173
|
-
|
174
|
-
# unless they're updated everywhere.
|
175
|
-
if unique_opts.by_args
|
176
|
-
any_unique_opts = true
|
177
|
-
get_params.encoded_args = insert_params.encoded_args
|
178
|
-
unique_key += "&args=#{insert_params.encoded_args}"
|
179
|
-
end
|
164
|
+
private def make_insert_params(args, insert_opts)
|
165
|
+
raise "args should respond to `#kind`" if !args.respond_to?(:kind)
|
180
166
|
|
181
|
-
|
182
|
-
|
167
|
+
# ~all objects in Ruby respond to `#to_json`, so check non-nil instead.
|
168
|
+
args_json = args.to_json
|
169
|
+
raise "args should return non-nil from `#to_json`" if !args_json
|
183
170
|
|
184
|
-
|
185
|
-
|
186
|
-
|
171
|
+
args_insert_opts = if args.respond_to?(:insert_opts)
|
172
|
+
args_with_insert_opts = args #: _JobArgsWithInsertOpts # rubocop:disable Layout/LeadingCommentSpace
|
173
|
+
args_with_insert_opts.insert_opts || EMPTY_INSERT_OPTS
|
174
|
+
else
|
175
|
+
EMPTY_INSERT_OPTS
|
187
176
|
end
|
188
177
|
|
189
|
-
|
190
|
-
any_unique_opts = true
|
191
|
-
get_params.queue = insert_params.queue
|
192
|
-
unique_key += "&queue=#{insert_params.queue}"
|
193
|
-
end
|
178
|
+
scheduled_at = insert_opts.scheduled_at || args_insert_opts.scheduled_at
|
194
179
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
180
|
+
insert_params = Driver::JobInsertParams.new(
|
181
|
+
encoded_args: args_json,
|
182
|
+
kind: args.kind,
|
183
|
+
max_attempts: insert_opts.max_attempts || args_insert_opts.max_attempts || MAX_ATTEMPTS_DEFAULT,
|
184
|
+
priority: insert_opts.priority || args_insert_opts.priority || PRIORITY_DEFAULT,
|
185
|
+
queue: insert_opts.queue || args_insert_opts.queue || QUEUE_DEFAULT,
|
186
|
+
scheduled_at: scheduled_at&.utc || Time.now,
|
187
|
+
state: scheduled_at ? JOB_STATE_SCHEDULED : JOB_STATE_AVAILABLE,
|
188
|
+
tags: validate_tags(insert_opts.tags || args_insert_opts.tags || [])
|
189
|
+
)
|
190
|
+
|
191
|
+
unique_opts = insert_opts.unique_opts || args_insert_opts.unique_opts
|
192
|
+
if unique_opts
|
193
|
+
unique_key, unique_states = make_unique_key_and_bitmask(insert_params, unique_opts)
|
194
|
+
insert_params.unique_key = unique_key
|
195
|
+
insert_params.unique_states = unique_states
|
202
196
|
end
|
197
|
+
insert_params
|
198
|
+
end
|
203
199
|
|
204
|
-
|
200
|
+
private def make_unique_key_and_bitmask(insert_params, unique_opts)
|
201
|
+
unique_key = ""
|
205
202
|
|
206
|
-
#
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
203
|
+
# It's extremely important here that this unique key format and algorithm
|
204
|
+
# match the one in the main River library _exactly_. Don't change them
|
205
|
+
# unless they're updated everywhere.
|
206
|
+
unless unique_opts.exclude_kind
|
207
|
+
unique_key += "&kind=#{insert_params.kind}"
|
211
208
|
end
|
212
209
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
lock_key = if @advisory_lock_prefix.nil?
|
219
|
-
FNV.fnv1_hash(lock_str, size: 64)
|
210
|
+
if unique_opts.by_args
|
211
|
+
parsed_args = JSON.parse(insert_params.encoded_args)
|
212
|
+
filtered_args = if unique_opts.by_args.is_a?(Array)
|
213
|
+
parsed_args.slice(*unique_opts.by_args)
|
220
214
|
else
|
221
|
-
|
222
|
-
prefix = @advisory_lock_prefix #: Integer # rubocop:disable Layout/LeadingCommentSpace
|
223
|
-
prefix << 32 | FNV.fnv1_hash(lock_str, size: 32)
|
215
|
+
parsed_args
|
224
216
|
end
|
225
217
|
|
226
|
-
|
227
|
-
|
228
|
-
# we can use the full bigint space (including negative numbers) for the
|
229
|
-
# advisory lock.
|
230
|
-
lock_key = uint64_to_int64(lock_key)
|
231
|
-
|
232
|
-
@driver.advisory_lock(lock_key)
|
233
|
-
|
234
|
-
existing_job = @driver.job_get_by_kind_and_unique_properties(get_params)
|
235
|
-
return InsertResult.new(existing_job, unique_skipped_as_duplicated: true) if existing_job
|
236
|
-
|
237
|
-
block.call
|
218
|
+
encoded_args = JSON.generate(filtered_args.sort.to_h)
|
219
|
+
unique_key += "&args=#{encoded_args}"
|
238
220
|
end
|
239
|
-
end
|
240
221
|
|
241
|
-
|
242
|
-
|
222
|
+
if unique_opts.by_period
|
223
|
+
lower_period_bound = truncate_time(insert_params.scheduled_at || @time_now_utc.call, unique_opts.by_period).utc
|
243
224
|
|
244
|
-
|
245
|
-
|
246
|
-
raise "args should return non-nil from `#to_json`" if !args_json
|
225
|
+
unique_key += "&period=#{lower_period_bound.strftime("%FT%TZ")}"
|
226
|
+
end
|
247
227
|
|
248
|
-
|
249
|
-
|
250
|
-
args_with_insert_opts.insert_opts || EMPTY_INSERT_OPTS
|
251
|
-
else
|
252
|
-
EMPTY_INSERT_OPTS
|
228
|
+
if unique_opts.by_queue
|
229
|
+
unique_key += "&queue=#{insert_params.queue}"
|
253
230
|
end
|
254
231
|
|
255
|
-
|
256
|
-
|
232
|
+
unique_key_hash = Digest::SHA256.digest(unique_key)
|
233
|
+
unique_states = validate_unique_states(unique_opts.by_state || DEFAULT_UNIQUE_STATES)
|
257
234
|
|
258
|
-
|
259
|
-
|
260
|
-
[
|
261
|
-
Driver::JobInsertParams.new(
|
262
|
-
encoded_args: args_json,
|
263
|
-
kind: args.kind,
|
264
|
-
max_attempts: insert_opts.max_attempts || args_insert_opts.max_attempts || MAX_ATTEMPTS_DEFAULT,
|
265
|
-
priority: insert_opts.priority || args_insert_opts.priority || PRIORITY_DEFAULT,
|
266
|
-
queue: insert_opts.queue || args_insert_opts.queue || QUEUE_DEFAULT,
|
267
|
-
scheduled_at: scheduled_at&.utc, # database defaults to now
|
268
|
-
state: scheduled_at ? JOB_STATE_SCHEDULED : JOB_STATE_AVAILABLE,
|
269
|
-
tags: validate_tags(insert_opts.tags || args_insert_opts.tags)
|
270
|
-
),
|
271
|
-
unique_opts
|
272
|
-
]
|
235
|
+
[unique_key_hash, UniqueBitmask.from_states(unique_states)]
|
273
236
|
end
|
274
237
|
|
275
238
|
# Truncates the given time down to the interval. For example:
|
@@ -290,11 +253,18 @@ module River
|
|
290
253
|
private_constant :TAG_RE
|
291
254
|
|
292
255
|
private def validate_tags(tags)
|
293
|
-
tags
|
256
|
+
tags.each do |tag|
|
294
257
|
raise ArgumentError, "tags should be 255 characters or less" if tag.length > 255
|
295
258
|
raise ArgumentError, "tag should match regex #{TAG_RE.inspect}" unless TAG_RE.match(tag)
|
296
259
|
end
|
297
260
|
end
|
261
|
+
|
262
|
+
private def validate_unique_states(states)
|
263
|
+
REQUIRED_UNIQUE_STATES.each do |required_state|
|
264
|
+
raise ArgumentError, "by_state should include required state #{required_state}" unless states.include?(required_state)
|
265
|
+
end
|
266
|
+
states
|
267
|
+
end
|
298
268
|
end
|
299
269
|
|
300
270
|
# A single job to insert that's part of an #insert_many batch insert. Unlike
|
@@ -322,7 +292,7 @@ module River
|
|
322
292
|
# job matching unique property already being present.
|
323
293
|
attr_reader :unique_skipped_as_duplicated
|
324
294
|
|
325
|
-
def initialize(job, unique_skipped_as_duplicated:
|
295
|
+
def initialize(job, unique_skipped_as_duplicated:)
|
326
296
|
@job = job
|
327
297
|
@unique_skipped_as_duplicated = unique_skipped_as_duplicated
|
328
298
|
end
|
data/lib/driver.rb
CHANGED
@@ -4,29 +4,6 @@ module River
|
|
4
4
|
# considered to be for internal use only and subject to change. API stability
|
5
5
|
# is not guaranteed.
|
6
6
|
module Driver
|
7
|
-
# Parameters for looking up a job by kind and unique properties.
|
8
|
-
class JobGetByKindAndUniquePropertiesParam
|
9
|
-
attr_accessor :created_at
|
10
|
-
attr_accessor :encoded_args
|
11
|
-
attr_accessor :kind
|
12
|
-
attr_accessor :queue
|
13
|
-
attr_accessor :state
|
14
|
-
|
15
|
-
def initialize(
|
16
|
-
kind:,
|
17
|
-
created_at: nil,
|
18
|
-
encoded_args: nil,
|
19
|
-
queue: nil,
|
20
|
-
state: nil
|
21
|
-
)
|
22
|
-
self.kind = kind
|
23
|
-
self.created_at = created_at
|
24
|
-
self.encoded_args = encoded_args
|
25
|
-
self.queue = queue
|
26
|
-
self.state = state
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
7
|
# Insert parameters for a job. This is sent to underlying drivers and is meant
|
31
8
|
# for internal use only. Its interface is subject to change.
|
32
9
|
class JobInsertParams
|
@@ -38,6 +15,8 @@ module River
|
|
38
15
|
attr_accessor :scheduled_at
|
39
16
|
attr_accessor :state
|
40
17
|
attr_accessor :tags
|
18
|
+
attr_accessor :unique_key
|
19
|
+
attr_accessor :unique_states
|
41
20
|
|
42
21
|
def initialize(
|
43
22
|
encoded_args:,
|
@@ -47,7 +26,9 @@ module River
|
|
47
26
|
queue:,
|
48
27
|
scheduled_at:,
|
49
28
|
state:,
|
50
|
-
tags
|
29
|
+
tags:,
|
30
|
+
unique_key: nil,
|
31
|
+
unique_states: nil
|
51
32
|
)
|
52
33
|
self.encoded_args = encoded_args
|
53
34
|
self.kind = kind
|
@@ -57,6 +38,8 @@ module River
|
|
57
38
|
self.scheduled_at = scheduled_at
|
58
39
|
self.state = state
|
59
40
|
self.tags = tags
|
41
|
+
self.unique_key = unique_key
|
42
|
+
self.unique_states = unique_states
|
60
43
|
end
|
61
44
|
end
|
62
45
|
end
|
data/lib/insert_opts.rb
CHANGED
@@ -72,12 +72,6 @@ module River
|
|
72
72
|
# given job kind, a single instance is allowed for each combination of args
|
73
73
|
# and queues. If either args or queue is changed on a new job, it's allowed to
|
74
74
|
# be inserted as a new job.
|
75
|
-
#
|
76
|
-
# Uniquenes is checked at insert time by taking a Postgres advisory lock,
|
77
|
-
# doing a look up for an equivalent row, and inserting only if none was found.
|
78
|
-
# There's no database-level mechanism that guarantees jobs stay unique, so if
|
79
|
-
# an equivalent row is inserted out of band (or batch inserted, where a unique
|
80
|
-
# check doesn't occur), it's conceivable that duplicates could coexist.
|
81
75
|
class UniqueOpts
|
82
76
|
# Indicates that uniqueness should be enforced for any specific instance of
|
83
77
|
# encoded args for a job.
|
@@ -114,23 +108,33 @@ module River
|
|
114
108
|
# Unlike other unique options, ByState gets a default when it's not set for
|
115
109
|
# user convenience. The default is equivalent to:
|
116
110
|
#
|
117
|
-
# by_state: [River::JOB_STATE_AVAILABLE, River::JOB_STATE_COMPLETED, River::JOB_STATE_RUNNING, River::JOB_STATE_RETRYABLE, River::JOB_STATE_SCHEDULED]
|
111
|
+
# by_state: [River::JOB_STATE_AVAILABLE, River::JOB_STATE_COMPLETED, River::JOB_STATE_PENDING, River::JOB_STATE_RUNNING, River::JOB_STATE_RETRYABLE, River::JOB_STATE_SCHEDULED]
|
118
112
|
#
|
119
113
|
# With this setting, any jobs of the same kind that have been completed or
|
120
114
|
# discarded, but not yet cleaned out by the system, won't count towards the
|
121
115
|
# uniqueness of a new insert.
|
116
|
+
#
|
117
|
+
# The pending, scheduled, available, and running states are required when
|
118
|
+
# customizing this list.
|
122
119
|
attr_accessor :by_state
|
123
120
|
|
121
|
+
# Indicates that the job kind should not be considered for uniqueness. This
|
122
|
+
# is useful when you want to enforce uniqueness based on other properties
|
123
|
+
# across multiple worker types.
|
124
|
+
attr_accessor :exclude_kind
|
125
|
+
|
124
126
|
def initialize(
|
125
127
|
by_args: nil,
|
126
128
|
by_period: nil,
|
127
129
|
by_queue: nil,
|
128
|
-
by_state: nil
|
130
|
+
by_state: nil,
|
131
|
+
exclude_kind: nil
|
129
132
|
)
|
130
133
|
self.by_args = by_args
|
131
134
|
self.by_period = by_period
|
132
135
|
self.by_queue = by_queue
|
133
136
|
self.by_state = by_state
|
137
|
+
self.exclude_kind = exclude_kind
|
134
138
|
end
|
135
139
|
end
|
136
140
|
end
|
data/lib/job.rb
CHANGED
@@ -3,6 +3,7 @@ module River
|
|
3
3
|
JOB_STATE_CANCELLED = "cancelled"
|
4
4
|
JOB_STATE_COMPLETED = "completed"
|
5
5
|
JOB_STATE_DISCARDED = "discarded"
|
6
|
+
JOB_STATE_PENDING = "pending"
|
6
7
|
JOB_STATE_RETRYABLE = "retryable"
|
7
8
|
JOB_STATE_RUNNING = "running"
|
8
9
|
JOB_STATE_SCHEDULED = "scheduled"
|
@@ -113,6 +114,9 @@ module River
|
|
113
114
|
# configuration.
|
114
115
|
attr_accessor :unique_key
|
115
116
|
|
117
|
+
# A list of states that the job must be in to be considered for uniqueness.
|
118
|
+
attr_accessor :unique_states
|
119
|
+
|
116
120
|
def initialize(
|
117
121
|
id:,
|
118
122
|
args:,
|
@@ -132,7 +136,8 @@ module River
|
|
132
136
|
errors: nil,
|
133
137
|
finalized_at: nil,
|
134
138
|
tags: nil,
|
135
|
-
unique_key: nil
|
139
|
+
unique_key: nil,
|
140
|
+
unique_states: nil
|
136
141
|
)
|
137
142
|
self.id = id
|
138
143
|
self.args = args
|
@@ -151,6 +156,7 @@ module River
|
|
151
156
|
self.state = state
|
152
157
|
self.tags = tags
|
153
158
|
self.unique_key = unique_key
|
159
|
+
self.unique_states = unique_states
|
154
160
|
end
|
155
161
|
end
|
156
162
|
|
data/lib/riverqueue.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
module River
|
2
|
+
class UniqueBitmask
|
3
|
+
JOB_STATE_BIT_POSITIONS = {
|
4
|
+
::River::JOB_STATE_AVAILABLE => 7,
|
5
|
+
::River::JOB_STATE_CANCELLED => 6,
|
6
|
+
::River::JOB_STATE_COMPLETED => 5,
|
7
|
+
::River::JOB_STATE_DISCARDED => 4,
|
8
|
+
::River::JOB_STATE_PENDING => 3,
|
9
|
+
::River::JOB_STATE_RETRYABLE => 2,
|
10
|
+
::River::JOB_STATE_RUNNING => 1,
|
11
|
+
::River::JOB_STATE_SCHEDULED => 0
|
12
|
+
}.freeze
|
13
|
+
private_constant :JOB_STATE_BIT_POSITIONS
|
14
|
+
|
15
|
+
def self.from_states(states)
|
16
|
+
val = 0
|
17
|
+
|
18
|
+
states.each do |state|
|
19
|
+
bit_index = JOB_STATE_BIT_POSITIONS[state]
|
20
|
+
|
21
|
+
bit_position = 7 - (bit_index % 8)
|
22
|
+
val |= 1 << bit_position
|
23
|
+
end
|
24
|
+
|
25
|
+
format("%08b", val)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.to_states(mask)
|
29
|
+
states = [] #: Array[jobStateAll] # rubocop:disable Layout/LeadingCommentSpace
|
30
|
+
|
31
|
+
JOB_STATE_BIT_POSITIONS.each do |state, bit_index|
|
32
|
+
bit_position = 7 - (bit_index % 8)
|
33
|
+
if (mask & (1 << bit_position)) != 0
|
34
|
+
states << state
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
states.sort
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: riverqueue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Blake Gentry
|
8
8
|
- Brandur Leach
|
9
|
-
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2025-04-12 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
13
|
description: River is a fast job queue for Go. Use this gem in conjunction with gems
|
15
14
|
riverqueue-activerecord or riverqueue-sequel to insert jobs in Ruby which will be
|
@@ -21,10 +20,10 @@ extra_rdoc_files: []
|
|
21
20
|
files:
|
22
21
|
- lib/client.rb
|
23
22
|
- lib/driver.rb
|
24
|
-
- lib/fnv.rb
|
25
23
|
- lib/insert_opts.rb
|
26
24
|
- lib/job.rb
|
27
25
|
- lib/riverqueue.rb
|
26
|
+
- lib/unique_bitmask.rb
|
28
27
|
homepage: https://riverqueue.com
|
29
28
|
licenses:
|
30
29
|
- LGPL-3.0-or-later
|
@@ -33,7 +32,6 @@ metadata:
|
|
33
32
|
changelog_uri: https://github.com/riverqueue/riverqueue-ruby/blob/master/CHANGELOG.md
|
34
33
|
rubygems_mfa_required: 'true'
|
35
34
|
source_code_uri: https://github.com/riverqueue/riverqueue-ruby
|
36
|
-
post_install_message:
|
37
35
|
rdoc_options: []
|
38
36
|
require_paths:
|
39
37
|
- lib
|
@@ -48,8 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
46
|
- !ruby/object:Gem::Version
|
49
47
|
version: '0'
|
50
48
|
requirements: []
|
51
|
-
rubygems_version: 3.
|
52
|
-
signing_key:
|
49
|
+
rubygems_version: 3.6.2
|
53
50
|
specification_version: 4
|
54
51
|
summary: River is a fast job queue for Go.
|
55
52
|
test_files: []
|
data/lib/fnv.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
module River
|
2
|
-
# FNV is the Fowler–Noll–Vo hash function, a simple hash that's very easy to
|
3
|
-
# implement, and hash the perfect characteristics for use with the 64 bits of
|
4
|
-
# available space in a PG advisory lock.
|
5
|
-
#
|
6
|
-
# I'm implemented it myself so that the River gem can stay dependency free
|
7
|
-
# (and because it's quite easy to do).
|
8
|
-
module FNV
|
9
|
-
def self.fnv1_hash(str, size:)
|
10
|
-
hash = OFFSET_BASIS.fetch(size)
|
11
|
-
mask = (2**size - 1).to_int # creates a mask of 1s of `size` bits long like 0xffffffff
|
12
|
-
prime = PRIME.fetch(size)
|
13
|
-
|
14
|
-
str.each_byte do |byte|
|
15
|
-
hash *= prime
|
16
|
-
hash &= mask
|
17
|
-
hash ^= byte
|
18
|
-
end
|
19
|
-
|
20
|
-
hash
|
21
|
-
end
|
22
|
-
|
23
|
-
OFFSET_BASIS = {
|
24
|
-
32 => 0x811c9dc5,
|
25
|
-
64 => 0xcbf29ce484222325
|
26
|
-
}.freeze
|
27
|
-
private_constant :OFFSET_BASIS
|
28
|
-
|
29
|
-
PRIME = {
|
30
|
-
32 => 0x01000193,
|
31
|
-
64 => 0x00000100000001B3
|
32
|
-
}.freeze
|
33
|
-
private_constant :PRIME
|
34
|
-
end
|
35
|
-
end
|