ruby_job 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/.ruby-version +1 -0
- data/.travis.yml +30 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/COPYING +674 -0
- data/COPYING.LESSER +165 -0
- data/Gemfile +8 -0
- data/Guardfile +50 -0
- data/LICENSE +5 -0
- data/LICENSE.txt +5 -0
- data/README.md +226 -0
- data/Rakefile +3 -0
- data/_config.yml +1 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/codecov.yml +28 -0
- data/lib/ruby_job/in_memory_job_store.rb +103 -0
- data/lib/ruby_job/job.rb +95 -0
- data/lib/ruby_job/job_processor.rb +16 -0
- data/lib/ruby_job/job_store.rb +41 -0
- data/lib/ruby_job/threaded_server.rb +49 -0
- data/lib/ruby_job/version.rb +5 -0
- data/lib/ruby_job/worker.rb +67 -0
- data/lib/ruby_job.rb +9 -0
- data/ruby_job.gemspec +39 -0
- metadata +212 -0
data/COPYING.LESSER
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
GNU LESSER GENERAL PUBLIC LICENSE
|
2
|
+
Version 3, 29 June 2007
|
3
|
+
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
6
|
+
of this license document, but changing it is not allowed.
|
7
|
+
|
8
|
+
|
9
|
+
This version of the GNU Lesser General Public License incorporates
|
10
|
+
the terms and conditions of version 3 of the GNU General Public
|
11
|
+
License, supplemented by the additional permissions listed below.
|
12
|
+
|
13
|
+
0. Additional Definitions.
|
14
|
+
|
15
|
+
As used herein, "this License" refers to version 3 of the GNU Lesser
|
16
|
+
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
17
|
+
General Public License.
|
18
|
+
|
19
|
+
"The Library" refers to a covered work governed by this License,
|
20
|
+
other than an Application or a Combined Work as defined below.
|
21
|
+
|
22
|
+
An "Application" is any work that makes use of an interface provided
|
23
|
+
by the Library, but which is not otherwise based on the Library.
|
24
|
+
Defining a subclass of a class defined by the Library is deemed a mode
|
25
|
+
of using an interface provided by the Library.
|
26
|
+
|
27
|
+
A "Combined Work" is a work produced by combining or linking an
|
28
|
+
Application with the Library. The particular version of the Library
|
29
|
+
with which the Combined Work was made is also called the "Linked
|
30
|
+
Version".
|
31
|
+
|
32
|
+
The "Minimal Corresponding Source" for a Combined Work means the
|
33
|
+
Corresponding Source for the Combined Work, excluding any source code
|
34
|
+
for portions of the Combined Work that, considered in isolation, are
|
35
|
+
based on the Application, and not on the Linked Version.
|
36
|
+
|
37
|
+
The "Corresponding Application Code" for a Combined Work means the
|
38
|
+
object code and/or source code for the Application, including any data
|
39
|
+
and utility programs needed for reproducing the Combined Work from the
|
40
|
+
Application, but excluding the System Libraries of the Combined Work.
|
41
|
+
|
42
|
+
1. Exception to Section 3 of the GNU GPL.
|
43
|
+
|
44
|
+
You may convey a covered work under sections 3 and 4 of this License
|
45
|
+
without being bound by section 3 of the GNU GPL.
|
46
|
+
|
47
|
+
2. Conveying Modified Versions.
|
48
|
+
|
49
|
+
If you modify a copy of the Library, and, in your modifications, a
|
50
|
+
facility refers to a function or data to be supplied by an Application
|
51
|
+
that uses the facility (other than as an argument passed when the
|
52
|
+
facility is invoked), then you may convey a copy of the modified
|
53
|
+
version:
|
54
|
+
|
55
|
+
a) under this License, provided that you make a good faith effort to
|
56
|
+
ensure that, in the event an Application does not supply the
|
57
|
+
function or data, the facility still operates, and performs
|
58
|
+
whatever part of its purpose remains meaningful, or
|
59
|
+
|
60
|
+
b) under the GNU GPL, with none of the additional permissions of
|
61
|
+
this License applicable to that copy.
|
62
|
+
|
63
|
+
3. Object Code Incorporating Material from Library Header Files.
|
64
|
+
|
65
|
+
The object code form of an Application may incorporate material from
|
66
|
+
a header file that is part of the Library. You may convey such object
|
67
|
+
code under terms of your choice, provided that, if the incorporated
|
68
|
+
material is not limited to numerical parameters, data structure
|
69
|
+
layouts and accessors, or small macros, inline functions and templates
|
70
|
+
(ten or fewer lines in length), you do both of the following:
|
71
|
+
|
72
|
+
a) Give prominent notice with each copy of the object code that the
|
73
|
+
Library is used in it and that the Library and its use are
|
74
|
+
covered by this License.
|
75
|
+
|
76
|
+
b) Accompany the object code with a copy of the GNU GPL and this license
|
77
|
+
document.
|
78
|
+
|
79
|
+
4. Combined Works.
|
80
|
+
|
81
|
+
You may convey a Combined Work under terms of your choice that,
|
82
|
+
taken together, effectively do not restrict modification of the
|
83
|
+
portions of the Library contained in the Combined Work and reverse
|
84
|
+
engineering for debugging such modifications, if you also do each of
|
85
|
+
the following:
|
86
|
+
|
87
|
+
a) Give prominent notice with each copy of the Combined Work that
|
88
|
+
the Library is used in it and that the Library and its use are
|
89
|
+
covered by this License.
|
90
|
+
|
91
|
+
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
92
|
+
document.
|
93
|
+
|
94
|
+
c) For a Combined Work that displays copyright notices during
|
95
|
+
execution, include the copyright notice for the Library among
|
96
|
+
these notices, as well as a reference directing the user to the
|
97
|
+
copies of the GNU GPL and this license document.
|
98
|
+
|
99
|
+
d) Do one of the following:
|
100
|
+
|
101
|
+
0) Convey the Minimal Corresponding Source under the terms of this
|
102
|
+
License, and the Corresponding Application Code in a form
|
103
|
+
suitable for, and under terms that permit, the user to
|
104
|
+
recombine or relink the Application with a modified version of
|
105
|
+
the Linked Version to produce a modified Combined Work, in the
|
106
|
+
manner specified by section 6 of the GNU GPL for conveying
|
107
|
+
Corresponding Source.
|
108
|
+
|
109
|
+
1) Use a suitable shared library mechanism for linking with the
|
110
|
+
Library. A suitable mechanism is one that (a) uses at run time
|
111
|
+
a copy of the Library already present on the user's computer
|
112
|
+
system, and (b) will operate properly with a modified version
|
113
|
+
of the Library that is interface-compatible with the Linked
|
114
|
+
Version.
|
115
|
+
|
116
|
+
e) Provide Installation Information, but only if you would otherwise
|
117
|
+
be required to provide such information under section 6 of the
|
118
|
+
GNU GPL, and only to the extent that such information is
|
119
|
+
necessary to install and execute a modified version of the
|
120
|
+
Combined Work produced by recombining or relinking the
|
121
|
+
Application with a modified version of the Linked Version. (If
|
122
|
+
you use option 4d0, the Installation Information must accompany
|
123
|
+
the Minimal Corresponding Source and Corresponding Application
|
124
|
+
Code. If you use option 4d1, you must provide the Installation
|
125
|
+
Information in the manner specified by section 6 of the GNU GPL
|
126
|
+
for conveying Corresponding Source.)
|
127
|
+
|
128
|
+
5. Combined Libraries.
|
129
|
+
|
130
|
+
You may place library facilities that are a work based on the
|
131
|
+
Library side by side in a single library together with other library
|
132
|
+
facilities that are not Applications and are not covered by this
|
133
|
+
License, and convey such a combined library under terms of your
|
134
|
+
choice, if you do both of the following:
|
135
|
+
|
136
|
+
a) Accompany the combined library with a copy of the same work based
|
137
|
+
on the Library, uncombined with any other library facilities,
|
138
|
+
conveyed under the terms of this License.
|
139
|
+
|
140
|
+
b) Give prominent notice with the combined library that part of it
|
141
|
+
is a work based on the Library, and explaining where to find the
|
142
|
+
accompanying uncombined form of the same work.
|
143
|
+
|
144
|
+
6. Revised Versions of the GNU Lesser General Public License.
|
145
|
+
|
146
|
+
The Free Software Foundation may publish revised and/or new versions
|
147
|
+
of the GNU Lesser General Public License from time to time. Such new
|
148
|
+
versions will be similar in spirit to the present version, but may
|
149
|
+
differ in detail to address new problems or concerns.
|
150
|
+
|
151
|
+
Each version is given a distinguishing version number. If the
|
152
|
+
Library as you received it specifies that a certain numbered version
|
153
|
+
of the GNU Lesser General Public License "or any later version"
|
154
|
+
applies to it, you have the option of following the terms and
|
155
|
+
conditions either of that published version or of any later version
|
156
|
+
published by the Free Software Foundation. If the Library as you
|
157
|
+
received it does not specify a version number of the GNU Lesser
|
158
|
+
General Public License, you may choose any version of the GNU Lesser
|
159
|
+
General Public License ever published by the Free Software Foundation.
|
160
|
+
|
161
|
+
If the Library as you received it specifies that a proxy can decide
|
162
|
+
whether future versions of the GNU Lesser General Public License shall
|
163
|
+
apply, that proxy's public statement of acceptance of any version is
|
164
|
+
permanent authorization for you to choose that version for the
|
165
|
+
Library.
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A sample Guardfile
|
4
|
+
# More info at https://github.com/guard/guard#readme
|
5
|
+
|
6
|
+
## Uncomment and set this to only include directories you want to watch
|
7
|
+
# directories %w(app lib config test spec features) \
|
8
|
+
# .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
9
|
+
|
10
|
+
## Note: if you are using the `directories` clause above and you are not
|
11
|
+
## watching the project directory ('.'), then you will want to move
|
12
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
13
|
+
#
|
14
|
+
# $ mkdir config
|
15
|
+
# $ mv Guardfile config/
|
16
|
+
# $ ln -s config/Guardfile .
|
17
|
+
#
|
18
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
19
|
+
|
20
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
21
|
+
# rspec may be run, below are examples of the most common uses.
|
22
|
+
# * bundler: 'bundle exec rspec'
|
23
|
+
# * bundler binstubs: 'bin/rspec'
|
24
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
25
|
+
# installed the spring binstubs per the docs)
|
26
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
27
|
+
# * 'just' rspec: 'rspec'
|
28
|
+
|
29
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
30
|
+
require 'guard/rspec/dsl'
|
31
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
32
|
+
|
33
|
+
# Feel free to open issues for suggestions and improvements
|
34
|
+
|
35
|
+
# RSpec files
|
36
|
+
rspec = dsl.rspec
|
37
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
38
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
39
|
+
watch(rspec.spec_files)
|
40
|
+
|
41
|
+
# Ruby files
|
42
|
+
ruby = dsl.ruby
|
43
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
44
|
+
|
45
|
+
# Turnip features and steps
|
46
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
47
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
48
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance'
|
49
|
+
end
|
50
|
+
end
|
data/LICENSE
ADDED
data/LICENSE.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
[![Code Climate](https://codeclimate.com/github/mimperatore/ruby_job.svg)](https://codeclimate.com/github/mimperatore/ruby_job)
|
2
|
+
[![Test Coverage](https://codeclimate.com/github/mimperatore/ruby_job/badges/coverage.svg)](https://codeclimate.com/github/mimperator/ruby_job/coverage)
|
3
|
+
[![Build Status](https://travis-ci.com/mimperatore/ruby_job.svg?branch=master)](https://codecov.io/gh/mimperatore/ruby_job/branch/master)
|
4
|
+
|
5
|
+
# RubyJob
|
6
|
+
|
7
|
+
RubyJob is a framework for running jobs.
|
8
|
+
|
9
|
+
The current version behaves much like [Sucker Punch](https://github.com/brandonhilkert/sucker_punch), in that it
|
10
|
+
only supports an [In-Memory Job Store](https://github.com/mimperatore/ruby_job/blob/master/lib/ruby_job/in_memory_job_store.rb)
|
11
|
+
implemented through a fast [Fibonacci Heap](https://github.com/mudge/fibonacci_heap).
|
12
|
+
|
13
|
+
The initial version, which supports only a single queue, runs **200% faster than Sucker Punch**, capable of processing **1,000,000** simple jobs in **28 seconds**
|
14
|
+
vs. Sucker Punch's 59 seconds (measured on on a MacBook Pro 2.3GHz with 16GB of RAM).
|
15
|
+
|
16
|
+
Additional features are in the works, including:
|
17
|
+
- Support for multiple queues & queue priorities
|
18
|
+
- Persistent Job Stores for:
|
19
|
+
- Redis
|
20
|
+
- Cassandra
|
21
|
+
- Batches & Job nesting
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Add this line to your application's Gemfile:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem 'ruby_job'
|
29
|
+
```
|
30
|
+
|
31
|
+
And then execute:
|
32
|
+
|
33
|
+
$ bundle
|
34
|
+
|
35
|
+
Or install it yourself as:
|
36
|
+
|
37
|
+
$ gem install ruby_job
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
### A simple example
|
42
|
+
|
43
|
+
#### Define your worker class
|
44
|
+
```ruby
|
45
|
+
class MyWorker
|
46
|
+
include RubyJob::Worker
|
47
|
+
|
48
|
+
def perform
|
49
|
+
#job code goes here
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
#### Setup your JobStore
|
55
|
+
```ruby
|
56
|
+
MyWorker.jobstore = RubyJob::InMemoryJobStore.new
|
57
|
+
MyWorker.perform_async
|
58
|
+
```
|
59
|
+
|
60
|
+
#### Run your server
|
61
|
+
```ruby
|
62
|
+
server = RubyJob::ThreadedServer.new(num_threads: 10, jobstore: MyWorker.jobstore)
|
63
|
+
server_thread = server.start
|
64
|
+
server_thread.join
|
65
|
+
```
|
66
|
+
|
67
|
+
### Setting up the default JobStore
|
68
|
+
Jobs are enqueued to the default JobStore of the worker class:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
MyWorker.jobstore = RubyJob::InMemoryJobStore.new # attach the JobStore to the MyWorker class
|
72
|
+
```
|
73
|
+
|
74
|
+
If the worker class doesn't have a JobStore attached to it, jobs will be enqueued to `Worker.jobstore`.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Worker.jobstore = RubyJob::InMemoryJobStore.new # jobs will be queued here, if MyWorker doesn't have `jobstore` set.
|
78
|
+
```
|
79
|
+
|
80
|
+
### Enqueuing jobs
|
81
|
+
There are 2 ways you can enqueue your jobs:
|
82
|
+
|
83
|
+
#### Using <i>#perform_*</i> (recommended approach)
|
84
|
+
```ruby
|
85
|
+
MyWorker.jobstore = RubyJob::InMemoryJobStore.new
|
86
|
+
MyWorker.perform_async # will enqueue on `MyWorker.jobstore`, or `Worker.jobstore` if the former isn't set.
|
87
|
+
```
|
88
|
+
|
89
|
+
**Note:** you must ensure either `MyWorker.jobstore` or `Worker.jobstore` is set to a valid JobStore.
|
90
|
+
|
91
|
+
#### Using Job#enqueue
|
92
|
+
```ruby
|
93
|
+
MyWorker.jobstore = RubyJob::InMemoryJobStore.new
|
94
|
+
job = Job.new(worker_class_name: 'MyWorker', args: [], start_at: Time.now)
|
95
|
+
job.enqueue
|
96
|
+
```
|
97
|
+
|
98
|
+
### Dequeuing jobs
|
99
|
+
In some situations, it's important to remove a previously enqueued job from the queue, so that it does not run in the future.
|
100
|
+
To do so:
|
101
|
+
```ruby
|
102
|
+
job.dequeue
|
103
|
+
```
|
104
|
+
|
105
|
+
### Schedule a Job for execution (asynchronously)
|
106
|
+
|
107
|
+
**Note:** Jobs are scheduled to nearest **millisecond** of the specified start time.
|
108
|
+
|
109
|
+
#### Immediately (ASAP)
|
110
|
+
```ruby
|
111
|
+
MyWorker.perform_async # schedule to run asynchonously, asap
|
112
|
+
```
|
113
|
+
|
114
|
+
#### Delayed
|
115
|
+
```ruby
|
116
|
+
MyWorker.perform_in(5.5) # schedule to run asynchonously, in 5.5 seconds
|
117
|
+
```
|
118
|
+
|
119
|
+
#### At a specific time
|
120
|
+
```ruby
|
121
|
+
MyWorker.perform_at(a_particular_time) # schedule to run asynchonously, at the specified time
|
122
|
+
```
|
123
|
+
|
124
|
+
### Executing a Job immediately (synchronously)
|
125
|
+
```ruby
|
126
|
+
MyWorker.perform # run the job synchronously now
|
127
|
+
```
|
128
|
+
|
129
|
+
### Threaded Server (the job processor)
|
130
|
+
A threaded server is provided to process the queued jobs. It is instantiated by specifying the number of workers (threads) to spawn,
|
131
|
+
and the JobStore it will be processing.
|
132
|
+
```ruby
|
133
|
+
server = RubyJob::ThreadedServer.new(num_threads: 10, jobstore: MyWorker.jobstore)
|
134
|
+
```
|
135
|
+
|
136
|
+
#### Server options
|
137
|
+
```ruby
|
138
|
+
server.set(wait: true)
|
139
|
+
```
|
140
|
+
|
141
|
+
- `wait`[boolean]: determines whether the server should exit when there aren't any processable jobs in the queue. Defaults to `true`.
|
142
|
+
- `wait_delay`[float]: number of seconds to wait (sleep). Defaults to `0.5`.
|
143
|
+
|
144
|
+
|
145
|
+
#### Starting the server
|
146
|
+
Queued jobs will only run when a Server, attached to the JobStore the jobs have been enqueued to, has been started.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
server_thread = server.start
|
150
|
+
server_thread.join # if needed, depending on your use case
|
151
|
+
```
|
152
|
+
|
153
|
+
#### Halting the server
|
154
|
+
A running server can be halted as follows:
|
155
|
+
```ruby
|
156
|
+
server.halt_at(Time.now + 5)
|
157
|
+
```
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
server.halt # equivalent to halt_at(Time.now)
|
161
|
+
```
|
162
|
+
|
163
|
+
`Halting` causes the server to stop processing jobs scheduled to start after the specified halt time. Once the halt time has been
|
164
|
+
reached, the server waits if the `wait` option is `true`, or exits otherwise.
|
165
|
+
|
166
|
+
Halting the server can be useful in production, when you want to temporarily pause job processing.
|
167
|
+
|
168
|
+
#### Resuming the server
|
169
|
+
A halted server can be resumed with:
|
170
|
+
```ruby
|
171
|
+
server.resume
|
172
|
+
```
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
server.resume_until(Time.now + 5) # equivalent to: resume && halt_at(Time.now + 5)
|
176
|
+
```
|
177
|
+
|
178
|
+
With `resume`, the server picks up jobs from where it left off and keeps processing them as if it never stopped. Note that a server
|
179
|
+
that's been halted for a significant amount of time will pick up old jobs that may have been intended to start significantly in the past, so
|
180
|
+
ensure you take that into account in your job processing code if you care about this situation.
|
181
|
+
|
182
|
+
### Retries
|
183
|
+
Jobs will be not be retried by default. To have jobs retry, the worker class must define a `retry?` method that
|
184
|
+
returns a tuple indicating whether the job should be retried, and how long the retry delay should be: [do_retry, retry_delay]
|
185
|
+
```ruby
|
186
|
+
MAX_RETRIES = 5
|
187
|
+
INITIAL_RETRY_DELAY = 0.5
|
188
|
+
|
189
|
+
def retry?(attempt:, error:)
|
190
|
+
# determine whether a retry is required, based on the attempt number and error passed in
|
191
|
+
do_retry = error.is_a?(RetriableError) && (attempt < MAX_RETRIES)
|
192
|
+
|
193
|
+
[do_retry, INITIAL_RETRY_DELAY * 2**(attempt-1)] # exponential backoff
|
194
|
+
end
|
195
|
+
```
|
196
|
+
|
197
|
+
`attempt` starts at `1` and `error` is the exception that was raised by the last attempt.
|
198
|
+
|
199
|
+
**Note:** the current implementation uses `sleep` to implement the retry delay. This isn't ideal, as it prevents the thread
|
200
|
+
processing the job from servicing another job that's ready to run. In the future, this will be changed such that the job
|
201
|
+
is put back onto the job queue to start at a later time. Feel free to put together a PR if you're interested in seeing this
|
202
|
+
change sooner rather than later.
|
203
|
+
|
204
|
+
**Note:** the retry delay is the time between the end of the last attempt and the start of the new attempt
|
205
|
+
|
206
|
+
## Development
|
207
|
+
|
208
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an
|
209
|
+
interactive prompt that will allow you to experiment.
|
210
|
+
|
211
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and
|
212
|
+
then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
213
|
+
|
214
|
+
## Contributing
|
215
|
+
|
216
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mimperatore/ruby_job. This project is intended to be a safe, welcoming space
|
217
|
+
for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
218
|
+
|
219
|
+
## License
|
220
|
+
|
221
|
+
The gem is available as open source under the terms of the [GNU Lesser General Public License Version 3 (LGPLv3)](https://www.gnu.org/licenses/lgpl-3.0.html).
|
222
|
+
|
223
|
+
## Code of Conduct
|
224
|
+
|
225
|
+
Everyone interacting in the RubyJob project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow
|
226
|
+
the [code of conduct](https://github.com/mimperatore/ruby_job/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/_config.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
theme: jekyll-theme-cayman
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'ruby_job'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/codecov.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
ignore:
|
2
|
+
- spec/**
|
3
|
+
|
4
|
+
codecov:
|
5
|
+
require_ci_to_pass: yes
|
6
|
+
|
7
|
+
coverage:
|
8
|
+
precision: 2
|
9
|
+
round: down
|
10
|
+
range: "99...100"
|
11
|
+
|
12
|
+
status:
|
13
|
+
project: yes
|
14
|
+
patch: yes
|
15
|
+
changes: no
|
16
|
+
|
17
|
+
parsers:
|
18
|
+
gcov:
|
19
|
+
branch_detection:
|
20
|
+
conditional: yes
|
21
|
+
loop: yes
|
22
|
+
method: yes
|
23
|
+
macro: no
|
24
|
+
|
25
|
+
comment:
|
26
|
+
layout: "reach,diff,flags,tree"
|
27
|
+
behavior: default
|
28
|
+
require_changes: no
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'fibonacci_heap'
|
5
|
+
require 'digest/sha1'
|
6
|
+
|
7
|
+
module RubyJob
|
8
|
+
class InMemoryJobStore < JobStore
|
9
|
+
attr_reader :pause_starting_at
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super
|
13
|
+
@semaphore = Mutex.new
|
14
|
+
@next_uuid = 0
|
15
|
+
@pause_starting_at = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def enqueue(job)
|
19
|
+
raise 'job does not have an assigned uuid' unless job.uuid
|
20
|
+
|
21
|
+
@semaphore.synchronize { queue.push(job) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def dequeue(job)
|
25
|
+
@semaphore.synchronize { queue.delete(job) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def pause_at(time)
|
29
|
+
@pause_starting_at = time
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch
|
33
|
+
@options[:wait] ? fetch_next_or_wait : fetch_next
|
34
|
+
end
|
35
|
+
|
36
|
+
def size
|
37
|
+
queue.size
|
38
|
+
end
|
39
|
+
|
40
|
+
def next_uuid
|
41
|
+
@semaphore.synchronize { @next_uuid += 1 }
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def queue
|
47
|
+
@queue ||= JobPriorityQueue.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def paused_before?(time)
|
51
|
+
@pause_starting_at && @pause_starting_at <= time
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch_next
|
55
|
+
@semaphore.synchronize do
|
56
|
+
queue.pop if (top = queue.top) && top.start_at <= [Time.now, @pause_starting_at].compact.min
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def fetch_next_or_wait
|
61
|
+
job = nil
|
62
|
+
loop do
|
63
|
+
job = fetch_next
|
64
|
+
break if job || (paused_before?(Time.now) && !@options[:wait])
|
65
|
+
|
66
|
+
sleep(@options[:wait_delay])
|
67
|
+
end
|
68
|
+
job
|
69
|
+
end
|
70
|
+
|
71
|
+
class JobPriorityQueue
|
72
|
+
extend Forwardable
|
73
|
+
|
74
|
+
def_delegators :@pqueue, :size
|
75
|
+
|
76
|
+
def initialize
|
77
|
+
@pqueue = FibonacciHeap::Heap.new
|
78
|
+
end
|
79
|
+
|
80
|
+
def push(job)
|
81
|
+
@pqueue.insert(job, key_for(job))
|
82
|
+
end
|
83
|
+
|
84
|
+
def pop
|
85
|
+
@pqueue.pop
|
86
|
+
end
|
87
|
+
|
88
|
+
def top
|
89
|
+
@pqueue.min
|
90
|
+
end
|
91
|
+
|
92
|
+
def delete(job)
|
93
|
+
@pqueue.delete(job)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def key_for(job)
|
99
|
+
job.start_at.to_f.round(3) + job.uuid.to_f / 1_000
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|