iopromise 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,16 +9,19 @@ module IOPromise
9
9
 
10
10
  def initialize(server = nil, key = nil)
11
11
  super()
12
-
12
+
13
+ # when created from a 'then' call, initialize nothing
14
+ return if server.nil? || key.nil?
15
+
13
16
  @server = server
14
17
  @key = key
15
18
  @start_time = nil
16
-
17
- ::IOPromise::ExecutorContext.current.register(self) unless @server.nil? || @key.nil?
19
+
20
+ ::IOPromise::ExecutorContext.current.register(self)
18
21
  end
19
22
 
20
23
  def wait
21
- if @server.nil? || @key.nil?
24
+ unless defined?(@server)
22
25
  super
23
26
  else
24
27
  ::IOPromise::ExecutorContext.current.wait_for_all_data(end_when_complete: self)
@@ -26,7 +29,12 @@ module IOPromise
26
29
  end
27
30
 
28
31
  def execute_pool
29
- DalliExecutorPool.for(@server)
32
+ return @pool if defined?(@pool)
33
+ if defined?(@server)
34
+ @pool = DalliExecutorPool.for(@server)
35
+ else
36
+ @pool = nil
37
+ end
30
38
  end
31
39
 
32
40
  def in_select_loop
@@ -3,7 +3,7 @@
3
3
  module IOPromise
4
4
  module Deferred
5
5
  class DeferredExecutorPool < ::IOPromise::ExecutorPool::Batch
6
- def execute_continue(ready_readers, ready_writers, ready_exceptions)
6
+ def execute_continue
7
7
  if @current_batch.empty?
8
8
  next_batch
9
9
  end
@@ -13,7 +13,6 @@ module IOPromise
13
13
  @current_batch.each do |promise|
14
14
  begin_executing(promise)
15
15
  promise.run_deferred
16
- complete(promise)
17
16
  end
18
17
 
19
18
  @current_batch = []
@@ -22,7 +21,6 @@ module IOPromise
22
21
  end
23
22
 
24
23
  # we always fully complete each cycle
25
- return [[], [], [], nil]
26
24
  end
27
25
  end
28
26
  end
@@ -26,7 +26,7 @@ module IOPromise
26
26
  end
27
27
 
28
28
  def run_deferred
29
- return if @block.nil?
29
+ return if @block.nil? || !pending?
30
30
  begin
31
31
  fulfill(@block.call)
32
32
  rescue => exception
@@ -1,32 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
+ require 'nio'
4
5
 
5
6
  module IOPromise
6
7
  class ExecutorContext
7
8
  class << self
8
- def push
9
- @contexts ||= []
10
- @contexts << ExecutorContext.new
11
- end
12
-
13
9
  def current
14
- @contexts.last
15
- end
16
-
17
- def pop
18
- @contexts.pop
10
+ @context ||= ExecutorContext.new
19
11
  end
20
12
  end
21
13
 
22
14
  def initialize
23
- @pools = Set.new
24
-
25
- @pool_ready_readers = {}
26
- @pool_ready_writers = {}
27
- @pool_ready_exceptions = {}
15
+ @pools = {}
28
16
 
29
17
  @pending_registrations = []
18
+
19
+ @selector = NIO::Selector.new
20
+
21
+ super
22
+ end
23
+
24
+ def register_observer_io(observer, io, interest)
25
+ monitor = @selector.register(io, interest)
26
+ monitor.value = observer
27
+ monitor
30
28
  end
31
29
 
32
30
  def register(promise)
@@ -37,32 +35,34 @@ module IOPromise
37
35
  loop do
38
36
  complete_pending_registrations
39
37
 
40
- readers, writers, exceptions, wait_time = continue_to_read_pools
41
-
38
+ @pools.each do |pool, _|
39
+ pool.execute_continue
40
+ end
41
+
42
42
  unless end_when_complete.nil?
43
43
  return unless end_when_complete.pending?
44
44
  end
45
-
46
- break if readers.empty? && writers.empty? && exceptions.empty? && @pending_registrations.empty?
45
+
46
+ break if @selector.empty?
47
47
 
48
48
  # if we have any pending promises to register, we'll not block at all so we immediately continue
49
- wait_time = 0 unless @pending_registrations.empty?
50
-
51
- # we could be clever and decide which ones to "continue" on next
52
- ready = IO.select(readers.keys, writers.keys, exceptions.keys, wait_time)
53
- ready = [[], [], []] if ready.nil?
54
- ready_readers, ready_writers, ready_exceptions = ready
55
-
56
- # group by the pool object that provided the fd
57
- @pool_ready_readers = ready_readers.group_by { |i| readers[i] }
58
- @pool_ready_writers = ready_writers.group_by { |i| writers[i] }
59
- @pool_ready_exceptions = ready_exceptions.group_by { |i| exceptions[i] }
49
+ unless @pending_registrations.empty?
50
+ wait_time = 0
51
+ else
52
+ wait_time = nil
53
+ @pools.each do |pool, _|
54
+ timeout = pool.select_timeout
55
+ wait_time = timeout if wait_time.nil? || (!timeout.nil? && timeout < wait_time)
56
+ end
57
+ end
58
+
59
+ ready_count = select(wait_time)
60
60
  end
