asynchronic 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/README.md +0 -70
- data/Rakefile +7 -0
- data/asynchronic.gemspec +5 -1
- data/lib/asynchronic/data_store/in_memory.rb +47 -0
- data/lib/asynchronic/data_store/key.rb +15 -0
- data/lib/asynchronic/data_store/lookup.rb +27 -0
- data/lib/asynchronic/data_store/redis.rb +52 -0
- data/lib/asynchronic/environment.rb +57 -0
- data/lib/asynchronic/error.rb +13 -0
- data/lib/asynchronic/hash.rb +31 -0
- data/lib/asynchronic/job.rb +46 -0
- data/lib/asynchronic/process.rb +117 -48
- data/lib/asynchronic/queue_engine/in_memory.rb +72 -0
- data/lib/asynchronic/queue_engine/ost.rb +73 -0
- data/lib/asynchronic/runtime.rb +40 -0
- data/lib/asynchronic/version.rb +1 -1
- data/lib/asynchronic/worker.rb +27 -18
- data/lib/asynchronic.rb +17 -32
- data/spec/coverage_helper.rb +0 -6
- data/spec/data_store/data_store_examples.rb +62 -0
- data/spec/data_store/in_memory_spec.rb +10 -0
- data/spec/data_store/key_spec.rb +36 -0
- data/spec/data_store/lookup_spec.rb +92 -0
- data/spec/data_store/redis_spec.rb +14 -0
- data/spec/expectations.rb +89 -0
- data/spec/facade_spec.rb +61 -0
- data/spec/jobs.rb +123 -33
- data/spec/minitest_helper.rb +12 -14
- data/spec/process/life_cycle_examples.rb +329 -0
- data/spec/process/life_cycle_in_memory_spec.rb +11 -0
- data/spec/process/life_cycle_redis_spec.rb +15 -0
- data/spec/queue_engine/in_memory_spec.rb +11 -0
- data/spec/queue_engine/ost_spec.rb +15 -0
- data/spec/queue_engine/queue_engine_examples.rb +47 -0
- data/spec/worker/in_memory_spec.rb +11 -0
- data/spec/worker/redis_spec.rb +16 -0
- data/spec/worker/worker_examples.rb +49 -0
- metadata +111 -18
- data/lib/asynchronic/persistent.rb +0 -61
- data/lib/asynchronic/pipeline.rb +0 -23
- data/spec/integration_spec.rb +0 -122
- data/spec/persistent_spec.rb +0 -88
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15caa715564ca133dd45cad9dce01db2de3cf7a6
|
4
|
+
data.tar.gz: 006fe0ebb38821c8cd1842e4b8060e14779d71c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c41cf2f761f07c3b17a0c05099361bbe90f2f33506bb29a349074f079827bd1ee588e12319158a1c59e399bbd0f641c616579b401f4aac2a82175be59916e73e
|
7
|
+
data.tar.gz: a64a7b0e1a5422bee3fa8a465d166ef37342ef5294865c24208a211423aed838f9f031b0320755cfde35788c2e6d34691b1fc762173af33bd3ed430359ffa112
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -24,76 +24,6 @@ Or install it yourself as:
|
|
24
24
|
|
25
25
|
## Usage
|
26
26
|
|
27
|
-
### Basic usage
|
28
|
-
|
29
|
-
class Job
|
30
|
-
extend Asynchronic::Pipeline
|
31
|
-
|
32
|
-
step :step_name do
|
33
|
-
...
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
Job.run
|
38
|
-
|
39
|
-
Asynchronic::Worker.start
|
40
|
-
|
41
|
-
### Enque job in specific queue
|
42
|
-
|
43
|
-
class Job
|
44
|
-
extend Asynchronic::Pipeline
|
45
|
-
|
46
|
-
queue :queue_name
|
47
|
-
|
48
|
-
step :step_name do
|
49
|
-
...
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
Job.run
|
54
|
-
|
55
|
-
Asynchronic::Worker.start :queue_name
|
56
|
-
|
57
|
-
### Pipeline with shared context
|
58
|
-
|
59
|
-
class Job
|
60
|
-
extend Asynchronic::Pipeline
|
61
|
-
|
62
|
-
step :first do |ctx|
|
63
|
-
ctx[:c] = ctx[:a] + ctx[:b]
|
64
|
-
100
|
65
|
-
end
|
66
|
-
|
67
|
-
step :second do |ctx, input|
|
68
|
-
input * ctx[:c] # 300
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
Job.run a: 1, b: 2
|
73
|
-
|
74
|
-
Asynchronic::Worker.start
|
75
|
-
|
76
|
-
### Specify queue for each step
|
77
|
-
|
78
|
-
class Job
|
79
|
-
extend Asynchronic::Pipeline
|
80
|
-
|
81
|
-
step :first_queue, queue: :queue1 do
|
82
|
-
...
|
83
|
-
end
|
84
|
-
|
85
|
-
step :second_queue, queue: ->(ctx){ctx[:dynamic_queue]} do
|
86
|
-
...
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
Job.run dynamic_queue: :queue2
|
91
|
-
|
92
|
-
[:queue1, :queue2].map do |queue|
|
93
|
-
Thread.new do
|
94
|
-
Asynchronic::Worker.start queue
|
95
|
-
end
|
96
|
-
end
|
97
27
|
|
98
28
|
## Contributing
|
99
29
|
|
data/Rakefile
CHANGED
data/asynchronic.gemspec
CHANGED
@@ -18,11 +18,15 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
+
spec.add_dependency 'redis', '~> 3.0'
|
21
22
|
spec.add_dependency 'ost', '~> 0.1'
|
22
|
-
|
23
|
+
spec.add_dependency 'class_config', '~> 0.0'
|
24
|
+
|
23
25
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
26
|
spec.add_development_dependency 'rake'
|
25
27
|
spec.add_development_dependency 'minitest', '~> 4.7'
|
28
|
+
spec.add_development_dependency 'minitest-great_expectations', '~> 0.0'
|
26
29
|
spec.add_development_dependency 'turn', '~> 0.9'
|
27
30
|
spec.add_development_dependency 'simplecov'
|
31
|
+
spec.add_development_dependency 'pry'
|
28
32
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Asynchronic
|
2
|
+
module DataStore
|
3
|
+
class InMemory
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@hash = {}
|
7
|
+
@mutex = Mutex.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(key)
|
11
|
+
@hash[key.to_s]
|
12
|
+
end
|
13
|
+
|
14
|
+
def set(key, value)
|
15
|
+
@mutex.synchronize { @hash[key.to_s] = value }
|
16
|
+
end
|
17
|
+
|
18
|
+
def merge(key, hash)
|
19
|
+
scoped_key = Key.new key
|
20
|
+
hash.each do |k,v|
|
21
|
+
set scoped_key[k].to_s, v
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash(key)
|
26
|
+
children_key = "#{key}:"
|
27
|
+
keys(children_key).inject({}) do |hash, k|
|
28
|
+
hash[k[children_key.size..-1]] = get k
|
29
|
+
hash
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def keys(key=nil)
|
34
|
+
key ? keys.select { |k| k.start_with? key.to_s } : @hash.keys
|
35
|
+
end
|
36
|
+
|
37
|
+
def clear(key=nil)
|
38
|
+
if key
|
39
|
+
@hash.delete_if { |k,v| k.start_with? key.to_s }
|
40
|
+
else
|
41
|
+
@hash.clear
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Asynchronic
|
2
|
+
module DataStore
|
3
|
+
class Lookup
|
4
|
+
|
5
|
+
KEYS = [:status, :data, :jobs, :error, :created_at, :queued_at, :started_at, :finalized_at]
|
6
|
+
|
7
|
+
def initialize(job)
|
8
|
+
@job = job
|
9
|
+
end
|
10
|
+
|
11
|
+
def id
|
12
|
+
if @job.parent
|
13
|
+
DataStore::Key.new(@job.parent)[:jobs][@job.id]
|
14
|
+
else
|
15
|
+
DataStore::Key.new(:job)[@job.id]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
KEYS.each do |key|
|
20
|
+
define_method key do
|
21
|
+
id[key]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Asynchronic
|
2
|
+
module DataStore
|
3
|
+
class Redis
|
4
|
+
|
5
|
+
attr_reader :connection
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
@connection = ::Redis.new *args
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(key)
|
12
|
+
value = connection.get root[key]
|
13
|
+
value ? Marshal.load(value) : nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(key, value)
|
17
|
+
connection.set root[key], Marshal.dump(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def merge(key, hash)
|
21
|
+
scoped_key = Key.new key
|
22
|
+
hash.each do |k,v|
|
23
|
+
set scoped_key[k], v
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash(key)
|
28
|
+
children_key = "#{key}:"
|
29
|
+
keys(children_key).inject({}) do |hash, k|
|
30
|
+
hash[k[children_key.size..-1]] = get k
|
31
|
+
hash
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def keys(key=nil)
|
36
|
+
keys = key ? connection.keys("#{root[key]}*") : connection.keys
|
37
|
+
keys.map { |k| k[(root.size + 1)..-1] }
|
38
|
+
end
|
39
|
+
|
40
|
+
def clear(key=nil)
|
41
|
+
keys(key).each { |k| connection.del root[k] }
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def root
|
47
|
+
Key.new :asynchronic
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Asynchronic
|
2
|
+
class Environment
|
3
|
+
|
4
|
+
attr_reader :queue_engine
|
5
|
+
attr_reader :data_store
|
6
|
+
|
7
|
+
def initialize(queue_engine, data_store)
|
8
|
+
@queue_engine = queue_engine
|
9
|
+
@data_store = data_store
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
data_store.get key
|
14
|
+
end
|
15
|
+
|
16
|
+
def []=(key, value)
|
17
|
+
data_store.set key, value
|
18
|
+
end
|
19
|
+
|
20
|
+
def queue(name)
|
21
|
+
queue_engine[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
def default_queue
|
25
|
+
queue(queue_engine.default_queue)
|
26
|
+
end
|
27
|
+
|
28
|
+
def enqueue(msg, queue=nil)
|
29
|
+
queue(queue || queue_engine.default_queue).push msg
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_job(job_class, options={})
|
33
|
+
Asynchronic.logger.debug('Asynchronic') { "Building job #{job_class} - #{options}" }
|
34
|
+
job_class.new(options).tap do |job|
|
35
|
+
self[job.lookup.id] = job
|
36
|
+
self[job.lookup.created_at] = Time.now
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_process(job_class, options={})
|
41
|
+
Process.new build_job(job_class, options), self
|
42
|
+
end
|
43
|
+
|
44
|
+
def load_process(pid)
|
45
|
+
Process.new self[pid], self
|
46
|
+
end
|
47
|
+
|
48
|
+
def processes
|
49
|
+
data_store.keys.
|
50
|
+
select { |k| k.match Regexp.new("job:#{Asynchronic::UUID_REGEXP}:created_at$") }.
|
51
|
+
sort_by {|k| data_store.get k }.
|
52
|
+
reverse.
|
53
|
+
map { |k| load_process k.gsub(':created_at', '') }
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Asynchronic
|
2
|
+
class Error
|
3
|
+
|
4
|
+
attr_reader :message
|
5
|
+
attr_reader :backtrace
|
6
|
+
|
7
|
+
def initialize(source)
|
8
|
+
@message = source.respond_to?(:message) ? source.message : source.to_s
|
9
|
+
@backtrace = source.respond_to?(:backtrace) ? source.backtrace : []
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Hash
|
2
|
+
def with_indiferent_access
|
3
|
+
HashWithIndiferentAccess.new self
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class HashWithIndiferentAccess < Hash
|
8
|
+
|
9
|
+
def initialize(hash=nil)
|
10
|
+
merge! hash if hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
if key?(key) || !transformable_key?(key)
|
15
|
+
super
|
16
|
+
else
|
17
|
+
super transform_key(key)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def transformable_key?(key)
|
24
|
+
key.is_a?(String) || key.is_a?(Symbol)
|
25
|
+
end
|
26
|
+
|
27
|
+
def transform_key(key)
|
28
|
+
key.is_a?(String) ? key.to_sym : key.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Asynchronic
|
2
|
+
class Job
|
3
|
+
|
4
|
+
attr_reader :id
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :queue
|
7
|
+
attr_reader :parent
|
8
|
+
attr_reader :dependencies
|
9
|
+
attr_reader :local
|
10
|
+
|
11
|
+
def initialize(options={})
|
12
|
+
@id = SecureRandom.uuid
|
13
|
+
@name = options.key?(:alias) ? options[:alias].to_s : self.class.to_s
|
14
|
+
@queue = options[:queue] || self.class.queue
|
15
|
+
@parent = options[:parent]
|
16
|
+
@dependencies = Array(options[:dependencies] || options[:dependency]).map(&:to_s)
|
17
|
+
@local = options[:local] || {}
|
18
|
+
|
19
|
+
raise 'Cant have dependencies without parent job' if dependencies.any? && parent.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def lookup
|
23
|
+
DataStore::Lookup.new self
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.queue(queue=nil)
|
27
|
+
queue ? @queue = queue : @queue
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.implementation
|
31
|
+
@implementation
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.enqueue(data={})
|
35
|
+
process = Asynchronic.environment.build_process self
|
36
|
+
process.enqueue data
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.define(&block)
|
42
|
+
@implementation = block
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/asynchronic/process.rb
CHANGED
@@ -1,79 +1,148 @@
|
|
1
1
|
module Asynchronic
|
2
2
|
class Process
|
3
3
|
|
4
|
-
|
4
|
+
STATUSES = [:pending, :queued, :running, :waiting, :completed, :aborted]
|
5
5
|
|
6
|
-
|
6
|
+
TIME_TRACKING_MAP = {
|
7
|
+
queued: :queued_at,
|
8
|
+
running: :started_at,
|
9
|
+
completed: :finalized_at,
|
10
|
+
aborted: :finalized_at
|
11
|
+
}
|
7
12
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
def_delegators :job, :id, :name, :queue
|
16
|
+
def_delegators :data, :[]
|
17
|
+
|
18
|
+
attr_reader :job
|
19
|
+
attr_reader :env
|
20
|
+
|
21
|
+
def initialize(job, env)
|
22
|
+
@job = job
|
23
|
+
@env = env
|
16
24
|
end
|
17
25
|
|
18
|
-
def
|
19
|
-
|
20
|
-
Ost[q.is_a?(Proc) ? q.call(context) : q].push id
|
26
|
+
def pid
|
27
|
+
lookup.id
|
21
28
|
end
|
22
29
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
def data
|
31
|
+
parent ? parent.data : env.data_store.to_hash(lookup.data).with_indiferent_access
|
32
|
+
end
|
33
|
+
|
34
|
+
def merge(data)
|
35
|
+
parent ? parent.merge(data) : env.data_store.merge(lookup.data, data)
|
36
|
+
end
|
37
|
+
|
38
|
+
def enqueue(data={})
|
39
|
+
merge data
|
40
|
+
env.enqueue lookup.id, queue
|
41
|
+
update_status :queued
|
42
|
+
|
43
|
+
lookup.id
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute
|
47
|
+
run
|
48
|
+
wakeup
|
49
|
+
end
|
50
|
+
|
51
|
+
def wakeup
|
52
|
+
if waiting?
|
53
|
+
if processes.any?(&:aborted?)
|
54
|
+
abort Error.new "Error caused by #{processes.select(&:aborted?).map{|p| p.job.name}.join(', ')}"
|
55
|
+
else
|
56
|
+
if processes.all?(&:completed?)
|
57
|
+
update_status :completed
|
36
58
|
else
|
37
|
-
|
59
|
+
processes.select(&:ready?).each { |p| p.enqueue }
|
38
60
|
end
|
39
61
|
end
|
40
62
|
end
|
63
|
+
|
64
|
+
parent.wakeup if parent && finalized?
|
41
65
|
end
|
42
66
|
|
43
|
-
def
|
44
|
-
|
45
|
-
process.enqueue(pipeline.steps.first.options[:queue])
|
46
|
-
process.id
|
67
|
+
def error
|
68
|
+
env[lookup.error]
|
47
69
|
end
|
48
70
|
|
49
|
-
|
71
|
+
def status
|
72
|
+
env[lookup.status] || :pending
|
73
|
+
end
|
74
|
+
|
75
|
+
STATUSES.each do |status|
|
76
|
+
define_method "#{status}?" do
|
77
|
+
self.status == status
|
78
|
+
end
|
79
|
+
end
|
50
80
|
|
51
|
-
def
|
52
|
-
|
81
|
+
def ready?
|
82
|
+
pending? && dependencies.all?(&:completed?)
|
53
83
|
end
|
54
84
|
|
55
|
-
def
|
56
|
-
|
85
|
+
def finalized?
|
86
|
+
completed? || aborted?
|
57
87
|
end
|
58
88
|
|
59
|
-
def
|
60
|
-
|
89
|
+
def processes(name=nil)
|
90
|
+
processes = env.data_store.keys(lookup.jobs).
|
91
|
+
select { |k| k.match Regexp.new("^#{lookup.jobs[Asynchronic::UUID_REGEXP]}$") }.
|
92
|
+
map { |k| env.load_process k }
|
93
|
+
|
94
|
+
name ? processes.detect { |p| p.name == name.to_s } : processes
|
95
|
+
end
|
96
|
+
|
97
|
+
def parent
|
98
|
+
@parent ||= Process.new env[job.parent], env if job.parent
|
99
|
+
end
|
100
|
+
|
101
|
+
def dependencies
|
102
|
+
@dependencies ||= parent.processes.select { |p| job.dependencies.include? p.name }
|
103
|
+
end
|
104
|
+
|
105
|
+
def created_at
|
106
|
+
env[lookup.created_at]
|
107
|
+
end
|
108
|
+
|
109
|
+
def queued_at
|
110
|
+
env[lookup.queued_at]
|
111
|
+
end
|
112
|
+
|
113
|
+
def started_at
|
114
|
+
env[lookup.started_at]
|
115
|
+
end
|
116
|
+
|
117
|
+
def finalized_at
|
118
|
+
env[lookup.finalized_at]
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def run
|
124
|
+
update_status :running
|
125
|
+
Runtime.evaluate self
|
126
|
+
update_status :waiting
|
127
|
+
rescue Exception => ex
|
128
|
+
message = "Failed job #{job.name} (#{lookup.id})\n#{ex.class} #{ex.message}\n#{ex.backtrace.join("\n")}"
|
129
|
+
Asynchronic.logger.error('Asynchronic') { message }
|
130
|
+
abort ex
|
61
131
|
end
|
62
132
|
|
63
|
-
def
|
64
|
-
|
133
|
+
def update_status(status)
|
134
|
+
Asynchronic.logger.info('Asynchronic') { "#{status.to_s.capitalize} #{job.name} (#{lookup.id})" }
|
135
|
+
env[lookup.status] = status
|
136
|
+
env[lookup.send(TIME_TRACKING_MAP[status])] = Time.now if TIME_TRACKING_MAP.key? status
|
65
137
|
end
|
66
138
|
|
67
|
-
def
|
68
|
-
|
139
|
+
def abort(exception)
|
140
|
+
env[lookup.error] = Error.new(exception)
|
141
|
+
update_status :aborted
|
69
142
|
end
|
70
143
|
|
71
|
-
def
|
72
|
-
|
73
|
-
Asynchronic.logger.info('Asynchronic') { "#{message} - Start" }
|
74
|
-
result = yield
|
75
|
-
Asynchronic.logger.info('Asynchronic') { "#{message} - End (Time: #{Time.now - start})" }
|
76
|
-
result
|
144
|
+
def lookup
|
145
|
+
@lookup ||= job.lookup
|
77
146
|
end
|
78
147
|
|
79
148
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Asynchronic
|
2
|
+
module QueueEngine
|
3
|
+
class InMemory
|
4
|
+
|
5
|
+
attr_reader :default_queue
|
6
|
+
|
7
|
+
def initialize(options={})
|
8
|
+
@default_queue = options.fetch(:default_queue, Asynchronic.default_queue)
|
9
|
+
@queues ||= Hash.new { |h,k| h[k] = Queue.new }
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](name)
|
13
|
+
@queues[name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def queues
|
17
|
+
@queues.keys.map(&:to_sym)
|
18
|
+
end
|
19
|
+
|
20
|
+
def clear
|
21
|
+
@queues.clear
|
22
|
+
end
|
23
|
+
|
24
|
+
def listener
|
25
|
+
Listener.new
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class Queue
|
30
|
+
|
31
|
+
extend Forwardable
|
32
|
+
|
33
|
+
def_delegators :@queue, :size, :empty?, :to_a
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@queue = []
|
37
|
+
@mutex = Mutex.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def pop
|
41
|
+
@mutex.synchronize { @queue.shift }
|
42
|
+
end
|
43
|
+
|
44
|
+
def push(message)
|
45
|
+
@mutex.synchronize { @queue.push message }
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class Listener
|
52
|
+
|
53
|
+
def listen(queue, &block)
|
54
|
+
@stopping = false
|
55
|
+
|
56
|
+
loop do
|
57
|
+
break if @stopping
|
58
|
+
item = queue.pop
|
59
|
+
next unless item
|
60
|
+
block.call item
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop
|
65
|
+
@stopping = true
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|