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