61
-
61
+
62
62
  unless end_when_complete.nil?
63
63
  raise ::IOPromise::Error.new('Internal error: IO loop completed without fulfilling the desired promise')
64
64
  else
65
- @pools.each do |pool|
65
+ @pools.each do |pool, _|
66
66
  pool.wait
67
67
  end
68
68
  end
@@ -72,7 +72,15 @@ module IOPromise
72
72
 
73
73
  private
74
74
 
75
+ def select(wait_time)
76
+ @selector.select(wait_time) do |monitor|
77
+ observer = monitor.value
78
+ observer.monitor_ready(monitor, monitor.readiness)
79
+ end
80
+ end
81
+
75
82
  def complete_pending_registrations
83
+ return if @pending_registrations.empty?
76
84
  pending = @pending_registrations
77
85
  @pending_registrations = []
78
86
  pending.each do |promise|
@@ -80,35 +88,10 @@ module IOPromise
80
88
  end
81
89
  end
82
90
 
83
- def continue_to_read_pools
84
- readers = {}
85
- writers = {}
86
- exceptions = {}
87
- max_timeout = nil
88
-
89
- @pools.each do |pool|
90
- rd, wr, ex, ti = pool.execute_continue(@pool_ready_readers[pool], @pool_ready_writers[pool], @pool_ready_exceptions[pool])
91
- rd.each do |io|
92
- readers[io] = pool
93
- end
94
- wr.each do |io|
95
- writers[io] = pool
96
- end
97
- ex.each do |io|
98
- exceptions[io] = pool
99
- end
100
- if max_timeout.nil? || (!ti.nil? && ti < max_timeout)
101
- max_timeout = ti
102
- end
103
- end
104
-
105
- [readers, writers, exceptions, max_timeout]
106
- end
107
-
108
91
  def register_now(promise)
109
92
  pool = promise.execute_pool
110
93
  pool.register(promise)
111
- @pools.add(pool)
94
+ @pools[pool] = true
112
95
  end
113
96
  end
114
97
  end
@@ -1,25 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'promise/observer'
4
+
3
5
  module IOPromise
4
6
  module ExecutorPool
5
7
  class Base
8
+ include Promise::Observer
9
+
6
10
  class << self
7
11
  def for(connection_pool)
8
12
  @executors ||= {}
9
13
  @executors[connection_pool] ||= new(connection_pool)
10
14
  end
11
15
  end
16
+
17
+ attr_accessor :select_timeout
12
18
 
13
19
  def initialize(connection_pool)
14
20
  @connection_pool = connection_pool
15
21
  @pending = []
22
+
23
+ @monitors = {}
24
+
25
+ @select_timeout = nil
16
26
  end
17
27
 
18
28
  def register(item)
19
29
  @pending << item
30
+ item.subscribe(self, item, item)
20
31
  end
21
32
 
22
- def complete(item)
33
+ def promise_fulfilled(_value, item)
34
+ @pending.delete(item)
35
+ end
36
+ def promise_rejected(_reason, item)
23
37
  @pending.delete(item)
24
38
  end
25
39
 
@@ -28,15 +42,17 @@ module IOPromise
28
42
  end
29
43
 
30
44
  # Continue execution of one or more pending IOPromises assigned to this pool.
31
- # Returns [readers, writers, exceptions, max_timeout], which are arrays of the
32
- # readers, writers, and exceptions to select on. The timeout specifies the maximum
33
- # time to block waiting for one of these IO objects to become ready, after which
34
- # this function is called again with empty "ready" arguments.
45
+ # Implementations may choose to pre-register IO handled using:
46
+ # ExecutorContext.current.register_observer_io(...)
47
+ # Alternatively, they can be registered when this function is called.
48
+ # During this function, implementations should check for timeouts and run
49
+ # any housekeeping operations.
50
+ #
35
51
  # Must be implemented by subclasses.
36
- def execute_continue(ready_readers, ready_writers, ready_exceptions)
52
+ def execute_continue
37
53
  raise NotImplementedError
38
54
  end
39
-
55
+
40
56
  def sync
41
57
  @pending.each do |promise|
42
58
  promise.sync if promise.is_a?(Promise)
@@ -3,8 +3,8 @@
3
3
  module IOPromise
4
4
  module ExecutorPool
5
5
  class Batch < Base
6
- def initialize(connection_pool)
7
- super(connection_pool)
6
+ def initialize(*)
7
+ super
8
8
 
9
9
  @current_batch = []
10
10
  end
@@ -16,7 +16,9 @@ module IOPromise
16
16
  end
17
17
 
18
18
  # every pending operation becomes part of the current batch
19
- @current_batch = @pending.dup
19
+ # we don't include promises with a source set, because that
20
+ # indicates that they depend on another promise now.
21
+ @current_batch = @pending.select { |p| p.pending? && p.source.nil? }
20
22
  end
