asynchronic 2.0.0 → 3.0.3

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