asynchronic 0.0.1 → 0.1.0
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/.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
|