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.
@@ -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