asynchronic 2.0.0 → 3.0.3

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: 7a0200ef4e9c9c465ddc70786d71e15bb2fa2f4d
4
- data.tar.gz: 0d5320739a21a0cd57f7e65cfe28d6dea571dbfb
2
+ SHA256:
3
+ metadata.gz: 7cc53216188aca01f66806e933e68d3edacda3dabca5e93b3da3eae1a41bbae5
4
+ data.tar.gz: ad209146fc5b92f5d0d38c5d0ce27b72b6c6f7308831f9facb08a9bc7a538904
5
5
  SHA512:
6
- metadata.gz: 2294c1215c7b3abb787c3ec9cd955b254b9b1ec235e6c028d1ff9620b881bc0f0a7ed937b98b14e1ef2537b0be2921e67772ccaec1d9b37616bccc8a11fbe8cf
7
- data.tar.gz: 8fa59d9eeafa9a952a9c76c2b91c807d4eaa0fcbc58ee2b1bed1bd637358b42f4c2ef8e1fb1bb864bad86664e45c67fbc44e8bd8e2099dedda6b6e08e95e6cc2
6
+ metadata.gz: 3e826e042a938689168e9ca085d4fd5f28250ed350ebdb658c871415b36df88088e290712c72558256c1535b247ead901fbae3818e692895f6e4d8052ab5f560
7
+ data.tar.gz: 2a1d517fb7cd29556d790e6680928e9294e79329b23a3e7ebab6e948ebc6a087d713cc2c3ca12908ca25f4798585198d35a88351c3f0723ad47d8e4b4416bb2d
data/.travis.yml CHANGED
@@ -4,11 +4,12 @@ rvm:
4
4
  - 2.0
5
5
  - 2.1
6
6
  - 2.2
7
- - 2.3.0
8
- - 2.4.0
9
- - 2.5.0
10
- - 2.6.0
11
- - 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
12
13
  - ruby-head
13
14
  - jruby-head
14
15
 
@@ -18,9 +19,5 @@ matrix:
18
19
  - rvm: ruby-head
19
20
  - rvm: jruby-head
20
21
 
21
- before_install:
22
- - rvm all-gemsets do gem uninstall bundler -ax || true
23
- - gem install bundler -v "< 2"
24
-
25
22
  services:
26
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
 
data/asynchronic.gemspec CHANGED
@@ -19,14 +19,15 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.add_dependency 'ost', '~> 1.0'
22
+ spec.add_dependency 'broadcaster', '~> 1.0', '>= 1.0.2'
22
23
  spec.add_dependency 'class_config', '~> 0.0'
23
24
  spec.add_dependency 'transparent_proxy', '~> 0.0'
24
25
  spec.add_dependency 'multi_require', '~> 1.0'
25
26
 
26
- spec.add_development_dependency 'bundler', '~> 1.12'
27
- spec.add_development_dependency 'rake', '~> 11.0'
27
+ spec.add_development_dependency 'rake', '~> 12.0'
28
28
  spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
29
29
  spec.add_development_dependency 'minitest-great_expectations', '~> 0.0'
30
+ spec.add_development_dependency 'minitest-stub_any_instance', '~> 1.0'
30
31
  spec.add_development_dependency 'minitest-colorin', '~> 0.1'
31
32
  spec.add_development_dependency 'minitest-line', '~> 0.6'
32
33
  spec.add_development_dependency 'simplecov', '~> 0.12'
data/lib/asynchronic.rb CHANGED
@@ -2,6 +2,7 @@ require 'forwardable'
2
2
  require 'securerandom'
3
3
  require 'ost'
4
4
  require 'redic'
5
+ require 'broadcaster'
5
6
  require 'class_config'
6
7
  require 'transparent_proxy'
7
8
  require 'logger'
@@ -18,15 +19,16 @@ module Asynchronic
18
19
  attr_config :default_queue, :asynchronic
19
20
  attr_config :queue_engine, QueueEngine::InMemory.new
20
21
  attr_config :data_store, DataStore::InMemory.new
