asynchronic 1.6.3 → 3.0.2

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 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