asynchronic 1.6.3 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d090602f5e7db3b74e67c5057d7aec4761c2007e
4
- data.tar.gz: b2e6ce2ff2d236510a71d91d7d5e0042179e4a64
2
+ SHA256:
3
+ metadata.gz: 17e77f96cb1792507a8d82d9745bbf766c484cf4308bf41e268f33b28fb7671c
4
+ data.tar.gz: 33e566a52873e57adb7f79db2cfc179a269bf990ac57d9c0db3ebe698800c42d
5
5
  SHA512:
6
- metadata.gz: dd1f22974965986b3ce74f2187edf7ad7911bee32562871a7591cea31a93fa22187beaa8b3e1fcf416a09560585460c51557669cc07c067eed6007d31d7959ed
7
- data.tar.gz: 1a8db5dddd1501792e2bc00cfe57be14b0ef0c8f6e44d8c29a77c03bb0ce0f80d0cda483f87b84703f474a245e3bed611909e2496e0fc44e444cebca6b1bab82
6
+ metadata.gz: fac5b943ac35aa8d40ea3ec3bf73f3a827ecc6e040814c958a5a17f5d44a3bed544aa8956db5e7679b32cb9f2ccde5a859f20e3e0f150e85fe5d52fec6936dee
7
+ data.tar.gz: 70750e88c0c4a3ac3647c1d98bccd9e70447c92c226a11829175153c2bcf401e77ee07aff82b702cf8aee8db4c6961b0bf7e467304060592a176be0c29d3755c
@@ -1,16 +1,15 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 1.9.3
5
4
  - 2.0
6
5
  - 2.1
7
6
  - 2.2
8
- - 2.3.0
9
- - 2.4.0
10
- - 2.5.0
11
- - 2.6.0
12
- - jruby-1.7.25
13
- - jruby-9.1.7.0
7
+ - 2.3
8
+ - 2.4
9
+ - 2.5
10
+ - 2.6
11
+ - 2.7
12
+ - jruby-9.2.9.0
14
13
  - ruby-head
15
14
  - jruby-head
16
15
 
@@ -20,9 +19,5 @@ matrix:
20
19
  - rvm: ruby-head
21
20
  - rvm: jruby-head
22
21
 
23
- before_install:
24
- - rvm all-gemsets do gem uninstall bundler -ax || true
25
- - gem install bundler -v "< 2"
26
-
27
22
  services:
28
23
  - redis-server
