diff options
author | Toshiaki Asai <toshi.alternative@gmail.com> | 2015-08-26 19:50:38 +0900 |
---|---|---|
committer | Toshiaki Asai <toshi.alternative@gmail.com> | 2015-08-26 23:07:39 +0900 |
commit | 37555d89ee0e562fc04ba38e7e50a99affe500d5 (patch) | |
tree | 326a26005cb048d00aae88805fa32b3b674fab82 | |
parent | 5551f7360398e46525e9d54553e441a6a713fb6d (diff) | |
download | mikutter-37555d89ee0e562fc04ba38e7e50a99affe500d5.tar.gz |
delayer-deferred
-rw-r--r-- | Gemfile | 1 | ||||
-rw-r--r-- | core/boot/delayer.rb | 30 | ||||
-rw-r--r-- | core/lib/deferred.rb | 4 | ||||
-rw-r--r-- | core/lib/deferred/deferred.rb | 106 | ||||
-rw-r--r-- | core/lib/deferred/deferredable.rb | 138 | ||||
-rw-r--r-- | core/lib/mikutwitter/query.rb | 2 | ||||
-rw-r--r-- | core/serialthread.rb | 3 | ||||
-rw-r--r-- | test/core/lib/test_deferred.rb | 201 | ||||
-rw-r--r-- | test/core/lib/test_instance_storage.rb | 55 |
9 files changed, 32 insertions, 508 deletions
@@ -16,6 +16,7 @@ group :default do gem 'typed-array', '~> 0.1' gem 'delayer', '~> 0.0' gem 'pluggaloid', '>= 1.0.1', '< 2.0' + gem 'delayer-deferred', '>= 1.0', '< 2.0' end group :test do diff --git a/core/boot/delayer.rb b/core/boot/delayer.rb index 7365698f..6daa48ae 100644 --- a/core/boot/delayer.rb +++ b/core/boot/delayer.rb @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -miquire :lib, "delayer" +require "delayer" +require "delayer/deferred" Delayer.default = Delayer.generate_class(priority: [:ui_response, :routine_active, @@ -10,8 +11,33 @@ Delayer.default = Delayer.generate_class(priority: [:ui_response, default: :routine_passive, expire: 0.02) +Deferred = Delayer::Deferred + +module Delayer::Deferred::Deferredable + # エラーをキャッチして、うまい具合にmikutterに表示する。 + # このあとにdeferredをくっつけることもできるが、基本的にはdeferredチェインの終了の時に使う。 + # なお、terminateは受け取ったエラーを再度発生させるので、terminateでエラーを処理した後に特別なエラー処理を挟むこともできる + # ==== Args + # [message] 表示用エラーメッセージ。偽ならエラーはユーザに表示しない(コンソールのみ) + # [&message_generator] エラーを引数に呼ばれる。 _message_ を返す + # ==== Return + # Deferred + def terminate(message = nil, &message_generator) + self.trap{ |e| + begin + notice e + message = message_generator.call(e) if message_generator + if(message) + if(e.is_a?(Net::HTTPResponse)) + Plugin.activity :error, "#{message} (#{e.code} #{e.body})" + else + e = 'error' if not e.respond_to?(:to_s) + Plugin.activity :error, "#{message} (#{e})", exception: e end end + rescue Exception => inner_error + error inner_error end + Deferred.fail(e) } end +end Delayer.register_remain_hook do Thread.main.wakeup end - diff --git a/core/lib/deferred.rb b/core/lib/deferred.rb deleted file mode 100644 index 4b7a396a..00000000 --- a/core/lib/deferred.rb +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- - -require 'deferred/deferredable' -require 'deferred/deferred' diff --git a/core/lib/deferred/deferred.rb b/core/lib/deferred/deferred.rb deleted file mode 100644 index c15dfa60..00000000 --- a/core/lib/deferred/deferred.rb +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- - -class Deferred - include Deferredable - - def initialize(follow = nil) - @follow = follow - @backtrace = caller if Mopt.debug - end - - alias :deferredable_cancel :cancel - def cancel - deferredable_cancel - @follow.cancel if @follow.is_a? Deferredable end - - class << self - # 実行中のDeferredを失敗させる。raiseと違って、Exception以外のオブジェクトをtrap()に渡すことができる。 - # Deferredのnextとtrapの中でだけ呼び出すことができる。 - # ==== Args - # [value] trap()に渡す値 - # ==== Throw - # :__deferredable_fail をthrowする - def fail(value) - throw(:__deferredable_fail, value) end - - # 複数のdeferredを引数に取って、それら全ての実行が終了したら、 - # その結果を引数の順番通りに格納したArrayを引数に呼ばれるDeferredを返す。 - # 引数のDeferredが一つでも失敗するとこのメソッドの返すDeferredも失敗する。 - # ==== Args - # [defer] 終了を待つDeferredオブジェクト - # [*follow] 他のDeferredオブジェクト - # ==== Return - # Deferred - def when(defer = nil, *follow) - return deferred{ [] } if defer.nil? - defer.next{ |res| - Deferred.when(*follow).next{ |follow_res| - [res] + follow_res - } - } - end - - # Kernel#systemを呼び出して、コマンドが成功たら成功するDeferredを返す。 - # 失敗した場合、trap{}ブロックには $? の値(Process::Status)か、例外が発生した場合それが渡される - # ==== Args - # [*args] Kernel#system の引数 - # ==== Return - # Deferred - def system(*args) - promise = Deferred.new(true) - Thread.new{ - if Kernel.system(*args) - promise.call(true) - else - promise.fail($?) end - }.trap{ |e| - promise.fail(e) } - promise - end - end - -end - -class Thread - include Deferredable - - alias _deferredable_trap initialize - def initialize(*args, &proc) - _deferredable_trap(*args){ |*args| - begin - result = proc.call(*args) - self.call(result) - result - rescue Exception => e - self.fail(e) - raise e - end - } - end - - alias :deferredable_cancel :cancel - def cancel - deferredable_cancel - kill end - -end - -module Enumerable - # 遅延each。あとで実行されるし、あんまりループに時間がかかるようなら一旦ループを終了する - def deach(&proc) - iteratee = to_a - iteratee = dup if equal?(iteratee) - deferred{ - result = nil - while not iteratee.empty? - item = iteratee.shift - proc.call(item) - if Delayer.expire? - break result = iteratee.deach(&proc) end end - result } - end -end - -def deferred(&proc) - Deferred.new.next(&proc) end -# ~> -:4: uninitialized constant Deferred::Deferredable (NameError) diff --git a/core/lib/deferred/deferredable.rb b/core/lib/deferred/deferredable.rb deleted file mode 100644 index 74c0db1f..00000000 --- a/core/lib/deferred/deferredable.rb +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- - -# なんでもDeferred -module Deferredable - - # このDeferredが成功した場合の処理を追加する。 - # 新しいDeferredのインスタンスを返す - def next(&proc) - _post(:ok, &proc) - end - alias deferred next - - # このDeferredが失敗した場合の処理を追加する。 - # 新しいDeferredのインスタンスを返す - def trap(&proc) - _post(:ng, &proc) - end - - # Deferredを直ちに実行する - def call(value = nil) - _call(:ok, value) - end - - # Deferredを直ちに失敗させる - def fail(exception = nil) - _call(:ng, exception) - end - - # この一連のDeferredをこれ以上実行しない - def cancel - @callback = { - :backtrace => {}, - :ok => lambda{ |x| x }, - :ng => Deferred.method(:fail) } - end - - def callback - @callback ||= { - :backtrace => {}, - :ok => lambda{ |x| x }, - :ng => Deferred.method(:fail) } end - - # エラーをキャッチして、うまい具合にmikutterに表示する。 - # このあとにdeferredをくっつけることもできるが、基本的にはdeferredチェインの終了の時に使う。 - # なお、terminateは受け取ったエラーを再度発生させるので、terminateでエラーを処理した後に特別なエラー処理を挟むこともできる - # ==== Args - # [message] 表示用エラーメッセージ。偽ならエラーはユーザに表示しない(コンソールのみ) - # [&message_generator] エラーを引数に呼ばれる。 _message_ を返す - # ==== Return - # Deferred - def terminate(message = nil, &message_generator) - self.trap{ |e| - begin - notice e - message = message_generator.call(e) if message_generator - if(message) - if(e.is_a?(Net::HTTPResponse)) - Plugin.activity :error, "#{message} (#{e.code} #{e.body})" - else - e = 'error' if not e.respond_to?(:to_s) - Plugin.activity :error, "#{message} (#{e})", exception: e end end - rescue Exception => inner_error - error inner_error end - Deferred.fail(e) } end - - # second 秒待って次を実行する - # ==== Args - # [second] 待つ秒数(second) - # ==== Return - # Deferred - def wait(second) - self.next{ Thread.new{ sleep second } } end - - private - - def _call(stat = :ok, value = nil) - begin - catch(:__deferredable_success) { - failed = catch(:__deferredable_fail) { - n_value = _execute(stat, value) - if n_value.is_a? Deferredable - n_value.next{ |result| - if defined?(@next) - @next.call(result) - else - @next end - }.trap{ |exception| - if defined?(@next) - @next.fail(exception) - else - @next end } - else - if defined?(@next) - Delayer.new{ @next.call(n_value) } - else - regist_next_call(:ok, n_value) end end - throw :__deferredable_success - } - _fail_action(failed) - } - rescue Exception => e - _fail_action(e) end end - - def _execute(stat, value) - if Mopt.debug - r_start = Process.times.utime - result = callback[stat].call(value) - if (r_end = Process.times.utime - r_start) > 0.1 - pos = callback[:backtrace][stat].find{|x|not x.include? "deferred"} - pos = callback[:backtrace][stat].first if not pos - Plugin.call(:processtime, :deferred, "#{"%.2f" % r_end},#{pos}") end - result - else - callback[stat].call(value) end end - - def _post(kind, &proc) - @next = 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? - call end - @next - end - - def regist_next_call(stat, value) - @next_call_stat, @next_call_value = stat, value - self end - - def _fail_action(e) - if defined?(@next) - Delayer.new{ @next.fail(e) } - else - regist_next_call(:ng, e) end - end - -end diff --git a/core/lib/mikutwitter/query.rb b/core/lib/mikutwitter/query.rb index 64a53c05..4d1e7d77 100644 --- a/core/lib/mikutwitter/query.rb +++ b/core/lib/mikutwitter/query.rb @@ -5,7 +5,7 @@ require "mikutwitter/connect" require "mikutwitter/utils" require "mikutwitter/cache" require "mikutwitter/error" -require "deferred" +require "delayer/deferred" require "monitor" miquire :lib, "weakstorage" diff --git a/core/serialthread.rb b/core/serialthread.rb index 914643bc..a1af4065 100644 --- a/core/serialthread.rb +++ b/core/serialthread.rb @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- require File.expand_path(File.join(File.dirname(__FILE__), 'utils')) -miquire :lib, 'delayer', 'deferred' +require 'delayer' +require 'delayer/deferred' require 'set' require 'thread' require 'timeout' diff --git a/test/core/lib/test_deferred.rb b/test/core/lib/test_deferred.rb deleted file mode 100644 index e7fd7c1f..00000000 --- a/test/core/lib/test_deferred.rb +++ /dev/null @@ -1,201 +0,0 @@ -# -*- coding: utf-8 -*- - -require File.expand_path(File.dirname(__FILE__) + '/../../helper') - -Dir.chdir(File.expand_path(File.dirname(__FILE__) + '/../../core')) -$LOAD_PATH.push '.' -require 'utils' - -miquire :lib, 'test_unit_extensions' -miquire :lib, 'deferred' - -class TC_Deferred < Test::Unit::TestCase - def setup - end - - def wait - while !Delayer.empty? || !(Thread.list - [Thread.current]).empty? - Delayer.run - (Thread.list - [Thread.current]).each &:join - end - end - - must "execute serially" do - ans = 0 - Deferred.new.next{ - 10 - }.next{ |x| - ans = x + 2 - } - wait - assert_equal(12, ans) - end - - must "trap exception" do - ans = 0 - exception = Exception.new - Deferred.new.next{ - 10 - }.next{ - raise exception - }.next{ |x| - ans = x + 2 - }.trap{ |e| - ans = e - } - wait - assert_equal(exception, ans) - end - - must "fail manually" do - ans = 0 - Deferred.new.next{ - 10 - }.next{ - Deferred.fail "mikutan peropero" - }.next{ |x| - ans = x + 2 - }.trap{ |e| - ans = e - } - wait - assert_equal("mikutan peropero", ans) - end - - must "thread be deferredable" do - ans = 0 - Thread.new{ - 39 - }.next{ |x| - x + 1 - }.next{ |x| - ans = x - } - wait - assert_equal(ans, 40) - end - - must "handle exception" do - ans = 0 - exception = Exception.new - th = nil - Thread.new{ - raise exception - }.tap{ |t| - th = t - }.next{ |x| - ans = x - }.trap{ |x| - ans = x - } - assert_raise(exception){ th.join } - wait - assert_equal(exception, ans) - end - - must "execute in deterministic order" do - trace = [] - deferred{ - trace << 1 - }.next{ |x| - trace << 2 - deferred{ - trace << 3 - }.next{ - Thread.new{ - trace << 4 - }.next{ - trace << 5 - raise - }.trap{ - trace << 6 - } - }.trap{ - trace << nil - } - } - trace << 0 - wait - assert_equal([0, 1, 2, 3, 4, 5, 6], trace) - end - - must "extend chain later" do - a = [] - d = deferred{ a << 1 } - wait - d.next{ a << 2 }.next{ a << 3 } - wait - assert_equal([1, 2, 3], a) - end - - must "trap error later" do - ans = nil - exception = Exception.new - d = deferred{ - raise exception - } - wait - d.trap{ |e| - ans = e - } - assert_equal(exception, ans) - end - - must "trap error later (thread)" do - ans = nil - exception = Exception.new - th = Thread.new{ - raise exception - } - assert_raise(exception){ th.join } - th.trap{ |e| - ans = e - } - assert_equal(exception, ans) - end - - must "control evaluation order" do - ans = nil - Deferred.when(deferred{ 1 }, deferred{ 2 }, deferred{ 3 }).next{ |res| - ans = res - } - wait - assert_equal([1, 2, 3], ans) - end - - must "cancel execution" do - ans = 0 - Thread.new{ - sleep(10) - 39 - }.next{ |x| - ans = x - }.cancel - wait - assert_equal(0, ans) - end - - must "deach work" do - ans = 0 - (1..100000).deach{ - ans += 1 - } - wait - assert_equal(100000, ans) - end - - must "give true when #system success" do - ans = 0 - Deferred.system("ruby", "-e", "exit").next{ |v| ans = v } - wait - assert_equal(true, ans) - end - - must "give error with exit code when #system fail" do - ans = 0 - Deferred.system("ruby", "-e", "abort").trap{ |v| ans = v } - wait - assert_kind_of(Process::Status, ans) - assert_equal(256, ans.to_i) - end -end diff --git a/test/core/lib/test_instance_storage.rb b/test/core/lib/test_instance_storage.rb deleted file mode 100644 index 3c340aff..00000000 --- a/test/core/lib/test_instance_storage.rb +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- - -require File.expand_path(File.dirname(__FILE__) + '/../../helper') - -Dir.chdir(File.expand_path(File.dirname(__FILE__) + '/../../core')) -$LOAD_PATH.push '.' -require 'utils' - -miquire :lib, 'test_unit_extensions' -miquire :lib, 'instance_storage' - -class TC_InstanceStorage < Test::Unit::TestCase - def setup - end - - must "get and create instance" do - klass = Class.new do - include InstanceStorage end - assert_same(klass[:foo], klass[:foo]) - assert_not_same(klass[:foo], klass[:bar]) - end - - must "get all instances" do - klass = Class.new do - include InstanceStorage end - assert_equal([], klass.instances) - assert_equal([klass[:a], klass[:b]], klass.instances) - end - - must "get all instances name" do - klass = Class.new do - include InstanceStorage end - assert_equal([], klass.instances_name) - klass[:a] - klass[:b] - assert_equal([:a, :b], klass.instances_name) - end - - must "destroy instance" do - klass = Class.new do - include InstanceStorage end - klass[:a] - assert(klass.instance_exist? :a) - klass.destroy(:a) - assert(! klass.instance_exist?(:a)) - end - - must "get existing instance" do - klass = Class.new do - include InstanceStorage end - assert_nil(klass.instance(:a)) - assert_equal(klass[:a], klass.instance(:a)) - end - -end |