localjob 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +89 -9
- data/ext/mkrf_conf.rb +17 -0
- data/lib/localjob/channel.rb +6 -4
- data/lib/localjob/linux_adapter.rb +34 -0
- data/lib/localjob/mock_adapter.rb +32 -0
- data/lib/localjob/version.rb +1 -1
- data/lib/localjob.rb +26 -24
- data/localjob.gemspec +1 -1
- data/test/localjob_test.rb +8 -6
- data/test/mock_adapter.rb +22 -0
- data/test/test_helper.rb +6 -0
- data/test/worker_test.rb +26 -26
- metadata +8 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c33dc84596d653899831a3697428d950028e0c5
|
4
|
+
data.tar.gz: 8547e6ca4022b618956578e8dc3f11fe65ae8b00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22eab445b06c1011c53a244a2677feec403a4f501e0dc35ca663b405d4d754f3f891914420fb811a5a840076f1fbd6af71f7afc31d5f1e9c76a297730fbf281c
|
7
|
+
data.tar.gz: d7ede95238c734c9ab043b2861152203197fb7606a86ee6b81083ce304c9f479cbaf50842515301518f6566733b7978a1b77dff828827df476e9dd5505191199
|
data/README.md
CHANGED
@@ -12,18 +12,18 @@ project is to be able to migrate to another background queue system by switching
|
|
12
12
|
adapter: `Localjob.adapter = Resque` to switch to Resque, without changes to
|
13
13
|
your own code.
|
14
14
|
|
15
|
-
The POSIX message queue is persistent till reboot. You will need to tune system
|
16
|
-
parameters for your application
|
15
|
+
The POSIX message queue is persistent till reboot. You **will need to tune system
|
16
|
+
parameters for your application**, please consult [posix-mqueue][pmq-gem]'s
|
17
17
|
documentation.
|
18
18
|
|
19
|
-
Localjob works on Ruby >= 2.0.0
|
19
|
+
Localjob works on Ruby >= 2.0.0. On Linux, it will use the POSIX message queue.
|
20
|
+
On OS X (and Windows, not tested) it will use a mock class instead of the
|
21
|
+
message queue, to aid you in testing and running Localjob in development.
|
20
22
|
|
21
|
-
|
22
|
-
doesn't work! It's not released as a gem yet, so add the following to your
|
23
|
-
Gemfile to use it:
|
23
|
+
Add it to your Gemfile:
|
24
24
|
|
25
25
|
```ruby
|
26
|
-
gem 'localjob'
|
26
|
+
gem 'localjob'
|
27
27
|
```
|
28
28
|
|
29
29
|
## Usage
|
@@ -49,8 +49,88 @@ queue = Localjob.new
|
|
49
49
|
queue << EmailJob.new(current_user.id, welcome_email)
|
50
50
|
```
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
A job is serialized with YAML and pushed onto a persistent POSIX message queue.
|
53
|
+
This means a worker does not have to listen on the queue to push things to it.
|
54
|
+
Workers will pop off the message queue, but only one will receive the job.
|
55
|
+
Deserialize the message to create an instance of your object, and call
|
56
|
+
`#perform` on the object.
|
57
|
+
|
58
|
+
### Rails initializer
|
59
|
+
|
60
|
+
For easy access to your queues in Rails, you can add an initializer to set up a
|
61
|
+
constant referencing each of your queues. This allows easy access anywhere in
|
62
|
+
your app. In `config/initializers/localjob.rb`:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
BackgroundQueue = Localjob.new("main-queue")
|
66
|
+
```
|
67
|
+
|
68
|
+
Then in your app you can simply reference the constant to push to the queue:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
BackgroundQueue << EmailJob.new(current_user.id, welcome_email)
|
72
|
+
```
|
73
|
+
|
74
|
+
### Managing workers
|
75
|
+
|
76
|
+
Spawning workers can be done with `localjob`. Run `localjob work` to spawn a
|
77
|
+
single worker. It takes a few arguments. The most important being `--require`
|
78
|
+
which takes a path the worker will require before processing jobs. For Rails,
|
79
|
+
you can run `localjob work` without any arguments. `localjob(2)` has a few other
|
80
|
+
commands such as `list` to list all queues and `size` to list the size of all
|
81
|
+
queues. `localjob help` to list all commands.
|
82
|
+
|
83
|
+
Gracefully shut down workers by sending `SIGQUIT` to them. This will make sure
|
84
|
+
the worker completes its current job before shutting down. Jobs can be sent to
|
85
|
+
the queue meanwhile, and the worker will process them once it starts again.
|
86
|
+
|
87
|
+
### Queues
|
88
|
+
|
89
|
+
Localjobs supports multiple queues, and workers can be assigned to queues. By
|
90
|
+
default everything is on a single queue. To push to a named queue:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
email = Localjob.new("email")
|
94
|
+
email << EmailJob.new(current_user.id, welcome_email)
|
95
|
+
```
|
96
|
+
|
97
|
+
The worker spawn command `localjob work` takes a `--queues` argument which is a
|
98
|
+
comma seperated list of queues to listen on, e.g. `localjob work --queues email,webhooks`.
|
99
|
+
|
100
|
+
### Testing
|
101
|
+
|
102
|
+
Create your instance of the queue as normal in your setup:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
def setup
|
106
|
+
@queue = LocalJob.new("test-queue")
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
In your `teardown` you'll want to destroy your queue:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
def teardown
|
114
|
+
@queue.destroy
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
You can get the size of your queue by calling `@queue.size`. You pop off the
|
119
|
+
queue with `@queue.shift`. Other than that, just use the normal API. You can
|
120
|
+
also read the tests for Localjob to get an idea of how to test. Sample test:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
def test_pop_and_send_to_worker
|
124
|
+
WalrusJob.any_instance.expects(:perform)
|
125
|
+
|
126
|
+
@localjob << WalrusJob.new("move")
|
127
|
+
|
128
|
+
job = @localjob.shift
|
129
|
+
@worker.process(job)
|
130
|
+
|
131
|
+
assert_equal 0, @localjob.size
|
132
|
+
end
|
133
|
+
```
|
54
134
|
|
55
135
|
[pmq]: http://linux.die.net/man/7/mq_overview
|
56
136
|
[pmq-gem]: https://github.com/Sirupsen/posix-mqueue
|
data/ext/mkrf_conf.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/command.rb'
|
3
|
+
require 'rubygems/dependency_installer.rb'
|
4
|
+
begin
|
5
|
+
Gem::Command.build_args = ARGV
|
6
|
+
rescue NoMethodError
|
7
|
+
end
|
8
|
+
|
9
|
+
inst = Gem::DependencyInstaller.new
|
10
|
+
|
11
|
+
begin
|
12
|
+
if RUBY_PLATFORM =~ /linux/
|
13
|
+
inst.install "posix-mqueue", "0.0.7"
|
14
|
+
end
|
15
|
+
rescue
|
16
|
+
exit(1)
|
17
|
+
end
|
data/lib/localjob/channel.rb
CHANGED
@@ -11,10 +11,12 @@ class Localjob
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def shift
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
begin
|
15
|
+
(queue,), = IO.select(@queues)
|
16
|
+
queue.shift
|
17
|
+
rescue POSIX::Mqueue::QueueEmpty
|
18
|
+
retry
|
19
|
+
end
|
18
20
|
end
|
19
21
|
|
20
22
|
private
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Localjob
|
2
|
+
class LinuxAdapter
|
3
|
+
attr_reader :mqueue
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@mqueue = POSIX::Mqueue.new(fix_queue_name(name))
|
7
|
+
end
|
8
|
+
|
9
|
+
def receive
|
10
|
+
@mqueue.timedreceive
|
11
|
+
end
|
12
|
+
|
13
|
+
def send(message)
|
14
|
+
@mqueue.timedsend message
|
15
|
+
end
|
16
|
+
|
17
|
+
def size
|
18
|
+
@mqueue.size
|
19
|
+
end
|
20
|
+
|
21
|
+
def destroy
|
22
|
+
@mqueue.unlink
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_io
|
26
|
+
@mqueue.to_io
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def fix_queue_name(name)
|
31
|
+
name.start_with?('/') ? name : "/#{name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Localjob
|
2
|
+
class Channel
|
3
|
+
def shift
|
4
|
+
queue = @queues.find { |q| q.size > 0 }
|
5
|
+
return queue.shift
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class MockAdapter
|
10
|
+
def initialize(name = 'default')
|
11
|
+
@@queues ||= {}
|
12
|
+
@name = name
|
13
|
+
@@queues[@name] ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def receive
|
17
|
+
@@queues[@name].shift
|
18
|
+
end
|
19
|
+
|
20
|
+
def send(message)
|
21
|
+
@@queues[@name] << message
|
22
|
+
end
|
23
|
+
|
24
|
+
def size
|
25
|
+
@@queues[@name].size
|
26
|
+
end
|
27
|
+
|
28
|
+
def destroy
|
29
|
+
@@queues[@name] = nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/localjob/version.rb
CHANGED
data/lib/localjob.rb
CHANGED
@@ -1,16 +1,26 @@
|
|
1
|
-
|
1
|
+
begin
|
2
|
+
require 'posix/mqueue'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
2
6
|
require 'yaml'
|
3
7
|
require 'logger'
|
8
|
+
require 'forwardable'
|
4
9
|
|
5
10
|
require "localjob/version"
|
6
11
|
require 'localjob/channel'
|
7
12
|
require 'localjob/worker'
|
8
13
|
|
9
14
|
class Localjob
|
10
|
-
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
attr_reader :name
|
18
|
+
attr_accessor :queue
|
19
|
+
|
20
|
+
def_delegators :queue, :to_io, :destroy, :size
|
11
21
|
|
12
|
-
def initialize(
|
13
|
-
@
|
22
|
+
def initialize(name = "localjob")
|
23
|
+
@name = name
|
14
24
|
end
|
15
25
|
|
16
26
|
def serializer
|
@@ -18,31 +28,23 @@ class Localjob
|
|
18
28
|
end
|
19
29
|
|
20
30
|
def queue
|
21
|
-
@queue
|
22
|
-
end
|
31
|
+
return @queue if @queue
|
23
32
|
|
24
|
-
|
25
|
-
|
33
|
+
case RUBY_PLATFORM
|
34
|
+
when /linux/
|
35
|
+
require 'localjob/linux_adapter'
|
36
|
+
@queue = LinuxAdapter.new(@name)
|
37
|
+
else
|
38
|
+
require 'localjob/mock_adapter'
|
39
|
+
@queue = MockAdapter.new(@name)
|
40
|
+
end
|
26
41
|
end
|
27
42
|
|
28
|
-
def
|
29
|
-
queue.
|
43
|
+
def <<(object)
|
44
|
+
queue.send serializer.dump(object)
|
30
45
|
end
|
31
46
|
|
32
47
|
def shift
|
33
|
-
serializer.load queue.
|
34
|
-
end
|
35
|
-
|
36
|
-
def destroy
|
37
|
-
queue.unlink
|
38
|
-
end
|
39
|
-
|
40
|
-
def to_io
|
41
|
-
queue.to_io
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
def fix_queue_name(queue)
|
46
|
-
queue.start_with?('/') ? queue : "/#{queue}"
|
48
|
+
serializer.load queue.receive
|
47
49
|
end
|
48
50
|
end
|
data/localjob.gemspec
CHANGED
@@ -17,8 +17,8 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
|
+
spec.extensions = ["ext/mkrf_conf.rb"]
|
20
21
|
|
21
|
-
spec.add_dependency "posix-mqueue", "0.0.9"
|
22
22
|
spec.add_dependency "thor", "0.18.1"
|
23
23
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
data/test/localjob_test.rb
CHANGED
@@ -18,12 +18,6 @@ class LocaljobTest < LocaljobTestCase
|
|
18
18
|
assert_equal "move", job.action
|
19
19
|
end
|
20
20
|
|
21
|
-
def test_throws_error_if_message_is_too_large
|
22
|
-
assert_raises Errno::EMSGSIZE do
|
23
|
-
@localjob << AngryWalrusJob.new("f" * @localjob.queue.msgsize)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
21
|
def test_handles_multiple_queues
|
28
22
|
@localjob << WalrusJob.new("move")
|
29
23
|
|
@@ -33,4 +27,12 @@ class LocaljobTest < LocaljobTestCase
|
|
33
27
|
assert_equal 1, @localjob.size
|
34
28
|
assert_equal 1, other.size
|
35
29
|
end
|
30
|
+
|
31
|
+
on_platform 'linux' do
|
32
|
+
def test_throws_error_if_message_is_too_large
|
33
|
+
assert_raises Errno::EMSGSIZE do
|
34
|
+
@localjob << AngryWalrusJob.new("f" * @localjob.queue.mqueue.msgsize)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
36
38
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MockAdapterTest < LocaljobTestCase
|
4
|
+
def setup
|
5
|
+
@localjob = queue
|
6
|
+
@localjob.queue = Localjob::MockAdapter.new("localjob")
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_push_to_queue
|
10
|
+
@localjob << "hello world"
|
11
|
+
assert_equal 1, @localjob.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_push_and_pop_from_queue
|
15
|
+
@localjob << "hello world"
|
16
|
+
assert_equal "hello world", @localjob.shift
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_destroy_queue
|
20
|
+
@localjob << "hello world"
|
21
|
+
end
|
22
|
+
end
|
data/test/test_helper.rb
CHANGED
data/test/worker_test.rb
CHANGED
@@ -18,38 +18,26 @@ class WorkerTest < LocaljobTestCase
|
|
18
18
|
def test_working_off_queue_in_child
|
19
19
|
@localjob << WalrusJob.new("move")
|
20
20
|
|
21
|
-
|
21
|
+
a = Thread.start {
|
22
22
|
job = @localjob.shift
|
23
23
|
@worker.process(job)
|
24
|
-
|
25
|
-
|
26
|
-
Process.wait
|
27
|
-
assert_equal 0, @localjob.size
|
28
|
-
end
|
29
|
-
|
30
|
-
def test_sigquit_terminates_the_worker
|
31
|
-
@localjob << WalrusJob.new("move")
|
32
|
-
|
33
|
-
assert_equal 1, @localjob.size
|
34
|
-
|
35
|
-
pid = fork { @worker.work }
|
24
|
+
}
|
36
25
|
|
37
|
-
|
38
|
-
Process.wait
|
26
|
+
a.join
|
39
27
|
|
40
28
|
assert_equal 0, @localjob.size
|
41
29
|
end
|
42
30
|
|
31
|
+
|
43
32
|
def test_doesnt_stop_on_error
|
44
33
|
@localjob << AngryWalrusJob.new(100)
|
45
34
|
@localjob << WalrusJob.new("be happy")
|
46
35
|
|
47
|
-
|
36
|
+
a = Thread.start { @worker.work }
|
48
37
|
|
49
38
|
# Hack to account for race condition, 0.01s should be plenty
|
50
39
|
sleep 0.01
|
51
|
-
|
52
|
-
Process.wait
|
40
|
+
a.kill
|
53
41
|
|
54
42
|
assert_equal 0, @localjob.size
|
55
43
|
end
|
@@ -62,24 +50,36 @@ class WorkerTest < LocaljobTestCase
|
|
62
50
|
|
63
51
|
@worker.channel << 'other-queue'
|
64
52
|
|
65
|
-
|
53
|
+
a = Thread.start { @worker.work }
|
66
54
|
|
67
|
-
# Hack to account for race condition, 0.01s should be plenty
|
68
55
|
sleep 0.01
|
69
|
-
|
70
|
-
Process.wait
|
56
|
+
a.kill
|
71
57
|
|
72
58
|
assert_equal 0, @localjob.size
|
73
59
|
assert_equal 0, other.size
|
74
60
|
end
|
75
61
|
|
76
62
|
def test_worker_doesnt_die_on_bad_serialization
|
77
|
-
@localjob.queue.
|
63
|
+
@localjob.queue.send "--- !ruby/object:Whatever {}\n"
|
78
64
|
|
79
|
-
|
65
|
+
a = Thread.start { @worker.work }
|
80
66
|
|
81
67
|
sleep 0.01
|
82
|
-
|
83
|
-
|
68
|
+
a.kill
|
69
|
+
end
|
70
|
+
|
71
|
+
on_platform 'linux' do
|
72
|
+
def test_sigquit_terminates_the_worker
|
73
|
+
@localjob << WalrusJob.new("move")
|
74
|
+
|
75
|
+
assert_equal 1, @localjob.size
|
76
|
+
|
77
|
+
pid = fork { @worker.work }
|
78
|
+
|
79
|
+
Process.kill("QUIT", pid)
|
80
|
+
Process.wait
|
81
|
+
|
82
|
+
assert_equal 0, @localjob.size
|
83
|
+
end
|
84
84
|
end
|
85
85
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: localjob
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Eskildsen
|
@@ -10,20 +10,6 @@ bindir: bin
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 2013-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: posix-mqueue
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - '='
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 0.0.9
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - '='
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 0.0.9
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: thor
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,7 +72,8 @@ email:
|
|
86
72
|
- sirup@sirupsen.com
|
87
73
|
executables:
|
88
74
|
- localjob
|
89
|
-
extensions:
|
75
|
+
extensions:
|
76
|
+
- ext/mkrf_conf.rb
|
90
77
|
extra_rdoc_files: []
|
91
78
|
files:
|
92
79
|
- .gitignore
|
@@ -95,14 +82,18 @@ files:
|
|
95
82
|
- README.md
|
96
83
|
- Rakefile
|
97
84
|
- bin/localjob
|
85
|
+
- ext/mkrf_conf.rb
|
98
86
|
- lib/localjob.rb
|
99
87
|
- lib/localjob/channel.rb
|
100
88
|
- lib/localjob/cli.rb
|
89
|
+
- lib/localjob/linux_adapter.rb
|
90
|
+
- lib/localjob/mock_adapter.rb
|
101
91
|
- lib/localjob/version.rb
|
102
92
|
- lib/localjob/worker.rb
|
103
93
|
- localjob.gemspec
|
104
94
|
- test/jobs.rb
|
105
95
|
- test/localjob_test.rb
|
96
|
+
- test/mock_adapter.rb
|
106
97
|
- test/test_helper.rb
|
107
98
|
- test/worker_test.rb
|
108
99
|
homepage: ''
|
@@ -133,5 +124,6 @@ summary: Simple, self-contained background queue built on top of POSIX message q
|
|
133
124
|
test_files:
|
134
125
|
- test/jobs.rb
|
135
126
|
- test/localjob_test.rb
|
127
|
+
- test/mock_adapter.rb
|
136
128
|
- test/test_helper.rb
|
137
129
|
- test/worker_test.rb
|