21
23
  end
22
24
  end
@@ -3,29 +3,21 @@
3
3
  module IOPromise
4
4
  module ExecutorPool
5
5
  class Sequential < Base
6
- def execute_continue_item(item, ready_readers, ready_writers, ready_exceptions)
7
- item.execute_continue(ready_readers, ready_writers, ready_exceptions)
6
+ def execute_continue_item(item)
7
+ item.execute_continue
8
8
  end
9
9
 
10
- def execute_continue(ready_readers, ready_writers, ready_exceptions)
10
+ def execute_continue
11
11
  @pending.dup.each do |active|
12
- status = if active.fulfilled?
13
- nil
14
- else
15
- execute_continue_item(active, ready_readers, ready_writers, ready_exceptions)
16
- end
12
+ execute_continue_item(active)
17
13
 
18
- unless status.nil?
14
+ unless active.fulfilled?
19
15
  # once we're waiting on our one next item, we're done
20
- return status
21
- else
22
- # we're done with this one, so remove it
23
- complete(active)
16
+ return
24
17
  end
25
18
  end
26
19
 
27
20
  # if we fall through to here, we have nothing to wait on.
28
- [[], [], [], nil]
29
21
  end
30
22
  end
31
23
  end
@@ -3,6 +3,12 @@
3
3
  module IOPromise
4
4
  module Memcached
5
5
  class MemcacheExecutorPool < ::IOPromise::ExecutorPool::Batch
6
+ def initialize(*)
7
+ super
8
+
9
+ @monitors = {}
10
+ end
11
+
6
12
  def next_batch
7
13
  super
8
14
 
@@ -14,7 +20,6 @@ module IOPromise
14
20
  rescue => e
15
21
  @keys_to_promises.values.flatten.each do |promise|
16
22
  promise.reject(e)
17
- complete(promise)
18
23
  @current_batch.delete(promise)
19
24
  end
20
25
 
@@ -23,12 +28,17 @@ module IOPromise
23
28
  end
24
29
  end
25
30
 
26
- def execute_continue(ready_readers, ready_writers, ready_exceptions)
31
+ def execute_continue
27
32
  if @current_batch.empty?
28
33
  next_batch
29
34
  end
30
35
 
31
- return [[], [], [], nil] if @current_batch.empty?
36
+ if @current_batch.empty?
37
+ @monitors.each do |_, monitor|
38
+ monitor.interests = nil
39
+ end
40
+ return
41
+ end
32
42
 
33
43
  so_far, readers, writers = memcache_client.continue_get_multi
34
44
 
@@ -45,12 +55,23 @@ module IOPromise
45
55
  next if promise.fulfilled?
46
56
 
47
57
  promise.fulfill(value)
48
- complete(promise)
49
58
  @current_batch.delete(promise)
50
59
  end
51
60
  end
52
61
 
53
- [readers, writers, [], nil]
62
+ @monitors.each do |_, monitor|
63
+ monitor.interests = nil
64
+ end
65
+
66
+ readers.each do |reader|
67
+ @monitors[reader] ||= ::IOPromise::ExecutorContext.current.register_observer_io(self, reader, :r)
68
+ @monitors[reader].add_interest(:r)
69
+ end
70
+
71
+ writers.each do |writer|
72
+ @monitors[writer] ||= ::IOPromise::ExecutorContext.current.register_observer_io(self, writer, :w)
73
+ @monitors[writer].add_interest(:w)
74
+ end
54
75
  end
55
76
 
56
77
  def memcache_client
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IOPromise
4
- VERSION = '0.1.1'
4
+ VERSION = '0.1.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iopromise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Theo Julienne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-27 00:00:00.000000000 Z
11
+ date: 2021-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: promise.rb
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.7.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: nio4r
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description: This gem extends promise.rb promises to support an extremely simple pattern
28
42
  for "continuing" execution of the promise in an asynchronous non-blocking way.
29
43
  email:
@@ -59,17 +73,10 @@ files:
59
73
  - lib/iopromise/executor_pool/base.rb
60
74
  - lib/iopromise/executor_pool/batch.rb
61
75
  - lib/iopromise/executor_pool/sequential.rb
62
- - lib/iopromise/faraday.rb
63
- - lib/iopromise/faraday/connection.rb
64
- - lib/iopromise/faraday/continuable_hydra.rb
65
- - lib/iopromise/faraday/executor_pool.rb
66
- - lib/iopromise/faraday/multi_socket_action.rb
67
- - lib/iopromise/faraday/promise.rb
68
76
  - lib/iopromise/memcached.rb
69
77
  - lib/iopromise/memcached/client.rb
70
78
  - lib/iopromise/memcached/executor_pool.rb
71
79
  - lib/iopromise/memcached/promise.rb
72
- - lib/iopromise/rack/context_middleware.rb
73
80
  - lib/iopromise/version.rb
74
81
  - lib/iopromise/view_component.rb
75
82
  - lib/iopromise/view_component/data_loader.rb