22
+ attr_config :notifier, Notifier::InMemory.new
21
23
  attr_config :logger, Logger.new($stdout)
22
24
  attr_config :retry_timeout, 30
23
25
  attr_config :garbage_collector_timeout, 30
24
26
  attr_config :redis_data_store_sync_timeout, 0.01
25
- attr_config :keep_alive_timeout, 0.1
26
- attr_config :connection_name, "HOST=#{Socket.gethostname},PID=#{::Process.pid}"
27
+ attr_config :keep_alive_timeout, 1
28
+ attr_config :connection_name, "HOST=#{Socket.gethostname},PID=#{::Process.pid},UUID=#{SecureRandom.uuid}"
27
29
 
28
30
  def self.environment
29
- Environment.new queue_engine, data_store
31
+ Environment.new queue_engine, data_store, notifier
30
32
  end
31
33
 
32
34
  def self.[](pid)
@@ -12,7 +12,7 @@ module Asynchronic
12
12
  end
13
13
 
14
14
  def [](key)
15
- value = @redis.call '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,29 +20,29 @@ module Asynchronic
20
20
  end
21
21
 
22
22
  def []=(key, value)
23
- @redis.call '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
- @redis.call 'DEL', @scope[key]
27
+ @redis.call! 'DEL', @scope[key]
28
28
  end
29
29
 
30
30
  def delete_cascade(key)
