asynchronic 2.0.1 → 4.0.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 +5 -5
- data/.travis.yml +7 -10
- data/README.md +1 -2
- data/asynchronic.gemspec +3 -2
- data/lib/asynchronic.rb +13 -3
- 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 +6 -6
- data/lib/asynchronic/error.rb +2 -3
- data/lib/asynchronic/garbage_collector.rb +2 -0
- data/lib/asynchronic/job.rb +12 -12
- data/lib/asynchronic/notifier/broadcaster.rb +34 -0
- data/lib/asynchronic/notifier/in_memory.rb +33 -0
- data/lib/asynchronic/process.rb +52 -38
- data/lib/asynchronic/queue_engine/in_memory.rb +17 -11
- data/lib/asynchronic/queue_engine/ost.rb +15 -12
- 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 -6
- data/spec/data_store/redis_spec.rb +4 -10
- data/spec/expectations.rb +2 -2
- data/spec/facade_spec.rb +5 -5
- data/spec/jobs.rb +12 -12
- data/spec/minitest_helper.rb +6 -12
- data/spec/process/life_cycle_examples.rb +111 -64
- data/spec/process/life_cycle_in_memory_spec.rb +1 -1
- data/spec/process/life_cycle_redis_spec.rb +1 -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 +2 -2
- data/spec/worker/redis_spec.rb +1 -6
- data/spec/worker/worker_examples.rb +7 -5
- metadata +38 -17
data/lib/asynchronic/job.rb
CHANGED
@@ -1,18 +1,6 @@
|
|
1
1
|
module Asynchronic
|
2
2
|
class Job
|
3
3
|
|
4
|
-
def initialize(process)
|
5
|
-
@process = process
|
6
|
-
end
|
7
|
-
|
8
|
-
def params
|
9
|
-
@process.params
|
10
|
-
end
|
11
|
-
|
12
|
-
def result(reference)
|
13
|
-
@process[reference].result
|
14
|
-
end
|
15
|
-
|
16
4
|
def self.queue(name=nil)
|
17
5
|
name ? @queue = name : @queue
|
18
6
|
end
|
@@ -23,6 +11,18 @@ module Asynchronic
|
|
23
11
|
process.id
|
24
12
|
end
|
25
13
|
|
14
|
+
def initialize(process)
|
15
|
+
@process = process
|
16
|
+
end
|
17
|
+
|
18
|
+
def params
|
19
|
+
process.params
|
20
|
+
end
|
21
|
+
|
22
|
+
def result(reference)
|
23
|
+
process[reference].result
|
24
|
+
end
|
25
|
+
|
26
26
|
private
|
27
27
|
|
28
28
|
attr_reader :process
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Asynchronic
|
2
|
+
module Notifier
|
3
|
+
class Broadcaster
|
4
|
+
|
5
|
+
def initialize(options={})
|
6
|
+
options[:logger] ||= Asynchronic.logger
|
7
|
+
@broadcaster = ::Broadcaster.new options
|
8
|
+
end
|
9
|
+
|
10
|
+
def publish(pid, event, data=nil)
|
11
|
+
broadcaster.publish DataStore::Key[pid][event], data
|
12
|
+
end
|
13
|
+
|
14
|
+
def subscribe(pid, event, &block)
|
15
|
+
broadcaster.subscribe DataStore::Key[pid][event] do |data|
|
16
|
+
block.call data
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def unsubscribe(subscription_id)
|
21
|
+
broadcaster.unsubscribe subscription_id
|
22
|
+
end
|
23
|
+
|
24
|
+
def unsubscribe_all
|
25
|
+
broadcaster.unsubscribe_all
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :broadcaster
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Asynchronic
|
2
|
+
module Notifier
|
3
|
+
class InMemory
|
4
|
+
|
5
|
+
def publish(pid, event, data=nil)
|
6
|
+
subscriptions[DataStore::Key[pid][event]].each_value do |block|
|
7
|
+
block.call data
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe(pid, event, &block)
|
12
|
+
SecureRandom.uuid.tap do |subscription_id|
|
13
|
+
subscriptions[DataStore::Key[pid][event]][subscription_id] = block
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def unsubscribe(subscription_id)
|
18
|
+
subscriptions.each_value { |s| s.delete subscription_id }
|
19
|
+
end
|
20
|
+
|
21
|
+
def unsubscribe_all
|
22
|
+
subscriptions.clear
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def subscriptions
|
28
|
+
@subscriptions ||= Hash.new { |h,k| h[k] = {} }
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/asynchronic/process.rb
CHANGED
@@ -13,10 +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'
|
18
|
+
DEAD_ERROR_MESSAGE = 'Process connection broken'
|
17
19
|
|
18
20
|
attr_reader :id
|
19
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
|
+
|
20
46
|
def initialize(environment, id, &block)
|
21
47
|
@environment = environment
|
22
48
|
@id = DataStore::Key[id]
|
@@ -51,6 +77,10 @@ module Asynchronic
|
|
51
77
|
(running? && !connected?) || processes.any?(&:dead?)
|
52
78
|
end
|
53
79
|
|
80
|
+
def abort_if_dead
|
81
|
+
abort! DEAD_ERROR_MESSAGE if dead?
|
82
|
+
end
|
83
|
+
|
54
84
|
def destroy
|
55
85
|
data_store.delete_cascade
|
56
86
|
end
|
@@ -78,9 +108,11 @@ module Asynchronic
|
|
78
108
|
end
|
79
109
|
|
80
110
|
def processes
|
81
|
-
data_store.scoped(:processes)
|
82
|
-
|
83
|
-
|
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] }
|
84
116
|
end
|
85
117
|
|
86
118
|
def parent
|
@@ -92,22 +124,20 @@ module Asynchronic
|
|
92
124
|
end
|
93
125
|
|
94
126
|
def real_error
|
95
|
-
return nil
|
127
|
+
return nil if !aborted?
|
96
128
|
|
97
|
-
processes.
|
98
|
-
return child.real_error if child.error
|
99
|
-
end
|
129
|
+
first_aborted_child = processes.select(&:aborted?).sort_by(&:finalized_at).first
|
100
130
|
|
101
|
-
error.message
|
131
|
+
first_aborted_child ? first_aborted_child.real_error : error.message
|
102
132
|
end
|
103
133
|
|
104
134
|
def dependencies
|
105
135
|
return [] if parent.nil? || data_store[:dependencies].empty?
|
106
|
-
|
136
|
+
|
107
137
|
parent_processes = parent.processes.each_with_object({}) do |process, hash|
|
108
138
|
hash[process.name] = process
|
109
139
|
end
|
110
|
-
|
140
|
+
|
111
141
|
data_store[:dependencies].map { |d| parent_processes[d.to_s] }
|
112
142
|
end
|
113
143
|
|
@@ -131,7 +161,7 @@ module Asynchronic
|
|
131
161
|
wakeup_children
|
132
162
|
end
|
133
163
|
Asynchronic.logger.info('Asynchronic') { "Wakeup finalized #{type} (#{id})" }
|
134
|
-
|
164
|
+
|
135
165
|
parent.wakeup if parent && finalized?
|
136
166
|
end
|
137
167
|
|
@@ -147,29 +177,6 @@ module Asynchronic
|
|
147
177
|
self.data = self.data.merge key => value
|
148
178
|
end
|
149
179
|
|
150
|
-
def self.create(environment, type, params={})
|
151
|
-
id = params.delete(:id) || SecureRandom.uuid
|
152
|
-
|
153
|
-
Asynchronic.logger.debug('Asynchronic') { "Created process #{type} - #{id} - #{params}" }
|
154
|
-
|
155
|
-
new(environment, id) do
|
156
|
-
self.type = type
|
157
|
-
self.name = (params.delete(:alias) || type).to_s
|
158
|
-
self.queue = params.delete(:queue) || type.queue || parent_queue
|
159
|
-
self.dependencies = Array(params.delete(:dependencies)) | Array(params.delete(:dependency)) | infer_dependencies(params)
|
160
|
-
self.params = params
|
161
|
-
self.data = {}
|
162
|
-
pending!
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
def self.all(environment)
|
167
|
-
environment.data_store.keys.
|
168
|
-
select { |k| k.sections.count == 2 && k.match(/created_at$/) }.
|
169
|
-
sort_by { |k| environment.data_store[k] }.reverse.
|
170
|
-
map { |k| Process.new environment, k.remove_last }
|
171
|
-
end
|
172
|
-
|
173
180
|
private
|
174
181
|
|
175
182
|
attr_reader :environment
|
@@ -190,8 +197,12 @@ module Asynchronic
|
|
190
197
|
|
191
198
|
def status=(status)
|
192
199
|
Asynchronic.logger.info('Asynchronic') { "#{status.to_s.capitalize} #{type} (#{id})" }
|
200
|
+
|
193
201
|
data_store[:status] = status
|
194
202
|
data_store[TIME_TRACKING_MAP[status]] = Time.now if TIME_TRACKING_MAP.key? status
|
203
|
+
|
204
|
+
environment.notifier.publish id, :status_changed, status
|
205
|
+
environment.notifier.publish id, :finalized if finalized?
|
195
206
|
end
|
196
207
|
|
197
208
|
STATUSES.each do |status|
|
@@ -206,8 +217,8 @@ module Asynchronic
|
|
206
217
|
end
|
207
218
|
end
|
208
219
|
|
209
|
-
def abort!(exception
|
210
|
-
self.error = Error.new exception
|
220
|
+
def abort!(exception)
|
221
|
+
self.error = Error.new exception
|
211
222
|
aborted!
|
212
223
|
end
|
213
224
|
|
@@ -215,13 +226,13 @@ module Asynchronic
|
|
215
226
|
self.connection_name = Asynchronic.connection_name
|
216
227
|
|
217
228
|
if root.aborted?
|
218
|
-
abort!
|
229
|
+
abort! AUTOMATIC_ABORTED_ERROR_MESSAGE
|
219
230
|
else
|
220
231
|
running!
|
221
232
|
self.result = job.call
|
222
233
|
waiting!
|
223
234
|
end
|
224
|
-
|
235
|
+
|
225
236
|
rescue Exception => ex
|
226
237
|
message = "Failed process #{type} (#{id})\n#{ex.class} #{ex.message}\n#{ex.backtrace.join("\n")}"
|
227
238
|
Asynchronic.logger.error('Asynchronic') { message }
|
@@ -258,6 +269,9 @@ module Asynchronic
|
|
258
269
|
|
259
270
|
def connected?
|
260
271
|
connection_name && environment.queue_engine.active_connections.include?(connection_name)
|
272
|
+
rescue => ex
|
273
|
+
Asynchronic.logger.error('Asynchronic') { "#{ex.message}\n#{ex.backtrace.join("\n")}" }
|
274
|
+
true
|
261
275
|
end
|
262
276
|
|
263
277
|
end
|
@@ -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,23 +5,23 @@ 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
|
-
|
24
|
-
redis.call('KEYS', 'ost:*').each { |k| redis.call('DEL', k) }
|
23
|
+
queues.clear
|
24
|
+
redis.call!('KEYS', 'ost:*').each { |k| redis.call!('DEL', k) }
|
25
25
|
end
|
26
26
|
|
27
27
|
def listener
|
@@ -33,17 +33,20 @@ module Asynchronic
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def active_connections
|
36
|
-
redis.call('CLIENT', 'LIST').split("\n").map do |connection_info|
|
37
|
-
connection_info.split(' ').detect { |a| a.match(/name=/) }
|
38
|
-
|
36
|
+
redis.call!('CLIENT', 'LIST').split("\n").map do |connection_info|
|
37
|
+
name_attr = connection_info.split(' ').detect { |a| a.match(/name=/) }
|
38
|
+
name_attr ? name_attr[5..-1] : nil
|
39
|
+
end.uniq.compact.reject(&:empty?)
|
39
40
|
end
|
40
41
|
|
41
42
|
private
|
42
43
|
|
44
|
+
attr_reader :queues
|
45
|
+
|
43
46
|
def notify_keep_alive
|
44
47
|
Thread.new do
|
45
48
|
loop do
|
46
|
-
redis.call 'CLIENT', 'SETNAME', Asynchronic.connection_name
|
49
|
+
redis.call! 'CLIENT', 'SETNAME', Asynchronic.connection_name
|
47
50
|
sleep Asynchronic.keep_alive_timeout
|
48
51
|
end
|
49
52
|
end
|
@@ -58,11 +61,11 @@ module Asynchronic
|
|
58
61
|
end
|
59
62
|
|
60
63
|
def pop
|
61
|
-
redis.call 'RPOP', key
|
64
|
+
redis.call! 'RPOP', key
|
62
65
|
end
|
63
66
|
|
64
67
|
def empty?
|
65
|
-
redis.call('EXISTS', key) == 0
|
68
|
+
redis.call!('EXISTS', key) == 0
|
66
69
|
end
|
67
70
|
|
68
71
|
def size
|
@@ -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
|