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,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
- begin
9
- loop do
10
- proc.call(self.next())
11
- if delayer.expire?
12
- break deach(delayer, &proc) end end
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
- def next(*rest, &block)
13
- __gen_promise.next(*rest, &block)
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 trap(*rest, &block)
17
- __gen_promise.trap(*rest, &block)
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.Deferred.new(true)
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
- failed = catch(:__deferredable_fail) do
32
- begin
33
- promise.call(tt.value)
34
- rescue Exception => err
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
- defer, *follow = args
31
- raise TypeError, "Argument of Deferred.when must be Delayer::Deferred::Deferredable" unless defer.is_a? Delayer::Deferred::Deferredable
32
- if follow.empty?
33
- defer.next{|res| [res] }
34
- else
35
- remain = self.when(*follow)
36
- defer.next do |res|
37
- remain.next do |follow_res|
38
- follow_res.unshift(res) end end end end
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
@@ -1,5 +1,5 @@
1
1
  module Delayer
2
2
  module Deferred
3
- VERSION = "1.1.1"
3
+ VERSION = "2.0.0"
4
4
  end
5
5
  end
@@ -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