[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