asynchronic 3.0.1 → 4.0.1
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 +5 -5
- data/.travis.yml +7 -10
- data/README.md +1 -2
- data/asynchronic.gemspec +2 -3
- data/lib/asynchronic.rb +8 -0
- data/lib/asynchronic/data_store/in_memory.rb +17 -15
- data/lib/asynchronic/data_store/key.rb +3 -3
- data/lib/asynchronic/data_store/lazy_value.rb +5 -3
- data/lib/asynchronic/data_store/redis.rb +22 -14
- data/lib/asynchronic/data_store/scoped_store.rb +18 -19
- data/lib/asynchronic/environment.rb +3 -3
- data/lib/asynchronic/error.rb +2 -3
- data/lib/asynchronic/garbage_collector.rb +2 -1
- data/lib/asynchronic/job.rb +12 -12
- data/lib/asynchronic/notifier/broadcaster.rb +8 -4
- data/lib/asynchronic/process.rb +42 -40
- data/lib/asynchronic/queue_engine/in_memory.rb +17 -11
- data/lib/asynchronic/queue_engine/ost.rb +7 -5
- data/lib/asynchronic/queue_engine/synchronic.rb +19 -7
- data/lib/asynchronic/version.rb +1 -1
- data/lib/asynchronic/worker.rb +7 -10
- data/spec/data_store/data_store_examples.rb +17 -9
- data/spec/data_store/in_memory_spec.rb +0 -2
- data/spec/data_store/key_spec.rb +1 -1
- data/spec/data_store/lazy_value_examples.rb +7 -5
- data/spec/data_store/redis_spec.rb +4 -10
- data/spec/expectations.rb +2 -2
- data/spec/facade_spec.rb +3 -3
- data/spec/jobs.rb +10 -10
- data/spec/minitest_helper.rb +5 -12
- data/spec/process/life_cycle_examples.rb +25 -23
- data/spec/process/life_cycle_in_memory_spec.rb +0 -1
- data/spec/process/life_cycle_redis_spec.rb +0 -1
- data/spec/queue_engine/in_memory_spec.rb +1 -3
- data/spec/queue_engine/ost_spec.rb +1 -7
- data/spec/queue_engine/queue_engine_examples.rb +17 -9
- data/spec/queue_engine/synchronic_spec.rb +1 -1
- data/spec/worker/in_memory_spec.rb +1 -2
- data/spec/worker/redis_spec.rb +0 -6
- data/spec/worker/worker_examples.rb +6 -4
- metadata +11 -20
@@ -8,23 +8,27 @@ module Asynchronic
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def publish(pid, event, data=nil)
|
11
|
-
|
11
|
+
broadcaster.publish DataStore::Key[pid][event], data
|
12
12
|
end
|
13
13
|
|
14
14
|
def subscribe(pid, event, &block)
|
15
|
-
|
15
|
+
broadcaster.subscribe DataStore::Key[pid][event] do |data|
|
16
16
|
block.call data
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
def unsubscribe(subscription_id)
|
21
|
-
|
21
|
+
broadcaster.unsubscribe subscription_id
|
22
22
|
end
|
23
23
|
|
24
24
|
def unsubscribe_all
|
25
|
-
|
25
|
+
broadcaster.unsubscribe_all
|
26
26
|
end
|
27
27
|
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :broadcaster
|
31
|
+
|
28
32
|
end
|
29
33
|
end
|
30
34
|
end
|
data/lib/asynchronic/process.rb
CHANGED
@@ -13,11 +13,36 @@ module Asynchronic
|
|
13
13
|
|
14
14
|
ATTRIBUTE_NAMES = [:type, :name, :queue, :status, :dependencies, :data, :result, :error, :connection_name] | TIME_TRACKING_MAP.values.uniq
|
15
15
|
|
16
|
+
AUTOMATIC_ABORTED_ERROR_MESSAGE = 'Automatic aborted before execution'
|
16
17
|
CANCELED_ERROR_MESSAGE = 'Canceled'
|
17
18
|
DEAD_ERROR_MESSAGE = 'Process connection broken'
|
18
19
|
|
19
20
|
attr_reader :id
|
20
21
|
|
22
|
+
def self.create(environment, type, params={})
|
23
|
+
id = params.delete(:id) || SecureRandom.uuid
|
24
|
+
|
25
|
+
Asynchronic.logger.debug('Asynchronic') { "Created process #{type} - #{id} - #{params}" }
|
26
|
+
|
27
|
+
new(environment, id) do
|
28
|
+
self.type = type
|
29
|
+
self.name = (params.delete(:alias) || type).to_s
|
30
|
+
self.queue = params.delete(:queue) || type.queue || parent_queue
|
31
|
+
self.dependencies = Array(params.delete(:dependencies)) | Array(params.delete(:dependency)) | infer_dependencies(params)
|
32
|
+
self.params = params
|
33
|
+
self.data = {}
|
34
|
+
pending!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.all(environment)
|
39
|
+
environment.data_store.keys
|
40
|
+
.select { |k| k.sections.count == 2 && k.match(/created_at$/) }
|
41
|
+
.sort_by { |k| environment.data_store[k] }
|
42
|
+
.reverse
|
43
|
+
.map { |k| Process.new environment, k.remove_last }
|
44
|
+
end
|
45
|
+
|
21
46
|
def initialize(environment, id, &block)
|
22
47
|
@environment = environment
|
23
48
|
@id = DataStore::Key[id]
|
@@ -83,9 +108,11 @@ module Asynchronic
|
|
83
108
|
end
|
84
109
|
|
85
110
|
def processes
|
86
|
-
data_store.scoped(:processes)
|
87
|
-
|
88
|
-
|
111
|
+
data_store.scoped(:processes)
|
112
|
+
.keys
|
113
|
+
.select { |k| k.sections.count == 2 && k.match(/\|name$/) }
|
114
|
+
.sort
|
115
|
+
.map { |k| Process.new environment, id[:processes][k.remove_last] }
|
89
116
|
end
|
90
117
|
|
91
118
|
def parent
|
@@ -97,22 +124,20 @@ module Asynchronic
|
|
97
124
|
end
|
98
125
|
|
99
126
|
def real_error
|
100
|
-
return nil
|
127
|
+
return nil if !aborted?
|
101
128
|
|
102
|
-
processes.
|
103
|
-
return child.real_error if child.error
|
104
|
-
end
|
129
|
+
first_aborted_child = processes.select(&:aborted?).sort_by(&:finalized_at).first
|
105
130
|
|
106
|
-
error.message
|
131
|
+
first_aborted_child ? first_aborted_child.real_error : error.message
|
107
132
|
end
|
108
133
|
|
109
134
|
def dependencies
|
110
135
|
return [] if parent.nil? || data_store[:dependencies].empty?
|
111
|
-
|
136
|
+
|
112
137
|
parent_processes = parent.processes.each_with_object({}) do |process, hash|
|
113
138
|
hash[process.name] = process
|
114
139
|
end
|
115
|
-
|
140
|
+
|
116
141
|
data_store[:dependencies].map { |d| parent_processes[d.to_s] }
|
117
142
|
end
|
118
143
|
|
@@ -136,7 +161,7 @@ module Asynchronic
|
|
136
161
|
wakeup_children
|
137
162
|
end
|
138
163
|
Asynchronic.logger.info('Asynchronic') { "Wakeup finalized #{type} (#{id})" }
|
139
|
-
|
164
|
+
|
140
165
|
parent.wakeup if parent && finalized?
|
141
166
|
end
|
142
167
|
|
@@ -152,29 +177,6 @@ module Asynchronic
|
|
152
177
|
self.data = self.data.merge key => value
|
153
178
|
end
|
154
179
|
|
155
|
-
def self.create(environment, type, params={})
|
156
|
-
id = params.delete(:id) || SecureRandom.uuid
|
157
|
-
|
158
|
-
Asynchronic.logger.debug('Asynchronic') { "Created process #{type} - #{id} - #{params}" }
|
159
|
-
|
160
|
-
new(environment, id) do
|
161
|
-
self.type = type
|
162
|
-
self.name = (params.delete(:alias) || type).to_s
|
163
|
-
self.queue = params.delete(:queue) || type.queue || parent_queue
|
164
|
-
self.dependencies = Array(params.delete(:dependencies)) | Array(params.delete(:dependency)) | infer_dependencies(params)
|
165
|
-
self.params = params
|
166
|
-
self.data = {}
|
167
|
-
pending!
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def self.all(environment)
|
172
|
-
environment.data_store.keys.
|
173
|
-
select { |k| k.sections.count == 2 && k.match(/created_at$/) }.
|
174
|
-
sort_by { |k| environment.data_store[k] }.reverse.
|
175
|
-
map { |k| Process.new environment, k.remove_last }
|
176
|
-
end
|
177
|
-
|
178
180
|
private
|
179
181
|
|
180
182
|
attr_reader :environment
|
@@ -195,10 +197,10 @@ module Asynchronic
|
|
195
197
|
|
196
198
|
def status=(status)
|
197
199
|
Asynchronic.logger.info('Asynchronic') { "#{status.to_s.capitalize} #{type} (#{id})" }
|
198
|
-
|
200
|
+
|
199
201
|
data_store[:status] = status
|
200
202
|
data_store[TIME_TRACKING_MAP[status]] = Time.now if TIME_TRACKING_MAP.key? status
|
201
|
-
|
203
|
+
|
202
204
|
environment.notifier.publish id, :status_changed, status
|
203
205
|
environment.notifier.publish id, :finalized if finalized?
|
204
206
|
end
|
@@ -215,8 +217,8 @@ module Asynchronic
|
|
215
217
|
end
|
216
218
|
end
|
217
219
|
|
218
|
-
def abort!(exception
|
219
|
-
self.error = Error.new exception
|
220
|
+
def abort!(exception)
|
221
|
+
self.error = Error.new exception
|
220
222
|
aborted!
|
221
223
|
end
|
222
224
|
|
@@ -224,13 +226,13 @@ module Asynchronic
|
|
224
226
|
self.connection_name = Asynchronic.connection_name
|
225
227
|
|
226
228
|
if root.aborted?
|
227
|
-
abort!
|
229
|
+
abort! AUTOMATIC_ABORTED_ERROR_MESSAGE
|
228
230
|
else
|
229
231
|
running!
|
230
232
|
self.result = job.call
|
231
233
|
waiting!
|
232
234
|
end
|
233
|
-
|
235
|
+
|
234
236
|
rescue Exception => ex
|
235
237
|
message = "Failed process #{type} (#{id})\n#{ex.class} #{ex.message}\n#{ex.backtrace.join("\n")}"
|
236
238
|
Asynchronic.logger.error('Asynchronic') { message }
|
@@ -2,27 +2,25 @@ module Asynchronic
|
|
2
2
|
module QueueEngine
|
3
3
|
class InMemory
|
4
4
|
|
5
|
-
attr_reader :default_queue
|
6
|
-
|
7
5
|
def initialize(options={})
|
8
|
-
@
|
6
|
+
@options = options
|
9
7
|
@queues ||= Hash.new { |h,k| h[k] = Queue.new }
|
10
8
|
end
|
11
9
|
|
12
10
|
def default_queue
|
13
|
-
@default_queue ||= Asynchronic.default_queue
|
11
|
+
@default_queue ||= options.fetch(:default_queue, Asynchronic.default_queue)
|
14
12
|
end
|
15
13
|
|
16
14
|
def [](name)
|
17
|
-
|
15
|
+
queues[name]
|
18
16
|
end
|
19
17
|
|
20
|
-
def
|
21
|
-
|
18
|
+
def queue_names
|
19
|
+
queues.keys.map(&:to_sym)
|
22
20
|
end
|
23
21
|
|
24
22
|
def clear
|
25
|
-
|
23
|
+
queues.clear
|
26
24
|
end
|
27
25
|
|
28
26
|
def listener
|
@@ -37,12 +35,16 @@ module Asynchronic
|
|
37
35
|
[Asynchronic.connection_name]
|
38
36
|
end
|
39
37
|
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :queues, :options
|
41
|
+
|
40
42
|
|
41
43
|
class Queue
|
42
44
|
|
43
45
|
extend Forwardable
|
44
46
|
|
45
|
-
def_delegators
|
47
|
+
def_delegators :queue, :size, :empty?, :to_a
|
46
48
|
|
47
49
|
def initialize
|
48
50
|
@queue = []
|
@@ -50,13 +52,17 @@ module Asynchronic
|
|
50
52
|
end
|
51
53
|
|
52
54
|
def pop
|
53
|
-
|
55
|
+
mutex.synchronize { queue.shift }
|
54
56
|
end
|
55
57
|
|
56
58
|
def push(message)
|
57
|
-
|
59
|
+
mutex.synchronize { queue.push message }
|
58
60
|
end
|
59
61
|
|
62
|
+
private
|
63
|
+
|
64
|
+
attr_reader :queue, :mutex
|
65
|
+
|
60
66
|
end
|
61
67
|
|
62
68
|
|
@@ -5,22 +5,22 @@ module Asynchronic
|
|
5
5
|
attr_reader :redis, :default_queue
|
6
6
|
|
7
7
|
def initialize(options={})
|
8
|
-
@redis =
|
8
|
+
@redis = Asynchronic.establish_redis_connection options
|
9
9
|
@default_queue = options.fetch(:default_queue, Asynchronic.default_queue)
|
10
10
|
@queues ||= Hash.new { |h,k| h[k] = Queue.new k, redis }
|
11
11
|
@keep_alive_thread = notify_keep_alive
|
12
12
|
end
|
13
13
|
|
14
14
|
def [](name)
|
15
|
-
|
15
|
+
queues[name]
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
(
|
18
|
+
def queue_names
|
19
|
+
(queues.values.map(&:key) | redis.call!('KEYS', 'ost:*')).map { |q| q.to_s[4..-1].to_sym }
|
20
20
|
end
|
21
21
|
|
22
22
|
def clear
|
23
|
-
|
23
|
+
queues.clear
|
24
24
|
redis.call!('KEYS', 'ost:*').each { |k| redis.call!('DEL', k) }
|
25
25
|
end
|
26
26
|
|
@@ -41,6 +41,8 @@ module Asynchronic
|
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
+
attr_reader :queues
|
45
|
+
|
44
46
|
def notify_keep_alive
|
45
47
|
Thread.new do
|
46
48
|
loop do
|
@@ -5,7 +5,7 @@ module Asynchronic
|
|
5
5
|
attr_reader :stubs
|
6
6
|
|
7
7
|
def initialize(options={})
|
8
|
-
@
|
8
|
+
@options = options
|
9
9
|
@stubs = {}
|
10
10
|
end
|
11
11
|
|
@@ -14,7 +14,7 @@ module Asynchronic
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def environment
|
17
|
-
@environment ||= Asynchronic.environment
|
17
|
+
@environment ||= options.fetch(:environment, Asynchronic.environment)
|
18
18
|
end
|
19
19
|
|
20
20
|
def [](name)
|
@@ -22,7 +22,7 @@ module Asynchronic
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def stub(job, &block)
|
25
|
-
|
25
|
+
stubs[job] = block
|
26
26
|
end
|
27
27
|
|
28
28
|
def asynchronic?
|
@@ -33,6 +33,10 @@ module Asynchronic
|
|
33
33
|
[Asynchronic.connection_name]
|
34
34
|
end
|
35
35
|
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :options
|
39
|
+
|
36
40
|
|
37
41
|
class Queue
|
38
42
|
|
@@ -41,11 +45,11 @@ module Asynchronic
|
|
41
45
|
end
|
42
46
|
|
43
47
|
def push(message)
|
44
|
-
process =
|
48
|
+
process = engine.environment.load_process(message)
|
45
49
|
|
46
|
-
if
|
50
|
+
if engine.stubs[process.type]
|
47
51
|
job = process.job
|
48
|
-
block =
|
52
|
+
block = engine.stubs[process.type]
|
49
53
|
process.define_singleton_method :job do
|
50
54
|
MockJob.new job, process, &block
|
51
55
|
end
|
@@ -54,6 +58,10 @@ module Asynchronic
|
|
54
58
|
process.execute
|
55
59
|
end
|
56
60
|
|
61
|
+
private
|
62
|
+
|
63
|
+
attr_reader :engine
|
64
|
+
|
57
65
|
end
|
58
66
|
|
59
67
|
|
@@ -66,12 +74,16 @@ module Asynchronic
|
|
66
74
|
end
|
67
75
|
|
68
76
|
def call
|
69
|
-
|
77
|
+
block.call process
|
70
78
|
end
|
71
79
|
|
72
80
|
def before_finalize
|
73
81
|
end
|
74
82
|
|
83
|
+
private
|
84
|
+
|
85
|
+
attr_reader :process, :block
|
86
|
+
|
75
87
|
end
|
76
88
|
|
77
89
|
end
|
data/lib/asynchronic/version.rb
CHANGED
data/lib/asynchronic/worker.rb
CHANGED
@@ -1,24 +1,21 @@
|
|
1
1
|
class Asynchronic::Worker
|
2
2
|
|
3
|
-
attr_reader :queue
|
4
|
-
attr_reader :queue_name
|
5
|
-
attr_reader :env
|
6
|
-
attr_reader :listener
|
3
|
+
attr_reader :queue, :queue_name, :environment, :listener
|
7
4
|
|
8
|
-
def initialize(queue_name,
|
5
|
+
def initialize(queue_name, environment)
|
9
6
|
@queue_name = queue_name
|
10
|
-
@queue =
|
11
|
-
@
|
12
|
-
@listener =
|
7
|
+
@queue = environment.queue_engine[queue_name]
|
8
|
+
@environment = environment
|
9
|
+
@listener = environment.queue_engine.listener
|
13
10
|
end
|
14
11
|
|
15
12
|
def start
|
16
13
|
Asynchronic.logger.info('Asynchronic') { "Starting worker of #{queue_name} (#{Process.pid})" }
|
17
14
|
|
18
15
|
Signal.trap('QUIT') { stop }
|
19
|
-
|
16
|
+
|
20
17
|
listener.listen(queue) do |pid|
|
21
|
-
|
18
|
+
environment.load_process(pid).execute
|
22
19
|
end
|
23
20
|
end
|
24
21
|
|
@@ -1,5 +1,11 @@
|
|
1
1
|
module DataStoreExamples
|
2
|
-
|
2
|
+
|
3
|
+
extend Minitest::Spec::DSL
|
4
|
+
|
5
|
+
after do
|
6
|
+
data_store.clear
|
7
|
+
end
|
8
|
+
|
3
9
|
it 'Get/Set value' do
|
4
10
|
data_store[:key] = 123
|
5
11
|
data_store[:key].must_equal 123
|
@@ -32,8 +38,8 @@ module DataStoreExamples
|
|
32
38
|
data_store.delete_cascade Asynchronic::DataStore::Key[:key_1]
|
33
39
|
|
34
40
|
data_store.keys.sort.must_equal [
|
35
|
-
Asynchronic::DataStore::Key[:key_2],
|
36
|
-
Asynchronic::DataStore::Key[:key_2][:key_2_1],
|
41
|
+
Asynchronic::DataStore::Key[:key_2],
|
42
|
+
Asynchronic::DataStore::Key[:key_2][:key_2_1],
|
37
43
|
Asynchronic::DataStore::Key[:key_2][:key_2_2]
|
38
44
|
]
|
39
45
|
end
|
@@ -110,18 +116,20 @@ module DataStoreExamples
|
|
110
116
|
|
111
117
|
it 'Synchronization' do
|
112
118
|
sum = 0
|
113
|
-
|
119
|
+
|
120
|
+
threads = 10.times.map do
|
114
121
|
Thread.new do
|
115
|
-
data_store.synchronize('
|
122
|
+
data_store.synchronize('lock_key') do
|
116
123
|
temp = sum
|
117
|
-
sleep 0
|
124
|
+
sleep 0.01
|
118
125
|
sum = temp + 1
|
119
126
|
end
|
120
127
|
end
|
121
128
|
end
|
129
|
+
|
122
130
|
threads.each(&:join)
|
123
|
-
|
124
|
-
sum.must_equal
|
131
|
+
|
132
|
+
sum.must_equal threads.count
|
125
133
|
end
|
126
|
-
|
134
|
+
|
127
135
|
end
|