31
- @redis.call 'DEL', @scope[key]
32
- @redis.call('KEYS', @scope[key]['*']).each { |k| @redis.call '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
- @redis.call('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 @redis.call('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
- @redis.call 'DEL', @scope[key][LOCKED]
45
+ @redis.call! 'DEL', @scope[key][LOCKED]
46
46
  end
47
47
 
48
48
  def connection_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
@@ -13,7 +13,9 @@ 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
 
@@ -51,6 +53,10 @@ module Asynchronic
51
53
  (running? && !connected?) || processes.any?(&:dead?)
52
54
  end
53
55
 
56
+ def abort_if_dead
57
+ abort! DEAD_ERROR_MESSAGE if dead?
58
+ end
59
+
54
60
  def destroy
55
61
  data_store.delete_cascade
56
62
  end
@@ -92,13 +98,11 @@ module Asynchronic
92
98
  end
93
99
 
94
100
  def real_error
95
- return nil unless error
101
+ return nil if !aborted?
96
102
 
97
- processes.each do |child|
98
- return child.real_error if child.error
99
- end
103
+ first_aborted_child = processes.select(&:aborted?).sort_by(&:finalized_at).first
100
104
 
101
- error.message
105
+ first_aborted_child ? first_aborted_child.real_error : error.message
102
106
  end
103
107
 
104
108
  def dependencies
@@ -190,8 +194,12 @@ module Asynchronic
190
194
 
191
195
  def status=(status)
192
196
  Asynchronic.logger.info('Asynchronic') { "#{status.to_s.capitalize} #{type} (#{id})" }
197
+
193
198
  data_store[:status] = status
194
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?
195
203
  end
196
204
 
197
205
  STATUSES.each do |status|
@@ -206,8 +214,8 @@ module Asynchronic
206
214
  end
207
215
  end
208
216
 
209
- def abort!(exception=nil)
210
- self.error = Error.new exception if exception
217
+ def abort!(exception)
218
+ self.error = Error.new exception
211
219
  aborted!
212
220
  end
213
221
 
@@ -215,7 +223,7 @@ module Asynchronic
215
223
  self.connection_name = Asynchronic.connection_name
216
224
 
217
225
  if root.aborted?
218
- abort!
226
+ abort! AUTOMATIC_ABORTED_ERROR_MESSAGE
219
227
  else
220
228
  running!
221
229
  self.result = job.call
@@ -258,6 +266,9 @@ module Asynchronic
258
266
 
259
267
  def connected?
260
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
261
272
  end
262
273
 
263
274
  end
@@ -2,12 +2,12 @@ module Asynchronic
2
2
  module QueueEngine
3
3
  class Ost
4
4
 
5
- attr_reader :default_queue
5
+ attr_reader :redis, :default_queue
6
6
 
7
7
  def initialize(options={})
8
8
  @redis = Redic.new(*Array(options[:redis]))
9
9
  @default_queue = options.fetch(:default_queue, Asynchronic.default_queue)
10
- @queues ||= Hash.new { |h,k| h[k] = Queue.new k }
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
 
@@ -16,12 +16,12 @@ module Asynchronic
16
16
  end
17
17
 
18
18
  def queues
19
- (@queues.values.map(&:key) | @redis.call('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 }
20
20
  end
21
21
 
22
22
  def clear
23
23
  @queues.clear
24
- @redis.call('KEYS', 'ost:*').each { |k| @redis.call('DEL', k) }
24
+ redis.call!('KEYS', 'ost:*').each { |k| redis.call!('DEL', k) }
25
25
  end
26
26
 
27
27
  def listener
@@ -33,9 +33,10 @@ 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=/) }[5..-1]
38
- end.uniq.reject(&:empty?)
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
@@ -43,7 +44,7 @@ module Asynchronic
43
44
  def notify_keep_alive
44
45
  Thread.new do
45
46
  loop do
46
- @redis.call 'CLIENT', 'SETNAME', Asynchronic.connection_name
47
+ redis.call! 'CLIENT', 'SETNAME', Asynchronic.connection_name
47
48
  sleep Asynchronic.keep_alive_timeout
48
49
  end
49
50
  end
@@ -52,12 +53,17 @@ module Asynchronic
52
53
 
53
54
  class Queue < ::Ost::Queue
54
55
 
56
+ def initialize(name, redis)
57
+ super name
58
+ self.redis = redis
59
+ end
60
+
55
61
  def pop
56
- redis.call 'RPOP', key
62
+ redis.call! 'RPOP', key
57
63
  end
58
64
 
59
65
  def empty?
60
- redis.call('EXISTS', key) == 0
66
+ redis.call!('EXISTS', key) == 0
61
67
  end
62
68
 
63
69
  def size
@@ -1,3 +1,3 @@
1
1
  module Asynchronic
2
- VERSION = '2.0.0'
2
+ VERSION = '3.0.3'
3
3
  end
@@ -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
 
data/spec/facade_spec.rb CHANGED
@@ -69,11 +69,11 @@ describe Asynchronic, 'Facade' do
69
69
  end
70
70
 
71
71
  it 'Keep alive timeout' do
72
- Asynchronic.keep_alive_timeout.must_equal 0.1
72
+ Asynchronic.keep_alive_timeout.must_equal 1
73
73
  end
74
74
 
75
75
  it 'Connection name' do
76
- Asynchronic.connection_name.must_equal "HOST=#{Socket.gethostname},PID=#{::Process.pid}"
76
+ Asynchronic.connection_name.must_match /^HOST=#{Socket.gethostname},PID=#{::Process.pid}/
77
77
  end
78
78
 
79
79
  end
data/spec/jobs.rb CHANGED
@@ -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,6 +3,7 @@ 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'
8
9
  require 'timeout'
@@ -1,12 +1,13 @@
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={})
@@ -17,10 +18,46 @@ module LifeCycleExamples
17
18
 
18
19
  def execute(queue)
19
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
+
20
28
  process.execute
29
+
21
30
  process.must_have_connection_name
22
31
  process.wont_be :dead?
32
+
23
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
24
61
  end
25
62
 
26
63
  it 'Basic' do
@@ -481,80 +518,81 @@ module LifeCycleExamples
481
518
  process.real_error.must_equal "Error in parent"
482
519
  end
483
520
 
484
- it 'Abort queued afert error' do
485
- process = create AbortQueuedAfertErrorJob
521
+ it 'Abort queued After error' do
522
+ process = create AbortQueuedAfterErrorJob
486
523
 
487
524
  process.enqueue
488
525
 
489
526
  execute queue
490
527
 
491
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :waiting,
492
- 'AbortQueuedAfertErrorJob::Child_1' => :queued,
493
- 'AbortQueuedAfertErrorJob::Child_2' => :queued,
494
- 'AbortQueuedAfertErrorJob::Child_3' => :queued,
495
- '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
496
533
 
497
534
  execute queue
498
535
 
499
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :waiting,
500
- 'AbortQueuedAfertErrorJob::Child_1' => :waiting,
536
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :waiting,
537
+ 'AbortQueuedAfterErrorJob::Child_1' => :waiting,
501
538
  'Child_1_1' => :queued,
502
539
  'Child_1_2' => :queued,
503
- 'AbortQueuedAfertErrorJob::Child_2' => :queued,
504
- 'AbortQueuedAfertErrorJob::Child_3' => :queued,
505
- 'AbortQueuedAfertErrorJob::Child_4' => :queued
540
+ 'AbortQueuedAfterErrorJob::Child_2' => :queued,
541
+ 'AbortQueuedAfterErrorJob::Child_3' => :queued,
542
+ 'AbortQueuedAfterErrorJob::Child_4' => :queued
506
543
 
507
544
  execute queue
508
545
 
509
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :waiting,
510
- 'AbortQueuedAfertErrorJob::Child_1' => :waiting,
546
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :waiting,
547
+ 'AbortQueuedAfterErrorJob::Child_1' => :waiting,
511
548
  'Child_1_1' => :queued,
512
549
  'Child_1_2' => :queued,
513
- 'AbortQueuedAfertErrorJob::Child_2' => :completed,
514
- 'AbortQueuedAfertErrorJob::Child_3' => :queued,
515
- 'AbortQueuedAfertErrorJob::Child_4' => :queued
550
+ 'AbortQueuedAfterErrorJob::Child_2' => :completed,
551
+ 'AbortQueuedAfterErrorJob::Child_3' => :queued,
552
+ 'AbortQueuedAfterErrorJob::Child_4' => :queued
516
553
 
517
554
  execute queue
518
555
 
519
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :aborted,
520
- 'AbortQueuedAfertErrorJob::Child_1' => :waiting,
556
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :aborted,
557
+ 'AbortQueuedAfterErrorJob::Child_1' => :waiting,
521
558
  'Child_1_1' => :queued,
522
559
  'Child_1_2' => :queued,
523
- 'AbortQueuedAfertErrorJob::Child_2' => :completed,
524
- 'AbortQueuedAfertErrorJob::Child_3' => :aborted,
525
- 'AbortQueuedAfertErrorJob::Child_4' => :queued
560
+ 'AbortQueuedAfterErrorJob::Child_2' => :completed,
561
+ 'AbortQueuedAfterErrorJob::Child_3' => :aborted,
562
+ 'AbortQueuedAfterErrorJob::Child_4' => :queued
526
563
 
527
564
  execute queue
528
565
 
529
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :aborted,
530
- 'AbortQueuedAfertErrorJob::Child_1' => :waiting,
566
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :aborted,
567
+ 'AbortQueuedAfterErrorJob::Child_1' => :waiting,
531
568
  'Child_1_1' => :queued,
532
569
  'Child_1_2' => :queued,
533
- 'AbortQueuedAfertErrorJob::Child_2' => :completed,
534
- 'AbortQueuedAfertErrorJob::Child_3' => :aborted,
535
- 'AbortQueuedAfertErrorJob::Child_4' => :aborted
570
+ 'AbortQueuedAfterErrorJob::Child_2' => :completed,
571
+ 'AbortQueuedAfterErrorJob::Child_3' => :aborted,
572
+ 'AbortQueuedAfterErrorJob::Child_4' => :aborted
536
573
 
537
574
  execute queue
538
575
 
539
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :aborted,
540
- 'AbortQueuedAfertErrorJob::Child_1' => :aborted,
576
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :aborted,
577
+ 'AbortQueuedAfterErrorJob::Child_1' => :aborted,
541
578
  'Child_1_1' => :aborted,
542
579
  'Child_1_2' => :queued,
543
- 'AbortQueuedAfertErrorJob::Child_2' => :completed,
544
- 'AbortQueuedAfertErrorJob::Child_3' => :aborted,
545
- 'AbortQueuedAfertErrorJob::Child_4' => :aborted
580
+ 'AbortQueuedAfterErrorJob::Child_2' => :completed,
581
+ 'AbortQueuedAfterErrorJob::Child_3' => :aborted,
582
+ 'AbortQueuedAfterErrorJob::Child_4' => :aborted
546
583
 
547
584
  execute queue
548
585
 
549
- process.full_status.must_equal 'AbortQueuedAfertErrorJob' => :aborted,
550
- 'AbortQueuedAfertErrorJob::Child_1' => :aborted,
586
+ process.full_status.must_equal 'AbortQueuedAfterErrorJob' => :aborted,
587
+ 'AbortQueuedAfterErrorJob::Child_1' => :aborted,
551
588
  'Child_1_1' => :aborted,
552
589
  'Child_1_2' => :aborted,
553
- 'AbortQueuedAfertErrorJob::Child_2' => :completed,
554
- 'AbortQueuedAfertErrorJob::Child_3' => :aborted,
555
- 'AbortQueuedAfertErrorJob::Child_4' => :aborted
590
+ 'AbortQueuedAfterErrorJob::Child_2' => :completed,
591
+ 'AbortQueuedAfterErrorJob::Child_3' => :aborted,
592
+ 'AbortQueuedAfterErrorJob::Child_4' => :aborted
556
593
 
557
594
  process.real_error.must_equal 'Forced error'
595
+ process.processes[0].processes[1].error.message.must_equal Asynchronic::Process::AUTOMATIC_ABORTED_ERROR_MESSAGE
558
596
  end
559
597
 
560
598
  it 'Manual abort' do
@@ -617,36 +655,44 @@ module LifeCycleExamples
617
655
  process_2.enqueue
618
656
  execute queue
619
657
 
658
+ process_3 = create BasicJob
659
+
620
660
  pid_1 = process_1.id
621
661
  pid_2 = process_2.id
662
+ pid_3 = process_3.id
622
663
 
623
664
  process_1.must_be_completed
624
665
  process_2.must_be_waiting
666
+ process_3.must_be_pending
625
667
 
626
668
  data_store.keys.select { |k| k.start_with? pid_1 }.count.must_equal 53
627
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
628
671
 
629
672
  gc = Asynchronic::GarbageCollector.new env, 0.001
630
673
 
631
- gc.add_condition('Completed', &:completed?)
674
+ gc.add_condition('Finalized', &:finalized?)
632
675
  gc.add_condition('Waiting', &:waiting?)
633
676
  gc.add_condition('Exception') { raise 'Invalid condition' }
634
677
 
635
- gc.conditions_names.must_equal ['Completed', 'Waiting', 'Exception']
678
+ gc.conditions_names.must_equal ['Finalized', 'Waiting', 'Exception']
636
679
 
637
680
  gc.remove_condition 'Waiting'
638
681
 
639
- gc.conditions_names.must_equal ['Completed', 'Exception']
682
+ gc.conditions_names.must_equal ['Finalized', 'Exception']
640
683
 
641
684
  Thread.new do
642
685
  sleep 0.01
643
686
  gc.stop
644
687
  end
645
688
 
646
- gc.start
689
+ Asynchronic::Process.stub_any_instance(:dead?, -> { id == pid_3 }) do
690
+ gc.start
691
+ end
647
692
 
648
693
  data_store.keys.select { |k| k.start_with? pid_1 }.count.must_equal 0
649
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
650
696
  end
651
697
 
652
698
  it 'Before finalize hook when completed' do
@@ -717,5 +763,4 @@ module LifeCycleExamples
717
763
  queue.must_be_empty
718
764
  end
719
765
 
720
-
721
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
@@ -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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asynchronic
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.3
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-06-18 00:00:00.000000000 Z
11
+ date: 2021-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ost
@@ -25,21 +25,27 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: class_config
28
+ name: broadcaster
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.0'
33
+ version: '1.0'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 1.0.2
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
41
  - - "~>"
39
42
  - !ruby/object:Gem::Version
40
- version: '0.0'
43
+ version: '1.0'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 1.0.2
41
47
  - !ruby/object:Gem::Dependency
42
- name: transparent_proxy
48
+ name: class_config
43
49
  requirement: !ruby/object:Gem::Requirement
44
50
  requirements:
45
51
  - - "~>"
@@ -53,47 +59,47 @@ dependencies:
53
59
  - !ruby/object:Gem::Version
54
60
  version: '0.0'
55
61
  - !ruby/object:Gem::Dependency
56
- name: multi_require
62
+ name: transparent_proxy
57
63
  requirement: !ruby/object:Gem::Requirement
58
64
  requirements:
59
65
  - - "~>"
60
66
  - !ruby/object:Gem::Version
61
- version: '1.0'
67
+ version: '0.0'
62
68
  type: :runtime
63
69
  prerelease: false
64
70
  version_requirements: !ruby/object:Gem::Requirement
65
71
  requirements:
66
72
  - - "~>"
67
73
  - !ruby/object:Gem::Version
68
- version: '1.0'
74
+ version: '0.0'
69
75
  - !ruby/object:Gem::Dependency
70
- name: bundler
76
+ name: multi_require
71
77
  requirement: !ruby/object:Gem::Requirement
72
78
  requirements:
73
79
  - - "~>"
74
80
  - !ruby/object:Gem::Version
75
- version: '1.12'
76
- type: :development
81
+ version: '1.0'
82
+ type: :runtime
77
83
  prerelease: false
78
84
  version_requirements: !ruby/object:Gem::Requirement
79
85
  requirements:
80
86
  - - "~>"
81
87
  - !ruby/object:Gem::Version
82
- version: '1.12'
88
+ version: '1.0'
83
89
  - !ruby/object:Gem::Dependency
84
90
  name: rake
85
91
  requirement: !ruby/object:Gem::Requirement
86
92
  requirements:
87
93
  - - "~>"
88
94
  - !ruby/object:Gem::Version
89
- version: '11.0'
95
+ version: '12.0'
90
96
  type: :development
91
97
  prerelease: false
92
98
  version_requirements: !ruby/object:Gem::Requirement
93
99
  requirements:
94
100
  - - "~>"
95
101
  - !ruby/object:Gem::Version
96
- version: '11.0'
102
+ version: '12.0'
97
103
  - !ruby/object:Gem::Dependency
98
104
  name: minitest
99
105
  requirement: !ruby/object:Gem::Requirement
@@ -128,6 +134,20 @@ dependencies:
128
134
  - - "~>"
129
135
  - !ruby/object:Gem::Version
130
136
  version: '0.0'
137
+ - !ruby/object:Gem::Dependency
138
+ name: minitest-stub_any_instance
139
+ requirement: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '1.0'
144
+ type: :development
145
+ prerelease: false
146
+ version_requirements: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: '1.0'
131
151
  - !ruby/object:Gem::Dependency
132
152
  name: minitest-colorin
133
153
  requirement: !ruby/object:Gem::Requirement
@@ -229,6 +249,8 @@ files:
229
249
  - lib/asynchronic/error.rb
230
250
  - lib/asynchronic/garbage_collector.rb
231
251
  - lib/asynchronic/job.rb
252
+ - lib/asynchronic/notifier/broadcaster.rb
253
+ - lib/asynchronic/notifier/in_memory.rb
232
254
  - lib/asynchronic/process.rb
233
255
  - lib/asynchronic/queue_engine/in_memory.rb
234
256
  - lib/asynchronic/queue_engine/ost.rb
@@ -275,8 +297,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
275
297
  - !ruby/object:Gem::Version
276
298
  version: '0'
277
299
  requirements: []
278
- rubyforge_project:
279
- rubygems_version: 2.6.8
300
+ rubygems_version: 3.0.6
280
301
  signing_key:
281
302
  specification_version: 4
282
303
  summary: DSL for asynchronic pipeline using queues over Redis