async-jobs 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 +7 -0
- data/.gitignore +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +66 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/async.gemspec +17 -0
- data/lib/async.rb +119 -0
- data/lib/async/locked.rb +141 -0
- data/lib/async/qu.rb +15 -0
- data/lib/async/resque.rb +19 -0
- data/lib/async/sidekiq.rb +25 -0
- data/lib/async/version.rb +3 -0
- data/spec/async_and_lock_spec.rb +121 -0
- data/spec/basics_spec.rb +40 -0
- data/spec/qu_spec.rb +23 -0
- data/spec/resque_spec.rb +28 -0
- data/spec/shared/models.rb +54 -0
- data/spec/sidekiq_spec.rb +23 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ad313be63e58bd780e2b68297719ec64cb7f46e6
|
4
|
+
data.tar.gz: 4f341fc468bf497f4266185e930351c3a1c6ce1b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eff272909fab9a45836846e6576762ef7e9da0bee6442e47aa769a3bc3be2d8eafdf0e33bed032dd37332ec69b410b0a1fe71d2e13ba25093585e571a8600352
|
7
|
+
data.tar.gz: 0fe48659523783bd0314e8c5b1e6425fd99bdba12f721c245bc36589d745c90466e73003e51388a8d1e573f4eaca3624aa6241f55446960856f8fe3f4ef3daee
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
async*.gem
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
async-jobs (0.0.3)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
celluloid (0.12.4)
|
10
|
+
facter (>= 1.6.12)
|
11
|
+
timers (>= 1.0.0)
|
12
|
+
connection_pool (1.0.0)
|
13
|
+
cubbyhole (0.2.1)
|
14
|
+
diff-lcs (1.1.3)
|
15
|
+
facter (1.6.18)
|
16
|
+
multi_json (1.7.0)
|
17
|
+
qu (0.2.0)
|
18
|
+
multi_json
|
19
|
+
rack (1.4.5)
|
20
|
+
rack-protection (1.4.0)
|
21
|
+
rack
|
22
|
+
redis (3.0.2)
|
23
|
+
redis-namespace (1.2.1)
|
24
|
+
redis (~> 3.0.0)
|
25
|
+
resque (1.23.0)
|
26
|
+
multi_json (~> 1.0)
|
27
|
+
redis-namespace (~> 1.0)
|
28
|
+
sinatra (>= 0.9.2)
|
29
|
+
vegas (~> 0.1.2)
|
30
|
+
rspec (2.11.0)
|
31
|
+
rspec-core (~> 2.11.0)
|
32
|
+
rspec-expectations (~> 2.11.0)
|
33
|
+
rspec-mocks (~> 2.11.0)
|
34
|
+
rspec-core (2.11.1)
|
35
|
+
rspec-expectations (2.11.2)
|
36
|
+
diff-lcs (~> 1.1.3)
|
37
|
+
rspec-mocks (2.11.1)
|
38
|
+
sidekiq (2.8.0)
|
39
|
+
celluloid (~> 0.12.0)
|
40
|
+
connection_pool (~> 1.0)
|
41
|
+
multi_json (~> 1)
|
42
|
+
redis (~> 3)
|
43
|
+
redis-namespace
|
44
|
+
sinatra (1.3.5)
|
45
|
+
rack (~> 1.4)
|
46
|
+
rack-protection (~> 1.3)
|
47
|
+
tilt (~> 1.3, >= 1.3.3)
|
48
|
+
tilt (1.3.4)
|
49
|
+
timers (1.1.0)
|
50
|
+
vegas (0.1.11)
|
51
|
+
rack (>= 1.0.0)
|
52
|
+
|
53
|
+
PLATFORMS
|
54
|
+
ruby
|
55
|
+
|
56
|
+
DEPENDENCIES
|
57
|
+
async-jobs!
|
58
|
+
cubbyhole
|
59
|
+
qu
|
60
|
+
redis
|
61
|
+
resque
|
62
|
+
rspec
|
63
|
+
sidekiq
|
64
|
+
|
65
|
+
BUNDLED WITH
|
66
|
+
1.12.5
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) Engine Yard. All rights reserved.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#Yet another background processing abstraction layer
|
2
|
+
|
3
|
+
Assuming that every time you want to do something in a background job, it's defined in a method on an active record object.
|
4
|
+
|
5
|
+
Zero explicit dependencies. (just respond to `id` and `find` like AR does)
|
6
|
+
|
7
|
+
##Example
|
8
|
+
|
9
|
+
gem is called `async-jobs`
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require 'async'
|
13
|
+
require 'async/resque'
|
14
|
+
Async.backend = Async::ResqueBackend
|
15
|
+
|
16
|
+
class Invoice < ActiveRecord::Base
|
17
|
+
def process(arg)
|
18
|
+
Async.run{ process_now(arg)}
|
19
|
+
end
|
20
|
+
def process_now(arg)
|
21
|
+
#actually do it
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
invoice.process 1
|
26
|
+
```
|
27
|
+
|
28
|
+
Will enqueue a Resque job that runs `invoice.process_now 1`
|
data/async.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "async/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "async-jobs"
|
7
|
+
s.version = Async::VERSION
|
8
|
+
s.author = "Jacob"
|
9
|
+
s.email = "jacob@engineyard.com"
|
10
|
+
s.homepage = "https://github.com/engineyard/async"
|
11
|
+
s.summary = "abstraction over background job systems"
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
|
17
|
+
end
|
data/lib/async.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
class Async
|
2
|
+
class << self
|
3
|
+
attr_accessor :backend
|
4
|
+
end
|
5
|
+
|
6
|
+
class MethodCatcher
|
7
|
+
attr_reader :_method_name, :_args
|
8
|
+
def method_missing(method_name, *args)
|
9
|
+
@_method_name = method_name
|
10
|
+
@_args = args
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.ensure_backend!
|
15
|
+
unless Async.backend
|
16
|
+
raise "Please configure the background processing system of choice by setting: Async.backend="
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.run(&block)
|
21
|
+
ensure_backend!
|
22
|
+
receiver = block.binding.eval("self")
|
23
|
+
mc = MethodCatcher.new
|
24
|
+
mc.instance_eval(&block)
|
25
|
+
run_later self.to_s, receiver, mc._method_name, *mc._args
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.run_later(job_class, receiver, method_name, *args)
|
29
|
+
if receiver.is_a?(Class)
|
30
|
+
receiver_class, receiver_id = receiver.to_s, nil
|
31
|
+
else
|
32
|
+
receiver_class, receiver_id = receiver.class.to_s, receiver.id
|
33
|
+
end
|
34
|
+
Async.backend.enqueue Async.backend.job_class, job_class, receiver_class, receiver_id, method_name, Job.transform_args(args)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.run_now(receiver, method_name, args)
|
38
|
+
Notifications.notify_job("run", receiver, method_name, args)
|
39
|
+
receiver.send(method_name, *args)
|
40
|
+
ensure
|
41
|
+
Notifications.notify_job("finish", receiver, method_name, args)
|
42
|
+
end
|
43
|
+
|
44
|
+
#TODO: test this
|
45
|
+
class Notifications
|
46
|
+
class << self
|
47
|
+
attr_accessor :handler
|
48
|
+
end
|
49
|
+
def self.notify_lock(thing, lock_name)
|
50
|
+
handler && handler.call(thing, {:lock_name => lock_name})
|
51
|
+
end
|
52
|
+
def self.notify_job(thing, receiver, method_name, args)
|
53
|
+
handler && handler.call(thing, {
|
54
|
+
:receiver => receiver,
|
55
|
+
:method_name => method_name.to_sym,
|
56
|
+
:args => args
|
57
|
+
})
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#TODO: test this
|
62
|
+
class ErrorReporting
|
63
|
+
class << self
|
64
|
+
attr_accessor :handler
|
65
|
+
end
|
66
|
+
def self.notify_exception(e, job_args)
|
67
|
+
handler && handler.call(e, job_args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Job
|
72
|
+
|
73
|
+
def self.perform(wrapper, receiver_class_str, receiver_id, method, args)
|
74
|
+
receiver_class = constantize(receiver_class_str)
|
75
|
+
receiver = receiver_id ? receiver_class.find(receiver_id) : receiver_class
|
76
|
+
untransform_args = untransform_args(args)
|
77
|
+
constantize(wrapper).run_now(receiver, method, untransform_args)
|
78
|
+
rescue => e
|
79
|
+
ErrorReporting.notify_exception(e,
|
80
|
+
receiver_class_str: receiver_class_str, receiver_id: receiver_id, method: method, args: args)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.transform_args(args)
|
84
|
+
args.map do |x|
|
85
|
+
if x.class.respond_to?(:find)
|
86
|
+
{'_transform_arg' => true, 'class' => x.class.to_s, 'id' => x.id}
|
87
|
+
elsif x.is_a?(Array)
|
88
|
+
transform_args(x)
|
89
|
+
else
|
90
|
+
x
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.untransform_args(args)
|
96
|
+
args.map do |x|
|
97
|
+
if x.is_a?(Hash) && x['_transform_arg']
|
98
|
+
constantize(x['class']).find(x['id'])
|
99
|
+
elsif x.is_a?(Array)
|
100
|
+
untransform_args(x)
|
101
|
+
else
|
102
|
+
x
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def self.constantize(str)
|
110
|
+
if str.respond_to?(:constantize)
|
111
|
+
str.constantize
|
112
|
+
else
|
113
|
+
eval(str)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
data/lib/async/locked.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
class Async
|
2
|
+
class << self
|
3
|
+
attr_accessor :redis, :lock_time
|
4
|
+
end
|
5
|
+
|
6
|
+
class Locked < Async
|
7
|
+
|
8
|
+
def self.run_now(receiver, method_name, args)
|
9
|
+
Notifications.notify_job("consider", receiver, method_name, args)
|
10
|
+
if Lock.is_lock_arg?(args.first)
|
11
|
+
lock_arg = args.shift
|
12
|
+
lock = Lock.claim(make_lock_name(receiver)) || Lock.create(make_lock_name(receiver))
|
13
|
+
else
|
14
|
+
lock = Lock.create(make_lock_name(receiver))
|
15
|
+
end
|
16
|
+
if lock
|
17
|
+
super
|
18
|
+
else
|
19
|
+
run_later(self.to_s, receiver, method_name, *args)
|
20
|
+
end
|
21
|
+
ensure
|
22
|
+
lock && lock.release
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.run_later(job_class, receiver, method_name, *args)
|
26
|
+
if lock = Lock.pass_on(make_lock_name(receiver))
|
27
|
+
super(job_class, receiver, method_name, lock.as_job_arg, *args)
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.make_lock_name(receiver)
|
34
|
+
if receiver.is_a?(Class)
|
35
|
+
Lock.make_name(receiver.to_s, nil)
|
36
|
+
else
|
37
|
+
Lock.make_name(receiver.class.to_s, receiver.id)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
class Lock
|
44
|
+
def self.make_name(*names)
|
45
|
+
"lock:"+names.join(":")
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(lock_name)
|
49
|
+
@lock_name = lock_name
|
50
|
+
@passed_on = false
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.is_lock_arg?(arg)
|
54
|
+
arg.is_a?(Hash) && arg["_lock_arg"]
|
55
|
+
end
|
56
|
+
|
57
|
+
def as_job_arg
|
58
|
+
{"_lock_arg" => true, 'lock_name' => @lock_name}
|
59
|
+
end
|
60
|
+
|
61
|
+
def claim
|
62
|
+
Notifications.notify_lock("claim", @lock_name)
|
63
|
+
if redis.get(@lock_name) #still locked
|
64
|
+
refresh!
|
65
|
+
return true
|
66
|
+
else
|
67
|
+
lock
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def lock
|
72
|
+
if redis.setnx(@lock_name, "locked")
|
73
|
+
refresh!
|
74
|
+
Notifications.notify_lock("lock", @lock_name)
|
75
|
+
return true
|
76
|
+
else
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def refresh!
|
82
|
+
redis.expire(@lock_name, Async::Locked.lock_time || 15)
|
83
|
+
end
|
84
|
+
|
85
|
+
def pass_on
|
86
|
+
if @passed_on
|
87
|
+
#already passed on, can't pass again
|
88
|
+
false
|
89
|
+
else
|
90
|
+
@passed_on = true
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def release
|
96
|
+
if @passed_on
|
97
|
+
return false
|
98
|
+
end
|
99
|
+
redis.del(@lock_name)
|
100
|
+
Notifications.notify_lock("release", @lock_name)
|
101
|
+
Thread.current["Async::Lock.named"][@lock_name] = nil
|
102
|
+
return true
|
103
|
+
end
|
104
|
+
|
105
|
+
def redis
|
106
|
+
Async::Locked.redis
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.thread_kill_lock(lock_name)
|
110
|
+
Thread.current["Async::Lock.named"] ||= {}
|
111
|
+
Thread.current["Async::Lock.named"][lock_name] = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.thread_save_lock(lock_name, lock)
|
115
|
+
Thread.current["Async::Lock.named"] ||= {}
|
116
|
+
Thread.current["Async::Lock.named"][lock_name] = lock
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.thread_fetch_lock(lock_name)
|
120
|
+
Thread.current["Async::Lock.named"] ||= {}
|
121
|
+
Thread.current["Async::Lock.named"][lock_name]
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.claim(lock_name)
|
125
|
+
lock = Lock.new(lock_name)
|
126
|
+
lock.claim && thread_save_lock(lock_name, lock) && lock
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.pass_on(lock_name)
|
130
|
+
lock = thread_fetch_lock(lock_name)
|
131
|
+
lock && lock.pass_on && lock
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.create(lock_name)
|
135
|
+
lock = Lock.new(lock_name)
|
136
|
+
lock.lock && thread_save_lock(lock_name, lock) && lock
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
data/lib/async/qu.rb
ADDED
data/lib/async/resque.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
class Async
|
2
|
+
|
3
|
+
class ResqueBackend
|
4
|
+
|
5
|
+
def self.enqueue(job_class, *args)
|
6
|
+
Resque.enqueue(job_class, *args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.job_class
|
10
|
+
Async::ResqueBackend::Job
|
11
|
+
end
|
12
|
+
|
13
|
+
class Job < Async::Job
|
14
|
+
@queue = :default
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
class Async
|
3
|
+
|
4
|
+
class SidekiqBackend
|
5
|
+
|
6
|
+
def self.enqueue(job_class, *args)
|
7
|
+
job_class.perform_async(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.job_class
|
11
|
+
Async::SidekiqBackend::Job
|
12
|
+
end
|
13
|
+
|
14
|
+
class Job < Async::Job
|
15
|
+
include Sidekiq::Worker
|
16
|
+
|
17
|
+
def perform(*args)
|
18
|
+
self.class.perform(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'async'
|
2
|
+
require 'async/qu'
|
3
|
+
require 'async/locked'
|
4
|
+
require 'qu'
|
5
|
+
require 'redis'
|
6
|
+
require 'shared/models'
|
7
|
+
|
8
|
+
require 'qu-immediate'
|
9
|
+
class PoppableQu < Qu::Backend::Immediate
|
10
|
+
|
11
|
+
def self.queue
|
12
|
+
@queue ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def enqueue(payload)
|
16
|
+
PoppableQu.queue.unshift(payload)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe Async::Locked do
|
22
|
+
before(:all) do
|
23
|
+
Async.backend = Async::QuBackend
|
24
|
+
Async::Locked.redis = Redis.new
|
25
|
+
end
|
26
|
+
before(:each) do
|
27
|
+
@qubackend = Qu.backend
|
28
|
+
Qu.backend = PoppableQu.new
|
29
|
+
end
|
30
|
+
after(:each) do
|
31
|
+
Qu.backend = @qubackend
|
32
|
+
end
|
33
|
+
|
34
|
+
it "works" do
|
35
|
+
Async::Locked.redis.flushdb
|
36
|
+
|
37
|
+
order_of_ops = []
|
38
|
+
Async::Notifications.handler = Proc.new do |name, hash|
|
39
|
+
# puts [name, (hash[:method_name] || hash[:lock_name]).to_s, hash[:args]].inspect
|
40
|
+
order_of_ops << [name, (hash[:method_name] || hash[:lock_name]).to_s]
|
41
|
+
end
|
42
|
+
|
43
|
+
Thread.current["Async::Lock.named"] = nil
|
44
|
+
y = Yard.new
|
45
|
+
y.save
|
46
|
+
y.do_all_the_work("front")
|
47
|
+
y.conflict
|
48
|
+
|
49
|
+
PoppableQu.queue.size.should eq 2
|
50
|
+
next_job = PoppableQu.queue.pop
|
51
|
+
next_job.args.should eq ["Async::Locked", "Yard", y.id, :now_do_all_the_work, ["front"]]
|
52
|
+
|
53
|
+
# puts ""
|
54
|
+
Thread.current["Async::Lock.named"] = nil
|
55
|
+
next_job.perform
|
56
|
+
|
57
|
+
PoppableQu.queue.size.should eq 3
|
58
|
+
next_job = PoppableQu.queue.pop
|
59
|
+
next_job.args.should eq ["Async::Locked", "Yard", 0, :now_conflict, []]
|
60
|
+
|
61
|
+
# puts ""
|
62
|
+
Thread.current["Async::Lock.named"] = nil
|
63
|
+
next_job.perform
|
64
|
+
|
65
|
+
PoppableQu.queue.size.should eq 3
|
66
|
+
next_job = PoppableQu.queue.pop
|
67
|
+
next_job.args.should eq ["Async::Locked", "Yard", y.id, :now_trim, [{"_lock_arg"=>true, "lock_name"=>"lock:Yard:0"}, "front"]]
|
68
|
+
|
69
|
+
# puts ""
|
70
|
+
Thread.current["Async::Lock.named"] = nil
|
71
|
+
next_job.perform
|
72
|
+
|
73
|
+
PoppableQu.queue.size.should eq 2
|
74
|
+
next_job = PoppableQu.queue.pop
|
75
|
+
next_job.args.should eq ["Async", "Yard", y.id, :now_mow, ["front"]]
|
76
|
+
|
77
|
+
# puts ""
|
78
|
+
Thread.current["Async::Lock.named"] = nil
|
79
|
+
next_job.perform
|
80
|
+
|
81
|
+
PoppableQu.queue.size.should eq 2
|
82
|
+
next_job = PoppableQu.queue.pop
|
83
|
+
next_job.args.should eq ["Async::Locked", "Yard", 0, :now_conflict, []]
|
84
|
+
|
85
|
+
# puts ""
|
86
|
+
Thread.current["Async::Lock.named"] = nil
|
87
|
+
next_job.perform
|
88
|
+
|
89
|
+
PoppableQu.queue.size.should eq 1
|
90
|
+
next_job = PoppableQu.queue.pop
|
91
|
+
next_job.args.should eq ["Async", "Yard", nil, :now_burn, []]
|
92
|
+
|
93
|
+
# puts ""
|
94
|
+
Thread.current["Async::Lock.named"] = nil
|
95
|
+
next_job.perform
|
96
|
+
|
97
|
+
PoppableQu.queue.size.should eq 0
|
98
|
+
|
99
|
+
order_of_ops.should eq [
|
100
|
+
["consider", "now_do_all_the_work"],
|
101
|
+
["lock", "lock:Yard:0"],
|
102
|
+
["run", "now_do_all_the_work"],
|
103
|
+
["finish", "now_do_all_the_work"],
|
104
|
+
["consider", "now_conflict"],
|
105
|
+
["consider", "now_trim"],
|
106
|
+
["claim", "lock:Yard:0"],
|
107
|
+
["run", "now_trim"],
|
108
|
+
["finish", "now_trim"],
|
109
|
+
["release", "lock:Yard:0"],
|
110
|
+
["run", "now_mow"],
|
111
|
+
["finish", "now_mow"],
|
112
|
+
["consider", "now_conflict"],
|
113
|
+
["lock", "lock:Yard:0"],
|
114
|
+
["run", "now_conflict"],
|
115
|
+
["finish", "now_conflict"],
|
116
|
+
["release", "lock:Yard:0"],
|
117
|
+
["run", "now_burn"],
|
118
|
+
["finish", "now_burn"]]
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
data/spec/basics_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'async'
|
2
|
+
require 'shared/models'
|
3
|
+
|
4
|
+
class TestBackend
|
5
|
+
def initialize
|
6
|
+
@jobs = []
|
7
|
+
end
|
8
|
+
def enqueue(job_class, *args)
|
9
|
+
@jobs << [job_class, args]
|
10
|
+
end
|
11
|
+
def run_all_jobs!
|
12
|
+
while(job = @jobs.pop)
|
13
|
+
job_class, args = job
|
14
|
+
job_class.perform(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
def job_class
|
18
|
+
Async::Job
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "Async basics" do
|
23
|
+
before(:all) do
|
24
|
+
@test_backend = TestBackend.new
|
25
|
+
Async.backend = @test_backend
|
26
|
+
end
|
27
|
+
|
28
|
+
it "works" do
|
29
|
+
y = Yard.new
|
30
|
+
y.save
|
31
|
+
y.mowed.should be_nil
|
32
|
+
y.mow("front")
|
33
|
+
|
34
|
+
@test_backend.run_all_jobs!
|
35
|
+
|
36
|
+
y.reload
|
37
|
+
y.mowed.should eq "front"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/spec/qu_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'async'
|
2
|
+
require 'async/qu'
|
3
|
+
require 'shared/models'
|
4
|
+
require 'qu'
|
5
|
+
require 'qu-immediate'
|
6
|
+
|
7
|
+
describe "Async qu" do
|
8
|
+
before(:all) do
|
9
|
+
Qu.backend = Qu::Backend::Immediate.new
|
10
|
+
Async.backend = Async::QuBackend
|
11
|
+
end
|
12
|
+
|
13
|
+
it "works" do
|
14
|
+
y = Yard.new
|
15
|
+
y.save
|
16
|
+
y.mowed.should be_nil
|
17
|
+
y.mow("front")
|
18
|
+
|
19
|
+
y.reload
|
20
|
+
y.mowed.should eq "front"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/resque_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'async'
|
2
|
+
require 'async/resque'
|
3
|
+
require 'shared/models'
|
4
|
+
require 'redis'
|
5
|
+
require 'resque'
|
6
|
+
|
7
|
+
describe "Async resque" do
|
8
|
+
before(:all) do
|
9
|
+
Resque.redis = Redis.new
|
10
|
+
Async.backend = Async::ResqueBackend
|
11
|
+
end
|
12
|
+
|
13
|
+
it "works" do
|
14
|
+
y = Yard.new
|
15
|
+
y.save
|
16
|
+
y.mowed.should be_nil
|
17
|
+
y.mow("front")
|
18
|
+
|
19
|
+
worker = Resque::Worker.new("*")
|
20
|
+
while job = worker.reserve
|
21
|
+
job.perform
|
22
|
+
end
|
23
|
+
|
24
|
+
y.reload
|
25
|
+
y.mowed.should eq "front"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'cubbyhole/base'
|
2
|
+
class Yard < Cubbyhole::Base
|
3
|
+
|
4
|
+
def self.backend
|
5
|
+
@backend ||= Hash.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.find(id)
|
9
|
+
get(id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def do_all_the_work(which)
|
13
|
+
Async::Locked.run{ now_do_all_the_work(which) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def now_do_all_the_work(which)
|
17
|
+
# puts "do_all_the_work"
|
18
|
+
trim(which)
|
19
|
+
mow(which)
|
20
|
+
end
|
21
|
+
|
22
|
+
def trim(which)
|
23
|
+
Async::Locked.run{ now_trim(which) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def now_trim(which)
|
27
|
+
# puts "trim #{which}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def mow(which)
|
31
|
+
Async.run{ now_mow(which) }
|
32
|
+
end
|
33
|
+
def now_mow(which)
|
34
|
+
# puts "mow #{which}"
|
35
|
+
self.mowed = which
|
36
|
+
self.save
|
37
|
+
Yard.burn
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.burn
|
41
|
+
Async.run{ now_burn }
|
42
|
+
end
|
43
|
+
def self.now_burn
|
44
|
+
# puts "burning..."
|
45
|
+
end
|
46
|
+
|
47
|
+
def conflict
|
48
|
+
Async::Locked.run{ now_conflict }
|
49
|
+
end
|
50
|
+
def now_conflict
|
51
|
+
# puts "conflict"
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'async'
|
2
|
+
require 'async/sidekiq'
|
3
|
+
require 'shared/models'
|
4
|
+
require 'sidekiq/testing'
|
5
|
+
|
6
|
+
describe "Async sidekiq" do
|
7
|
+
before(:all) do
|
8
|
+
Async.backend = Async::SidekiqBackend
|
9
|
+
end
|
10
|
+
|
11
|
+
it "works" do
|
12
|
+
y = Yard.new
|
13
|
+
y.save
|
14
|
+
y.mowed.should be_nil
|
15
|
+
y.mow("front")
|
16
|
+
|
17
|
+
Async::SidekiqBackend::Job.drain
|
18
|
+
|
19
|
+
y.reload
|
20
|
+
y.mowed.should eq "front"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: async-jobs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jacob
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-30 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: jacob@engineyard.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- ".gitignore"
|
20
|
+
- Gemfile
|
21
|
+
- Gemfile.lock
|
22
|
+
- MIT-LICENSE
|
23
|
+
- README.md
|
24
|
+
- async.gemspec
|
25
|
+
- lib/async.rb
|
26
|
+
- lib/async/locked.rb
|
27
|
+
- lib/async/qu.rb
|
28
|
+
- lib/async/resque.rb
|
29
|
+
- lib/async/sidekiq.rb
|
30
|
+
- lib/async/version.rb
|
31
|
+
- spec/async_and_lock_spec.rb
|
32
|
+
- spec/basics_spec.rb
|
33
|
+
- spec/qu_spec.rb
|
34
|
+
- spec/resque_spec.rb
|
35
|
+
- spec/shared/models.rb
|
36
|
+
- spec/sidekiq_spec.rb
|
37
|
+
homepage: https://github.com/engineyard/async
|
38
|
+
licenses: []
|
39
|
+
metadata: {}
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 2.5.1
|
57
|
+
signing_key:
|
58
|
+
specification_version: 4
|
59
|
+
summary: abstraction over background job systems
|
60
|
+
test_files:
|
61
|
+
- spec/async_and_lock_spec.rb
|
62
|
+
- spec/basics_spec.rb
|
63
|
+
- spec/qu_spec.rb
|
64
|
+
- spec/resque_spec.rb
|
65
|
+
- spec/shared/models.rb
|
66
|
+
- spec/sidekiq_spec.rb
|
67
|
+
has_rdoc:
|