delayer-deferred 1.0.4 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e7ac95be8514c4cce712ba5be5f9959daefdf012
4
- data.tar.gz: b63a1e2ef68c15092edd125c28a7d3a68e92a43f
3
+ metadata.gz: 17fb3ffd22cfc243b60926a084efa91b6f9dee15
4
+ data.tar.gz: f73bcf244106e7699719884d36d374d52be94080
5
5
  SHA512:
6
- metadata.gz: 85b360491e8385fccdf2b5aa5b7679dff0d57f36f9b988e26675f1cc8759a4c3ad23adf2bae933766f330633b9d0da6ce896d18489dc12ccd566ee043b5239df
7
- data.tar.gz: 8dcb697c132e1ecefb19923988fdac533e63aa176e1346117d11e3e8ee86536209abe2558730ca69c478be1e8ce578da4ec2e4676c6a7ec6b7a946557f24649f
6
+ metadata.gz: ac64243c4a9440c32086bb99a94de79339b57f2aca5c57f2f068249d7adc9ac09736c09d7e8ccca220acbc52157c843eba3be63fe8577b1cd1e21b6f0cdbbc86
7
+ data.tar.gz: 7ec556c87dad8a807a1d9e2318678f3f5fd95dd5accab5c18228194902ac4cc22b85eae1d39cb913f28cffda006bc22751b72306938ee82453be65b9cb918293
data/Rakefile CHANGED
@@ -6,3 +6,11 @@ Rake::TestTask.new do |t|
6
6
  t.warning = true
7
7
  t.verbose = true
8
8
  end
9
+
10
+ task :benchmark do
11
+ FileList['test/*_benchmark.rb'].each{|f| load f }
12
+ end
13
+
14
+ task :profile do
15
+ FileList['test/*_profiler.rb'].each{|f| load f }
16
+ end
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "bundler", "~> 1.7"
26
26
  spec.add_development_dependency "rake", "~> 10.0"
27
27
  spec.add_development_dependency "minitest", "~> 5.7"
28
+ spec.add_development_dependency "ruby-prof"
28
29
  end
@@ -49,5 +49,21 @@ module Delayer::Deferred
49
49
  sprintf("#<%s: %p stat:%s value:%s>".freeze, self.class, object_id, @next_call_stat.inspect, @next_call_value.inspect)
50
50
  end
51
51
  end
52
+
53
+ def self.fiber(&block)
54
+ @_fiber ||= Fiber.new do |b|
55
+ loop do
56
+ b = Fiber.yield(b.())
57
+ end
58
+ end
59
+ result = @_fiber.resume(block)
60
+ if result.is_a?(Delayer::Deferred::ResultContainer)
61
+ result
62
+ else
63
+ _fiber = @_fiber
64
+ @_fiber = nil
65
+ return result, _fiber
66
+ end
67
+ end
52
68
  end
53
69
  end
@@ -1,10 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  require "delayer/deferred/version"
3
+ require "delayer/deferred/result_container"
3
4
 
4
5
  # なんでもDeferred
5
6
  module Delayer::Deferred::Deferredable
6
7
  Callback = Struct.new(*%i<ok ng backtrace>)
7
- BackTrace = Struct.new(*%i<ok ng>)
8
8
  CallbackDefaultOK = lambda{ |x| x }
9
9
  CallbackDefaultNG = lambda{ |err| Delayer::Deferred.fail(err) }
10
10
 
@@ -28,16 +28,27 @@ module Delayer::Deferred::Deferredable
28
28
  def fail(exception = nil)
29
29
  _call(:ng, exception) end
30
30
 
