delayer-deferred 1.1.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,27 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Delayer::Deferred::Deferredable
|
4
|
+
module Awaitable
|
5
|
+
|
6
|
+
# _self_ が終了して結果が出るまで呼び出し側のDeferredを停止し、 _self_ の結果を返す。
|
7
|
+
# 呼び出し側はDeferredブロック内でなければならないが、 _Deferred#next_ を使わずに
|
8
|
+
# 直接戻り値を得ることが出来る。
|
9
|
+
# _self_ が失敗した場合は、呼び出し側のDeferredの直近の _trap_ ブロックが呼ばれる。
|
10
|
+
def +@
|
11
|
+
response = Fiber.yield(Delayer::Deferred::Request::Await.new(self))
|
12
|
+
if response.ok?
|
13
|
+
response.value
|
14
|
+
else
|
15
|
+
Delayer::Deferred.fail(response.value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def enter_await
|
20
|
+
change_sequence(:await)
|
21
|
+
end
|
22
|
+
|
23
|
+
def exit_await
|
24
|
+
change_sequence(:resume)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "delayer/deferred/deferredable/awaitable"
|
3
|
+
require "delayer/deferred/deferredable/graph"
|
4
|
+
require "delayer/deferred/deferredable/node_sequence"
|
5
|
+
|
6
|
+
module Delayer::Deferred::Deferredable
|
7
|
+
module Chainable
|
8
|
+
include Awaitable
|
9
|
+
include Graph
|
10
|
+
include NodeSequence
|
11
|
+
|
12
|
+
attr_reader :child
|
13
|
+
|
14
|
+
# このDeferredが成功した場合の処理を追加する。
|
15
|
+
# 新しいDeferredのインスタンスを返す。
|
16
|
+
# このメソッドはスレッドセーフです。
|
17
|
+
# TODO: procが空のとき例外を発生させる
|
18
|
+
def next(&proc)
|
19
|
+
add_child(Delayer::Deferred::Chain::Next.new(&proc))
|
20
|
+
end
|
21
|
+
alias deferred next
|
22
|
+
|
23
|
+
# このDeferredが失敗した場合の処理を追加する。
|
24
|
+
# 新しいDeferredのインスタンスを返す。
|
25
|
+
# このメソッドはスレッドセーフです。
|
26
|
+
# TODO: procが空のとき例外を発生させる
|
27
|
+
def trap(&proc)
|
28
|
+
add_child(Delayer::Deferred::Chain::Trap.new(&proc))
|
29
|
+
end
|
30
|
+
alias error trap
|
31
|
+
|
32
|
+
# この一連のDeferredをこれ以上実行しない。
|
33
|
+
# このメソッドはスレッドセーフです。
|
34
|
+
def cancel
|
35
|
+
change_sequence(:genocide) unless spoiled?
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_child?
|
39
|
+
child ? true : false
|
40
|
+
end
|
41
|
+
|
42
|
+
# 子を追加する。
|
43
|
+
# _Delayer::Deferred::Chainable_ を直接指定できる。通常外部から呼ぶときは _next_ か _trap_ メソッドを使うこと。
|
44
|
+
# このメソッドはスレッドセーフです。
|
45
|
+
# ==== Args
|
46
|
+
# [chainable] 子となるDeferred
|
47
|
+
# ==== Return
|
48
|
+
# 必ず _chainable_ を返す
|
49
|
+
# ==== Raise
|
50
|
+
# [Delayer::Deferred::SequenceError]
|
51
|
+
# 既に子が存在している場合
|
52
|
+
def add_child(chainable)
|
53
|
+
change_sequence(:get_child) do
|
54
|
+
chainable.parent = self
|
55
|
+
@child = chainable
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# 子が追加された時に一度だけコールバックするオブジェクトを登録する。
|
60
|
+
# observerと言っているが、実際には _Delayer::Deferred::Worker_ を渡して利用している。
|
61
|
+
# このメソッドはスレッドセーフです。
|
62
|
+
# ==== Args
|
63
|
+
# [observer] pushメソッドを備えているもの。引数に _@child_ の値が渡される
|
64
|
+
# ==== Return
|
65
|
+
# self
|
66
|
+
def add_child_observer(observer)
|
67
|
+
change_sequence(:gaze) do
|
68
|
+
@child_observer = observer
|
69
|
+
end
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def awaited
|
74
|
+
@awaited ||= [].freeze
|
75
|
+
end
|
76
|
+
|
77
|
+
def has_awaited?
|
78
|
+
not awaited.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_awaited(awaitable)
|
82
|
+
@awaited = [*awaited, awaitable].freeze
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
# activateメソッドを呼ぶDelayerジョブを登録する寸前に呼ばれる。
|
87
|
+
def reserve_activate
|
88
|
+
change_sequence(:reserve)
|
89
|
+
end
|
90
|
+
|
91
|
+
def enter_pass
|
92
|
+
change_sequence(:pass)
|
93
|
+
end
|
94
|
+
|
95
|
+
def exit_pass
|
96
|
+
change_sequence(:resume)
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
# 親を再帰的に辿り、一番最初のノードを返す。
|
102
|
+
# 親が複数見つかった場合は、それらを返す。
|
103
|
+
def ancestor
|
104
|
+
if @parent
|
105
|
+
@parent.ancestor
|
106
|
+
else
|
107
|
+
self
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# cancelとかデバッグ用のコールグラフを得るために親を登録しておく。
|
112
|
+
# add_childから呼ばれる。
|
113
|
+
def parent=(chainable)
|
114
|
+
@parent = chainable
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def call_child_observer
|
120
|
+
if has_child? and defined?(@child_observer)
|
121
|
+
change_sequence(:called)
|
122
|
+
@child_observer.push(@child)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def on_sequence_changed(old_seq, flow, new_seq)
|
127
|
+
case new_seq
|
128
|
+
when NodeSequence::BURST_OUT
|
129
|
+
call_child_observer
|
130
|
+
when NodeSequence::GENOCIDE
|
131
|
+
@parent.cancel if defined?(@parent) and @parent
|
132
|
+
when NodeSequence::RESERVED_C, NodeSequence::RUN_C, NodeSequence::PASS_C, NodeSequence::AWAIT_C, NodeSequence::GRAFT_C
|
133
|
+
if !has_child?
|
134
|
+
notice "child: #{@child.inspect}"
|
135
|
+
raise Delayer::Deferred::SequenceError.new("Sequence changed `#{old_seq.name}' to `#{flow}', but it has no child")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# ノードの名前。サブクラスでオーバライドし、ノードが定義されたファイルの名前や行数などを入れておく。
|
141
|
+
def node_name
|
142
|
+
self.class.to_s
|
143
|
+
end
|
144
|
+
|
145
|
+
def graph_mynode
|
146
|
+
if defined?(@seq_logger)
|
147
|
+
label = "#{node_name}\n(#{@seq_logger.map(&:name).join('→')})"
|
148
|
+
else
|
149
|
+
label = "#{node_name}\n(#{sequence.name})"
|
150
|
+
end
|
151
|
+
"#{__id__} [shape=#{graph_shape},label=#{label.inspect}]"
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Delayer::Deferred::Deferredable
|
4
|
+
=begin rdoc
|
5
|
+
graphvizによってChainableなDeferredをDOT言語形式でダンプする機能を追加するmix-in。
|
6
|
+
いずれかのノードに対して _graph_ メソッドを呼ぶと、再帰的に親子を全て辿り、digraphの断片の文字列を得ることが出来る。
|
7
|
+
|
8
|
+
== 出力例
|
9
|
+
|
10
|
+
20892180 [shape=egg,label="#<Class:0x000000027da288>.Promise\n(reserved)"]
|
11
|
+
20892480 [shape=box,label="test/thread_test.rb:53\n(connected)"]
|
12
|
+
20891440 [shape=diamond,label="test/thread_test.rb:56\n(fresh)"]
|
13
|
+
20892480 -> 20891440
|
14
|
+
20892180 -> 20892480
|
15
|
+
|
16
|
+
=end
|
17
|
+
module Graph
|
18
|
+
# この一連のDeferredチェインの様子を、DOT言語フォーマットで出力する
|
19
|
+
# ==== Args
|
20
|
+
# [child_only:]
|
21
|
+
# _true_ なら、このノードとその子孫のみを描画する。
|
22
|
+
# _false_ なら、再帰的に親を遡り、そこから描画を開始する。
|
23
|
+
# [output:]
|
24
|
+
# このオブジェクトに、 _<<_ メソッドで内容が書かれる。
|
25
|
+
# 省略した場合は、戻り値が _String_ になる。
|
26
|
+
# ==== Return
|
27
|
+
# [String] DOT言語によるグラフ
|
28
|
+
# [output:] 引数 output: に指定されたオブジェクト
|
29
|
+
def graph(child_only: false, output: String.new)
|
30
|
+
if child_only
|
31
|
+
output << "digraph Deferred {\n".freeze
|
32
|
+
Enumerator.new{ |yielder|
|
33
|
+
graph_child(output: yielder)
|
34
|
+
}.lazy.each{|l|
|
35
|
+
output << "\t#{l}\n"
|
36
|
+
}
|
37
|
+
output << '}'.freeze
|
38
|
+
else
|
39
|
+
ancestor.graph(child_only: true, output: output)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Graph.graph の結果を内容とする一時ファイルを作成して返す。
|
44
|
+
# ただし、ブロックを渡された場合は、一時ファイルを引数にそのブロックを一度だけ実行し、ブロックの戻り値をこのメソッドの戻り値とする。
|
45
|
+
# ==== Args
|
46
|
+
# [&block] 一時ファイルを利用する処理
|
47
|
+
# ==== Return
|
48
|
+
# [Tempfile] ブロックを指定しなかった場合。作成された一時ファイルオブジェクト
|
49
|
+
# [Object] ブロックが指定された場合。ブロックの実行結果。
|
50
|
+
def graph_save(permanent: false, &block)
|
51
|
+
if block
|
52
|
+
Tempfile.open{|tmp|
|
53
|
+
graph(output: tmp)
|
54
|
+
tmp.seek(0)
|
55
|
+
block.(tmp)
|
56
|
+
}
|
57
|
+
else
|
58
|
+
tmp = Tempfile.open
|
59
|
+
graph(output: tmp).tap{|t|t.seek(0)}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# 画像ファイルとしてグラフを書き出す。
|
64
|
+
# dotコマンドが使えないと失敗する。
|
65
|
+
# ==== Args
|
66
|
+
# [format:] 画像の拡張子
|
67
|
+
# ==== Return
|
68
|
+
# [String] 書き出したファイル名
|
69
|
+
def graph_draw(dir: '/tmp', format: 'svg'.freeze)
|
70
|
+
graph_save do |dotfile|
|
71
|
+
base = File.basename(dotfile.path)
|
72
|
+
dest = File.join(dir, "#{base}.#{format}")
|
73
|
+
system("dot -T#{format} #{dotfile.path} -o #{dest}")
|
74
|
+
dest
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# このノードとその子全てのDeferredチェインの様子を、DOT言語フォーマットで出力する。
|
79
|
+
# Delayer::Deferred::Deferredable::Graph#graph の内部で利用されるため、将来このメソッドのインターフェイスは変更される可能性がある。
|
80
|
+
def graph_child(output:)
|
81
|
+
output << graph_mynode
|
82
|
+
if has_child?
|
83
|
+
@child.graph_child(output: output)
|
84
|
+
output << "#{__id__} -> #{@child.__id__}"
|
85
|
+
end
|
86
|
+
if has_awaited?
|
87
|
+
awaited.each do |awaitable|
|
88
|
+
if awaitable.is_a?(Delayer::Deferred::Deferredable::Chainable)
|
89
|
+
awaitable.ancestor.graph_child(output: output)
|
90
|
+
else
|
91
|
+
label = "#{awaitable.class}"
|
92
|
+
output << "#{awaitable.__id__} [shape=oval,label=#{label.inspect}]"
|
93
|
+
end
|
94
|
+
output << "#{awaitable.__id__} -> #{__id__} [label = \"await\", style = \"dotted\"]"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# このノードを描画する時の形の名前を文字列で返す。
|
103
|
+
# 以下のページにあるような、graphvizで取り扱える形の中から選ぶこと。
|
104
|
+
# http://www.graphviz.org/doc/info/shapes.html
|
105
|
+
def graph_shape
|
106
|
+
'oval'.freeze
|
107
|
+
end
|
108
|
+
|
109
|
+
# このノードの形などをDOT言語の断片で返す。
|
110
|
+
# このメソッドをオーバライドすることで、描画されるノードの見た目を自由に変更することが出来る。
|
111
|
+
# ただし、簡単な変更だけなら別のメソッドをオーバライドするだけで可能なので、このmix-inの他のメソッドも参照すること。
|
112
|
+
def graph_mynode
|
113
|
+
label = "#{node_name}\n(#{sequence.name})"
|
114
|
+
"#{__id__} [shape=#{graph_shape},label=#{label.inspect}]"
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'delayer/deferred/error'
|
3
|
+
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
module Delayer::Deferred::Deferredable
|
7
|
+
module NodeSequence
|
8
|
+
class Sequence
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(name)
|
12
|
+
@name = name.to_sym
|
13
|
+
@map = {}
|
14
|
+
@exceptions = Hash.new(Delayer::Deferred::SequenceError)
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(seq, flow = seq.name)
|
18
|
+
@map[flow] = seq
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def exception(exc, flow)
|
23
|
+
@exceptions[flow] = exc
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def pull(flow)
|
28
|
+
if @map.has_key?(flow.to_sym)
|
29
|
+
@map[flow.to_sym]
|
30
|
+
else
|
31
|
+
raise @exceptions[flow.to_sym], "Invalid sequence flow `#{name}' to `#{flow}'."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
"#<#{self.class}: #{name}>"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
FRESH = Sequence.new(:fresh)
|
41
|
+
CONNECTED = Sequence.new(:connected) # 子がいる、未実行
|
42
|
+
RESERVED = Sequence.new(:reserved) # 実行キュー待ち
|
43
|
+
RESERVED_C= Sequence.new(:reserved) # 実行キュー待ち(子がいる)
|
44
|
+
RUN = Sequence.new(:run) # 実行中
|
45
|
+
RUN_C = Sequence.new(:run) # 実行中(子がいる)
|
46
|
+
PASS = Sequence.new(:pass) # パス中
|
47
|
+
PASS_C = Sequence.new(:pass) # パス中
|
48
|
+
AWAIT = Sequence.new(:await) # Await中
|
49
|
+
AWAIT_C = Sequence.new(:await) # Await中(子がいる)
|
50
|
+
GRAFT = Sequence.new(:graft) # 戻り値がAwaitableの時
|
51
|
+
GRAFT_C = Sequence.new(:graft) # 戻り値がAwaitableの時(子がいる)
|
52
|
+
CALL_CHILD= Sequence.new(:call_child) # 完了、子がいる
|
53
|
+
STOP = Sequence.new(:stop) # 完了、子なし
|
54
|
+
WAIT = Sequence.new(:wait) # 完了、オブザーバ登録済み
|
55
|
+
BURST_OUT = Sequence.new(:burst_out) # 完了、オブザーバ登録済み、子追加済み
|
56
|
+
ROTTEN = Sequence.new(:rotten).freeze # 終了
|
57
|
+
GENOCIDE = Sequence.new(:genocide).freeze# この地ではかつて大量虐殺があったという。
|
58
|
+
|
59
|
+
FRESH
|
60
|
+
.add(CONNECTED, :get_child)
|
61
|
+
.add(RESERVED, :reserve)
|
62
|
+
.add(GENOCIDE).freeze
|
63
|
+
CONNECTED
|
64
|
+
.add(RESERVED_C, :reserve)
|
65
|
+
.exception(Delayer::Deferred::MultipleAssignmentError, :get_child)
|
66
|
+
.add(GENOCIDE).freeze
|
67
|
+
RESERVED
|
68
|
+
.add(RUN, :activate)
|
69
|
+
.add(RESERVED_C, :get_child)
|
70
|
+
.add(GENOCIDE).freeze
|
71
|
+
RESERVED_C
|
72
|
+
.add(RUN_C, :activate)
|
73
|
+
.exception(Delayer::Deferred::MultipleAssignmentError, :get_child)
|
74
|
+
.add(GENOCIDE).freeze
|
75
|
+
RUN
|
76
|
+
.add(RUN_C, :get_child)
|
77
|
+
.add(PASS)
|
78
|
+
.add(AWAIT, :await)
|
79
|
+
.add(STOP, :complete)
|
80
|
+
.add(GENOCIDE).freeze
|
81
|
+
RUN_C
|
82
|
+
.add(PASS_C)
|
83
|
+
.add(AWAIT_C, :await)
|
84
|
+
.add(CALL_CHILD, :complete)
|
85
|
+
.exception(Delayer::Deferred::MultipleAssignmentError, :get_child)
|
86
|
+
.add(GENOCIDE).freeze
|
87
|
+
PASS
|
88
|
+
.add(PASS_C, :get_child)
|
89
|
+
.add(RUN, :resume)
|
90
|
+
.add(GENOCIDE).freeze
|
91
|
+
PASS_C
|
92
|
+
.add(RUN_C, :resume)
|
93
|
+
.add(GENOCIDE).freeze
|
94
|
+
AWAIT
|
95
|
+
.add(RUN, :resume)
|
96
|
+
.add(AWAIT_C, :get_child)
|
97
|
+
.add(GENOCIDE).freeze
|
98
|
+
AWAIT_C
|
99
|
+
.add(RUN_C, :resume)
|
100
|
+
.exception(Delayer::Deferred::MultipleAssignmentError, :get_child)
|
101
|
+
.add(GENOCIDE).freeze
|
102
|
+
CALL_CHILD
|
103
|
+
.add(GRAFT_C, :await)
|
104
|
+
.add(ROTTEN, :called)
|
105
|
+
.add(GENOCIDE).freeze
|
106
|
+
GRAFT
|
107
|
+
.add(STOP, :resume)
|
108
|
+
.add(GRAFT_C, :get_child)
|
109
|
+
.add(GENOCIDE).freeze
|
110
|
+
GRAFT_C
|
111
|
+
.add(CALL_CHILD, :resume)
|
112
|
+
.add(GENOCIDE).freeze
|
113
|
+
STOP
|
114
|
+
.add(GRAFT, :await)
|
115
|
+
.add(WAIT, :gaze)
|
116
|
+
.add(GENOCIDE).freeze
|
117
|
+
WAIT
|
118
|
+
.add(BURST_OUT, :get_child)
|
119
|
+
.add(GENOCIDE).freeze
|
120
|
+
BURST_OUT
|
121
|
+
.add(ROTTEN, :called)
|
122
|
+
.add(GENOCIDE).freeze
|
123
|
+
|
124
|
+
SEQUENCE_LOCK = Monitor.new
|
125
|
+
|
126
|
+
def sequence
|
127
|
+
@sequence ||= FRESH
|
128
|
+
end
|
129
|
+
|
130
|
+
# このメソッドはスレッドセーフです
|
131
|
+
def change_sequence(flow, &block)
|
132
|
+
SEQUENCE_LOCK.synchronize do
|
133
|
+
old_seq = sequence
|
134
|
+
new_seq = @sequence = sequence.pull(flow)
|
135
|
+
(@seq_logger ||= [old_seq]) << new_seq
|
136
|
+
if block
|
137
|
+
result = block.()
|
138
|
+
on_sequence_changed(old_seq, flow, new_seq)
|
139
|
+
result
|
140
|
+
else
|
141
|
+
on_sequence_changed(old_seq, flow, new_seq)
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def on_sequence_changed(old_seq, flow, new_seq)
|
148
|
+
end
|
149
|
+
|
150
|
+
def activated?
|
151
|
+
![FRESH, CONNECTED, RUN, RUN_C].include?(sequence)
|
152
|
+
end
|
153
|
+
|
154
|
+
def spoiled?
|
155
|
+
sequence == ROTTEN || sequence == GENOCIDE
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|