data/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
  [![Build Status](https://travis-ci.org/gabynaiman/asynchronic.svg?branch=master)](https://travis-ci.org/gabynaiman/asynchronic)
5
5
  [![Coverage Status](https://coveralls.io/repos/gabynaiman/asynchronic/badge.svg?branch=master)](https://coveralls.io/r/gabynaiman/asynchronic?branch=master)
6
6
  [![Code Climate](https://codeclimate.com/github/gabynaiman/asynchronic.svg)](https://codeclimate.com/github/gabynaiman/asynchronic)
7
- [![Dependency Status](https://gemnasium.com/gabynaiman/asynchronic.svg)](https://gemnasium.com/gabynaiman/asynchronic)
8
7
 
9
8
  DSL for asynchronic pipeline using queues over Redis
10
9
 
@@ -18,25 +18,19 @@ 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'
22
- spec.add_dependency 'ost', '~> 0.1'
21
+ spec.add_dependency 'ost', '~> 1.0'
22
+ spec.add_dependency 'broadcaster', '~> 1.0'
23
23
  spec.add_dependency 'class_config', '~> 0.0'
24
24
  spec.add_dependency 'transparent_proxy', '~> 0.0'
25
25
  spec.add_dependency 'multi_require', '~> 1.0'
26
26
 
27
- spec.add_development_dependency 'bundler', '~> 1.12'
28
- spec.add_development_dependency 'rake', '~> 11.0'
27
+ spec.add_development_dependency 'rake', '~> 12.0'
29
28
  spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
30
29
  spec.add_development_dependency 'minitest-great_expectations', '~> 0.0'
30
+ spec.add_development_dependency 'minitest-stub_any_instance', '~> 1.0'
31
31
  spec.add_development_dependency 'minitest-colorin', '~> 0.1'
32
32
  spec.add_development_dependency 'minitest-line', '~> 0.6'
33
33
  spec.add_development_dependency 'simplecov', '~> 0.12'
34
34
  spec.add_development_dependency 'coveralls', '~> 0.8'
35
35
  spec.add_development_dependency 'pry-nav', '~> 0.2'
36
-
37
- if RUBY_VERSION < '2'
38
- spec.add_development_dependency 'term-ansicolor', '~> 1.3.0'
39
- spec.add_development_dependency 'tins', '~> 1.6.0'
40
- spec.add_development_dependency 'json', '~> 1.8'
41
- end
42
36
  end
@@ -1,11 +1,14 @@
1
1
  require 'forwardable'
2
2
  require 'securerandom'
3
- require 'redis'
4
3
  require 'ost'
4
+ require 'redic'
5
+ require 'broadcaster'
5
6
  require 'class_config'
6
7
  require 'transparent_proxy'
7
8
  require 'logger'
8
9
  require 'multi_require'
10
+ require 'timeout'
11
+ require 'socket'
9
12
 
10
13
  MultiRequire.require_relative_pattern 'asynchronic/**/*.rb'
11
14
 
@@ -16,13 +19,16 @@ module Asynchronic
16
19
  attr_config :default_queue, :asynchronic
17
20
  attr_config :queue_engine, QueueEngine::InMemory.new
18
21
  attr_config :data_store, DataStore::InMemory.new
22
+ attr_config :notifier, Notifier::InMemory.new
19
23
  attr_config :logger, Logger.new($stdout)
20
24
  attr_config :retry_timeout, 30
21
25
  attr_config :garbage_collector_timeout, 30
22
26
  attr_config :redis_data_store_sync_timeout, 0.01
27
+ attr_config :keep_alive_timeout, 1
28
+ attr_config :connection_name, "HOST=#{Socket.gethostname},PID=#{::Process.pid},UUID=#{SecureRandom.uuid}"
23
29
 
24
30
  def self.environment
25
- Environment.new queue_engine, data_store
31
+ Environment.new queue_engine, data_store, notifier
26
32
  end
27
33
 
28
34
  def self.[](pid)
@@ -18,7 +18,7 @@ module Asynchronic
18
18
  end
19
19
 
20
20
  def data_store
21
- @data_store_class.connect *@data_store_connection_args
21
+ @data_store_class.connect(*@data_store_connection_args)
22
22
  end
23
23
 
24
24
  def to_value
@@ -8,11 +8,11 @@ module Asynchronic
8
8
 
9
9
  def initialize(scope, *args)
10
10
  @scope = Key[scope]
11
- @connection = ::Redis.new(*args)
11
+ @redis = Redic.new(*args)
12
12
  end
13
13
 
14
14
  def [](key)
15
- value = @connection.get @scope[key]
15
+ value = @redis.call! 'GET', @scope[key]
16
16
  value ? Marshal.load(value) : nil
17
17
  rescue => ex
18
18
  Asynchronic.logger.warn('Asynchronic') { ex.message }
@@ -20,33 +20,33 @@ module Asynchronic
20
20
  end
21
21
 
22
22
  def []=(key, value)
23
- @connection.set @scope[key], Marshal.dump(value)
23
+ @redis.call! 'SET', @scope[key], Marshal.dump(value)
24
24
  end
25
25
 
26
26
  def delete(key)
27
- @connection.del @scope[key]
27
+ @redis.call! 'DEL', @scope[key]
28
28
  end
29
29
 
30
30
  def delete_cascade(key)
31
- @connection.del @scope[key]
32
- @connection.keys(@scope[key]['*']).each { |k| @connection.del k }
31
+ @redis.call! 'DEL', @scope[key]
32
+ @redis.call!('KEYS', @scope[key]['*']).each { |k| @redis.call! 'DEL', k }
33
33
  end
34
34
 
35
35
  def keys
36
- @connection.keys(@scope['*']).map { |k| Key[k].remove_first }
36
+ @redis.call!('KEYS', @scope['*']).map { |k| Key[k].remove_first }
37
37
  end
38
38
 
39
39
  def synchronize(key)
40
- while @connection.getset(@scope[key][LOCKED], LOCKED) == LOCKED
40
+ while @redis.call!('GETSET', @scope[key][LOCKED], LOCKED) == LOCKED
41
41
  sleep Asynchronic.redis_data_store_sync_timeout
42
42
  end
43
43
  yield
44
44
  ensure
45
- @connection.del @scope[key][LOCKED]
45
+ @redis.call! 'DEL', @scope[key][LOCKED]
46
46
  end
47
47
 
48
48
  def connection_args
49
- [@scope, @connection.client.options]
49
+ [@scope, @redis.url]
50
50
  end
51
51
 
52
52
  def self.connect(*args)
@@ -1,12 +1,12 @@
1
1
  module Asynchronic
2
2
  class Environment
3
3
 
4
- attr_reader :queue_engine
5
- attr_reader :data_store
4
+ attr_reader :queue_engine, :data_store, :notifier
6
5
 
7
- def initialize(queue_engine, data_store)
6
+ def initialize(queue_engine, data_store, notifier)
8
7
  @queue_engine = queue_engine
9
8
  @data_store = data_store
9
+ @notifier = notifier
10
10
  end
11
11
 
12
12
  def queue(name)
@@ -18,6 +18,8 @@ module Asynchronic
18
18
  while @running
19
19
  processes = environment.processes
20
20
 
21
+ processes.each(&:abort_if_dead)
22
+
21
23
  conditions.each do |name, condition|
22
24
  Asynchronic.logger.info('Asynchronic') { "Running GC - #{name}" }
23
25
  begin
@@ -0,0 +1,30 @@
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
+ end
29
+ end
30
+ 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
@@ -11,9 +11,11 @@ module Asynchronic
11
11
  aborted: :finalized_at
12
12
  }
13
13
 
14
- ATTRIBUTE_NAMES = [:type, :name, :queue, :status, :dependencies, :data, :result, :error] | TIME_TRACKING_MAP.values.uniq
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
 
@@ -47,6 +49,14 @@ module Asynchronic
47
49
  abort! CANCELED_ERROR_MESSAGE
48
50
  end
49
51
 
52
+ def dead?
53
+ (running? && !connected?) || processes.any?(&:dead?)
54
+ end
55
+
56
+ def abort_if_dead
57
+ abort! DEAD_ERROR_MESSAGE if dead?
58
+ end
59
+
50
60
  def destroy
51
61
  data_store.delete_cascade
52
62
  end
@@ -75,7 +85,7 @@ module Asynchronic
75
85
 
76
86
  def processes
77
87
  data_store.scoped(:processes).keys.
78
- select { |k| k.sections.count == 2 && k.match(/name$/) }.
88
+ select { |k| k.sections.count == 2 && k.match(/\|name$/) }.
79
89
  sort.map { |k| Process.new environment, id[:processes][k.remove_last] }
80
90
  end
81
91
 
@@ -88,13 +98,11 @@ module Asynchronic
88
98
  end
89
99
 
90
100
  def real_error
91
- return nil unless error
101
+ return nil if !aborted?
92
102
 
93
- processes.each do |child|
94
- return child.real_error if child.error
95
- end
103
+ first_aborted_child = processes.select(&:aborted?).sort_by(&:finalized_at).first
96
104
 
97
- error.message
105
+ first_aborted_child ? first_aborted_child.real_error : error.message
98
106
  end
99
107
 
100
108
  def dependencies
@@ -186,8 +194,12 @@ module Asynchronic
186
194
 
187
195
  def status=(status)
188
196
  Asynchronic.logger.info('Asynchronic') { "#{status.to_s.capitalize} #{type} (#{id})" }
197
+
189
198
  data_store[:status] = status
190
199
  data_store[TIME_TRACKING_MAP[status]] = Time.now if TIME_TRACKING_MAP.key? status
200
+
201
+ environment.notifier.publish id, :status_changed, status
202
+ environment.notifier.publish id, :finalized if finalized?
191
203
  end
192
204
 
193
205
  STATUSES.each do |status|
@@ -202,19 +214,22 @@ module Asynchronic
202
214
  end
203
215
  end
204
216
 
205
- def abort!(exception=nil)
206
- self.error = Error.new exception if exception
217
+ def abort!(exception)
218
+ self.error = Error.new exception
207
219
  aborted!
208
220
  end
209
221
 
210
222
  def run
223
+ self.connection_name = Asynchronic.connection_name
224
+
211
225
  if root.aborted?
212
- abort!
226
+ abort! AUTOMATIC_ABORTED_ERROR_MESSAGE
213
227
  else
214
228
  running!
215
229
  self.result = job.call
216
230
  waiting!
217
231
  end
232
+
218
233
  rescue Exception => ex
219
234
  message = "Failed process #{type} (#{id})\n#{ex.class} #{ex.message}\n#{ex.backtrace.join("\n")}"
220
235
  Asynchronic.logger.error('Asynchronic') { message }
@@ -249,5 +264,12 @@ module Asynchronic
249
264
  parent.queue if parent
250
265
  end
251
266
 
267
+ def connected?
268
+ connection_name && environment.queue_engine.active_connections.include?(connection_name)
269
+ rescue => ex
270
+ Asynchronic.logger.error('Asynchronic') { "#{ex.message}\n#{ex.backtrace.join("\n")}" }
271
+ true
272
+ end
273
+
252
274
  end
253
275
  end
@@ -33,6 +33,11 @@ module Asynchronic
33
33
  true
34
34
  end
35
35
 
36
+ def active_connections
37
+ [Asynchronic.connection_name]
38
+ end
39
+
40
+
36
41
  class Queue
37
42
 
38
43
  extend Forwardable
@@ -1,17 +1,14 @@
1
1
  module Asynchronic
2
2
  module QueueEngine
3
3
  class Ost
4
-
5
- attr_reader :default_queue
6
4
 
7
- def initialize(options={})
8
- ::Ost.connect options[:redis] if options.key?(:redis)
9
- @default_queue = options[:default_queue]
10
- @queues ||= Hash.new { |h,k| h[k] = Queue.new k }
11
- end
5
+ attr_reader :redis, :default_queue
12
6
 
13
- def default_queue
14
- @default_queue ||= Asynchronic.default_queue
7
+ def initialize(options={})
8
+ @redis = Redic.new(*Array(options[:redis]))
9
+ @default_queue = options.fetch(:default_queue, Asynchronic.default_queue)
10
+ @queues ||= Hash.new { |h,k| h[k] = Queue.new k, redis }
11
+ @keep_alive_thread = notify_keep_alive
15
12
  end
16
13
 
17
14
  def [](name)
@@ -19,12 +16,12 @@ module Asynchronic
19
16
  end
20
17
 
21
18
  def queues
22
- (@queues.values.map(&:key) | redis.keys('ost:*')).map { |q| q.to_s[4..-1].to_sym }
19
+ (@queues.values.map(&:key) | redis.call!('KEYS', 'ost:*')).map { |q| q.to_s[4..-1].to_sym }
23
20
  end
24
21
 
25
22
  def clear
26
23
  @queues.clear
27
- redis.keys('ost:*').each { |k| redis.del k }
24
+ redis.call!('KEYS', 'ost:*').each { |k| redis.call!('DEL', k) }
28
25
  end
29
26
 
30
27
  def listener
@@ -35,21 +32,38 @@ module Asynchronic
35
32
  true
36
33
  end
37
34
 
35
+ def active_connections
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?)
40
+ end
41
+
38
42
  private
39
43
 
40
- def redis
41
- @redis ||= Redis.connect(::Ost.options)
44
+ def notify_keep_alive
45
+ Thread.new do
46
+ loop do
47
+ redis.call! 'CLIENT', 'SETNAME', Asynchronic.connection_name
48
+ sleep Asynchronic.keep_alive_timeout
49
+ end
50
+ end
42
51
  end
43
52
 
44
53
 
45
54
  class Queue < ::Ost::Queue
46
55
 
56
+ def initialize(name, redis)
57
+ super name
58
+ self.redis = redis
59
+ end
60
+
47
61
  def pop
48
- key.rpop
62
+ redis.call! 'RPOP', key
49
63
  end
50
64
 
51
65
  def empty?
52
- !redis.exists(key)
66
+ redis.call!('EXISTS', key) == 0
53
67
  end
54
68
 
55
69
  def size
@@ -68,7 +82,7 @@ module Asynchronic
68
82
  def listen(queue, &block)
69
83
  @current_queue = queue
70
84
  Asynchronic.retry_execution(self.class, 'listen') do
71
- queue.each &block
85
+ queue.each(&block)
72
86
  end
73
87
  end
74
88
 
@@ -29,6 +29,11 @@ module Asynchronic
29
29
  false
30
30
  end
31
31
 
32
+ def active_connections
33
+ [Asynchronic.connection_name]
34
+ end
35
+
36
+
32
37
  class Queue
33
38
 
34
39
  def initialize(engine)
@@ -1,3 +1,3 @@
1
1
  module Asynchronic
2
- VERSION = '1.6.3'
3
- end
2
+ VERSION = '3.0.2'
3
+ end
@@ -23,7 +23,7 @@ class Asynchronic::Worker
23
23
  end
24
24
 
25
25
  def stop
26
- Asynchronic.logger.info('Asynchronic') { "Stopping worker of #{@queue_name} (#{Process.pid})" }
26
+ Asynchronic.logger.info('Asynchronic') { "Stopping worker of #{queue_name} (#{Process.pid})" }
27
27
  listener.stop
28
28
  end
29
29
 
@@ -22,19 +22,19 @@ module DataStoreExamples
22
22
  end
23
23
 
24
24
  it 'Delete cascade' do
25
- data_store[Key[:key_1]] = 1
26
- data_store[Key[:key_1][:key_1_1]] = 2
27
- data_store[Key[:key_1][:key_1_2]] = 3
28
- data_store[Key[:key_2]] = 4
29
- data_store[Key[:key_2][:key_2_1]] = 5
30
- data_store[Key[:key_2][:key_2_2]] = 6
25
+ data_store[Asynchronic::DataStore::Key[:key_1]] = 1
26
+ data_store[Asynchronic::DataStore::Key[:key_1][:key_1_1]] = 2
27
+ data_store[Asynchronic::DataStore::Key[:key_1][:key_1_2]] = 3
28
+ data_store[Asynchronic::DataStore::Key[:key_2]] = 4
29
+ data_store[Asynchronic::DataStore::Key[:key_2][:key_2_1]] = 5
30
+ data_store[Asynchronic::DataStore::Key[:key_2][:key_2_2]] = 6
31
31
 
32
- data_store.delete_cascade Key[:key_1]
32
+ data_store.delete_cascade Asynchronic::DataStore::Key[:key_1]
33
33
 
34
34
  data_store.keys.sort.must_equal [
35
- Key[:key_2],
36
- Key[:key_2][:key_2_1],
37
- Key[:key_2][:key_2_2]
35
+ Asynchronic::DataStore::Key[:key_2],
36
+ Asynchronic::DataStore::Key[:key_2][:key_2_1],
37
+ Asynchronic::DataStore::Key[:key_2][:key_2_2]
38
38
  ]
39
39
  end
40
40
 
@@ -26,7 +26,6 @@ module LazyValueExamples
26
26
  it 'Transparent proxy' do
27
27
  value = lazy_value :key
28
28
  data_store[:key] = 1
29
- value.must_be_instance_of Fixnum
30
29
  value.must_equal 1
31
30
  end
32
31
 
@@ -75,6 +75,10 @@ module MiniTest::Assertions
75
75
  process.finalized_at.must_be_instance_of Time
76
76
  end
77
77
 
78
+ def assert_have_connection_name(process)
79
+ process.connection_name.must_equal Asynchronic.connection_name
80
+ end
81
+
78
82
  end
79
83
 
80
84
  Asynchronic::QueueEngine::InMemory::Queue.infect_an_assertion :assert_enqueued, :must_enqueued
@@ -85,4 +89,5 @@ Asynchronic::Process.infect_an_assertion :assert_be_pending, :must_be_pending, :
85
89
  Asynchronic::Process.infect_an_assertion :assert_be_queued, :must_be_queued, :unary
86
90
  Asynchronic::Process.infect_an_assertion :assert_be_waiting, :must_be_waiting, :unary
87
91
  Asynchronic::Process.infect_an_assertion :assert_be_completed, :must_be_completed, :unary
88
- Asynchronic::Process.infect_an_assertion :assert_be_aborted, :must_be_aborted, :unary
92
+ Asynchronic::Process.infect_an_assertion :assert_be_aborted, :must_be_aborted, :unary
93
+ Asynchronic::Process.infect_an_assertion :assert_have_connection_name, :must_have_connection_name, :unary
@@ -64,4 +64,16 @@ describe Asynchronic, 'Facade' do
64
64
  Asynchronic.garbage_collector.must_be_instance_of Asynchronic::GarbageCollector
65
65
  end
66
66
 
67
+ it 'Redis data store sync timeout' do
68
+ Asynchronic.redis_data_store_sync_timeout.must_equal 0.01
69
+ end
70
+
71
+ it 'Keep alive timeout' do
72
+ Asynchronic.keep_alive_timeout.must_equal 1
73
+ end
74
+
75
+ it 'Connection name' do
76
+ Asynchronic.connection_name.must_match /^HOST=#{Socket.gethostname},PID=#{::Process.pid}/
77
+ end
78
+
67
79
  end
@@ -200,7 +200,7 @@ end
200
200
  class WithRetriesJob < Asynchronic::Job
201
201
  def call
202
202
  @counter = 0
203
- retry_when [RuntimeError] do
203
+ retry_when [RuntimeError], 0.1 do
204
204
  @counter += 1
205
205
  raise 'Counter < 3' if @counter < 3
206
206
  @counter
@@ -326,7 +326,7 @@ class NestedJobWithErrorInChildJob < Asynchronic::Job
326
326
  end
327
327
 
328
328
 
329
- class AbortQueuedAfertErrorJob < Asynchronic::Job
329
+ class AbortQueuedAfterErrorJob < Asynchronic::Job
330
330
  def call
331
331
  async Child_1
332
332
  async Child_2
@@ -3,8 +3,10 @@ require 'asynchronic'
3
3
  require 'minitest/autorun'
4
4
  require 'minitest/colorin'
5
5
  require 'minitest/great_expectations'
6
+ require 'minitest/stub_any_instance'
6
7
  require 'jobs'
7
8
  require 'expectations'
9
+ require 'timeout'
8
10
  require 'pry-nav'
9
11
 
10
12
  class Module
@@ -1,26 +1,68 @@
1
1
  module LifeCycleExamples
2
2
 
3
- let(:env) { Asynchronic::Environment.new queue_engine, data_store }
3
+ let(:env) { Asynchronic::Environment.new queue_engine, data_store, notifier }
4
4
 
5
5
  let(:queue) { env.default_queue }
6
6
 
7
7
  after do
8
8
  data_store.clear
9
9
  queue_engine.clear
10
+ notifier.unsubscribe_all
10
11
  end
11
12
 
12
13
  def create(type, params={})
13
- env.create_process type, params
14
+ env.create_process(type, params).tap do |process|
15
+ process.must_be_initialized
16
+ end
14
17
  end
15
18
 
16
19
  def execute(queue)
17
- env.load_process(queue.pop).execute
20
+ process = env.load_process(queue.pop)
21
+
22
+ events = []
23
+ status_changed_id = notifier.subscribe(process.id, :status_changed) { |status| events << status }
24
+
25
+ is_finalized = false
26
+ finalized_id = notifier.subscribe(process.id, :finalized) { is_finalized = true }
27
+
28
+ process.execute
29
+
30
+ process.must_have_connection_name
31
+ process.wont_be :dead?
32
+
33
+ process.send(:connected?).must_be_true
34
+
35
+ env.queue_engine.stub(:active_connections, ->() { raise 'Forced error' }) do
36
+ process.send(:connected?).must_be_true
37
+ end
38
+
39
+ with_retries do
40
+ events.last.must_equal process.status
41
+ process.finalized?.must_equal is_finalized
42
+ end
43
+
44
+ notifier.unsubscribe status_changed_id
45
+ notifier.unsubscribe finalized_id
46
+
47
+ status = process.status
48
+ process.abort_if_dead
49
+ process.status.must_equal status
50
+ end
51
+
52
+ def with_retries(&block)
53
+ Timeout.timeout(3) do
54
+ begin
55
+ block.call
56
+ rescue Minitest::Assertion
57
+ sleep 0.001
58
+ retry
59
+ end
60
+ end
18
61
  end
19
62
 
20
63
  it 'Basic' do
21
64
  process = create BasicJob, input: 1
22
65
 
23
- process.must_be_initialized
24
66
  process.must_have_params input: 1
25
67
  queue.must_be_empty
26
68
 
@@ -39,7 +81,6 @@ module LifeCycleExamples
39
81
  it 'Sequential' do
40
82
  process = create SequentialJob, input: 50
41
83
 
42
- process.must_be_initialized
43
84
  process.must_have_params input: 50
44
85
  queue.must_be_empty
45
86
 
@@ -77,7 +118,6 @@ module LifeCycleExamples
77
118
  it 'Graph' do
78
119
  process = create GraphJob, input: 100
79
120
 
80
- process.must_be_initialized
81
121
  process.must_have_params input: 100
82
122
  queue.must_be_empty
83
123
 
@@ -133,7 +173,6 @@ module LifeCycleExamples
133
173
  it 'Parallel' do
134
174
  process = create ParallelJob, input: 10, times: 3
135
175
 
136
- process.must_be_initialized
137
176
  process.must_have_params input: 10, times: 3
138
177
  queue.must_be_empty
139
178
 
@@ -167,7 +206,6 @@ module LifeCycleExamples
167
206
  it 'Nested' do
168
207
  process = create NestedJob, input: 4
169
208
 
170
- process.must_be_initialized
171
209
  process.must_have_params input: 4
172
210
  queue.must_be_empty
173
211
 
@@ -207,7 +245,6 @@ module LifeCycleExamples
207
245
  it 'Alias' do
208
246
  process = create AliasJob
209
247
 
210
- process.must_be_initialized
211
248
  queue.must_be_empty
212
249
 
213
250
  process.enqueue
@@ -261,7 +298,6 @@ module LifeCycleExamples
261
298
  it 'Custom queue' do
262
299
  process = create CustomQueueJob, input: 'hello'
263
300
 
264
- process.must_be_initialized
265
301
  process.must_have_params input: 'hello'
266
302
 
267
303
  env.queue(:queue_1).must_be_empty
@@ -302,7 +338,6 @@ module LifeCycleExamples
302
338
  it 'Exception' do
303
339
  process = create ExceptionJob
304
340
 
305
- process.must_be_initialized
306
341
  queue.must_be_empty
307
342
 
308
343
  process.enqueue
@@ -320,7 +355,6 @@ module LifeCycleExamples
320
355
  it 'Inner exception' do
321
356
  process = create InnerExceptionJob
322
357
 
323
- process.must_be_initialized
324
358
  queue.must_be_empty
325
359
 
326
360
  process.enqueue
@@ -348,7 +382,6 @@ module LifeCycleExamples
348
382
  it 'Forward reference' do
349
383
  process = create ForwardReferenceJob
350
384
 
351
- process.must_be_initialized
352
385
  queue.must_be_empty
353
386
 
354
387
  process.enqueue
@@ -391,7 +424,6 @@ module LifeCycleExamples
391
424
  it 'Job with retries' do
392
425
  process = create WithRetriesJob
393
426
 
394
- process.must_be_initialized
395
427
  queue.must_be_empty
396
428
 
397
429
  process.enqueue
@@ -486,80 +518,81 @@ module LifeCycleExamples
486
518
  process.real_error.must_equal "Error in parent"
487
519
  end
488
520
 
489
- it 'Abort queued afert error' do
490
- process = create AbortQueuedAfertErrorJob
521
+ it 'Abort queued After error' do
522
+ process = create AbortQueuedAfterErrorJob
491
523
 
492
524
  process.enqueue
493
525
 
494
526
  execute queue
495
527
 
496
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :waiting,
497
- 'AbortQueuedAfertErrorJob::Child_1' => :queued,
498
- 'AbortQueuedAfertErrorJob::Child_2' => :queued,
499
- 'AbortQueuedAfertErrorJob::Child_3' => :queued,
500
- 'AbortQueuedAfertErrorJob::Child_4' => :queued
528
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :waiting,
529
+ 'AbortQueuedAfterErrorJob::Child_1' => :queued,
530
+ 'AbortQueuedAfterErrorJob::Child_2' => :queued,
531
+ 'AbortQueuedAfterErrorJob::Child_3' => :queued,
532
+ 'AbortQueuedAfterErrorJob::Child_4' => :queued
501
533
 
502
534
  execute queue
503
535
 
504
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :waiting,
505
- 'AbortQueuedAfertErrorJob::Child_1' => :waiting,
536
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :waiting,
537
+ 'AbortQueuedAfterErrorJob::Child_1' => :waiting,
506
538
  'Child_1_1' => :queued,
507
539
  'Child_1_2' => :queued,
508
- 'AbortQueuedAfertErrorJob::Child_2' => :queued,
509
- 'AbortQueuedAfertErrorJob::Child_3' => :queued,
510
- 'AbortQueuedAfertErrorJob::Child_4' => :queued
540
+ 'AbortQueuedAfterErrorJob::Child_2' => :queued,
541
+ 'AbortQueuedAfterErrorJob::Child_3' => :queued,
542
+ 'AbortQueuedAfterErrorJob::Child_4' => :queued
511
543
 
512
544
  execute queue
513
545
 
514
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :waiting,
515
- 'AbortQueuedAfertErrorJob::Child_1' => :waiting,
546
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :waiting,
547
+ 'AbortQueuedAfterErrorJob::Child_1' => :waiting,
516
548
  'Child_1_1' => :queued,
517
549
  'Child_1_2' => :queued,
518
- 'AbortQueuedAfertErrorJob::Child_2' => :completed,
519
- 'AbortQueuedAfertErrorJob::Child_3' => :queued,
520
- 'AbortQueuedAfertErrorJob::Child_4' => :queued
550
+ 'AbortQueuedAfterErrorJob::Child_2' => :completed,
551
+ 'AbortQueuedAfterErrorJob::Child_3' => :queued,
552
+ 'AbortQueuedAfterErrorJob::Child_4' => :queued
521
553
 
522
554
  execute queue
523
555
 
524
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :aborted,
525
- 'AbortQueuedAfertErrorJob::Child_1' => :waiting,
556
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :aborted,
557
+ 'AbortQueuedAfterErrorJob::Child_1' => :waiting,
526
558
  'Child_1_1' => :queued,
527
559
  'Child_1_2' => :queued,
528
- 'AbortQueuedAfertErrorJob::Child_2' => :completed,
529
- 'AbortQueuedAfertErrorJob::Child_3' => :aborted,
530
- 'AbortQueuedAfertErrorJob::Child_4' => :queued
560
+ 'AbortQueuedAfterErrorJob::Child_2' => :completed,
561
+ 'AbortQueuedAfterErrorJob::Child_3' => :aborted,
562
+ 'AbortQueuedAfterErrorJob::Child_4' => :queued
531
563
 
532
564
  execute queue
533
565
 
534
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :aborted,
535
- 'AbortQueuedAfertErrorJob::Child_1' => :waiting,
566
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :aborted,
567
+ 'AbortQueuedAfterErrorJob::Child_1' => :waiting,
536
568
  'Child_1_1' => :queued,
537
569
  'Child_1_2' => :queued,
538
- 'AbortQueuedAfertErrorJob::Child_2' => :completed,
539
- 'AbortQueuedAfertErrorJob::Child_3' => :aborted,
540
- 'AbortQueuedAfertErrorJob::Child_4' => :aborted
570
+ 'AbortQueuedAfterErrorJob::Child_2' => :completed,
571
+ 'AbortQueuedAfterErrorJob::Child_3' => :aborted,
572
+ 'AbortQueuedAfterErrorJob::Child_4' => :aborted
541
573
 
542
574
  execute queue
543
575
 
544
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :aborted,
545
- 'AbortQueuedAfertErrorJob::Child_1' => :aborted,
576
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :aborted,
577
+ 'AbortQueuedAfterErrorJob::Child_1' => :aborted,
546
578
  'Child_1_1' => :aborted,
547
579
  'Child_1_2' => :queued,
548
- 'AbortQueuedAfertErrorJob::Child_2' => :completed,
549
- 'AbortQueuedAfertErrorJob::Child_3' => :aborted,
550
- 'AbortQueuedAfertErrorJob::Child_4' => :aborted
580
+ 'AbortQueuedAfterErrorJob::Child_2' => :completed,
581
+ 'AbortQueuedAfterErrorJob::Child_3' => :aborted,
582
+ 'AbortQueuedAfterErrorJob::Child_4' => :aborted
551
583
 
552
584
  execute queue
553
585
 
554
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :aborted,
555
- 'AbortQueuedAfertErrorJob::Child_1' => :aborted,
586
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :aborted,
587
+ 'AbortQueuedAfterErrorJob::Child_1' => :aborted,
556
588
  'Child_1_1' => :aborted,
557
589
  'Child_1_2' => :aborted,
558
- 'AbortQueuedAfertErrorJob::Child_2' => :completed,
559
- 'AbortQueuedAfertErrorJob::Child_3' => :aborted,
560
- 'AbortQueuedAfertErrorJob::Child_4' => :aborted
590
+ 'AbortQueuedAfterErrorJob::Child_2' => :completed,
591
+ 'AbortQueuedAfterErrorJob::Child_3' => :aborted,
592
+ 'AbortQueuedAfterErrorJob::Child_4' => :aborted
561
593
 
562
594
  process.real_error.must_equal 'Forced error'
595
+ process.processes[0].processes[1].error.message.must_equal Asynchronic::Process::AUTOMATIC_ABORTED_ERROR_MESSAGE
563
596
  end
564
597
 
565
598
  it 'Manual abort' do
@@ -604,7 +637,7 @@ module LifeCycleExamples
604
637
  pid_1 = process_1.id
605
638
  pid_2 = process_2.id
606
639
 
607
- data_store.keys.select { |k| k.start_with? pid_1 }.count.must_equal 37
640
+ data_store.keys.select { |k| k.start_with? pid_1 }.count.must_equal 38
608
641
  data_store.keys.select { |k| k.start_with? pid_2 }.count.must_equal 7
609
642
 
610
643
  process_1.destroy
@@ -622,42 +655,49 @@ module LifeCycleExamples
622
655
  process_2.enqueue
623
656
  execute queue
624
657
 
658
+ process_3 = create BasicJob
659
+
625
660
  pid_1 = process_1.id
626
661
  pid_2 = process_2.id
662
+ pid_3 = process_3.id
627
663
 
628
664
  process_1.must_be_completed
629
665
  process_2.must_be_waiting
666
+ process_3.must_be_pending
630
667
 
631
- data_store.keys.select { |k| k.start_with? pid_1 }.count.must_equal 49
632
- data_store.keys.select { |k| k.start_with? pid_2 }.count.must_equal 37
668
+ data_store.keys.select { |k| k.start_with? pid_1 }.count.must_equal 53
669
+ data_store.keys.select { |k| k.start_with? pid_2 }.count.must_equal 38
670
+ data_store.keys.select { |k| k.start_with? pid_3 }.count.must_equal 7
633
671
 
634
672
  gc = Asynchronic::GarbageCollector.new env, 0.001
635
673
 
636
- gc.add_condition('Completed', &:completed?)
674
+ gc.add_condition('Finalized', &:finalized?)
637
675
  gc.add_condition('Waiting', &:waiting?)
638
676
  gc.add_condition('Exception') { raise 'Invalid condition' }
639
677
 
640
- gc.conditions_names.must_equal ['Completed', 'Waiting', 'Exception']
678
+ gc.conditions_names.must_equal ['Finalized', 'Waiting', 'Exception']
641
679
 
642
680
  gc.remove_condition 'Waiting'
643
681
 
644
- gc.conditions_names.must_equal ['Completed', 'Exception']
682
+ gc.conditions_names.must_equal ['Finalized', 'Exception']
645
683
 
646
684
  Thread.new do
647
685
  sleep 0.01
648
686
  gc.stop
649
687
  end
650
688
 
651
- gc.start
689
+ Asynchronic::Process.stub_any_instance(:dead?, -> { id == pid_3 }) do
690
+ gc.start
691
+ end
652
692
 
653
693
  data_store.keys.select { |k| k.start_with? pid_1 }.count.must_equal 0
654
- data_store.keys.select { |k| k.start_with? pid_2 }.count.must_equal 37
694
+ data_store.keys.select { |k| k.start_with? pid_2 }.count.must_equal 38
695
+ data_store.keys.select { |k| k.start_with? pid_3 }.count.must_equal 0
655
696
  end
656
697
 
657
698
  it 'Before finalize hook when completed' do
658
699
  process = create BeforeFinalizeCompletedJob
659
700
 
660
- process.must_be_initialized
661
701
  queue.must_be_empty
662
702
 
663
703
  process.enqueue
@@ -675,7 +715,6 @@ module LifeCycleExamples
675
715
  it 'Before finalize hook when aborted' do
676
716
  process = create BeforeFinalizeAbortedJob
677
717
 
678
- process.must_be_initialized
679
718
  queue.must_be_empty
680
719
 
681
720
  process.enqueue
@@ -693,7 +732,6 @@ module LifeCycleExamples
693
732
  it 'Before finalize raises exception and aborts' do
694
733
  process = create BeforeFinalizeRaisesExceptionJob
695
734
 
696
- process.must_be_initialized
697
735
  queue.must_be_empty
698
736
 
699
737
  process.enqueue
@@ -711,7 +749,6 @@ module LifeCycleExamples
711
749
  it 'Before finalize raises exception on aborted job' do
712
750
  process = create BeforeFinalizeExceptionOnAbortedJob
713
751
 
714
- process.must_be_initialized
715
752
  queue.must_be_empty
716
753
 
717
754
  process.enqueue
@@ -726,5 +763,4 @@ module LifeCycleExamples
726
763
  queue.must_be_empty
727
764
  end
728
765
 
729
-
730
766
  end
@@ -5,6 +5,7 @@ describe Asynchronic::Process, 'Life cycle - InMemory' do
5
5
 
6
6
  let(:queue_engine) { Asynchronic::QueueEngine::InMemory.new }
7
7
  let(:data_store) { Asynchronic::DataStore::InMemory.new }
8
+ let(:notifier) { Asynchronic::Notifier::InMemory.new }
8
9
 
9
10
  include LifeCycleExamples
10
11
 
@@ -5,6 +5,7 @@ describe Asynchronic::Process, 'Life cycle - Redis' do
5
5
 
6
6
  let(:queue_engine) { Asynchronic::QueueEngine::Ost.new }
7
7
  let(:data_store) { Asynchronic::DataStore::Redis.new :asynchronic_test }
8
+ let(:notifier) { Asynchronic::Notifier::Broadcaster.new }
8
9
 
9
10
  include LifeCycleExamples
10
11
 
@@ -11,5 +11,9 @@ describe Asynchronic::QueueEngine::Ost do
11
11
  end
12
12
 
13
13
  include QueueEngineExamples
14
+
15
+ it 'Engine and queues use same redis connection' do
16
+ engine.redis.must_equal queue.redis
17
+ end
14
18
 
15
19
  end
@@ -31,17 +31,23 @@ module QueueEngineExamples
31
31
  end
32
32
 
33
33
  it 'Listener' do
34
- queue.push 'msg_1'
35
- queue.push 'msg_2'
34
+ Timeout.timeout(5) do
35
+ queue.push 'msg_1'
36
+ queue.push 'msg_2'
36
37
 
37
- messages = []
38
+ messages = []
38
39
 
39
- listener.listen(queue) do |msg|
40
- messages << msg
41
- listener.stop if queue.empty?
40
+ listener.listen(queue) do |msg|
41
+ messages << msg
42
+ listener.stop if queue.empty?
43
+ end
44
+
45
+ messages.must_equal %w(msg_1 msg_2)
42
46
  end
47
+ end
43
48
 
44
- messages.must_equal %w(msg_1 msg_2)
49
+ it 'Active connections' do
50
+ engine.active_connections.must_equal [Asynchronic.connection_name]
45
51
  end
46
52
 
47
53
  end
@@ -34,5 +34,9 @@ describe Asynchronic::QueueEngine::Synchronic do
34
34
  process.must_be_completed
35
35
  process.result.must_equal '10%' => 20, '20%' => 40
36
36
  end
37
-
37
+
38
+ it 'Active connections' do
39
+ Asynchronic.queue_engine.active_connections.must_equal [Asynchronic.connection_name]
40
+ end
41
+
38
42
  end
@@ -5,6 +5,7 @@ describe Asynchronic::Worker, 'InMemory' do
5
5
 
6
6
  let(:queue_engine) { Asynchronic::QueueEngine::InMemory.new }
7
7
  let(:data_store) { Asynchronic::DataStore::InMemory.new }
8
+ let(:notifier) { Asynchronic::Notifier::InMemory.new }
8
9
 
9
10
  include WorkerExamples
10
11
 
@@ -5,6 +5,7 @@ describe Asynchronic::Worker, 'Redis' do
5
5
 
6
6
  let(:queue_engine) { Asynchronic::QueueEngine::Ost.new }
7
7
  let(:data_store) { Asynchronic::DataStore::Redis.new :asynchronic_test}
8
+ let(:notifier) { Asynchronic::Notifier::Broadcaster.new }
8
9
 
9
10
  after do
10
11
  data_store.clear
@@ -1,6 +1,6 @@
1
1
  module WorkerExamples
2
2
 
3
- let(:env) { Asynchronic::Environment.new queue_engine, data_store }
3
+ let(:env) { Asynchronic::Environment.new queue_engine, data_store, notifier }
4
4
  let(:queue_name) { :test_worker }
5
5
  let(:queue) { env.queue queue_name }
6
6
 
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asynchronic
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.3
4
+ version: 3.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel Naiman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-05 00:00:00.000000000 Z
11
+ date: 2020-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: redis
14
+ name: ost
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '3.0'
19
+ version: '1.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '3.0'
26
+ version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: ost
28
+ name: broadcaster
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.1'
33
+ version: '1.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.1'
40
+ version: '1.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: class_config
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,34 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.0'
83
- - !ruby/object:Gem::Dependency
84
- name: bundler
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '1.12'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '1.12'
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: rake
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
87
  - - "~>"
102
88
  - !ruby/object:Gem::Version
103
- version: '11.0'
89
+ version: '12.0'
104
90
  type: :development
105
91
  prerelease: false
106
92
  version_requirements: !ruby/object:Gem::Requirement
107
93
  requirements:
108
94
  - - "~>"
109
95
  - !ruby/object:Gem::Version
110
- version: '11.0'
96
+ version: '12.0'
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: minitest
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -142,6 +128,20 @@ dependencies:
142
128
  - - "~>"
143
129
  - !ruby/object:Gem::Version
144
130
  version: '0.0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: minitest-stub_any_instance
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '1.0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '1.0'
145
145
  - !ruby/object:Gem::Dependency
146
146
  name: minitest-colorin
147
147
  requirement: !ruby/object:Gem::Requirement
@@ -243,6 +243,8 @@ files:
243
243
  - lib/asynchronic/error.rb
244
244
  - lib/asynchronic/garbage_collector.rb
245
245
  - lib/asynchronic/job.rb
246
+ - lib/asynchronic/notifier/broadcaster.rb
247
+ - lib/asynchronic/notifier/in_memory.rb
246
248
  - lib/asynchronic/process.rb
247
249
  - lib/asynchronic/queue_engine/in_memory.rb
248
250
  - lib/asynchronic/queue_engine/ost.rb
@@ -289,8 +291,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
289
291
  - !ruby/object:Gem::Version
290
292
  version: '0'
291
293
  requirements: []
292
- rubyforge_project:
293
- rubygems_version: 2.6.8
294
+ rubygems_version: 3.0.6
294
295
  signing_key:
295
296
  specification_version: 4
296
297
  summary: DSL for asynchronic pipeline using queues over Redis