delayer-deferred 1.1.1 → 2.0.0
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/README.md +77 -3
- data/delayer-deferred.gemspec +1 -0
- data/lib/delayer/deferred.rb +11 -10
- data/lib/delayer/deferred/chain.rb +10 -0
- data/lib/delayer/deferred/chain/await.rb +43 -0
- data/lib/delayer/deferred/chain/base.rb +35 -0
- data/lib/delayer/deferred/chain/next.rb +16 -0
- data/lib/delayer/deferred/chain/trap.rb +16 -0
- data/lib/delayer/deferred/deferred.rb +4 -72
- data/lib/delayer/deferred/deferredable.rb +8 -158
- data/lib/delayer/deferred/deferredable/awaitable.rb +27 -0
- data/lib/delayer/deferred/deferredable/chainable.rb +155 -0
- data/lib/delayer/deferred/deferredable/graph.rb +118 -0
- data/lib/delayer/deferred/deferredable/node_sequence.rb +158 -0
- data/lib/delayer/deferred/deferredable/trigger.rb +34 -0
- data/lib/delayer/deferred/enumerator.rb +5 -7
- data/lib/delayer/deferred/error.rb +9 -0
- data/lib/delayer/deferred/promise.rb +78 -0
- data/lib/delayer/deferred/request.rb +61 -0
- data/lib/delayer/deferred/response.rb +26 -0
- data/lib/delayer/deferred/thread.rb +29 -14
- data/lib/delayer/deferred/tools.rb +20 -10
- data/lib/delayer/deferred/version.rb +1 -1
- data/lib/delayer/deferred/worker.rb +112 -0
- data/test/deferred_test.rb +14 -0
- data/test/enumerable_test.rb +1 -1
- data/test/graph_test.rb +52 -0
- data/test/helper.rb +8 -3
- data/test/promise_test.rb +91 -0
- data/test/thread_test.rb +9 -5
- metadata +35 -3
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "delayer/deferred/deferredable/chainable"
|
3
|
+
require "delayer/deferred/deferredable/node_sequence"
|
4
|
+
require "delayer/deferred/response"
|
5
|
+
|
6
|
+
module Delayer::Deferred::Deferredable
|
7
|
+
=begin rdoc
|
8
|
+
Promiseなど、親を持たず、自身がWorkerを作成できるもの。
|
9
|
+
=end
|
10
|
+
module Trigger
|
11
|
+
include NodeSequence
|
12
|
+
include Chainable
|
13
|
+
|
14
|
+
# Deferredを直ちに実行する。
|
15
|
+
# このメソッドはスレッドセーフです。
|
16
|
+
def call(value = nil)
|
17
|
+
execute(Delayer::Deferred::Response::Ok.new(value))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Deferredを直ちに失敗させる。
|
21
|
+
# このメソッドはスレッドセーフです。
|
22
|
+
def fail(exception = nil)
|
23
|
+
execute(Delayer::Deferred::Response::Ng.new(exception))
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def execute(value)
|
29
|
+
worker = Delayer::Deferred::Worker.new(delayer: self.class.delayer,
|
30
|
+
initial: value)
|
31
|
+
worker.push(self)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -5,12 +5,10 @@ require "delayer/deferred/deferred"
|
|
5
5
|
class Enumerator
|
6
6
|
def deach(delayer=Delayer, &proc)
|
7
7
|
delayer.Deferred.new.next do
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
rescue StopIteration
|
14
|
-
nil end end
|
8
|
+
self.each do |node|
|
9
|
+
delayer.Deferred.pass
|
10
|
+
proc.(node)
|
11
|
+
end
|
12
|
+
end
|
15
13
|
end
|
16
14
|
end
|
@@ -10,4 +10,13 @@ module Delayer::Deferred
|
|
10
10
|
@process = process
|
11
11
|
end
|
12
12
|
end
|
13
|
+
|
14
|
+
SequenceError = Class.new(Error) do
|
15
|
+
attr_accessor :deferred
|
16
|
+
def initialize(message, deferred: nil)
|
17
|
+
super(message)
|
18
|
+
@deferred = deferred
|
19
|
+
end
|
20
|
+
end
|
21
|
+
MultipleAssignmentError = Class.new(SequenceError)
|
13
22
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "delayer/deferred/tools"
|
3
|
+
require "delayer/deferred/deferredable/trigger"
|
4
|
+
|
5
|
+
module Delayer::Deferred
|
6
|
+
class Promise
|
7
|
+
extend Delayer::Deferred::Tools
|
8
|
+
include Deferredable::Trigger
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def new(stop=false, name: caller_locations(1,1).first.to_s, &block)
|
12
|
+
result = promise = super(name: name)
|
13
|
+
result = promise.next(&block) if block_given?
|
14
|
+
promise.call(true) unless stop
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
def Thread
|
19
|
+
@thread_class ||= gen_thread_class end
|
20
|
+
|
21
|
+
def Promise
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def delayer
|
26
|
+
::Delayer
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"#{self.delayer}.Promise"
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def gen_thread_class
|
36
|
+
the_delayer = delayer
|
37
|
+
Class.new(Thread) do
|
38
|
+
define_singleton_method(:delayer) do
|
39
|
+
the_delayer
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(name:)
|
46
|
+
super()
|
47
|
+
@name = name
|
48
|
+
end
|
49
|
+
|
50
|
+
def activate(response)
|
51
|
+
change_sequence(:activate)
|
52
|
+
change_sequence(:complete)
|
53
|
+
response
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"#<#{self.class} seq:#{sequence.name}>"
|
58
|
+
end
|
59
|
+
|
60
|
+
def ancestor
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def parent=(chainable)
|
65
|
+
fail Error, "#{self.class} can't has parent."
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def graph_shape
|
71
|
+
'egg'.freeze
|
72
|
+
end
|
73
|
+
|
74
|
+
def node_name
|
75
|
+
@name.to_s
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# -*- coding: utf-8 -*-
|
4
|
+
|
5
|
+
module Delayer::Deferred::Request
|
6
|
+
class Base
|
7
|
+
attr_reader :value
|
8
|
+
def initialize(value)
|
9
|
+
@value = value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
=begin rdoc
|
14
|
+
Fiberが次のWorkerを要求している時に返す値。
|
15
|
+
新たなインスタンスは作らず、 _NEXT_WORKER_ にあるインスタンスを使うこと。
|
16
|
+
=end
|
17
|
+
class NextWorker < Base
|
18
|
+
# _deferred_ に渡された次のChainableに、 _deferred_ の戻り値を渡す要求を出す。
|
19
|
+
# ==== Args
|
20
|
+
# [deferred] 実行が完了したDeferred 。次のDeferredとして _deferred.child_ を呼び出す
|
21
|
+
# [worker] このDeferredチェインを実行しているWorker
|
22
|
+
def accept_request(worker:, deferred:)
|
23
|
+
if deferred.has_child?
|
24
|
+
worker.push(deferred.child)
|
25
|
+
else
|
26
|
+
deferred.add_child_observer(worker)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
=begin rdoc
|
32
|
+
Chainable#+@ が呼ばれた時に、一旦そこで処理を止めるためのリクエスト。
|
33
|
+
_value_ には、実行完了を待つDeferredが入っている。
|
34
|
+
==== わかりやすい!
|
35
|
+
accept_requestメソッドの引数のdeferred {
|
36
|
+
+value
|
37
|
+
}
|
38
|
+
=end
|
39
|
+
class Await < Base
|
40
|
+
alias_method :foreign_deferred, :value
|
41
|
+
def accept_request(worker:, deferred:)
|
42
|
+
deferred.enter_await
|
43
|
+
foreign_deferred.add_child(Delayer::Deferred::Chain::Await.new(worker: worker, deferred: deferred))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
=begin rdoc
|
48
|
+
一旦処理を中断して、Delayerキューに並び直すためのリクエスト。
|
49
|
+
Tools#pass から利用される。
|
50
|
+
新たなインスタンスは作らず、 _PASS_ にあるインスタンスを使うこと。
|
51
|
+
=end
|
52
|
+
class Pass < Base
|
53
|
+
def accept_request(worker:, deferred:)
|
54
|
+
deferred.enter_pass
|
55
|
+
worker.resume_pass(deferred)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
NEXT_WORKER = NextWorker.new(nil).freeze
|
60
|
+
PASS = Pass.new(nil).freeze
|
61
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Delayer::Deferred::Response
|
4
|
+
class Base
|
5
|
+
attr_reader :value
|
6
|
+
def initialize(value)
|
7
|
+
@value = value
|
8
|
+
end
|
9
|
+
|
10
|
+
def ng?
|
11
|
+
!ok?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Ok < Base
|
16
|
+
def ok?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Ng < Base
|
22
|
+
def ok?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,26 +1,40 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
require "delayer"
|
3
|
-
require "delayer/deferred/deferredable"
|
3
|
+
require "delayer/deferred/deferredable/awaitable"
|
4
4
|
|
5
5
|
class Thread
|
6
|
-
include ::Delayer::Deferred::Deferredable
|
6
|
+
include ::Delayer::Deferred::Deferredable::Awaitable
|
7
7
|
|
8
8
|
def self.delayer
|
9
9
|
Delayer
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
# このDeferredが成功した場合の処理を追加する。
|
13
|
+
# 新しいDeferredのインスタンスを返す。
|
14
|
+
# このメソッドはスレッドセーフです。
|
15
|
+
# TODO: procが空のとき例外を発生させる
|
16
|
+
def next(name: caller_locations(1,1).first.to_s, &proc)
|
17
|
+
add_child(Delayer::Deferred::Chain::Next.new(&proc), name: name)
|
14
18
|
end
|
19
|
+
alias deferred next
|
20
|
+
|
21
|
+
# このDeferredが失敗した場合の処理を追加する。
|
22
|
+
# 新しいDeferredのインスタンスを返す。
|
23
|
+
# このメソッドはスレッドセーフです。
|
24
|
+
# TODO: procが空のとき例外を発生させる
|
25
|
+
def trap(name: caller_locations(1,1).first.to_s, &proc)
|
26
|
+
add_child(Delayer::Deferred::Chain::Trap.new(&proc), name: name)
|
27
|
+
end
|
28
|
+
alias error trap
|
15
29
|
|
16
|
-
def
|
17
|
-
__gen_promise.
|
30
|
+
def add_child(chainable, name: caller_locations(1,1).first.to_s)
|
31
|
+
__gen_promise(name).add_child(chainable)
|
18
32
|
end
|
19
33
|
|
20
34
|
private
|
21
35
|
|
22
|
-
def __gen_promise
|
23
|
-
promise = delayer.
|
36
|
+
def __gen_promise(name)
|
37
|
+
promise = self.class.delayer.Promise.new(true, name: name)
|
24
38
|
Thread.new(self) do |tt|
|
25
39
|
__promise_callback(tt, promise)
|
26
40
|
end
|
@@ -28,15 +42,16 @@ class Thread
|
|
28
42
|
end
|
29
43
|
|
30
44
|
def __promise_callback(tt, promise)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
45
|
+
begin
|
46
|
+
result = tt.value
|
47
|
+
self.class.delayer.new do
|
48
|
+
promise.call(result)
|
49
|
+
end
|
50
|
+
rescue Exception => err
|
51
|
+
self.class.delayer.new do
|
35
52
|
promise.fail(err)
|
36
53
|
end
|
37
|
-
return
|
38
54
|
end
|
39
|
-
promise.fail(failed)
|
40
55
|
end
|
41
56
|
|
42
57
|
end
|
@@ -18,6 +18,12 @@ module Delayer::Deferred
|
|
18
18
|
def fail(value)
|
19
19
|
throw(:__deferredable_fail, value) end
|
20
20
|
|
21
|
+
# 実行中のDeferredを、Delayerのタイムリミットが来ている場合に限り一旦中断する。
|
22
|
+
# 長期に渡る可能性のある処理で、必要に応じて他のタスクを先に実行してもよい場合に呼び出す。
|
23
|
+
def pass
|
24
|
+
Fiber.yield(Request::PASS) if delayer.expire?
|
25
|
+
end
|
26
|
+
|
21
27
|
# 複数のdeferredを引数に取って、それら全ての実行が終了したら、
|
22
28
|
# その結果を引数の順番通りに格納したArrayを引数に呼ばれるDeferredを返す。
|
23
29
|
# 引数のDeferredが一つでも失敗するとこのメソッドの返すDeferredも失敗する。
|
@@ -27,16 +33,20 @@ module Delayer::Deferred
|
|
27
33
|
# Deferred
|
28
34
|
def when(*args)
|
29
35
|
return self.next{[]} if args.empty?
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
args = args.flatten
|
37
|
+
args.each_with_index{|d, index|
|
38
|
+
unless d.is_a?(Deferredable::Chainable) || d.is_a?(Deferredable::Awaitable)
|
39
|
+
raise TypeError, "Argument #{index} of Deferred.when must be #{Deferredable::Chainable}, but given #{d.class}"
|
40
|
+
end
|
41
|
+
if d.respond_to?(:has_child?) && d.has_child?
|
42
|
+
raise "Already assigned child for argument #{index}"
|
43
|
+
end
|
44
|
+
}
|
45
|
+
defer, *follow = *args
|
46
|
+
defer.next{|res|
|
47
|
+
[res, *follow.map{|d| +d }]
|
48
|
+
}
|
49
|
+
end
|
40
50
|
# Kernel#systemを呼び出して、コマンドが成功たら成功するDeferredを返す。
|
41
51
|
# 失敗した場合、trap{}ブロックには $? の値(Process::Status)か、例外が発生した場合それが渡される
|
42
52
|
# ==== Args
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "delayer/deferred/request"
|
3
|
+
require "delayer/deferred/response"
|
4
|
+
|
5
|
+
module Delayer::Deferred
|
6
|
+
=begin rdoc
|
7
|
+
Deferredを実行するためのWorker。Deferredチェインを実行するFiberを
|
8
|
+
管理する。
|
9
|
+
|
10
|
+
== pushに渡すオブジェクトについて
|
11
|
+
Worker#push に渡す引数は、activateメソッドを実装している必要がある。
|
12
|
+
|
13
|
+
=== activate(response)
|
14
|
+
==== Args
|
15
|
+
response :: Delayer::Deferred::Response::Base Deferredに渡す値
|
16
|
+
==== Returns
|
17
|
+
[Delayer::Deferred::Response::Base]
|
18
|
+
これを返すと、値の自動変換が行われないため、意図的に失敗させたり、Deferredを次のブロックに伝搬させることができる。
|
19
|
+
[Delayer::Deferred::Chainable]
|
20
|
+
戻り値のDeferredが終わるまでWorkerの処理を停止する。
|
21
|
+
再開された時、結果は戻り値のDeferredの結果に置き換えられる。
|
22
|
+
[else]
|
23
|
+
_Delayer::Deferred::Response::Ok.new_ の引数に渡され、その結果が利用される
|
24
|
+
=end
|
25
|
+
class Worker
|
26
|
+
def initialize(delayer:, initial:)
|
27
|
+
@delayer, @initial = delayer, initial
|
28
|
+
end
|
29
|
+
|
30
|
+
def push(deferred)
|
31
|
+
deferred.reserve_activate
|
32
|
+
@delayer.new do
|
33
|
+
next if deferred.spoiled?
|
34
|
+
begin
|
35
|
+
fiber.resume(deferred).accept_request(worker: self,
|
36
|
+
deferred: deferred)
|
37
|
+
rescue Delayer::Deferred::SequenceError => err
|
38
|
+
err.deferred = deferred
|
39
|
+
raise
|
40
|
+
end
|
41
|
+
end
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Awaitから復帰した時に呼ばれる。
|
46
|
+
# ==== Args
|
47
|
+
# [response] Awaitの結果(Delayer::Deferred::Response::Base)
|
48
|
+
# [deferred] 現在実行中のDeferred
|
49
|
+
def give_response(response, deferred)
|
50
|
+
@delayer.new do
|
51
|
+
next if deferred.spoiled?
|
52
|
+
deferred.exit_await
|
53
|
+
fiber.resume(response).accept_request(worker: self,
|
54
|
+
deferred: deferred)
|
55
|
+
end
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# Tools#pass から復帰した時に呼ばれる。
|
60
|
+
# ==== Args
|
61
|
+
# [deferred] 現在実行中のDeferred
|
62
|
+
def resume_pass(deferred)
|
63
|
+
deferred.exit_pass
|
64
|
+
@delayer.new do
|
65
|
+
next if deferred.spoiled?
|
66
|
+
fiber.resume(nil).accept_request(worker: self,
|
67
|
+
deferred: deferred)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def fiber
|
74
|
+
@fiber ||= Fiber.new{|response|
|
75
|
+
loop do
|
76
|
+
response = wait_and_activate(response)
|
77
|
+
case response.value
|
78
|
+
when Delayer::Deferred::SequenceError
|
79
|
+
raise response.value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
}.tap{|f| f.resume(@initial); @initial = nil }
|
83
|
+
end
|
84
|
+
|
85
|
+
def wait_and_activate(argument)
|
86
|
+
response = catch(:success) do
|
87
|
+
failed = catch(:__deferredable_fail) do
|
88
|
+
begin
|
89
|
+
if argument.value.is_a? Deferredable::Awaitable
|
90
|
+
throw :success, +argument.value
|
91
|
+
else
|
92
|
+
defer = Fiber.yield(Request::NEXT_WORKER)
|
93
|
+
res = defer.activate(argument)
|
94
|
+
if res.is_a? Delayer::Deferred::Deferredable::Awaitable
|
95
|
+
defer.add_awaited(res)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
throw :success, res
|
99
|
+
rescue Exception => err
|
100
|
+
throw :__deferredable_fail, err
|
101
|
+
end
|
102
|
+
end
|
103
|
+
Response::Ng.new(failed)
|
104
|
+
end
|
105
|
+
if response.is_a?(Response::Base)
|
106
|
+
response
|
107
|
+
else
|
108
|
+
Response::Ok.new(response)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|