31
+ # _self_ が終了して結果が出るまで呼び出し側のDeferredを停止し、 _self_ の結果を返す。
32
+ # 呼び出し側はDeferredブロック内でなければならないが、 _Deferred#next_ を使わずに
33
+ # 直接戻り値を得ることが出来る。
34
+ # _self_ が失敗した場合は、呼び出し側のDeferredの直近の _trap_ ブロックが呼ばれる。
35
+ def +@
36
+ interrupt = Fiber.yield(self)
37
+ if interrupt.ok?
38
+ interrupt.value
39
+ else
40
+ Delayer::Deferred.fail(interrupt.value)
41
+ end
42
+ end
43
+
31
44
  # この一連のDeferredをこれ以上実行しない
32
45
  def cancel
33
46
  @callback = Callback.new(CallbackDefaultOK,
34
- CallbackDefaultNG,
35
- BackTrace.new(nil, nil).freeze).freeze end
47
+ CallbackDefaultNG).freeze end
36
48
 
37
49
  def callback
38
50
  @callback ||= Callback.new(CallbackDefaultOK,
39
- CallbackDefaultNG,
40
- BackTrace.new(nil, nil)) end
51
+ CallbackDefaultNG) end
41
52
 
42
53
  # second 秒待って次を実行する
43
54
  # ==== Args
@@ -54,32 +65,74 @@ module Delayer::Deferred::Deferredable
54
65
  end
55
66
 
56
67
  def _call(stat = :ok, value = nil)
57
- begin
58
- catch(:__deferredable_success) do
59
- failed = catch(:__deferredable_fail) do
60
- n_value = _execute(stat, value)
61
- if n_value.is_a? Delayer::Deferred::Deferredable
62
- n_value.next{ |result|
63
- @next.call(result)
64
- }.trap{ |exception|
65
- @next.fail(exception) }
68
+ delayer.new do
69
+ result, fiber = delayer.Deferred.fiber do
70
+ begin
71
+ result = catch(:__deferredable_fail) do
72
+ Delayer::Deferred::ResultContainer.new(true, _execute(stat, value))
73
+ end
74
+ if result.is_a?(Delayer::Deferred::ResultContainer)
75
+ result
66
76
  else
67
- if defined?(@next)
68
- delayer.new{ @next.call(n_value) }
69
- else
70
- register_next_call(:ok, n_value) end end
71
- throw :__deferredable_success end
72
- _fail_action(failed) end
73
- rescue Exception => exception
74
- _fail_action(exception) end end
77
+ Delayer::Deferred::ResultContainer.new(false, result)
78
+ end
79
+ rescue Exception => exception
80
+ Delayer::Deferred::ResultContainer.new(false, exception)
81
+ end
82
+ end
83
+ #_wait_fiber(fiber, nil)
84
+ if fiber
85
+ _fiber_stopped(result){|i| _wait_fiber(fiber, i) }
86
+ else
87
+ _fiber_completed(result)
88
+ end
89
+ end
90
+ end
91
+
75
92
 
76
93
  def _execute(stat, value)
77
- callback[stat].call(value) end
94
+ callback[stat].call(value)
95
+ end
96
+
97
+ def _wait_fiber(fiber, resume_value)
98
+ result = fiber.resume(resume_value)
99
+ if result.is_a?(Delayer::Deferred::ResultContainer)
100
+ _fiber_completed(result)
101
+ else
102
+ _fiber_stopped(result){|i| _wait_fiber(fiber, i) }
103
+ end
104
+ end
105
+
106
+ # Deferredブロックが最後まで終わり、これ以上やることがない時に呼ばれる
107
+ def _fiber_completed(result)
108
+ result_value = result.value
109
+ if result.ok?
110
+ if result_value.is_a?(Delayer::Deferred::Deferredable)
111
+ result_value.next{|v|
112
+ _success_action(v)
113
+ }.trap{|v|
114
+ _fail_action(v)
115
+ }
116
+ else
117
+ _success_action(result_value)
118
+ end
119
+ else
120
+ _fail_action(result_value)
121
+ end
122
+ end
123
+
124
+ # Deferredable#@+によって停止され、 _defer_ の完了次第処理を再開する必要がある時に呼ばれる
125
+ def _fiber_stopped(defer, &cont)
126
+ defer.next{|v|
127
+ cont.(Delayer::Deferred::ResultContainer.new(true, v))
128
+ }.trap{|v|
129
+ cont.(Delayer::Deferred::ResultContainer.new(false, v))
130
+ }
131
+ end
78
132
 
