iopromise 0.1.0 → 0.1.4
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 +4 -4
- data/.github/workflows/main.yml +1 -2
- data/Gemfile +4 -10
- data/Gemfile.lock +7 -30
- data/README.md +23 -3
- data/bin/setup +0 -3
- data/iopromise.gemspec +1 -0
- data/lib/iopromise.rb +47 -18
- data/lib/iopromise/cancel_context.rb +51 -0
- data/lib/iopromise/data_loader.rb +58 -0
- data/lib/iopromise/deferred.rb +2 -2
- data/lib/iopromise/deferred/executor_pool.rb +26 -10
- data/lib/iopromise/deferred/promise.rb +15 -2
- data/lib/iopromise/executor_context.rb +47 -59
- data/lib/iopromise/executor_pool/base.rb +26 -7
- data/lib/iopromise/executor_pool/batch.rb +5 -3
- data/lib/iopromise/executor_pool/sequential.rb +6 -14
- data/lib/iopromise/rack/context_middleware.rb +5 -6
- data/lib/iopromise/version.rb +1 -1
- data/lib/iopromise/view_component/data_loader.rb +3 -44
- metadata +18 -18
- data/lib/iopromise/dalli.rb +0 -13
- data/lib/iopromise/dalli/client.rb +0 -146
- data/lib/iopromise/dalli/executor_pool.rb +0 -13
- data/lib/iopromise/dalli/patch_dalli.rb +0 -337
- data/lib/iopromise/dalli/promise.rb +0 -52
- data/lib/iopromise/dalli/response.rb +0 -25
- data/lib/iopromise/faraday.rb +0 -17
- data/lib/iopromise/faraday/connection.rb +0 -25
- data/lib/iopromise/faraday/continuable_hydra.rb +0 -29
- data/lib/iopromise/faraday/executor_pool.rb +0 -19
- data/lib/iopromise/faraday/multi_socket_action.rb +0 -107
- data/lib/iopromise/faraday/promise.rb +0 -42
- data/lib/iopromise/memcached.rb +0 -13
- data/lib/iopromise/memcached/client.rb +0 -22
- data/lib/iopromise/memcached/executor_pool.rb +0 -61
- data/lib/iopromise/memcached/promise.rb +0 -32
@@ -5,10 +5,14 @@ require_relative 'executor_pool'
|
|
5
5
|
module IOPromise
|
6
6
|
module Deferred
|
7
7
|
class DeferredPromise < ::IOPromise::Base
|
8
|
-
def initialize(&block)
|
8
|
+
def initialize(timeout: nil, &block)
|
9
9
|
super()
|
10
10
|
|
11
11
|
@block = block
|
12
|
+
|
13
|
+
unless timeout.nil?
|
14
|
+
@defer_until = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
15
|
+
end
|
12
16
|
|
13
17
|
::IOPromise::ExecutorContext.current.register(self) unless @block.nil?
|
14
18
|
end
|
@@ -26,13 +30,22 @@ module IOPromise
|
|
26
30
|
end
|
27
31
|
|
28
32
|
def run_deferred
|
29
|
-
return if @block.nil?
|
33
|
+
return if @block.nil? || !pending?
|
30
34
|
begin
|
31
35
|
fulfill(@block.call)
|
32
36
|
rescue => exception
|
33
37
|
reject(exception)
|
34
38
|
end
|
35
39
|
end
|
40
|
+
|
41
|
+
def time_until_execution
|
42
|
+
return 0 unless defined?(@defer_until)
|
43
|
+
|
44
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
45
|
+
return 0 if now > @defer_until
|
46
|
+
|
47
|
+
@defer_until - now
|
48
|
+
end
|
36
49
|
end
|
37
50
|
end
|
38
51
|
end
|
@@ -1,68 +1,73 @@
|
|
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
|
-
@
|
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 =
|
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)
|
33
31
|
@pending_registrations << promise
|
32
|
+
IOPromise::CancelContext.current&.subscribe(promise)
|
34
33
|
end
|
35
34
|
|
36
35
|
def wait_for_all_data(end_when_complete: nil)
|
36
|
+
unless end_when_complete.nil?
|
37
|
+
raise IOPromise::CancelledError if end_when_complete.cancelled?
|
38
|
+
end
|
39
|
+
|
37
40
|
loop do
|
38
41
|
complete_pending_registrations
|
39
42
|
|
40
|
-
|
41
|
-
|
43
|
+
@pools.each do |pool, _|
|
44
|
+
pool.execute_continue
|
45
|
+
end
|
46
|
+
|
42
47
|
unless end_when_complete.nil?
|
43
48
|
return unless end_when_complete.pending?
|
44
49
|
end
|
45
|
-
|
46
|
-
break if
|
50
|
+
|
51
|
+
break if @selector.empty?
|
47
52
|
|
48
53
|
# if we have any pending promises to register, we'll not block at all so we immediately continue
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
54
|
+
unless @pending_registrations.empty?
|
55
|
+
wait_time = 0
|
56
|
+
else
|
57
|
+
wait_time = nil
|
58
|
+
@pools.each do |pool, _|
|
59
|
+
timeout = pool.select_timeout
|
60
|
+
wait_time = timeout if wait_time.nil? || (!timeout.nil? && timeout < wait_time)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
ready_count = select(wait_time)
|
60
65
|
end
|
61
|
-
|
66
|
+
|
62
67
|
unless end_when_complete.nil?
|
63
68
|
raise ::IOPromise::Error.new('Internal error: IO loop completed without fulfilling the desired promise')
|
64
69
|
else
|
65
|
-
@pools.each do |pool|
|
70
|
+
@pools.each do |pool, _|
|
66
71
|
pool.wait
|
67
72
|
end
|
68
73
|
end
|
@@ -72,43 +77,26 @@ module IOPromise
|
|
72
77
|
|
73
78
|
private
|
74
79
|
|
80
|
+
def select(wait_time)
|
81
|
+
@selector.select(wait_time) do |monitor|
|
82
|
+
observer = monitor.value
|
83
|
+
observer.monitor_ready(monitor, monitor.readiness)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
75
87
|
def complete_pending_registrations
|
88
|
+
return if @pending_registrations.empty?
|
76
89
|
pending = @pending_registrations
|
77
90
|
@pending_registrations = []
|
78
91
|
pending.each do |promise|
|
79
|
-
register_now(promise)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
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
|
92
|
+
register_now(promise) unless promise.cancelled?
|
103
93
|
end
|
104
|
-
|
105
|
-
[readers, writers, exceptions, max_timeout]
|
106
94
|
end
|
107
95
|
|
108
96
|
def register_now(promise)
|
109
97
|
pool = promise.execute_pool
|
110
98
|
pool.register(promise)
|
111
|
-
@pools
|
99
|
+
@pools[pool] = true
|
112
100
|
end
|
113
101
|
end
|
114
102
|
end
|
@@ -1,25 +1,42 @@
|
|
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
|
33
|
+
def promise_fulfilled(_value, item)
|
34
|
+
@pending.delete(item)
|
35
|
+
end
|
36
|
+
def promise_rejected(_reason, item)
|
37
|
+
@pending.delete(item)
|
38
|
+
end
|
39
|
+
def promise_cancelled(item)
|
23
40
|
@pending.delete(item)
|
24
41
|
end
|
25
42
|
|
@@ -28,15 +45,17 @@ module IOPromise
|
|
28
45
|
end
|
29
46
|
|
30
47
|
# Continue execution of one or more pending IOPromises assigned to this pool.
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# this function
|
48
|
+
# Implementations may choose to pre-register IO handled using:
|
49
|
+
# ExecutorContext.current.register_observer_io(...)
|
50
|
+
# Alternatively, they can be registered when this function is called.
|
51
|
+
# During this function, implementations should check for timeouts and run
|
52
|
+
# any housekeeping operations.
|
53
|
+
#
|
35
54
|
# Must be implemented by subclasses.
|
36
|
-
def execute_continue
|
55
|
+
def execute_continue
|
37
56
|
raise NotImplementedError
|
38
57
|
end
|
39
|
-
|
58
|
+
|
40
59
|
def sync
|
41
60
|
@pending.each do |promise|
|
42
61
|
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(
|
7
|
-
super
|
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
|
-
|
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
|
7
|
-
item.execute_continue
|
6
|
+
def execute_continue_item(item)
|
7
|
+
item.execute_continue
|
8
8
|
end
|
9
9
|
|
10
|
-
def execute_continue
|
10
|
+
def execute_continue
|
11
11
|
@pending.dup.each do |active|
|
12
|
-
|
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
|
14
|
+
unless active.fulfilled?
|
19
15
|
# once we're waiting on our one next item, we're done
|
20
|
-
return
|
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
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'iopromise'
|
1
4
|
|
2
5
|
module IOPromise
|
3
6
|
module Rack
|
@@ -7,13 +10,9 @@ module IOPromise
|
|
7
10
|
end
|
8
11
|
|
9
12
|
def call(env)
|
10
|
-
|
11
|
-
|
12
|
-
status, headers, body = @app.call(env)
|
13
|
-
ensure
|
14
|
-
::IOPromise::ExecutorContext.pop
|
13
|
+
IOPromise::CancelContext.with_new_context do
|
14
|
+
@app.call(env)
|
15
15
|
end
|
16
|
-
[status, headers, body]
|
17
16
|
end
|
18
17
|
end
|
19
18
|
end
|
data/lib/iopromise/version.rb
CHANGED
@@ -1,56 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "../data_loader"
|
3
4
|
require "view_component/engine"
|
4
5
|
|
5
6
|
module IOPromise
|
6
7
|
module ViewComponent
|
7
8
|
module DataLoader
|
8
|
-
|
9
|
-
def attr_promised_data(*args)
|
10
|
-
@promised_data ||= []
|
11
|
-
@promised_data.concat(args)
|
12
|
-
|
13
|
-
args.each do |arg|
|
14
|
-
self.class_eval("def #{arg};@#{arg}.sync;end")
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def promised_data_keys
|
19
|
-
@promised_data ||= []
|
20
|
-
end
|
21
|
-
end
|
9
|
+
include ::IOPromise::DataLoader
|
22
10
|
|
23
11
|
def self.included(base)
|
24
|
-
base.extend(ClassMethods)
|
25
|
-
end
|
26
|
-
|
27
|
-
def data_as_promise
|
28
|
-
@data_promise ||= begin
|
29
|
-
promises = self.class.promised_data_keys.map do |k|
|
30
|
-
p = instance_variable_get('@' + k.to_s)
|
31
|
-
if p.is_a?(IOPromise::ViewComponent::DataLoader)
|
32
|
-
# recursively preload all nested data
|
33
|
-
p.data_as_promise
|
34
|
-
else
|
35
|
-
# for any local promises, we'll unwrap them before completing
|
36
|
-
p.then do |result|
|
37
|
-
if result.is_a?(IOPromise::ViewComponent::DataLoader)
|
38
|
-
# likewise, if we resolved a promise that we can recurse, load that data too.
|
39
|
-
result.data_as_promise
|
40
|
-
else
|
41
|
-
result
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
Promise.all(promises)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def sync
|
52
|
-
data_as_promise.sync
|
53
|
-
self
|
12
|
+
base.extend(::IOPromise::DataLoader::ClassMethods)
|
54
13
|
end
|
55
14
|
|
56
15
|
def render_in(*)
|
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.
|
4
|
+
version: 0.1.4
|
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-
|
11
|
+
date: 2021-07-28 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:
|
@@ -46,12 +60,8 @@ files:
|
|
46
60
|
- bin/setup
|
47
61
|
- iopromise.gemspec
|
48
62
|
- lib/iopromise.rb
|
49
|
-
- lib/iopromise/
|
50
|
-
- lib/iopromise/
|
51
|
-
- lib/iopromise/dalli/executor_pool.rb
|
52
|
-
- lib/iopromise/dalli/patch_dalli.rb
|
53
|
-
- lib/iopromise/dalli/promise.rb
|
54
|
-
- lib/iopromise/dalli/response.rb
|
63
|
+
- lib/iopromise/cancel_context.rb
|
64
|
+
- lib/iopromise/data_loader.rb
|
55
65
|
- lib/iopromise/deferred.rb
|
56
66
|
- lib/iopromise/deferred/executor_pool.rb
|
57
67
|
- lib/iopromise/deferred/promise.rb
|
@@ -59,16 +69,6 @@ files:
|
|
59
69
|
- lib/iopromise/executor_pool/base.rb
|
60
70
|
- lib/iopromise/executor_pool/batch.rb
|
61
71
|
- 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
|
-
- lib/iopromise/memcached.rb
|
69
|
-
- lib/iopromise/memcached/client.rb
|
70
|
-
- lib/iopromise/memcached/executor_pool.rb
|
71
|
-
- lib/iopromise/memcached/promise.rb
|
72
72
|
- lib/iopromise/rack/context_middleware.rb
|
73
73
|
- lib/iopromise/version.rb
|
74
74
|
- lib/iopromise/view_component.rb
|