[DRE-commits] [ruby-delayer-deferred] 01/06: New upstream version 1.1.0
Daisuke Higuchi
dai at moszumanska.debian.org
Mon Mar 20 01:45:38 UTC 2017
This is an automated email from the git hooks/post-receive script.
dai pushed a commit to branch exp/debian
in repository ruby-delayer-deferred.
commit 7fe3ca65a7f114264d4355acffde28cbfd467b64
Author: HIGUCHI Daisuke (VDR dai) <dai at debian.org>
Date: Sat Mar 11 12:23:51 2017 +0900
New upstream version 1.1.0
---
Rakefile | 8 +++
delayer-deferred.gemspec | 1 +
lib/delayer/deferred/deferred.rb | 16 +++++
lib/delayer/deferred/deferredable.rb | 115 ++++++++++++++++++++++++-------
lib/delayer/deferred/error.rb | 13 ++++
lib/delayer/deferred/result_container.rb | 11 +++
lib/delayer/deferred/thread.rb | 48 +++++++------
lib/delayer/deferred/tools.rb | 13 ++--
lib/delayer/deferred/version.rb | 2 +-
test/deferred_benchmark.rb | 63 +++++++++++++++++
test/deferred_profiler.rb | 28 ++++++++
test/deferred_test.rb | 57 +++++++++++++--
test/thread_test.rb | 69 ++++++++++++++++++-
13 files changed, 385 insertions(+), 59 deletions(-)
diff --git a/Rakefile b/Rakefile
index 5b0e18d..4e12f4a 100644
--- a/Rakefile
+++ b/Rakefile
@@ -6,3 +6,11 @@ Rake::TestTask.new do |t|
t.warning = true
t.verbose = true
end
+
+task :benchmark do
+ FileList['test/*_benchmark.rb'].each{|f| load f }
+end
+
+task :profile do
+ FileList['test/*_profiler.rb'].each{|f| load f }
+end
diff --git a/delayer-deferred.gemspec b/delayer-deferred.gemspec
index 688818f..94f4cef 100644
--- a/delayer-deferred.gemspec
+++ b/delayer-deferred.gemspec
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "bundler", "~> 1.7"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "minitest", "~> 5.7"
+ spec.add_development_dependency "ruby-prof"
end
diff --git a/lib/delayer/deferred/deferred.rb b/lib/delayer/deferred/deferred.rb
index 0e60784..801d68c 100644
--- a/lib/delayer/deferred/deferred.rb
+++ b/lib/delayer/deferred/deferred.rb
@@ -49,5 +49,21 @@ module Delayer::Deferred
sprintf("#<%s: %p stat:%s value:%s>".freeze, self.class, object_id, @next_call_stat.inspect, @next_call_value.inspect)
end
end
+
+ def self.fiber(&block)
+ @_fiber ||= Fiber.new do |b|
+ loop do
+ b = Fiber.yield(b.())
+ end
+ end
+ result = @_fiber.resume(block)
+ if result.is_a?(Delayer::Deferred::ResultContainer)
+ result
+ else
+ _fiber = @_fiber
+ @_fiber = nil
+ return result, _fiber
+ end
+ end
end
end
diff --git a/lib/delayer/deferred/deferredable.rb b/lib/delayer/deferred/deferredable.rb
index 0e35824..c0d16cd 100644
--- a/lib/delayer/deferred/deferredable.rb
+++ b/lib/delayer/deferred/deferredable.rb
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
require "delayer/deferred/version"
+require "delayer/deferred/result_container"
# なんでもDeferred
module Delayer::Deferred::Deferredable
Callback = Struct.new(*%i<ok ng backtrace>)
- BackTrace = Struct.new(*%i<ok ng>)
CallbackDefaultOK = lambda{ |x| x }
CallbackDefaultNG = lambda{ |err| Delayer::Deferred.fail(err) }
@@ -28,16 +28,27 @@ module Delayer::Deferred::Deferredable
def fail(exception = nil)
_call(:ng, exception) end
+ # _self_ が終了して結果が出るまで呼び出し側のDeferredを停止し、 _self_ の結果を返す。
+ # 呼び出し側はDeferredブロック内でなければならないが、 _Deferred#next_ を使わずに
+ # 直接戻り値を得ることが出来る。
+ # _self_ が失敗した場合は、呼び出し側のDeferredの直近の _trap_ ブロックが呼ばれる。
+ def +@
+ interrupt = Fiber.yield(self)
+ if interrupt.ok?
+ interrupt.value
+ else
+ Delayer::Deferred.fail(interrupt.value)
+ end
+ end
+
# この一連のDeferredをこれ以上実行しない
def cancel
@callback = Callback.new(CallbackDefaultOK,
- CallbackDefaultNG,
- BackTrace.new(nil, nil).freeze).freeze end
+ CallbackDefaultNG).freeze end
def callback
@callback ||= Callback.new(CallbackDefaultOK,
- CallbackDefaultNG,
- BackTrace.new(nil, nil)) end
+ CallbackDefaultNG) end
# second 秒待って次を実行する
# ==== Args
@@ -54,32 +65,74 @@ module Delayer::Deferred::Deferredable
end
def _call(stat = :ok, value = nil)
- begin
- catch(:__deferredable_success) do
- failed = catch(:__deferredable_fail) do
- n_value = _execute(stat, value)
- if n_value.is_a? Delayer::Deferred::Deferredable
- n_value.next{ |result|
- @next.call(result)
- }.trap{ |exception|
- @next.fail(exception) }
+ delayer.new do
+ result, fiber = delayer.Deferred.fiber do
+ begin
+ result = catch(:__deferredable_fail) do
+ Delayer::Deferred::ResultContainer.new(true, _execute(stat, value))
+ end
+ if result.is_a?(Delayer::Deferred::ResultContainer)
+ result
else
- if defined?(@next)
- delayer.new{ @next.call(n_value) }
- else
- register_next_call(:ok, n_value) end end
- throw :__deferredable_success end
- _fail_action(failed) end
- rescue Exception => exception
- _fail_action(exception) end end
+ Delayer::Deferred::ResultContainer.new(false, result)
+ end
+ rescue Exception => exception
+ Delayer::Deferred::ResultContainer.new(false, exception)
+ end
+ end
+ #_wait_fiber(fiber, nil)
+ if fiber
+ _fiber_stopped(result){|i| _wait_fiber(fiber, i) }
+ else
+ _fiber_completed(result)
+ end
+ end
+ end
+
def _execute(stat, value)
- callback[stat].call(value) end
+ callback[stat].call(value)
+ end
+
+ def _wait_fiber(fiber, resume_value)
+ result = fiber.resume(resume_value)
+ if result.is_a?(Delayer::Deferred::ResultContainer)
+ _fiber_completed(result)
+ else
+ _fiber_stopped(result){|i| _wait_fiber(fiber, i) }
+ end
+ end
+
+ # Deferredブロックが最後まで終わり、これ以上やることがない時に呼ばれる
+ def _fiber_completed(result)
+ result_value = result.value
+ if result.ok?
+ if result_value.is_a?(Delayer::Deferred::Deferredable)
+ result_value.next{|v|
+ _success_action(v)
+ }.trap{|v|
+ _fail_action(v)
+ }
+ else
+ _success_action(result_value)
+ end
+ else
+ _fail_action(result_value)
+ end
+ end
+
+ # Deferredable#@+によって停止され、 _defer_ の完了次第処理を再開する必要がある時に呼ばれる
+ def _fiber_stopped(defer, &cont)
+ defer.next{|v|
+ cont.(Delayer::Deferred::ResultContainer.new(true, v))
+ }.trap{|v|
+ cont.(Delayer::Deferred::ResultContainer.new(false, v))
+ }
+ end
def _post(kind, &proc)
@next = delayer.Deferred.new(self)
@next.callback[kind] = proc
- @next.callback.backtrace[kind] = caller(1)
if defined?(@next_call_stat) and defined?(@next_call_value)
@next.__send__({ok: :call, ng: :fail}[@next_call_stat], @next_call_value)
elsif defined?(@follow) and @follow.nil?
@@ -90,10 +143,20 @@ module Delayer::Deferred::Deferredable
@next_call_stat, @next_call_value = stat, value
self end
+ def _success_action(obj)
+ if defined?(@next)
+ @next.call(obj)
+ else
+ register_next_call(:ok, obj)
+ end
+ end
+
def _fail_action(err_obj)
if defined?(@next)
- delayer.new{ @next.fail(err_obj) }
+ @next.fail(err_obj)
else
- register_next_call(:ng, err_obj) end end
+ register_next_call(:ng, err_obj)
+ end
+ end
end
diff --git a/lib/delayer/deferred/error.rb b/lib/delayer/deferred/error.rb
new file mode 100644
index 0000000..ab597df
--- /dev/null
+++ b/lib/delayer/deferred/error.rb
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+
+module Delayer::Deferred
+ Error = Class.new(StandardError)
+
+ class ForeignCommandAborted < Error
+ attr_reader :process
+ def initialize(message, process:)
+ super(message)
+ @process = process
+ end
+ end
+end
diff --git a/lib/delayer/deferred/result_container.rb b/lib/delayer/deferred/result_container.rb
new file mode 100644
index 0000000..ef9f138
--- /dev/null
+++ b/lib/delayer/deferred/result_container.rb
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+
+Delayer::Deferred::ResultContainer = Struct.new(:success_flag, :value) do
+ def ok?
+ success_flag
+ end
+
+ def ng?
+ !success_flag
+ end
+end
diff --git a/lib/delayer/deferred/thread.rb b/lib/delayer/deferred/thread.rb
index 8f31165..38c7cee 100644
--- a/lib/delayer/deferred/thread.rb
+++ b/lib/delayer/deferred/thread.rb
@@ -9,28 +9,34 @@ class Thread
Delayer
end
- alias _deferredable_trap initialize
- def initialize(*args, &proc)
- _deferredable_trap(*args, &_deferredable_trap_proc(&proc)) end
+ def next(*rest, &block)
+ __gen_promise.next(*rest, &block)
+ end
- alias :deferredable_cancel :cancel
- def cancel
- deferredable_cancel
- kill end
+ def trap(*rest, &block)
+ __gen_promise.trap(*rest, &block)
+ end
private
- def _deferredable_trap_proc
- proc = Proc.new
- ->(*args) do
- catch(:__deferredable_success) do
- failed = catch(:__deferredable_fail) do
- begin
- result = proc.call(*args)
- self.call(result)
- result
- rescue Exception => exception
- self.fail(exception)
- raise exception end
- throw :__deferredable_success end
- self.fail(failed) end end end
+
+ def __gen_promise
+ promise = delayer.Deferred.new(true)
+ Thread.new(self) do |tt|
+ __promise_callback(tt, promise)
+ end
+ promise
+ end
+
+ def __promise_callback(tt, promise)
+ failed = catch(:__deferredable_fail) do
+ begin
+ promise.call(tt.value)
+ rescue Exception => err
+ promise.fail(err)
+ end
+ return
+ end
+ promise.fail(failed)
+ end
+
end
diff --git a/lib/delayer/deferred/tools.rb b/lib/delayer/deferred/tools.rb
index bca1011..ffde7eb 100644
--- a/lib/delayer/deferred/tools.rb
+++ b/lib/delayer/deferred/tools.rb
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+require 'delayer/deferred/error'
module Delayer::Deferred
module Tools
@@ -43,10 +44,14 @@ module Delayer::Deferred
# ==== Return
# Deferred
def system(*args)
- delayer.Deferred.Thread.new do
- if Kernel.system(*args)
- $?
+ delayer.Deferred.Thread.new {
+ Process.waitpid2(Kernel.spawn(*args))
+ }.next{|_pid, status|
+ if status && status.success?
+ status
else
- delayer.Deferred.fail($?) end end end
+ raise ForeignCommandAborted.new("command aborted: #{args.join(' ')}", process: $?) end
+ }
+ end
end
end
diff --git a/lib/delayer/deferred/version.rb b/lib/delayer/deferred/version.rb
index be93daf..4d27467 100644
--- a/lib/delayer/deferred/version.rb
+++ b/lib/delayer/deferred/version.rb
@@ -1,5 +1,5 @@
module Delayer
module Deferred
- VERSION = "1.0.4"
+ VERSION = "1.1.0"
end
end
diff --git a/test/deferred_benchmark.rb b/test/deferred_benchmark.rb
new file mode 100644
index 0000000..2e83a9b
--- /dev/null
+++ b/test/deferred_benchmark.rb
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+require 'benchmark'
+require 'bundler/setup'
+require 'delayer/deferred'
+require_relative 'testutils.rb'
+
+Benchmark.bmbm do |r|
+ extend TestUtils
+ n = 10000
+
+ r.report "construct" do
+ delayer = Delayer.generate_class
+ n.times do
+ delayer.Deferred.new
+ end
+ end
+
+ r.report "register next block" do
+ delayer = Delayer.generate_class
+ n.times do
+ delayer.Deferred.new.next{|x|
+ x
+ }
+ end
+ end
+
+ r.report "execute next block" do
+ delayer = Delayer.generate_class
+ eval_all_events(delayer) do
+ n.times do
+ delayer.Deferred.new.next{|x|
+ x
+ }
+ end
+ end
+ end
+
+ r.report "double next block" do
+ delayer = Delayer.generate_class
+ eval_all_events(delayer) do
+ n.times do
+ delayer.Deferred.new.next{|x|
+ x
+ }.next{|x|
+ x
+ }
+ end
+ end
+ end
+
+ r.report "trap block" do
+ delayer = Delayer.generate_class
+ eval_all_events(delayer) do
+ n.times do
+ delayer.Deferred.new.next{|x|
+ x
+ }.trap{|x|
+ x
+ }
+ end
+ end
+ end
+end
diff --git a/test/deferred_profiler.rb b/test/deferred_profiler.rb
new file mode 100644
index 0000000..1a8d2e9
--- /dev/null
+++ b/test/deferred_profiler.rb
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+require 'bundler/setup'
+require 'delayer/deferred'
+require 'ruby-prof'
+require_relative 'testutils.rb'
+
+extend TestUtils
+n = 1000
+
+RubyProf.start
+delayer = Delayer.generate_class
+eval_all_events(delayer) do
+ n.times do
+ delayer.Deferred.new.next{|x|
+ x
+ }.trap{|x|
+ x
+ }
+ end
+end
+
+result = RubyProf.stop
+printer = RubyProf::CallTreePrinter.new(result)
+path = File.expand_path(File.join(__dir__, '..', 'profile', Time.new.strftime('%Y-%m-%d-%H%M%S')))
+FileUtils.mkdir_p(path)
+puts "profile: writing to #{path}"
+printer.print(path: path)
+puts "profile: done."
diff --git a/test/deferred_test.rb b/test/deferred_test.rb
index 7853b45..c1de727 100644
--- a/test/deferred_test.rb
+++ b/test/deferred_test.rb
@@ -78,8 +78,8 @@ describe(Delayer::Deferred) do
}.trap{ |exception|
failure = exception }
end
- assert_equal uuid, result
assert_equal false, failure
+ assert_equal uuid, result
end
it "join Deferredable#next after end of previous Deferredable" do
@@ -218,7 +218,7 @@ describe(Delayer::Deferred) do
succeed = failure = false
delayer = Delayer.generate_class
eval_all_events(delayer) do
- delayer.Deferred.system("ruby", "-e", "exit 0").next{ |value|
+ delayer.Deferred.system("/bin/sh", "-c", "exit 0").next{ |value|
succeed = value
}.trap{ |exception|
failure = exception } end
@@ -230,13 +230,60 @@ describe(Delayer::Deferred) do
succeed = failure = false
delayer = Delayer.generate_class
eval_all_events(delayer) do
- delayer.Deferred.system("ruby", "-e", "exit 1").next{ |value|
+ delayer.Deferred.system("/bin/sh", "-c", "exit 1").next{ |value|
succeed = value
}.trap{ |exception|
failure = exception } end
refute succeed, "next block did not called"
- assert failure.exited?, "command exited"
- assert_equal 1, failure.exitstatus, "command exit status is 1"
+ assert_instance_of Delayer::Deferred::ForeignCommandAborted, failure
+ assert failure.process.exited?, "command exited"
+ assert_equal 1, failure.process.exitstatus, "command exit status is 1"
+ end
+ end
+
+ describe 'Deferredable#+@' do
+ it 'stops +@ deferred chain, then it returns result after receiver completed' do
+ delayer = Delayer.generate_class
+ log = Array.new
+ eval_all_events(delayer) do
+ delayer.Deferred.new.next{
+ log << :a1
+ b = delayer.Deferred.new.next{
+ log << :b1 << +delayer.Deferred.new.next{
+ log << :c1 << +delayer.Deferred.new.next{
+ log << :d1
+ :d2
+ }
+ :c2
+ }
+ :b2
+ }
+ log << :a2 << +b << :a3
+ }
+ end
+
+ assert_equal [:a1, :a2, :b1, :c1, :d1, :d2, :c2, :b2, :a3], log, 'incorrect call order'
+ end
+
+ it 'fails receiver of +@, then fails callee Deferred' do
+ delayer = Delayer.generate_class
+ log = Array.new
+ eval_all_events(delayer) do
+ delayer.Deferred.new.next{
+ log << :a1
+ b = delayer.Deferred.new.next{
+ log << :b1
+ delayer.Deferred.fail(:be)
+ :b2
+ }
+ log << :a2 << +b << :a3
+ }.trap do |err|
+ log << :ae << err
+ end
+ end
+
+ assert_equal [:a1, :a2, :b1, :ae, :be], log, 'incorrect call order'
+
end
end
end
diff --git a/test/thread_test.rb b/test/thread_test.rb
index 84153ca..5b9fb00 100644
--- a/test/thread_test.rb
+++ b/test/thread_test.rb
@@ -67,7 +67,7 @@ describe(Thread) do
uuid = SecureRandom.uuid
eval_all_events(delayer) do
delayer.Deferred.Thread.new {
- Delayer::Deferred.fail(uuid)
+ raise uuid
}.next {
succeed = true
}.trap { |value|
@@ -75,7 +75,8 @@ describe(Thread) do
}.next {
recover = true } end
refute succeed, "Raised exception but it was executed successed route."
- assert_equal uuid, failure, "trap block takes incorrect value"
+ assert_instance_of RuntimeError, failure, "trap block takes incorrect value"
+ assert_equal uuid, failure.message, "trap block takes incorrect value"
assert recover, "next block did not executed when after trap"
end
@@ -113,4 +114,68 @@ describe(Thread) do
assert_equal false, failure
end
+ describe 'Race conditions' do
+ it "calls Thread#next for running thread" do
+ thread = succeed = result = false
+ uuid = SecureRandom.uuid
+ eval_all_events do
+ lock = true
+ th = Thread.new {
+ while lock; Thread.pass end
+ thread = true
+ uuid
+ }
+ th.next do |param|
+ succeed = true
+ result = param
+ end
+ lock = false
+ end
+ assert thread, "Thread did not executed."
+ assert succeed, "next block did not executed."
+ assert_equal uuid, result
+ end
+
+ it "calls Thread#next for stopped thread" do
+ thread = succeed = result = false
+ uuid = SecureRandom.uuid
+ eval_all_events do
+ th = Thread.new {
+ thread = true
+ uuid
+ }
+ while th.alive?; Thread.pass end
+ th.next do |param|
+ succeed = true
+ result = param
+ end
+ end
+ assert thread, "Thread did not executed."
+ assert succeed, "next block did not executed."
+ assert_equal uuid, result
+ end
+ end
+
+ it "wait ended Thread for +thread" do
+ result = failure = false
+ delayer = Delayer.generate_class
+ uuid1, uuid2, uuid3 = SecureRandom.uuid, SecureRandom.uuid, SecureRandom.uuid
+ eval_all_events(delayer) do
+ delayer.Deferred.new.next{
+ [
+ +delayer.Deferred.Thread.new{ uuid1 },
+ +delayer.Deferred.Thread.new{ uuid2 },
+ +delayer.Deferred.Thread.new{ uuid3 }
+ ]
+ }.next{ |value|
+ result = value
+ }.trap{ |exception|
+ failure = exception }
+ end
+ assert_equal false, failure
+ assert_instance_of Array, result
+ assert_equal uuid1, result[0]
+ assert_equal uuid2, result[1]
+ assert_equal uuid3, result[2]
+ end
end
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-delayer-deferred.git
More information about the Pkg-ruby-extras-commits
mailing list