79
133
  def _post(kind, &proc)
80
134
  @next = delayer.Deferred.new(self)
81
135
  @next.callback[kind] = proc
82
- @next.callback.backtrace[kind] = caller(1)
83
136
  if defined?(@next_call_stat) and defined?(@next_call_value)
84
137
  @next.__send__({ok: :call, ng: :fail}[@next_call_stat], @next_call_value)
85
138
  elsif defined?(@follow) and @follow.nil?
@@ -90,10 +143,20 @@ module Delayer::Deferred::Deferredable
90
143
  @next_call_stat, @next_call_value = stat, value
91
144
  self end
92
145
 
146
+ def _success_action(obj)
147
+ if defined?(@next)
148
+ @next.call(obj)
149
+ else
150
+ register_next_call(:ok, obj)
151
+ end
152
+ end
153
+
93
154
  def _fail_action(err_obj)
94
155
  if defined?(@next)
95
- delayer.new{ @next.fail(err_obj) }
156
+ @next.fail(err_obj)
96
157
  else
97
- register_next_call(:ng, err_obj) end end
158
+ register_next_call(:ng, err_obj)
159
+ end
160
+ end
98
161
 
99
162
  end
@@ -0,0 +1,13 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Delayer::Deferred
4
+ Error = Class.new(StandardError)
5
+
6
+ class ForeignCommandAborted < Error
7
+ attr_reader :process
8
+ def initialize(message, process:)
9
+ super(message)
10
+ @process = process
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ Delayer::Deferred::ResultContainer = Struct.new(:success_flag, :value) do
4
+ def ok?
5
+ success_flag
6
+ end
7
+
8
+ def ng?
9
+ !success_flag
10
+ end
11
+ end
@@ -9,28 +9,34 @@ class Thread
9
9
  Delayer
10
10
  end
11
11
 
12
- alias _deferredable_trap initialize
13
- def initialize(*args, &proc)
14
- _deferredable_trap(*args, &_deferredable_trap_proc(&proc)) end
12
+ def next(*rest, &block)
13
+ __gen_promise.next(*rest, &block)
14
+ end
15
15
 
16
- alias :deferredable_cancel :cancel
17
- def cancel
18
- deferredable_cancel
19
- kill end
16
+ def trap(*rest, &block)
17
+ __gen_promise.trap(*rest, &block)
18
+ end
20
19
 
21
20
  private
22
- def _deferredable_trap_proc
23
- proc = Proc.new
24
- ->(*args) do
25
- catch(:__deferredable_success) do
26
- failed = catch(:__deferredable_fail) do
27
- begin
28
- result = proc.call(*args)
29
- self.call(result)
30
- result
31
- rescue Exception => exception
32
- self.fail(exception)
33
- raise exception end
34
- throw :__deferredable_success end
35
- self.fail(failed) end end end
21
+
22
+ def __gen_promise
23
+ promise = delayer.Deferred.new(true)
24
+ Thread.new(self) do |tt|
25
+ __promise_callback(tt, promise)
26
+ end
27
+ promise
28
+ end
29
+
30
+ def __promise_callback(tt, promise)
31
+ failed = catch(:__deferredable_fail) do
32
+ begin
33
+ promise.call(tt.value)
34
+ rescue Exception => err
35
+ promise.fail(err)
36
+ end
37
+ return
38
+ end
39
+ promise.fail(failed)
40
+ end
41
+
36
42
  end
@@ -1,4 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
+ require 'delayer/deferred/error'
2
3
 
3
4
  module Delayer::Deferred
4
5
  module Tools
@@ -43,10 +44,14 @@ module Delayer::Deferred
43
44
  # ==== Return
