iopromise 0.1.1 → 0.1.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.
@@ -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