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,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
|