44
45
  # Deferred
45
46
  def system(*args)
46
- delayer.Deferred.Thread.new do
47
- if Kernel.system(*args)
48
- $?
47
+ delayer.Deferred.Thread.new {
48
+ Process.waitpid2(Kernel.spawn(*args))
49
+ }.next{|_pid, status|
50
+ if status && status.success?
51
+ status
49
52
  else
50
- delayer.Deferred.fail($?) end end end
53
+ raise ForeignCommandAborted.new("command aborted: #{args.join(' ')}", process: $?) end
54
+ }
55
+ end
51
56
  end
52
57
  end
@@ -1,5 +1,5 @@
1
1
  module Delayer
2
2
  module Deferred
3
- VERSION = "1.0.4"
3
+ VERSION = "1.1.0"
4
4
  end
5
5
  end
@@ -0,0 +1,63 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'benchmark'
3
+ require 'bundler/setup'
4
+ require 'delayer/deferred'
5
+ require_relative 'testutils.rb'
6
+
7
+ Benchmark.bmbm do |r|
8
+ extend TestUtils
9
+ n = 10000
10
+
11
+ r.report "construct" do
12
+ delayer = Delayer.generate_class
13
+ n.times do
14
+ delayer.Deferred.new
15
+ end
16
+ end
17
+
18
+ r.report "register next block" do
19
+ delayer = Delayer.generate_class
20
+ n.times do
21
+ delayer.Deferred.new.next{|x|
22
+ x
23
+ }
24
+ end
25
+ end
26
+
27
+ r.report "execute next block" do
28
+ delayer = Delayer.generate_class
29
+ eval_all_events(delayer) do
30
+ n.times do
31
+ delayer.Deferred.new.next{|x|
32
+ x
33
+ }
34
+ end
35
+ end
36
+ end
37
+
38
+ r.report "double next block" do
39
+ delayer = Delayer.generate_class
40
+ eval_all_events(delayer) do
41
+ n.times do
42
+ delayer.Deferred.new.next{|x|
43
+ x
44
+ }.next{|x|
45
+ x
46
+ }
47
+ end
48
+ end
49
+ end
50
+
51
+ r.report "trap block" do
52
+ delayer = Delayer.generate_class
53
+ eval_all_events(delayer) do
54
+ n.times do
55
+ delayer.Deferred.new.next{|x|
56
+ x
57
+ }.trap{|x|
58
+ x
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,28 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'bundler/setup'
3
+ require 'delayer/deferred'
4
+ require 'ruby-prof'
5
+ require_relative 'testutils.rb'
6
+
7
+ extend TestUtils
8
+ n = 1000
9
+
10
+ RubyProf.start
11
+ delayer = Delayer.generate_class
12
+ eval_all_events(delayer) do
13
+ n.times do
14
+ delayer.Deferred.new.next{|x|
15
+ x
16
+ }.trap{|x|
17
+ x
18
+ }
19
+ end
20
+ end
21
+
22
+ result = RubyProf.stop
23
+ printer = RubyProf::CallTreePrinter.new(result)
24
+ path = File.expand_path(File.join(__dir__, '..', 'profile', Time.new.strftime('%Y-%m-%d-%H%M%S')))
25
+ FileUtils.mkdir_p(path)
26
+ puts "profile: writing to #{path}"
27
+ printer.print(path: path)
28
+ puts "profile: done."
@@ -78,8 +78,8 @@ describe(Delayer::Deferred) do
78
78
  }.trap{ |exception|
79
79
  failure = exception }
80
80
  end
81
- assert_equal uuid, result
82
81
  assert_equal false, failure
82
+ assert_equal uuid, result
83
83
  end
84
84
 
85
85
  it "join Deferredable#next after end of previous Deferredable" do
@@ -218,7 +218,7 @@ describe(Delayer::Deferred) do
218
218
  succeed = failure = false
219
219
  delayer = Delayer.generate_class
