orbitalqueue 0.0.1 → 0.0.3
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/README.md +268 -4
- data/lib/orbitalqueue.rb +224 -21
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ebd1ce38a6bd9bdc2acd6f36571966aeddddef703cedb97136d67b8cad64316
|
4
|
+
data.tar.gz: '061397dd5691647ddb49753f48d7f931fdc17840b4bda68857b3f2f1b1d88431'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37c7cd635345003b83446d3b82ce7036b91a0b3ba30913d41d62f7b06a0c618e7f6648784aab2c17661eb837b96330abedb06751a8db54f781587ae529daf2de
|
7
|
+
data.tar.gz: 3ee034e1f5d6ef47fb49ce744ea2355bf69ce54d8571c94436c1c092a04c448423e8ea2f01435eda36eecebf04e3a07980a45f66a94157895730d684e99faf71
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# Orbital Queue
|
2
2
|
|
3
3
|
[](https://github.com/reasonset/orbital-queue/actions/workflows/ci.yml)
|
4
4
|
|
@@ -39,18 +39,282 @@ Since enqueued objects are serialized using `Marshal` and persisted as files, ca
|
|
39
39
|
require 'orbitalqueue'
|
40
40
|
|
41
41
|
queue = OrbitalQueue.new("/path/to/queue")
|
42
|
-
|
42
|
+
item = queue.pop
|
43
|
+
data = item.data
|
43
44
|
|
44
45
|
# Something, something...
|
45
46
|
|
46
|
-
|
47
|
+
item.complete
|
47
48
|
```
|
48
49
|
|
49
50
|
Calling `#pop` retrieves a single item from the queue in no particular order.
|
50
51
|
|
52
|
+
```ruby
|
53
|
+
queue = OrbitalQueue.new("/path/to/queue")
|
54
|
+
item = queue.pop!
|
55
|
+
|
56
|
+
# Something, something...
|
57
|
+
```
|
58
|
+
|
51
59
|
The retrieved item enters a checkout state, and must be finalized by calling `#complete` once processing is finished.
|
52
60
|
|
53
61
|
If guaranteed completion is not required and the item should be removed immediately upon retrieval, use `#pop!` instead.
|
54
62
|
|
55
|
-
Both `#pop` and `#pop!`
|
63
|
+
Both `#pop` and `#pop!` methods returns `OrbitalQueue::QueueObject` object.
|
64
|
+
You can access queue data via `OrbitalQueue::QueueObject#data`.
|
65
|
+
|
66
|
+
When calling `#complete`, `#destruct`, or `#defer` directly on `OrbitalQueue`, `queue_id` must be given.
|
67
|
+
If you use these methods via `OrbitalQueue::QueueObject`, the `queue_id` is internally handled and can be omitted.
|
68
|
+
|
69
|
+
### Dequeue with block
|
70
|
+
|
71
|
+
You can call `#pop` with a block.
|
72
|
+
|
73
|
+
When call with block, block is called with queue data as an argument.
|
74
|
+
|
75
|
+
queue item automatically complete when blocke ends without error.
|
76
|
+
|
77
|
+
The `#pop` method can be called with an optional block.
|
78
|
+
When used this way, the block is invoked with the item's data.
|
79
|
+
After successful execution (without exceptions), the item is considered complete and removed from the queue.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
queue = OrbitalQueue.new("/path/to/queue")
|
83
|
+
queue.pop do |data|
|
84
|
+
#...
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
### Dequeue loop
|
89
|
+
|
90
|
+
While technically possible to loop over `#pop`, it doesn't support block-based iteration.
|
91
|
+
|
92
|
+
Use `#each` when you want to process items with a block in a loop.
|
93
|
+
It provides a clean and idiomatic Ruby style for sequential processing.
|
94
|
+
|
95
|
+
For full control over each item, use `#each_item`, which yields a `QueueObject` instead of just the data.
|
96
|
+
|
97
|
+
`#each_item` iterates over queue items as `QueueObject` instances, not raw data.
|
98
|
+
This allows direct control over each job—for example, deferring its execution using `#defer`.
|
99
|
+
|
100
|
+
Unlike `#each`, which automatically marks items as complete after the block runs,
|
101
|
+
`#each_item` exposes queue control for cases where completion isn't guaranteed or deferred handling is needed.
|
102
|
+
|
103
|
+
### Job deferral
|
104
|
+
|
105
|
+
`OrbitalQueue` supports job deferral, enabling queue items to be scheduled for retry or postponed execution with precise control.
|
106
|
+
|
107
|
+
Calling `#defer` transitions a queue item into the deferred state.
|
108
|
+
This moves the item's file into the `.defer` directory and creates a retry metadata file in `.retry`.
|
109
|
+
|
110
|
+
Once an item has been deferred, it is associated with retry information, referred to as `retry_data`.
|
111
|
+
This is a `Hash` object that tracks rescheduling behavior.
|
112
|
+
|
113
|
+
`#defer` uses `retry_data` to determine whether the item can be retried, including retry count limits.
|
114
|
+
When called with a block, `#defer` yields `retry_data` as an argument, allowing custom modifications.
|
115
|
+
|
116
|
+
Regardless of how it's called, `retry_data` is persisted after the method returns.
|
117
|
+
To control deferral behavior, modify values inside `retry_data`—typically by changing the `:until` field (Unix timestamp).
|
118
|
+
This field specifies when the item should become eligible for re-queueing, making it ideal for implementing backoff strategies.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
queue.each_item do |item|
|
122
|
+
begin
|
123
|
+
# Something...
|
124
|
+
rescue
|
125
|
+
item.defer(Time.now + 300) # Retry after 5 minutes.
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
with block:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
queue.each_item do |item|
|
134
|
+
begin
|
135
|
+
# Something...
|
136
|
+
rescue
|
137
|
+
item.defer do |retry_item|
|
138
|
+
if retry_item[:count] > 5
|
139
|
+
item.destruct
|
140
|
+
else
|
141
|
+
retry_item[:until] = Time.now + 300 * retry_item[:count]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
The `#defer` method's main role is to move the queue item file into the `.defer` directory.
|
149
|
+
Since `OrbitalQueue` operates without a server, it cannot scan `.defer` efficiently or restore deferred items automatically.
|
150
|
+
|
151
|
+
Aside from the internal keys `:count` and `:until`, all other values in `retry_data` are preserved as-is.
|
152
|
+
You can freely store custom metadata inside it—such as failure reasons or backoff parameters.
|
153
|
+
|
154
|
+
The `#destruct` method removes all files associated with a queue item and raises `OrbitalQueue::ItemDestructed`.
|
155
|
+
This exception is caught inside `#defer`, allowing `#destruct` to abort the entire deferral process.
|
156
|
+
|
157
|
+
⚠️ Do not rescue `OrbitalQueue::ItemDestructed` within a `#defer` block.
|
158
|
+
If the block completes normally after destruction, queue integrity may be violated.
|
159
|
+
|
160
|
+
The `#archive` method creates a Marshal-serialized file under `.archive`, containing the original data, its `retry_data`, and an `archiveinfo` hash.
|
161
|
+
After archiving, it calls `#destruct` to remove the live queue item.
|
162
|
+
|
163
|
+
Archived files are never accessed by OrbitalQueue itself.
|
164
|
+
|
165
|
+
Note: Because `archive` discards in-memory `retry_data`, you cannot modify it before archiving.
|
166
|
+
Instead, extra metadata should be passed as arguments to `archive` and will be merged into `archiveinfo`.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
queue.each_item do |item|
|
170
|
+
begin
|
171
|
+
#...
|
172
|
+
rescue
|
173
|
+
item.defer do |retry_data|
|
174
|
+
if retry_data[:count] > 5
|
175
|
+
item.archive({reason: "Host timeout"})
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
### Resume deferred job
|
183
|
+
|
184
|
+
Deferred queue items must be manually restored using the `resume` method.
|
185
|
+
This method is typically executed by a separate worker from the one handling regular queue operations.
|
186
|
+
|
187
|
+
It is defined as an instance method:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
queue = OrbitalQueue.new("/path/to/queue")
|
191
|
+
queue.resume
|
192
|
+
```
|
193
|
+
|
194
|
+
For convenience, resume can also be called as a class method:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
OrbitalQueue.resume("/path/to/queue")
|
198
|
+
```
|
199
|
+
|
200
|
+
# About Orbital Design
|
201
|
+
|
202
|
+
## Description
|
203
|
+
|
204
|
+
Orbital Design is a programming pattern optimized for distributed systems.
|
205
|
+
It is especially well-suited to environments where:
|
206
|
+
|
207
|
+
* New data constantly arrives without pause
|
208
|
+
* Processing workloads vary in complexity and demand asymmetric distribution
|
209
|
+
* Systems start small but must scale seamlessly to clustered deployments
|
210
|
+
|
211
|
+
## Philosophy
|
212
|
+
|
213
|
+
Orbital Design distinguishes between "agents" and "workers".
|
214
|
+
In most cases, an agent refers to a program, while a worker is a process.
|
215
|
+
|
216
|
+
The core principle is that **workers only need to care about what they do**.
|
217
|
+
Upon starting, a worker picks a single available job prepared for it and executes it—no coordination or negotiation required.
|
218
|
+
|
219
|
+
This behavior mirrors that of individuals in a larger society, or cells within a living organism.
|
220
|
+
Each unit performs its specific role independently.
|
221
|
+
|
222
|
+
This philosophy is deeply aligned with the Unix principle:
|
223
|
+
_"Do one thing, and do it well."_
|
224
|
+
|
225
|
+
## Core Rules of Orbital Design
|
226
|
+
|
227
|
+
Orbital Design defines a set of principles to preserve decoupling, clarity, and safety in distributed systems:
|
228
|
+
|
229
|
+
- *Agents must remain small and focused*.
|
230
|
+
Each agent is responsible for doing one thing, and doing it well.
|
231
|
+
|
232
|
+
- *Workers must not access data unrelated to their task*, nor inspect other workers' state or progress.
|
233
|
+
|
234
|
+
- *Write access to a database or dataset must be held by exactly one agent*.
|
235
|
+
This prevents conflicting updates and maintains integrity.
|
236
|
+
|
237
|
+
- *Deletion from a database may only be performed by:*
|
238
|
+
- A worker with exclusive read access to the data, or
|
239
|
+
- A sweeper worker that receives notifications from all readers
|
240
|
+
|
241
|
+
- *Agents must not block on I/O*.
|
242
|
+
Blocking input/output disrupts concurrency and undermines distributed fairness.
|
243
|
+
|
244
|
+
## Benefits of Orbital Design
|
245
|
+
|
246
|
+
### Ease of Implementation
|
247
|
+
|
248
|
+
Each program is small and focused, with clearly defined responsibilities.
|
249
|
+
Because agents cannot access global state and avoid blocking operations, race conditions are structurally prevented.
|
250
|
+
|
251
|
+
This allows each unit to concentrate solely on its task—no need to worry about concurrency or system state.
|
252
|
+
|
253
|
+
## Simplicity
|
254
|
+
|
255
|
+
Orbital Design requires minimal complexity.
|
256
|
+
It does not depend on heavy frameworks or advanced techniques.
|
257
|
+
|
258
|
+
It can be fully implemented using standard OS features such as file systems, processes, and signals.
|
259
|
+
No special measures are needed to achieve scalability.
|
260
|
+
|
261
|
+
## Language Agnosticism
|
262
|
+
|
263
|
+
Programs are isolated and do not interfere with one another.
|
264
|
+
This allows you to implement each agent in any language that suits the task.
|
265
|
+
|
266
|
+
You can choose a language based on convenience, libraries, or performance.
|
267
|
+
Critical paths can be written in C, C++, Rust, or Nim as needed, while simpler agents may use scripting languages.
|
268
|
+
|
269
|
+
Even agents with similar functionality can be written in different languages depending on input format or operational context.
|
270
|
+
|
271
|
+
## Parallelism and Decomposition
|
272
|
+
|
273
|
+
Restricted I/O paths eliminate contention during parallel execution.
|
274
|
+
By following the design pattern, concurrency becomes straightforward.
|
275
|
+
|
276
|
+
No locking or synchronization is required—so parallel processing not only becomes easier to write, but also more performance-effective.
|
277
|
+
|
278
|
+
Additionally, replacing I/O layers with network interfaces naturally extends the system into distributed computing.
|
279
|
+
|
280
|
+
## Signal Friendliness
|
281
|
+
|
282
|
+
Although OS-level signals are simple and often underutilized for concurrency,
|
283
|
+
Orbital Design enables practical use of signals for multi-worker environments.
|
284
|
+
|
285
|
+
This can provide a minor advantage when building cooperative worker pools.
|
286
|
+
|
287
|
+
## Compatibility with Systemd
|
288
|
+
|
289
|
+
Systemd's `@.service` unit files support multi-instance execution.
|
290
|
+
|
291
|
+
Agents designed with Orbital Design require no more than a worker name as an argument, making them trivially scalable to multiple instances.
|
292
|
+
This provides a low-effort pathway to multi-worker deployments, with restarts handled by Systemd itself.
|
293
|
+
|
294
|
+
## Compatibility with Job Schedulers
|
295
|
+
|
296
|
+
Orbital Design is not limited to multi-worker or multi-instance models.
|
297
|
+
It is especially well-suited to systems that rely on periodic execution by job schedulers.
|
298
|
+
|
299
|
+
While worker-driven systems react to runtime state, job schedulers operate on time-based triggers.
|
300
|
+
Thanks to its stateless model, Orbital Design allows agents to run regardless of timing or system condition.
|
301
|
+
|
302
|
+
## Shell Script Friendly
|
303
|
+
|
304
|
+
Each agent has a clear and narrow scope, with no need for shared database schemas.
|
305
|
+
This makes it easy to write parts of the system in shell scripts where appropriate.
|
306
|
+
|
307
|
+
In practice, this results in simpler and more maintainable solutions in more cases than expected.
|
308
|
+
|
309
|
+
## Replaceable Components
|
310
|
+
|
311
|
+
Programs in Orbital Design are small and well-scoped.
|
312
|
+
When a language, library, or performance characteristic becomes a limitation, swapping out components comes at low cost.
|
313
|
+
|
314
|
+
This helps maintain long-term system health and avoids software decay.
|
315
|
+
|
316
|
+
# Finally
|
317
|
+
|
318
|
+
“The library is minimal. The idea is not.”
|
56
319
|
|
320
|
+
Orbital Design is not a framework. It's a way of thinking. It thrives where ideas are shared freely.
|
data/lib/orbitalqueue.rb
CHANGED
@@ -14,19 +14,29 @@ class OrbitalQueue
|
|
14
14
|
class QueueUnexisting < QueueError
|
15
15
|
end
|
16
16
|
|
17
|
+
class ItemDestructed < QueueError
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return deferred item to queue
|
21
|
+
def self.resume dir
|
22
|
+
self.new(dir).resume
|
23
|
+
end
|
24
|
+
|
17
25
|
# Create queue master in presented dir.
|
18
26
|
#
|
19
|
-
# dir
|
20
|
-
# create
|
27
|
+
# dir:: Queue directory
|
28
|
+
# create:: If true is given, creates the queue directory when it is missing
|
21
29
|
def initialize dir, create=false
|
22
30
|
@queue_dir = dir
|
23
31
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
32
|
+
%w:.checkout .defer .retry .archive:.each do |subdir|
|
33
|
+
unless File.exist?(File.join(dir, subdir))
|
34
|
+
if create
|
35
|
+
require 'fileutils'
|
36
|
+
FileUtils.mkdir_p(File.join(dir, subdir))
|
37
|
+
else
|
38
|
+
raise QueueUnexisting.new("Queue directory #{dir} does not exist.")
|
39
|
+
end
|
30
40
|
end
|
31
41
|
end
|
32
42
|
end
|
@@ -45,6 +55,12 @@ class OrbitalQueue
|
|
45
55
|
|
46
56
|
# Pop data from queue.
|
47
57
|
# Popped queue items are placed in the checkout directory. After processing is complete, +#complete+ must be called to remove the item from the queue.
|
58
|
+
#
|
59
|
+
# If block is given, complete automatically after yield.
|
60
|
+
#
|
61
|
+
# :call-seq:
|
62
|
+
# pop() -> queue_object
|
63
|
+
# pop() {|data| ... } -> queue_id
|
48
64
|
def pop
|
49
65
|
queue_data = nil
|
50
66
|
queue_id = nil
|
@@ -57,38 +73,225 @@ class OrbitalQueue
|
|
57
73
|
next
|
58
74
|
end
|
59
75
|
|
60
|
-
|
76
|
+
data = Marshal.load(File.read File.join(@queue_dir, ".checkout", File.basename(qf)))
|
61
77
|
queue_id = File.basename qf, ".marshal"
|
62
78
|
|
63
|
-
|
64
|
-
queue_data[:queue_id] = queue_id
|
65
|
-
else
|
66
|
-
queue_data = {queue_id: queue_id, data: queue_data}
|
67
|
-
end
|
79
|
+
queue_data = OrbitalQueue::QueueObject.new(self, data, queue_id)
|
68
80
|
break
|
69
81
|
end
|
70
82
|
|
71
|
-
queue_data
|
83
|
+
if queue_data && block_given?
|
84
|
+
yield queue_data.data
|
85
|
+
complete queue_id
|
86
|
+
else
|
87
|
+
queue_data
|
88
|
+
end
|
72
89
|
end
|
73
90
|
|
74
|
-
# Pop data
|
91
|
+
# Pop data and remove it from queue.
|
92
|
+
#
|
93
|
+
# :call-seq:
|
94
|
+
# pop!() -> queue_object
|
75
95
|
def pop!
|
76
|
-
|
77
|
-
if
|
78
|
-
complete
|
96
|
+
queue_item = pop
|
97
|
+
if queue_item
|
98
|
+
queue_item.complete
|
99
|
+
end
|
100
|
+
|
101
|
+
queue_item
|
102
|
+
end
|
103
|
+
|
104
|
+
# Iterate each queue item data.
|
105
|
+
def each
|
106
|
+
while item = pop
|
107
|
+
yield item.data
|
108
|
+
item.complete
|
79
109
|
end
|
110
|
+
end
|
80
111
|
|
81
|
-
|
112
|
+
# Iterate each queue item.
|
113
|
+
def each_item
|
114
|
+
while item = pop
|
115
|
+
yield item
|
116
|
+
item.complete unless item.deferred?
|
117
|
+
end
|
82
118
|
end
|
83
119
|
|
84
120
|
# Remove checked out queue item.
|
85
121
|
def complete queue_id
|
86
122
|
begin
|
87
|
-
File.
|
123
|
+
checkout_file = File.join(@queue_dir, ".checkout", (queue_id + ".marshal"))
|
124
|
+
retry_file = File.join(@queue_dir, ".retry", (queue_id + ".marshal"))
|
125
|
+
File.delete(checkout_file)
|
126
|
+
File.delete(retry_file) if File.exist?(retry_file)
|
88
127
|
rescue SystemCallError => e
|
89
128
|
raise QueueRemoveError, "Failed to complete queue #{queue_id}: #{e.class}"
|
90
129
|
end
|
91
130
|
|
92
131
|
queue_id
|
93
132
|
end
|
133
|
+
|
134
|
+
# Delete all related files with queue_id, and raise ItemDectructed exception.
|
135
|
+
def destruct queue_id
|
136
|
+
queue_files = Dir.glob([@queue_dir, "**", (queue_id + ".marshal")].join("/"), File::FNM_DOTMATCH)
|
137
|
+
File.delete(*queue_files) unless queue_files.empty?
|
138
|
+
raise ItemDestructed, "#{queue_id} is destructed."
|
139
|
+
end
|
140
|
+
|
141
|
+
# Archive current queue relative data and call +destruct+.
|
142
|
+
# This method should be called from QueueObject.
|
143
|
+
def archive queue_id, data, archiveinfo_additional={} # :nodoc:
|
144
|
+
archiveinfo = archiveinfo_additional.merge({
|
145
|
+
archived_at: Time.now.to_i
|
146
|
+
})
|
147
|
+
|
148
|
+
retry_data = load_retryobj queue_id
|
149
|
+
|
150
|
+
archive_data = {
|
151
|
+
archiveinfo: archiveinfo,
|
152
|
+
retry_data: retry_data,
|
153
|
+
data: data
|
154
|
+
}
|
155
|
+
|
156
|
+
File.open(File.join(@queue_dir, ".archive", (["archive", archiveinfo[:archived_at], queue_id].join("-") + ".marshal")), "w") {|f| Marshal.dump archive_data, f}
|
157
|
+
|
158
|
+
destruct queue_id
|
159
|
+
end
|
160
|
+
|
161
|
+
# Mark queue item as deferred.
|
162
|
+
#
|
163
|
+
# :call-seq:
|
164
|
+
# defer(queue_id, time_at, max_count=nil) -> retry_data | nil
|
165
|
+
# defer() {|retry_data| ... } -> retry_data | nil
|
166
|
+
def defer queue_id, time_at=nil, max_count=nil
|
167
|
+
retry_data = load_retryobj queue_id
|
168
|
+
retry_data[:count] += 1
|
169
|
+
if block_given?
|
170
|
+
yield retry_data
|
171
|
+
else
|
172
|
+
unless time_at
|
173
|
+
raise ArgumentError, "time_at is required when no block is given."
|
174
|
+
end
|
175
|
+
|
176
|
+
if max_count && retry_data[:count] > max_count
|
177
|
+
destruct queue_id
|
178
|
+
end
|
179
|
+
retry_data[:until] = time_at.to_i
|
180
|
+
end
|
181
|
+
|
182
|
+
dump_retryobj queue_id, retry_data
|
183
|
+
|
184
|
+
checkout_path = File.join(@queue_dir, ".checkout", (queue_id) + ".marshal")
|
185
|
+
defer_path = File.join(@queue_dir, ".defer", (queue_id) + ".marshal")
|
186
|
+
File.rename checkout_path, defer_path
|
187
|
+
|
188
|
+
retry_data
|
189
|
+
rescue ItemDestructed
|
190
|
+
nil
|
191
|
+
end
|
192
|
+
|
193
|
+
# Return deferred item to queue.
|
194
|
+
def resume
|
195
|
+
now = Time.now.to_i
|
196
|
+
deferred_files = Dir.children(File.join(@queue_dir, ".retry"))
|
197
|
+
deferred_files.each do |fn|
|
198
|
+
retry_path = File.join(@queue_dir, ".retry", fn)
|
199
|
+
retry_data = Marshal.load File.read retry_path
|
200
|
+
|
201
|
+
if retry_data[:until] < now
|
202
|
+
queue_path = File.join(@queue_dir, fn)
|
203
|
+
defer_path = File.join(@queue_dir, ".defer", fn)
|
204
|
+
File.rename(defer_path, queue_path)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
nil
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
# Save to .retry
|
214
|
+
def dump_retryobj queue_id, data
|
215
|
+
retry_path = File.join(@queue_dir, ".retry", (queue_id) + ".marshal")
|
216
|
+
File.open(retry_path, "w") {|f| Marshal.dump data, f }
|
217
|
+
nil
|
218
|
+
end
|
219
|
+
|
220
|
+
# Load from .retry
|
221
|
+
def load_retryobj queue_id
|
222
|
+
retry_path = File.join(@queue_dir, ".retry", (queue_id) + ".marshal")
|
223
|
+
retry_data = nil
|
224
|
+
if File.exist? retry_path
|
225
|
+
retry_data = Marshal.load File.read retry_path
|
226
|
+
else
|
227
|
+
retry_data = {
|
228
|
+
count: 0,
|
229
|
+
until: nil
|
230
|
+
}
|
231
|
+
end
|
232
|
+
|
233
|
+
retry_data
|
234
|
+
end
|
94
235
|
end
|
236
|
+
|
237
|
+
# Queue item capsule.
|
238
|
+
class OrbitalQueue::QueueObject
|
239
|
+
def initialize(queue, data, queue_id)
|
240
|
+
@queue = queue
|
241
|
+
@data = data
|
242
|
+
@queue_id = queue_id
|
243
|
+
@completed = false
|
244
|
+
@deferred = false
|
245
|
+
end
|
246
|
+
|
247
|
+
attr_reader :data
|
248
|
+
|
249
|
+
# Another complete interface.
|
250
|
+
def complete
|
251
|
+
if @completed
|
252
|
+
nil
|
253
|
+
else
|
254
|
+
@queue.complete(@queue_id)
|
255
|
+
@completed = true
|
256
|
+
@queue_id
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Wrap for the end of queue item.
|
261
|
+
def destruct
|
262
|
+
@completed = true
|
263
|
+
@queue.destruct(@queue_id)
|
264
|
+
end
|
265
|
+
|
266
|
+
# Archive current queue relative data and call +destruct+.
|
267
|
+
def archive archiveinfo_additional={}
|
268
|
+
@completed = true
|
269
|
+
@queue.archive @queue_id, @data, archiveinfo_additional
|
270
|
+
end
|
271
|
+
|
272
|
+
# Terrible redundunt method.
|
273
|
+
def complete? # :nodoc:
|
274
|
+
@completed
|
275
|
+
end
|
276
|
+
|
277
|
+
# Retry later.
|
278
|
+
#
|
279
|
+
# time_at:: Deferring retry until this time
|
280
|
+
# max_count:: Retry count limit
|
281
|
+
#
|
282
|
+
# :call-seq:
|
283
|
+
# defer(time_at, max_count=nil) -> retry_data
|
284
|
+
# defer() {|retry_data| ... } -> retry_data
|
285
|
+
def defer time_at=nil, max_count=nil, &block
|
286
|
+
if block
|
287
|
+
@queue.defer(@queue_id, &block)
|
288
|
+
else
|
289
|
+
@queue.defer(@queue_id, time_at, max_count)
|
290
|
+
end
|
291
|
+
@deferred = true
|
292
|
+
end
|
293
|
+
|
294
|
+
def deferred?
|
295
|
+
@deferred
|
296
|
+
end
|
297
|
+
end
|