cloudtasker-tonix 0.1.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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/lint_rubocop.yml +15 -0
  3. data/.github/workflows/test_ruby_3.x.yml +40 -0
  4. data/.gitignore +23 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +96 -0
  7. data/Appraisals +76 -0
  8. data/CHANGELOG.md +248 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +18 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +1311 -0
  13. data/Rakefile +8 -0
  14. data/_config.yml +1 -0
  15. data/app/controllers/cloudtasker/worker_controller.rb +107 -0
  16. data/bin/console +15 -0
  17. data/bin/setup +8 -0
  18. data/cloudtasker.gemspec +42 -0
  19. data/config/routes.rb +5 -0
  20. data/docs/BATCH_JOBS.md +144 -0
  21. data/docs/CRON_JOBS.md +129 -0
  22. data/docs/STORABLE_JOBS.md +68 -0
  23. data/docs/UNIQUE_JOBS.md +190 -0
  24. data/exe/cloudtasker +30 -0
  25. data/gemfiles/.bundle/config +2 -0
  26. data/gemfiles/google_cloud_tasks_1.0.gemfile +17 -0
  27. data/gemfiles/google_cloud_tasks_1.1.gemfile +17 -0
  28. data/gemfiles/google_cloud_tasks_1.2.gemfile +17 -0
  29. data/gemfiles/google_cloud_tasks_1.3.gemfile +17 -0
  30. data/gemfiles/google_cloud_tasks_1.4.gemfile +17 -0
  31. data/gemfiles/google_cloud_tasks_1.5.gemfile +17 -0
  32. data/gemfiles/google_cloud_tasks_2.0.gemfile +17 -0
  33. data/gemfiles/google_cloud_tasks_2.1.gemfile +17 -0
  34. data/gemfiles/rails_6.1.gemfile +20 -0
  35. data/gemfiles/rails_7.0.gemfile +18 -0
  36. data/gemfiles/rails_7.1.gemfile +18 -0
  37. data/gemfiles/rails_8.0.gemfile +18 -0
  38. data/gemfiles/rails_8.1.gemfile +18 -0
  39. data/gemfiles/semantic_logger_3.4.gemfile +16 -0
  40. data/gemfiles/semantic_logger_4.6.gemfile +16 -0
  41. data/gemfiles/semantic_logger_4.7.0.gemfile +16 -0
  42. data/gemfiles/semantic_logger_4.7.2.gemfile +16 -0
  43. data/lib/active_job/queue_adapters/cloudtasker_adapter.rb +89 -0
  44. data/lib/cloudtasker/authentication_error.rb +6 -0
  45. data/lib/cloudtasker/authenticator.rb +90 -0
  46. data/lib/cloudtasker/backend/google_cloud_task_v1.rb +228 -0
  47. data/lib/cloudtasker/backend/google_cloud_task_v2.rb +231 -0
  48. data/lib/cloudtasker/backend/memory_task.rb +202 -0
  49. data/lib/cloudtasker/backend/redis_task.rb +291 -0
  50. data/lib/cloudtasker/batch/batch_progress.rb +142 -0
  51. data/lib/cloudtasker/batch/extension/worker.rb +13 -0
  52. data/lib/cloudtasker/batch/job.rb +558 -0
  53. data/lib/cloudtasker/batch/middleware/server.rb +14 -0
  54. data/lib/cloudtasker/batch/middleware.rb +25 -0
  55. data/lib/cloudtasker/batch.rb +5 -0
  56. data/lib/cloudtasker/cli.rb +194 -0
  57. data/lib/cloudtasker/cloud_task.rb +130 -0
  58. data/lib/cloudtasker/config.rb +319 -0
  59. data/lib/cloudtasker/cron/job.rb +205 -0
  60. data/lib/cloudtasker/cron/middleware/server.rb +14 -0
  61. data/lib/cloudtasker/cron/middleware.rb +20 -0
  62. data/lib/cloudtasker/cron/schedule.rb +308 -0
  63. data/lib/cloudtasker/cron.rb +5 -0
  64. data/lib/cloudtasker/dead_worker_error.rb +6 -0
  65. data/lib/cloudtasker/engine.rb +24 -0
  66. data/lib/cloudtasker/invalid_worker_error.rb +6 -0
  67. data/lib/cloudtasker/local_server.rb +99 -0
  68. data/lib/cloudtasker/max_task_size_exceeded_error.rb +14 -0
  69. data/lib/cloudtasker/meta_store.rb +86 -0
  70. data/lib/cloudtasker/middleware/chain.rb +250 -0
  71. data/lib/cloudtasker/missing_worker_arguments_error.rb +6 -0
  72. data/lib/cloudtasker/redis_client.rb +166 -0
  73. data/lib/cloudtasker/retry_worker_error.rb +6 -0
  74. data/lib/cloudtasker/storable/worker.rb +78 -0
  75. data/lib/cloudtasker/storable.rb +3 -0
  76. data/lib/cloudtasker/testing.rb +184 -0
  77. data/lib/cloudtasker/unique_job/conflict_strategy/base_strategy.rb +39 -0
  78. data/lib/cloudtasker/unique_job/conflict_strategy/raise.rb +28 -0
  79. data/lib/cloudtasker/unique_job/conflict_strategy/reject.rb +11 -0
  80. data/lib/cloudtasker/unique_job/conflict_strategy/reschedule.rb +30 -0
  81. data/lib/cloudtasker/unique_job/job.rb +168 -0
  82. data/lib/cloudtasker/unique_job/lock/base_lock.rb +70 -0
  83. data/lib/cloudtasker/unique_job/lock/no_op.rb +11 -0
  84. data/lib/cloudtasker/unique_job/lock/until_completed.rb +40 -0
  85. data/lib/cloudtasker/unique_job/lock/until_executed.rb +36 -0
  86. data/lib/cloudtasker/unique_job/lock/until_executing.rb +30 -0
  87. data/lib/cloudtasker/unique_job/lock/while_executing.rb +25 -0
  88. data/lib/cloudtasker/unique_job/lock_error.rb +8 -0
  89. data/lib/cloudtasker/unique_job/middleware/client.rb +15 -0
  90. data/lib/cloudtasker/unique_job/middleware/server.rb +14 -0
  91. data/lib/cloudtasker/unique_job/middleware.rb +36 -0
  92. data/lib/cloudtasker/unique_job.rb +32 -0
  93. data/lib/cloudtasker/version.rb +5 -0
  94. data/lib/cloudtasker/worker.rb +487 -0
  95. data/lib/cloudtasker/worker_handler.rb +250 -0
  96. data/lib/cloudtasker/worker_logger.rb +231 -0
  97. data/lib/cloudtasker/worker_wrapper.rb +52 -0
  98. data/lib/cloudtasker.rb +57 -0
  99. data/lib/tasks/setup_queue.rake +20 -0
  100. metadata +241 -0
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudtasker
4
+ # Manage meta information on workers. This meta stored is intended
5
+ # to be used by middlewares needing to store extra information on the
6
+ # job.
7
+ # The objective of this class is to provide a shared store to middleware
8
+ # while controlling access to its keys by preveenting access the hash directly
9
+ # (e.g. avoid wild merge or replace operations).
10
+ class MetaStore
11
+ #
12
+ # Build a new instance of the class.
13
+ #
14
+ # @param [<Type>] hash The worker meta hash
15
+ #
16
+ def initialize(hash = {})
17
+ @meta = JSON.parse((hash || {}).to_json, symbolize_names: true)
18
+ end
19
+
20
+ #
21
+ # Retrieve meta entry.
22
+ #
23
+ # @param [String, Symbol] key The key of the meta entry.
24
+ #
25
+ # @return [Any] The value of the meta entry.
26
+ #
27
+ def get(key)
28
+ @meta[key.to_sym] if key
29
+ end
30
+
31
+ #
32
+ # Set meta entry
33
+ #
34
+ # @param [String, Symbol] key The key of the meta entry.
35
+ # @param [Any] val The value of the meta entry.
36
+ #
37
+ # @return [Any] The value set
38
+ #
39
+ def set(key, val)
40
+ @meta[key.to_sym] = val if key
41
+ end
42
+
43
+ #
44
+ # Remove a meta information.
45
+ #
46
+ # @param [String, Symbol] key The key of the entry to delete.
47
+ #
48
+ # @return [Any] The value of the deleted key
49
+ #
50
+ def del(key)
51
+ @meta.delete(key.to_sym) if key
52
+ end
53
+
54
+ #
55
+ # Return the meta store as Hash.
56
+ #
57
+ # @return [Hash] The meta store as Hash.
58
+ #
59
+ def to_h
60
+ # Deep dup
61
+ JSON.parse(@meta.to_json, symbolize_names: true)
62
+ end
63
+
64
+ #
65
+ # Return the meta store as json.
66
+ #
67
+ # @param [Array<any>] *arg The to_json args.
68
+ #
69
+ # @return [String] The meta store as json.
70
+ #
71
+ def to_json(*arg)
72
+ @meta.to_json(*arg)
73
+ end
74
+
75
+ #
76
+ # Equality operator.
77
+ #
78
+ # @param [Any] other The object being compared.
79
+ #
80
+ # @return [Boolean] True if the object is equal.
81
+ #
82
+ def ==(other)
83
+ to_json == other.try(:to_json)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudtasker
4
+ module Middleware
5
+ # The class below was originally taken from Sidekiq.
6
+ # See: https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/middleware/chain.rb
7
+ #
8
+ # Middleware are callables configured to run before/after a message is processed.
9
+ # Middlewares can be configured to run on the client side (when jobs are pushed
10
+ # to Cloud Tasks) as well as on the server side (when jobs are processed by
11
+ # your application)
12
+ #
13
+ # To add middleware for the client:
14
+ #
15
+ # Cloudtasker.configure do |config|
16
+ # config.client_middleware do |chain|
17
+ # chain.add MyClientHook
18
+ # end
19
+ # end
20
+ #
21
+ # To modify middleware for the server, just call
22
+ # with another block:
23
+ #
24
+ # Cloudtasker.configure do |config|
25
+ # config.server_middleware do |chain|
26
+ # chain.add MyServerHook
27
+ # chain.remove ActiveRecord
28
+ # end
29
+ # end
30
+ #
31
+ # To insert immediately preceding another entry:
32
+ #
33
+ # Cloudtasker.configure do |config|
34
+ # config.client_middleware do |chain|
35
+ # chain.insert_before ActiveRecord, MyClientHook
36
+ # end
37
+ # end
38
+ #
39
+ # To insert immediately after another entry:
40
+ #
41
+ # Cloudtasker.configure do |config|
42
+ # config.client_middleware do |chain|
43
+ # chain.insert_after ActiveRecord, MyClientHook
44
+ # end
45
+ # end
46
+ #
47
+ # This is an example of a minimal server middleware:
48
+ #
49
+ # class MyServerHook
50
+ # def call(worker_instance, msg, queue)
51
+ # puts "Before work"
52
+ # yield
53
+ # puts "After work"
54
+ # end
55
+ # end
56
+ #
57
+ # This is an example of a minimal client middleware, note
58
+ # the method must return the result or the job will not push
59
+ # to Redis:
60
+ #
61
+ # class MyClientHook
62
+ # def call(worker_class, msg, queue, redis_pool)
63
+ # puts "Before push"
64
+ # result = yield
65
+ # puts "After push"
66
+ # result
67
+ # end
68
+ # end
69
+ #
70
+ class Chain
71
+ include Enumerable
72
+
73
+ #
74
+ # Build a new middleware chain.
75
+ #
76
+ def initialize
77
+ @entries = nil
78
+ yield self if block_given?
79
+ end
80
+
81
+ #
82
+ # Iterate over the list middlewares and execute the block on each item.
83
+ #
84
+ # @param [Proc] &block The block to execute on each item.
85
+ #
86
+ def each(&block)
87
+ entries.each(&block)
88
+ end
89
+
90
+ #
91
+ # Return the list of middlewares.
92
+ #
93
+ # @return [Array<Cloudtasker::Middleware::Chain::Entry>] The list of middlewares
94
+ #
95
+ def entries
96
+ @entries ||= []
97
+ end
98
+
99
+ #
100
+ # Remove a middleware from the list.
101
+ #
102
+ # @param [Class] klass The middleware class to remove.
103
+ #
104
+ def remove(klass)
105
+ entries.delete_if { |entry| entry.klass == klass }
106
+ end
107
+
108
+ #
109
+ # Add a middleware at the end of the list.
110
+ #
111
+ # @param [Class] klass The middleware class to add.
112
+ # @param [Arry<any>] *args The list of arguments to the middleware.
113
+ #
114
+ # @return [Array<Cloudtasker::Middleware::Chain::Entry>] The updated list of middlewares
115
+ #
116
+ def add(klass, *args)
117
+ remove(klass) if exists?(klass)
118
+ entries << Entry.new(klass, *args)
119
+ end
120
+
121
+ #
122
+ # Add a middleware at the beginning of the list.
123
+ #
124
+ # @param [Class] klass The middleware class to add.
125
+ # @param [Arry<any>] *args The list of arguments to the middleware.
126
+ #
127
+ # @return [Array<Cloudtasker::Middleware::Chain::Entry>] The updated list of middlewares
128
+ #
129
+ def prepend(klass, *args)
130
+ remove(klass) if exists?(klass)
131
+ entries.insert(0, Entry.new(klass, *args))
132
+ end
133
+
134
+ #
135
+ # Add a middleware before another middleware.
136
+ #
137
+ # @param [Class] oldklass The middleware class before which the new middleware should be inserted.
138
+ # @param [Class] newklass The middleware class to insert.
139
+ # @param [Arry<any>] *args The list of arguments for the inserted middleware.
140
+ #
141
+ # @return [Array<Cloudtasker::Middleware::Chain::Entry>] The updated list of middlewares
142
+ #
143
+ def insert_before(oldklass, newklass, *args)
144
+ i = entries.index { |entry| entry.klass == newklass }
145
+ new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
146
+ i = entries.index { |entry| entry.klass == oldklass } || 0
147
+ entries.insert(i, new_entry)
148
+ end
149
+
150
+ #
151
+ # Add a middleware after another middleware.
152
+ #
153
+ # @param [Class] oldklass The middleware class after which the new middleware should be inserted.
154
+ # @param [Class] newklass The middleware class to insert.
155
+ # @param [Arry<any>] *args The list of arguments for the inserted middleware.
156
+ #
157
+ # @return [Array<Cloudtasker::Middleware::Chain::Entry>] The updated list of middlewares
158
+ #
159
+ def insert_after(oldklass, newklass, *args)
160
+ i = entries.index { |entry| entry.klass == newklass }
161
+ new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
162
+ i = entries.index { |entry| entry.klass == oldklass } || (entries.count - 1)
163
+ entries.insert(i + 1, new_entry)
164
+ end
165
+
166
+ #
167
+ # Checks if middleware has been added to the list.
168
+ #
169
+ # @param [Class] klass The middleware class to check.
170
+ #
171
+ # @return [Boolean] Return true if the middleware is in the list.
172
+ #
173
+ def exists?(klass)
174
+ any? { |entry| entry.klass == klass }
175
+ end
176
+
177
+ #
178
+ # Checks if the middlware list is empty
179
+ #
180
+ # @return [Boolean] Return true if the middleware list is empty.
181
+ #
182
+ def empty?
183
+ @entries.nil? || @entries.empty?
184
+ end
185
+
186
+ #
187
+ # Return a list of instantiated middlewares. Each middleware gets
188
+ # initialize with the args originally passed to `add`, `insert_before` etc.
189
+ #
190
+ # @return [Array<any>] The list of instantiated middlewares.
191
+ #
192
+ def retrieve
193
+ map(&:make_new)
194
+ end
195
+
196
+ #
197
+ # Empty the list of middlewares.
198
+ #
199
+ # @return [Array<Cloudtasker::Middleware::Chain::Entry>] The updated list of middlewares
200
+ #
201
+ def clear
202
+ entries.clear
203
+ end
204
+
205
+ #
206
+ # Invoke the chain of middlewares.
207
+ #
208
+ # @param [Array<any>] *args The args to pass to each middleware.
209
+ #
210
+ def invoke(*args)
211
+ return yield if empty?
212
+
213
+ chain = retrieve.dup
214
+ traverse_chain = lambda do
215
+ if chain.empty?
216
+ yield
217
+ else
218
+ chain.shift.call(*args, &traverse_chain)
219
+ end
220
+ end
221
+ traverse_chain.call
222
+ end
223
+ end
224
+
225
+ # Middleware list item.
226
+ class Entry
227
+ attr_reader :klass, :args
228
+
229
+ #
230
+ # Build a new entry.
231
+ #
232
+ # @param [Class] klass The middleware class.
233
+ # @param [Array<any>] *args The list of arguments for the middleware.
234
+ #
235
+ def initialize(klass, *args)
236
+ @klass = klass
237
+ @args = args
238
+ end
239
+
240
+ #
241
+ # Return an instantiated middleware.
242
+ #
243
+ # @return [Any] The instantiated middleware.
244
+ #
245
+ def make_new
246
+ @klass.new(*@args)
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudtasker
4
+ class MissingWorkerArgumentsError < StandardError
5
+ end
6
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redis'
4
+ require 'connection_pool'
5
+
6
+ module Cloudtasker
7
+ # A wrapper with helper methods for redis
8
+ class RedisClient
9
+ # Suffix added to cache keys when locking them
10
+ LOCK_KEY_PREFIX = 'cloudtasker/lock'
11
+ LOCK_DURATION = 2 # seconds
12
+ LOCK_WAIT_DURATION = 0.03 # seconds
13
+
14
+ # Default pool size used for Redis
15
+ DEFAULT_POOL_SIZE = ENV.fetch('RAILS_MAX_THREADS', 25)
16
+ DEFAULT_POOL_TIMEOUT = 5
17
+
18
+ def self.client
19
+ @client ||= begin
20
+ pool_size = Cloudtasker.config.redis&.dig(:pool_size) || DEFAULT_POOL_SIZE
21
+ pool_timeout = Cloudtasker.config.redis&.dig(:pool_timeout) || DEFAULT_POOL_TIMEOUT
22
+ ConnectionPool.new(size: pool_size, timeout: pool_timeout) do
23
+ Redis.new(Cloudtasker.config.redis || {})
24
+ end
25
+ end
26
+ end
27
+
28
+ #
29
+ # Return the underlying redis client.
30
+ #
31
+ # @return [Redis] The redis client.
32
+ #
33
+ def client
34
+ @client ||= self.class.client
35
+ end
36
+
37
+ #
38
+ # Get a cache entry and parse it as JSON.
39
+ #
40
+ # @param [String, Symbol] key The cache key to fetch.
41
+ #
42
+ # @return [Hash, Array] The content of the cache key, parsed as JSON.
43
+ #
44
+ def fetch(key)
45
+ return nil unless (val = get(key.to_s))
46
+
47
+ JSON.parse(val, symbolize_names: true)
48
+ rescue JSON::ParserError
49
+ nil
50
+ end
51
+
52
+ #
53
+ # Write a cache entry as JSON.
54
+ #
55
+ # @param [String, Symbol] key The cache key to write.
56
+ # @param [Hash, Array] content The content to write.
57
+ #
58
+ # @return [String] Redis response code.
59
+ #
60
+ def write(key, content)
61
+ set(key.to_s, content.to_json)
62
+ end
63
+
64
+ #
65
+ # Acquire a lock on a cache entry.
66
+ #
67
+ # Locks are enforced to be short-lived (2s).
68
+ # The yielded block should limit its logic to short operations (e.g. redis get/set).
69
+ #
70
+ # @example
71
+ # redis = RedisClient.new
72
+ # redis.with_lock('foo')
73
+ # content = redis.fetch('foo')
74
+ # redis.set(content.merge(bar: 'bar).to_json)
75
+ # end
76
+ #
77
+ # @param [String] cache_key The cache key to access.
78
+ # @param [Integer] max_wait The number of seconds after which the lock will be cleared anyway.
79
+ #
80
+ def with_lock(cache_key, max_wait: nil)
81
+ return nil unless cache_key
82
+
83
+ # Set max wait
84
+ max_wait = (max_wait || LOCK_DURATION).to_i
85
+
86
+ # Wait to acquire lock
87
+ lock_key = [LOCK_KEY_PREFIX, cache_key].join('/')
88
+ client.with do |conn|
89
+ sleep(LOCK_WAIT_DURATION) until conn.set(lock_key, true, nx: true, ex: max_wait)
90
+ end
91
+
92
+ # yield content
93
+ yield
94
+ ensure
95
+ del(lock_key)
96
+ end
97
+
98
+ #
99
+ # Clear all redis keys
100
+ #
101
+ # @return [Integer] The number of keys deleted
102
+ #
103
+ def clear
104
+ all_keys = keys
105
+ return 0 if all_keys.empty?
106
+
107
+ # Delete all keys
108
+ del(*all_keys)
109
+ end
110
+
111
+ #
112
+ # Return all keys matching the provided patterns.
113
+ #
114
+ # @param [String] pattern A redis compatible pattern.
115
+ #
116
+ # @return [Array<String>] The list of matching keys
117
+ #
118
+ def search(pattern)
119
+ # Initialize loop variables
120
+ cursor = nil
121
+ list = []
122
+
123
+ # Scan and capture matching keys
124
+ client.with do |conn|
125
+ while cursor != 0
126
+ scan = conn.scan(cursor || 0, match: pattern)
127
+ list += scan[1]
128
+ cursor = scan[0].to_i
129
+ end
130
+ end
131
+
132
+ list
133
+ end
134
+
135
+ #
136
+ # Delegate all methods to the redis client.
137
+ # Ruby 3 delegation method style.
138
+ #
139
+ # @param [String, Symbol] name The method to delegate.
140
+ # @param [Array<any>] *args The list of method positional arguments.
141
+ # @param [Hash<any>] *kwargs The list of method keyword arguments.
142
+ # @param [Proc] &block Block passed to the method.
143
+ #
144
+ # @return [Any] The method return value
145
+ #
146
+ def method_missing(name, ...)
147
+ if Redis.method_defined?(name)
148
+ client.with { |c| c.send(name, ...) }
149
+ else
150
+ super
151
+ end
152
+ end
153
+
154
+ #
155
+ # Check if the class respond to a certain method.
156
+ #
157
+ # @param [String, Symbol] name The name of the method.
158
+ # @param [Boolean] include_private Whether to check private methods or not. Default to false.
159
+ #
160
+ # @return [Boolean] Return true if the class respond to this method.
161
+ #
162
+ def respond_to_missing?(name, include_private = false)
163
+ Redis.method_defined?(name) || super
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudtasker
4
+ class RetryWorkerError < StandardError
5
+ end
6
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudtasker
4
+ module Storable
5
+ # Add ability to store and pull workers in Redis under a specific namespace
6
+ module Worker
7
+ # Add class method to including class
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ # Module class methods
13
+ module ClassMethods
14
+ #
15
+ # Return the namespaced store key used to store jobs that
16
+ # have been parked and should be manually popped later.
17
+ #
18
+ # @param [String] namespace The user-provided store namespace
19
+ #
20
+ # @return [String] The full store cache key
21
+ #
22
+ def store_cache_key(namespace)
23
+ cache_key([Config::WORKER_STORE_PREFIX, namespace])
24
+ end
25
+
26
+ #
27
+ # Push the worker to a namespaced store.
28
+ #
29
+ # @param [String] namespace The store namespace
30
+ # @param [Array<any>] *args List of worker arguments
31
+ #
32
+ # @return [String] The number of elements added to the store
33
+ #
34
+ def push_to_store(namespace, *args)
35
+ redis.rpush(store_cache_key(namespace), [args.to_json])
36
+ end
37
+
38
+ #
39
+ # Push many workers to a namespaced store at once.
40
+ #
41
+ # @param [String] namespace The store namespace
42
+ # @param [Array<Array<any>>] args_list A list of arguments for each worker
43
+ #
44
+ # @return [String] The number of elements added to the store
45
+ #
46
+ def push_many_to_store(namespace, args_list)
47
+ redis.rpush(store_cache_key(namespace), args_list.map(&:to_json))
48
+ end
49
+
50
+ #
51
+ # Pull the jobs from the namespaced store and enqueue them.
52
+ #
53
+ # @param [String] namespace The store namespace.
54
+ # @param [Integer] page_size The number of items to pull on each page. Defaults to 1000.
55
+ #
56
+ def pull_all_from_store(namespace, page_size: 1000)
57
+ items = nil
58
+
59
+ while items.nil? || items.present?
60
+ # Pull items
61
+ items = redis.lpop(store_cache_key(namespace), page_size).to_a
62
+
63
+ # For each item, execute block or enqueue it
64
+ items.each do |args_json|
65
+ worker_args = JSON.parse(args_json)
66
+
67
+ if block_given?
68
+ yield(worker_args)
69
+ else
70
+ perform_async(*worker_args)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'storable/worker'