220
220
  eval_all_events(delayer) do
221
- delayer.Deferred.system("ruby", "-e", "exit 0").next{ |value|
221
+ delayer.Deferred.system("/bin/sh", "-c", "exit 0").next{ |value|
222
222
  succeed = value
223
223
  }.trap{ |exception|
224
224
  failure = exception } end
@@ -230,13 +230,60 @@ describe(Delayer::Deferred) do
230
230
  succeed = failure = false
231
231
  delayer = Delayer.generate_class
232
232
  eval_all_events(delayer) do
233
- delayer.Deferred.system("ruby", "-e", "exit 1").next{ |value|
233
+ delayer.Deferred.system("/bin/sh", "-c", "exit 1").next{ |value|
234
234
  succeed = value
235
235
  }.trap{ |exception|
236
236
  failure = exception } end
237
237
  refute succeed, "next block did not called"
238
- assert failure.exited?, "command exited"
239
- assert_equal 1, failure.exitstatus, "command exit status is 1"
238
+ assert_instance_of Delayer::Deferred::ForeignCommandAborted, failure
239
+ assert failure.process.exited?, "command exited"
240
+ assert_equal 1, failure.process.exitstatus, "command exit status is 1"
241
+ end
242
+ end
243
+
244
+ describe 'Deferredable#+@' do
245
+ it 'stops +@ deferred chain, then it returns result after receiver completed' do
246
+ delayer = Delayer.generate_class
247
+ log = Array.new
248
+ eval_all_events(delayer) do
249
+ delayer.Deferred.new.next{
250
+ log << :a1
251
+ b = delayer.Deferred.new.next{
252
+ log << :b1 << +delayer.Deferred.new.next{
253
+ log << :c1 << +delayer.Deferred.new.next{
254
+ log << :d1
255
+ :d2
256
+ }
257
+ :c2
258
+ }
259
+ :b2
260
+ }
261
+ log << :a2 << +b << :a3
262
+ }
263
+ end
264
+
265
+ assert_equal [:a1, :a2, :b1, :c1, :d1, :d2, :c2, :b2, :a3], log, 'incorrect call order'
266
+ end
267
+
268
+ it 'fails receiver of +@, then fails callee Deferred' do
269
+ delayer = Delayer.generate_class
270
+ log = Array.new
271
+ eval_all_events(delayer) do
272
+ delayer.Deferred.new.next{
273
+ log << :a1
274
+ b = delayer.Deferred.new.next{
275
+ log << :b1
276
+ delayer.Deferred.fail(:be)
277
+ :b2
278
+ }
279
+ log << :a2 << +b << :a3
280
+ }.trap do |err|
281
+ log << :ae << err
282
+ end
283
+ end
284
+
285
+ assert_equal [:a1, :a2, :b1, :ae, :be], log, 'incorrect call order'
286
+
240
287
  end
241
288
  end
242
289
  end
@@ -67,7 +67,7 @@ describe(Thread) do
67
67
  uuid = SecureRandom.uuid
68
68
  eval_all_events(delayer) do
69
69
  delayer.Deferred.Thread.new {
70
- Delayer::Deferred.fail(uuid)
70
+ raise uuid
71
71
  }.next {
72
72
  succeed = true
73
73
  }.trap { |value|
@@ -75,7 +75,8 @@ describe(Thread) do
75
75
  }.next {
76
76
  recover = true } end
77
77
  refute succeed, "Raised exception but it was executed successed route."
78
- assert_equal uuid, failure, "trap block takes incorrect value"
78
+ assert_instance_of RuntimeError, failure, "trap block takes incorrect value"
79
+ assert_equal uuid, failure.message, "trap block takes incorrect value"
79
80
  assert recover, "next block did not executed when after trap"
80
81
  end
81
82
 
@@ -113,4 +114,68 @@ describe(Thread) do
113
114
  assert_equal false, failure
114
115
  end
115
116
 
117
+ describe 'Race conditions' do
118
+ it "calls Thread#next for running thread" do
119
+ thread = succeed = result = false
120
+ uuid = SecureRandom.uuid
121
+ eval_all_events do
122
+ lock = true
123
+ th = Thread.new {
124
+ while lock; Thread.pass end
125
+ thread = true
126
+ uuid
127
+ }
128
+ th.next do |param|
129
+ succeed = true
130
+ result = param
131
+ end
132
+ lock = false
133
+ end
134
+ assert thread, "Thread did not executed."
135
+ assert succeed, "next block did not executed."
136
+ assert_equal uuid, result
137
+ end
138
+
139
+ it "calls Thread#next for stopped thread" do
140
+ thread = succeed = result = false
141
+ uuid = SecureRandom.uuid
142
+ eval_all_events do
143
+ th = Thread.new {
144
+ thread = true
145
+ uuid
146
+ }
147
+ while th.alive?; Thread.pass end
148
+ th.next do |param|
149
+ succeed = true
150
+ result = param
151
+ end
152
+ end
153
+ assert thread, "Thread did not executed."
154
+ assert succeed, "next block did not executed."
155
+ assert_equal uuid, result
156
+ end
157
+ end
158
+
159
+ it "wait ended Thread for +thread" do
160
+ result = failure = false
161
+ delayer = Delayer.generate_class
162
+ uuid1, uuid2, uuid3 = SecureRandom.uuid, SecureRandom.uuid, SecureRandom.uuid
163
+ eval_all_events(delayer) do
164
+ delayer.Deferred.new.next{
165
+ [
166
+ +delayer.Deferred.Thread.new{ uuid1 },
167
+ +delayer.Deferred.Thread.new{ uuid2 },
168
+ +delayer.Deferred.Thread.new{ uuid3 }
169
+ ]
170
+ }.next{ |value|
171
+ result = value
172
+ }.trap{ |exception|
173
+ failure = exception }
174
+ end
175
+ assert_equal false, failure
176
+ assert_instance_of Array, result
177
+ assert_equal uuid1, result[0]
178
+ assert_equal uuid2, result[1]
179
+ assert_equal uuid3, result[2]
180
+ end
116
181
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delayer-deferred
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Toshiaki Asai
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-22 00:00:00.000000000 Z
11
+ date: 2017-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: delayer
@@ -72,6 +72,20 @@ dependencies:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
74
  version: '5.7'
75
+ - !ruby/object:Gem::Dependency
76
+ name: ruby-prof
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
75
89
  description: Deferred for Delayer.
76
90
  email:
77
91
  - toshi.alternative@gmail.com
@@ -90,9 +104,13 @@ files:
90
104
  - lib/delayer/deferred/deferredable.rb
91
105
  - lib/delayer/deferred/enumerable.rb
92
106
  - lib/delayer/deferred/enumerator.rb
107
+ - lib/delayer/deferred/error.rb
108
+ - lib/delayer/deferred/result_container.rb
93
109
  - lib/delayer/deferred/thread.rb
94
110
  - lib/delayer/deferred/tools.rb
95
111
  - lib/delayer/deferred/version.rb
112
+ - test/deferred_benchmark.rb
113
+ - test/deferred_profiler.rb
96
114
  - test/deferred_test.rb
97
115
  - test/enumerable_test.rb
98
116
  - test/helper.rb
@@ -118,11 +136,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
136
  version: '0'
119
137
  requirements: []
120
138
  rubyforge_project:
121
- rubygems_version: 2.5.1
139
+ rubygems_version: 2.6.8
122
140
  signing_key:
123
141
  specification_version: 4
124
142
  summary: Deferred for Delayer
125
143
  test_files:
144
+ - test/deferred_benchmark.rb
145
+ - test/deferred_profiler.rb
126
146
  - test/deferred_test.rb
127
147
  - test/enumerable_test.rb
128
148
  - test/helper.rb