delayer-1.2.1/0000755000004100000410000000000014052603426013172 5ustar www-datawww-datadelayer-1.2.1/test/0000755000004100000410000000000014052603426014151 5ustar www-datawww-datadelayer-1.2.1/test/test_delayer.rb0000644000004100000410000002335414052603426017171 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'rubygems' require 'bundler/setup' require 'test/unit' require 'delayer' class TestDelayer < Test::Unit::TestCase def setup Delayer.default = nil end def test_delayed delayer = Delayer.generate_class a = 0 delayer.new { a = 1 } assert_equal(0, a) delayer.run assert_equal(1, a) end def test_default a = 0 Delayer.new { a = 1 } assert_equal(0, a) Delayer.run assert_equal(1, a) end def test_timelimited delayer = Delayer.generate_class(expire: 0.01) a = 0 delayer.new { sleep 0.1 } delayer.new { a = 1 } assert_equal(0, a) delayer.run assert_equal(0, a) delayer.run assert_equal(1, a) end def test_busy delayer = Delayer.generate_class a = false delayer.new { a = delayer.busy? } assert_equal(false, a) assert_equal(false, delayer.busy?) delayer.run assert_equal(false, delayer.busy?) assert_equal(true, a) end def test_empty delayer = Delayer.generate_class a = false delayer.new { a = delayer.empty? } assert_equal(false, a) assert_equal(false, delayer.empty?) delayer.run assert_equal(true, delayer.empty?) assert_equal(true, a) end def test_size delayer = Delayer.generate_class a = 0 assert_equal(0, delayer.size) delayer.new { a += 1 } assert_equal(1, delayer.size) delayer.new { a += 1 } delayer.new { a += 1 } assert_equal(3, delayer.size) delayer.run assert_equal(0, delayer.size) end def test_cancel_begin delayer = Delayer.generate_class a = 0 d = delayer.new { a += 1 } delayer.new { a += 2 } delayer.new { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(6, a) end def test_cancel_center delayer = Delayer.generate_class a = 0 delayer.new { a += 1 } d = delayer.new { a += 2 } delayer.new { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(5, a) end def test_cancel_end delayer = Delayer.generate_class a = 0 delayer.new { a += 1 } delayer.new { a += 2 } d = delayer.new { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(3, a) end def test_priority_asc delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) buffer = [] delayer.new(:high) { buffer << 1 } delayer.new(:middle) { buffer << 2 } delayer.new(:low) { buffer << 3 } delayer.run assert_equal([1,2,3], buffer) end def test_priority_desc delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) buffer = [] delayer.new(:low) { buffer << 3 } delayer.new(:middle) { buffer << 2 } delayer.new(:high) { buffer << 1 } delayer.run assert_equal([1,2,3], buffer) end def test_priority_complex delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) buffer = [] delayer.new(:high) { buffer << 1 } delayer.new(:middle) { buffer << 2 } delayer.new(:low) { buffer << 3 } delayer.new(:middle) { buffer << 4 } delayer.new(:high) { buffer << 5 } delayer.new(:middle) { buffer << 6 } delayer.new(:low) { buffer << 7 } delayer.new(:middle) { buffer << 8 } delayer.new(:high) { buffer << 9 } delayer.run assert_equal([1,5,9,2,4,6,8,3,7], buffer) buffer = [] delayer.new(:high) { buffer << 1 } delayer.new(:low) { buffer << 2 } delayer.new(:high) { buffer << 3 } delayer.new(:low) { buffer << 4 } delayer.run assert_equal([1,3,2,4], buffer) end def test_priority_cancel_begin delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) a = 0 d = delayer.new { a += 1 } delayer.new { a += 2 } delayer.new { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(6, a) a = 0 d = delayer.new(:low) { a += 1 } delayer.new(:high) { a += 2 } delayer.new(:high) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(6, a) a = 0 d = delayer.new(:high) { a += 1 } delayer.new(:low) { a += 2 } delayer.new(:low) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(6, a) end def test_priority_cancel_center delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) a = 0 delayer.new { a += 1 } d = delayer.new { a += 2 } delayer.new { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(5, a) a = 0 delayer.new(:low) { a += 1 } d = delayer.new(:high) { a += 2 } delayer.new(:low) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(5, a) a = 0 delayer.new(:high) { a += 1 } d = delayer.new(:low) { a += 2 } delayer.new(:high) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(5, a) end def test_priority_cancel_end delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) a = 0 delayer.new { a += 1 } delayer.new { a += 2 } d = delayer.new { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(3, a) a = 0 delayer.new(:low) { a += 1 } delayer.new(:low) { a += 2 } d = delayer.new(:high) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(3, a) a = 0 delayer.new(:high) { a += 1 } delayer.new(:high) { a += 2 } d = delayer.new(:low) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(3, a) end def test_multithread_register delayer = Delayer.generate_class buffer = [] threads = [] 10.times do threads << Thread.new do 1000.times do |number| delayer.new { buffer << number } end end end delayer.run threads.each(&:join) delayer.run assert_equal(10000, buffer.size) assert_equal((0..999).inject(&:+)*10, buffer.inject(&:+)) end def test_nested delayer = Delayer.generate_class buffer = [] delayer.new { buffer << 1 } delayer.new do delayer.new { buffer << 3 } delayer.new do delayer.new { buffer << 5 } delayer.new { buffer << 6 } end delayer.new { buffer << 4 } end delayer.new { buffer << 2 } delayer.run assert_equal([1,2,3,4,5,6], buffer) end def test_remain_hook delayer = Delayer.generate_class expire: 0.01 a = [] delayer.register_remain_hook { a << :remain } delayer.new { a << 0 } delayer.new { a << 1; sleep 0.1 } delayer.new { a << 2 } delayer.run delayer.new { a << 3 } delayer.new { a << 4 } delayer.run assert_equal([:remain, 0, 1, :remain, 2, 3, 4], a) end def test_recursive_mainloop delayer = Delayer.generate_class a = 0 delayer.new { a = 1 } assert_equal(0, delayer.stash_level) delayer.stash_enter! assert_equal(1, delayer.stash_level) delayer.new { a = 2 } assert_equal(0, a) delayer.run assert_equal(2, a) delayer.stash_exit! assert_equal(0, delayer.stash_level) delayer.run assert_equal(1, a) end def test_pop_recursive_mainloop_remain_jobs delayer = Delayer.generate_class delayer.stash_enter! delayer.new{ ; } assert_raise Delayer::RemainJobsError do delayer.stash_exit! end end def test_pop_recursive_mainloop_in_level_zero delayer = Delayer.generate_class assert_raise Delayer::NoLowerLevelError do delayer.stash_exit! end end def test_timer delayer = Delayer.generate_class expire: 0.01 a = [] delayer.new(delay: 0.01) { a << 0 } delayer.new { a << 1 } delayer.run delayer.new { a << 2 } sleep 0.1 delayer.run delayer.new { a << 3 } delayer.run assert_equal([1, 2, 0, 3], a) end def test_timer_give_time delayer = Delayer.generate_class expire: 0.01 a = [] delayer.new(delay: Time.new) { a << 0 } delayer.run assert_equal([0], a) end def test_plural_timer delayer = Delayer.generate_class expire: 0.01 a = [] delayer.new(delay: 0.01) { a << 0 } delayer.new(delay: 0.11) { a << 1 } delayer.new { a << 2 } delayer.run delayer.new { a << 3 } sleep 0.1 delayer.run sleep 0.1 delayer.new { a << 4 } delayer.run assert_equal([2, 3, 0, 4, 1], a) end def test_many_timer delayer = Delayer.generate_class expire: 0.01 a = [] (0..10).to_a.shuffle.each do |i| delayer.new(delay: i / 100.0) { a << i } end sleep 0.1 delayer.run assert_equal((0..10).to_a, a) end def test_cancel_timer delayer = Delayer.generate_class a = 0 delayer.new(delay: 0.01) { a += 1 } d = delayer.new(delay: 0.01) { a += 2 } delayer.new(delay: 0.01) { a += 4 } assert_equal(0, a) d.cancel sleep 0.1 delayer.run assert_equal(5, a) end def test_cancel_timer_after_expire delayer = Delayer.generate_class a = 0 delayer.new(delay: 0.01) { a += 1 } d = delayer.new(delay: 0.01) { a += 2 } delayer.new{ d.cancel } delayer.new(delay: 0.01) { a += 4 } assert_equal(0, a) sleep 0.1 delayer.run assert_equal(5, a) end def test_reserve_new_timer_after_cancel delayer = Delayer.generate_class a = 0 delayer.new(delay: 0.01) { a += 1 } d = delayer.new(delay: 0.02) { a += 2 } d.cancel delayer.new(delay: 0.03) { a += 4 } assert_equal(0, a) sleep 0.1 delayer.run assert_equal(5, a) end end delayer-1.2.1/test/test_priority.rb0000644000004100000410000001473614052603426017431 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'rubygems' require 'bundler/setup' require 'test/unit' require 'delayer' class TestPriorityDelayer < Test::Unit::TestCase def test_asc delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) buffer = [] delayer.new(:high) { buffer << 1 } delayer.new(:middle) { buffer << 2 } delayer.new(:low) { buffer << 3 } delayer.run assert_equal([1,2,3], buffer) end def test_desc delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) buffer = [] delayer.new(:low) { buffer << 3 } delayer.new(:middle) { buffer << 2 } delayer.new(:high) { buffer << 1 } delayer.run assert_equal([1,2,3], buffer) end def test_complex delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) buffer = [] delayer.new(:high) { buffer << 1 } delayer.new(:middle) { buffer << 2 } delayer.new(:low) { buffer << 3 } delayer.new(:middle) { buffer << 4 } delayer.new(:high) { buffer << 5 } delayer.new(:middle) { buffer << 6 } delayer.new(:low) { buffer << 7 } delayer.new(:middle) { buffer << 8 } delayer.new(:high) { buffer << 9 } delayer.run assert_equal([1,5,9,2,4,6,8,3,7], buffer) buffer = [] delayer.new(:high) { buffer << 1 } delayer.new(:low) { buffer << 2 } delayer.new(:high) { buffer << 3 } delayer.new(:low) { buffer << 4 } delayer.run assert_equal([1,3,2,4], buffer) end def test_timelimited delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle, expire: 0.01) a = 0 delayer.new { sleep 0.1 } delayer.new { a = 1 } assert_equal(0, a) delayer.run assert_equal(0, a) delayer.run assert_equal(1, a) end def test_busy delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) a = false delayer.new { a = delayer.busy? } assert_equal(false, a) assert_equal(false, delayer.busy?) delayer.run assert_equal(false, delayer.busy?) assert_equal(true, a) end def test_empty delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) a = false delayer.new { a = delayer.empty? } assert_equal(false, a) assert_equal(false, delayer.empty?) delayer.run assert_equal(true, delayer.empty?) assert_equal(true, a) end def test_size delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) a = 0 assert_equal(0, delayer.size) delayer.new { a += 1 } assert_equal(1, delayer.size) delayer.new { a += 1 } delayer.new { a += 1 } assert_equal(3, delayer.size) delayer.run assert_equal(0, delayer.size) end def test_cancel_begin delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) a = 0 d = delayer.new { a += 1 } delayer.new { a += 2 } delayer.new { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(6, a) a = 0 d = delayer.new(:low) { a += 1 } delayer.new(:high) { a += 2 } delayer.new(:high) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(6, a) a = 0 d = delayer.new(:high) { a += 1 } delayer.new(:low) { a += 2 } delayer.new(:low) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(6, a) end def test_cancel_center delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) a = 0 delayer.new { a += 1 } d = delayer.new { a += 2 } delayer.new { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(5, a) a = 0 delayer.new(:low) { a += 1 } d = delayer.new(:high) { a += 2 } delayer.new(:low) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(5, a) a = 0 delayer.new(:high) { a += 1 } d = delayer.new(:low) { a += 2 } delayer.new(:high) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(5, a) end def test_cancel_end delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) a = 0 delayer.new { a += 1 } delayer.new { a += 2 } d = delayer.new { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(3, a) a = 0 delayer.new(:low) { a += 1 } delayer.new(:low) { a += 2 } d = delayer.new(:high) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(3, a) a = 0 delayer.new(:high) { a += 1 } delayer.new(:high) { a += 2 } d = delayer.new(:low) { a += 4 } assert_equal(0, a) d.cancel delayer.run assert_equal(3, a) end def test_multithread_register delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) buffer = [] threads = [] 10.times do threads << Thread.new do 1000.times do |number| delayer.new { buffer << number } end end end delayer.run threads.each(&:join) delayer.run assert_equal(10000, buffer.size) assert_equal((0..999).inject(&:+)*10, buffer.inject(&:+)) end def test_nested delayer = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) buffer = [] delayer.new { buffer << 1 } delayer.new do delayer.new { buffer << 3 } delayer.new do delayer.new { buffer << 5 } delayer.new { buffer << 6 } end delayer.new { buffer << 4 } end delayer.new { buffer << 2 } delayer.run assert_equal([1,2,3,4,5,6], buffer) end def test_invalid_priority delayer = Delayer.generate_class(priority: [:high, :middle, :low]) buffer = [] assert_raise Delayer::InvalidPriorityError do delayer.new(0) { buffer << 1 } end assert_raise Delayer::InvalidPriorityError do delayer.new("middle") { buffer << 2 } end assert_raise Delayer::InvalidPriorityError do delayer.new { buffer << 3 } end delayer.run assert_equal([], buffer) end end delayer-1.2.1/README.md0000644000004100000410000000212314052603426014447 0ustar www-datawww-data# Delayer [![toshia](https://circleci.com/gh/toshia/delayer.svg?style=svg)](https://circleci.com/gh/toshia/delayer) Delay Any task. Similar priority-queue. ## Installation Add this line to your application's Gemfile: gem 'delayer' And then execute: $ bundle Or install it yourself as: $ gem install delayer ## Usage Task = Delayer.generate_class # Define basic class Task = Delayer.generate_class(priority: [:high, :middle, :low], default: :middle) # or, Priority delayer Task = Delayer.generate_class(expire: 0.5) # and/or, Time limited delayer. task = Task.new { delayed code ... } # Register task task = Task.new(:high) { delayed code ... } # or, You can specify priority. task.cancel # Task can cancel before Delayer#run. Task.run # Execute all tasks. Task.run(1) # or, You can specify expire. ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request delayer-1.2.1/.circleci/0000755000004100000410000000000014052603426015025 5ustar www-datawww-datadelayer-1.2.1/.circleci/config.yml0000644000004100000410000000140214052603426017012 0ustar www-datawww-dataversion: '2.1' executors: ruby: parameters: tag: type: string docker: - image: circleci/ruby:<< parameters.tag >> jobs: build: parameters: ruby-version: type: string executor: name: ruby tag: << parameters.ruby-version >> steps: - checkout - run: name: Which bundler? command: bundle -v - run: command: bundle install --path vendor/bundle - run: name: test command: bundle exec rake test workflows: build: jobs: - build: name: 'ruby-2.6' ruby-version: '2.6.7' - build: name: 'ruby-2.7' ruby-version: '2.7.3' - build: name: 'ruby-3.0' ruby-version: '3.0.1' delayer-1.2.1/.gitignore0000644000004100000410000000024314052603426015161 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp /vendor/ delayer-1.2.1/Rakefile0000644000004100000410000000050714052603426014641 0ustar www-datawww-datarequire "bundler/gem_tasks" require 'rake/testtask' task :default => [:test] Rake::TestTask.new do |test| # $LOAD_PATH に追加するパス (デフォルトで 'lib' は入っている) test.libs << 'test' # テスト対象ファイルの指定 test.test_files = Dir[ 'test/**/test_*.rb' ] test.verbose = true enddelayer-1.2.1/lib/0000755000004100000410000000000014052603426013740 5ustar www-datawww-datadelayer-1.2.1/lib/delayer.rb0000644000004100000410000000213014052603426015706 0ustar www-datawww-data# -*- coding: utf-8 -*- require "delayer/version" require "delayer/error" require "delayer/extend" require "delayer/procedure" require "delayer/delayed_procedure" require "monitor" module Delayer class << self attr_accessor :default # Generate new Delayer class. # ==== Args # [options] # Hash # expire :: processing expire (secs, 0=unlimited) # priority :: priorities # default :: default priotity # ==== Return # A new class def generate_class(options = {}) Class.new do include ::Delayer @expire = options[:expire] || 0 if options.has_key?(:priority) @priorities = options[:priority].to_a.freeze @default_priority = options[:default] else @priorities = [:normal] @default_priority = :normal end end end def method_missing(fn, *args, **kwrest, &proc) if kwrest.empty? (@default ||= generate_class).__send__(fn, *args, &proc) else (@default ||= generate_class).__send__(fn, *args, **kwrest, &proc) end end end end delayer-1.2.1/lib/delayer/0000755000004100000410000000000014052603426015365 5ustar www-datawww-datadelayer-1.2.1/lib/delayer/version.rb0000644000004100000410000000010614052603426017374 0ustar www-datawww-data# frozen_string_literal: true module Delayer VERSION = '1.2.1' end delayer-1.2.1/lib/delayer/extend.rb0000644000004100000410000001554614052603426017214 0ustar www-datawww-data# frozen_string_literal: true require 'set' module Delayer attr_reader :priority class Bucket attr_accessor :first, :last, :priority_of, :stashed def initialize(first, last, priority_of, stashed) @first = first @last = last @priority_of = priority_of @stashed = stashed end def stash_size s = stashed if s 1 + s.stash_size else 0 end end end def self.included(klass) klass.class_eval do extend Extend end end def initialize(priority = self.class.instance_eval { @default_priority }, *_args, delay: 0, &proc) self.class.validate_priority priority @priority = priority if delay == 0 @procedure = Procedure.new(self, &proc) else @procedure = DelayedProcedure.new(self, delay: delay, &proc) end end # Cancel this job # ==== Exception # Delayer::AlreadyExecutedError :: if already called run() # ==== Return # self def cancel @procedure.cancel self end module Extend attr_accessor :expire attr_reader :exception def self.extended(klass) klass.class_eval do @busy = false @expire = 0 @remain_hook = nil @exception = nil @remain_received = false @lock = Monitor.new @bucket = Bucket.new(nil, nil, {}, nil) @last_reserve = nil @reserves = Set.new end end def pop_reserve(start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)) if @last_reserve&.reserve_at&.<=(start_time) lock.synchronize do while @last_reserve&.reserve_at&.<=(start_time) @last_reserve.register @last_reserve = @reserves.min @reserves.delete(@last_reserve) end end end end # Run registered jobs. # ==== Args # [current_expire] expire for processing (secs, 0=unexpired) # ==== Return # self def run(current_expire = @expire) start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_f pop_reserve(start_time) if current_expire == 0 run_once_without_pop_reserve until empty? else @end_time = end_time = start_time + @expire run_once_without_pop_reserve while !empty? && (end_time >= Process.clock_gettime(Process::CLOCK_MONOTONIC)) @end_time = nil end if @remain_hook @remain_received = !empty? @remain_hook.call if @remain_received end rescue Exception => e @exception = e raise e end def expire? !!@end_time&.<(Time.new.to_f) end # Run a job and forward pointer. # ==== Return # self def run_once pop_reserve run_once_without_pop_reserve end private def run_once_without_pop_reserve if @bucket.first @busy = true procedure = forward procedure = forward while @bucket.first && procedure&.canceled? if procedure && !procedure.canceled? procedure.run end end ensure @busy = false end # Return if some jobs processing now. # ==== Args # [args] # ==== Return # true if Delayer processing job def busy? @busy end # Return true if no jobs has. # ==== Return # true if no jobs has. def empty? !@bucket.first end # Return remain jobs quantity. # ==== Return # Count of remain jobs def size(node = @bucket.first) if node 1 + size(node.next) else 0 end end # register new job. # ==== Args # [procedure] job(Delayer::Procedure) # ==== Return # self def register(procedure) priority = procedure.delayer.priority lock.synchronize do last_pointer = get_prev_point(priority) if last_pointer @bucket.priority_of[priority] = last_pointer.break procedure else procedure.next = @bucket.first @bucket.priority_of[priority] = @bucket.first = procedure end @bucket.last = @bucket.priority_of[priority] if @bucket.last if @remain_hook && !@remain_received @remain_received = true @remain_hook.call end end self end # Register reserved job. # It does not execute immediately. # it calls register() in _procedure.reserve_at_. # ==== Args # [procedure] job(Delayer::DelayedProcedure) # ==== Return # self def reserve(procedure) lock.synchronize do if @last_reserve if @last_reserve > procedure @reserves.add(@last_reserve) @last_reserve = procedure else @reserves.add(procedure) end else @last_reserve = procedure end end self end def register_remain_hook(&proc) @remain_hook = proc end def get_prev_point(priority) if @bucket.priority_of[priority] @bucket.priority_of[priority] else @priorities.index(priority)&.yield_self do |index| next_index = index - 1 get_prev_point @priorities[next_index] if next_index >= 0 end end end def validate_priority(symbol) unless @priorities.include? symbol raise Delayer::InvalidPriorityError, "undefined priority '#{symbol}'" end end # DelayerのStashレベルをインクリメントする。 # このメソッドが呼ばれたら、その時存在するジョブは退避され、stash_exit!が呼ばれるまで実行されない。 def stash_enter! @bucket = Bucket.new(nil, nil, {}, @bucket) self end # DelayerのStashレベルをデクリメントする。 # このメソッドを呼ぶ前に、現在のレベルに存在するすべてのジョブを実行し、Delayer#empty?がtrueを返すような状態になっている必要がある。 # ==== Raises # [Delayer::NoLowerLevelError] stash_enter!が呼ばれていない時 # [Delayer::RemainJobsError] ジョブが残っているのにこのメソッドを呼んだ時 def stash_exit! stashed = @bucket.stashed raise Delayer::NoLowerLevelError, 'stash_exit! called in level 0.' unless stashed raise Delayer::RemainJobsError, 'Current level has remain jobs. It must be empty current level jobs in call this method.' unless empty? @bucket = stashed end # 現在のDelayer Stashレベルを返す。 def stash_level @bucket.stash_size end private def forward lock.synchronize do prev = @bucket.first raise 'Current bucket not found' unless prev nex = @bucket.first = prev.next @bucket.last = nil unless nex @bucket.priority_of.each do |priority, pointer| @bucket.priority_of[priority] = nex if prev == pointer end prev.next = nil prev end end def lock @lock end end end delayer-1.2.1/lib/delayer/error.rb0000644000004100000410000000116414052603426017045 0ustar www-datawww-data# frozen_string_literal: true module Delayer class Error < ::StandardError; end class TooLate < Error; end class AlreadyExecutedError < TooLate; end class AlreadyCanceledError < TooLate; end class AlreadyRunningError < TooLate; end class InvalidPriorityError < Error; end class RecursiveError < Error; end class NoLowerLevelError < RecursiveError; end class RemainJobsError < RecursiveError; end def self.StateError(state) case state when :run AlreadyRunningError when :done AlreadyExecutedError when :cancel AlreadyCanceledError else TooLate end end end delayer-1.2.1/lib/delayer/procedure.rb0000644000004100000410000000240014052603426017676 0ustar www-datawww-data# frozen_string_literal: true module Delayer class Procedure attr_reader :state, :delayer attr_accessor :next def initialize(delayer, &proc) @delayer = delayer @proc = proc @state = :stop @next = nil @delayer.class.register(self) end # Run a process # ==== Exception # Delayer::TooLate :: if already called run() # ==== Return # node def run unless @state == :stop raise Delayer::StateError(@state), 'call twice Delayer::Procedure' end @state = :run @proc&.call @state = :done @proc = nil end # Cancel this job # ==== Exception # Delayer::TooLate :: if already called run() # ==== Return # self def cancel unless @state == :stop raise Delayer::StateError(@state), 'cannot cancel Delayer::Procedure' end @state = :cancel self end # Return true if canceled this task # ==== Return # true if canceled this task def canceled? @state == :cancel end # insert node between self and self.next # ==== Args # [node] insertion # ==== Return # node def break(node) tail = @next @next = node node.next = tail node end end end delayer-1.2.1/lib/delayer/delayed_procedure.rb0000644000004100000410000000223714052603426021375 0ustar www-datawww-data# frozen_string_literal: true module Delayer class DelayedProcedure include Comparable attr_reader :state, :delayer, :reserve_at def initialize(delayer, delay:, &proc) @delayer = delayer @proc = proc case delay when Time @reserve_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + delay.to_f - Time.now.to_f else @reserve_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + delay.to_f end @cancel = false @procedure = nil @delayer.class.reserve(self) end def register if !canceled? @procedure = Procedure.new(@delayer, &@proc) end self end def <=>(other) @reserve_at <=> other.reserve_at end # Cancel this job # ==== Exception # Delayer::TooLate :: if already called run() # ==== Return # self def cancel @procedure&.cancel @cancel = true self end # Return true if canceled this task # ==== Return # true if canceled this task def canceled? procedure = @procedure if procedure procedure.canceled? else @cancel end end end end delayer-1.2.1/delayer.gemspec0000644000004100000410000000170214052603426016164 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'delayer/version' Gem::Specification.new do |spec| spec.name = "delayer" spec.version = Delayer::VERSION spec.authors = ["Toshiaki Asai"] spec.email = ["toshi.alternative@gmail.com"] spec.description = %q{Delay the processing} spec.summary = %q{Delay the processing} spec.homepage = "https://github.com/toshia/delayer" spec.license = "MIT" spec.required_ruby_version = '>= 2.6.0' spec.files = `git ls-files`.split($/) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_development_dependency "bundler" spec.add_development_dependency "rake", '>= 12.3.2' spec.add_development_dependency "test-unit", '>= 3.3.3', '< 4.0' end delayer-1.2.1/sig/0000755000004100000410000000000014052603426013754 5ustar www-datawww-datadelayer-1.2.1/sig/delayer.rbs0000644000004100000410000000766314052603426016125 0ustar www-datawww-data# TypeProf 0.13.0 # Classes module Delayer type clock = Float | Integer | Rational VERSION: String extend Extend extend Delayer self.@expire: clock self.@priorities: Array[Symbol] self.@default_priority: Symbol self.@default: singleton(Delayer) @procedure: Procedure | DelayedProcedure self.@busy: bool self.@remain_hook: ^() -> void self.@exception: Exception? self.@remain_received: bool self.@lock: Monitor self.@bucket: Bucket self.@last_reserve: nil self.@reserves: untyped @default_priority: Symbol self.@end_time: clock? class GeneratedDelayerAbstract include ::Delayer end def self.generate_class: (?Hash[Symbol,Integer|Symbol|Enumerable[Symbol]] options) -> singleton(Delayer) def self.method_missing: (Symbol, *untyped, **untyped) { (*untyped) -> untyped } -> untyped def self.StateError: (:cancel) -> singleton(AlreadyCanceledError) | (:done) -> singleton(AlreadyExecutedError) | (:run) -> singleton(AlreadyRunningError) | (Symbol) -> singleton(TooLate) attr_reader priority: Symbol def self.included: (singleton(Delayer) klass) -> void def initialize: (?untyped priority, *untyped _args, ?delay: Time | clock) -> void def cancel: -> Delayer def stash_size: -> (Integer) def __send__: (Symbol, *untyped, **untyped) { (untyped) -> untyped } -> untyped class DelayedProcedure include Comparable @proc: (^() -> void) @cancel: bool @procedure: Procedure? attr_reader state: untyped attr_reader delayer: Delayer attr_reader reserve_at: clock def initialize: (Delayer delayer, delay: Time | clock) { () -> void } -> void def register: -> self def <=>: (DelayedProcedure other) -> Integer? def cancel: -> self def canceled?: -> bool end class Error < StandardError end class TooLate < Error end class AlreadyExecutedError < TooLate end class AlreadyCanceledError < TooLate end class AlreadyRunningError < TooLate end class InvalidPriorityError < Error end class RecursiveError < Error end class NoLowerLevelError < RecursiveError end class RemainJobsError < RecursiveError end class Bucket attr_accessor first(): Procedure? attr_accessor last(): Procedure? attr_accessor priority_of(): Hash[Symbol, Procedure?] attr_accessor stashed(): Bucket? def initialize: (Procedure?,Procedure?,Hash[Symbol, Procedure],Bucket?) -> void def stash_size: () -> Integer end module Extend @last_reserve: untyped @lock: Monitor @end_time: clock? @bucket: Bucket @remain_hook: ^() -> void @remain_received: bool @busy: bool @priorities: Array[Symbol] @reserves: Set[DelayedProcedure] attr_accessor expire: clock attr_reader exception: Exception? def self.extended: (singleton(Delayer) klass) -> singleton(Delayer) def pop_reserve: (?clock start_time) -> nil def run: (?clock? current_expire) -> void def expire?: -> bool def run_once: -> void private def run_once_without_pop_reserve: -> void public def busy?: -> bool def empty?: -> bool def size: (?Delayer::Procedure? node) -> Integer def register: (untyped procedure) -> Extend def reserve: (untyped procedure) -> Extend def register_remain_hook: () { () -> void } -> void def get_prev_point: (Symbol) -> ::Delayer::Procedure? def validate_priority: (Symbol) -> void def stash_enter!: -> Extend def stash_exit!: -> Bucket? def stash_level: -> untyped private def forward: -> Procedure? def lock: -> Monitor end class Procedure @proc: (^() -> void)? attr_reader state: :cancel | :done | :run | :stop attr_reader delayer: untyped attr_accessor next: Delayer::Procedure? def initialize: (Delayer delayer) { () -> void } -> void def run: -> void def cancel: -> Procedure def canceled?: -> bool def break: (untyped node) -> untyped end end delayer-1.2.1/Gemfile0000644000004100000410000000013414052603426014463 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in delayer.gemspec gemspec delayer-1.2.1/Steepfile0000644000004100000410000000070314052603426015035 0ustar www-datawww-datatarget :lib do signature "sig" check "lib" # Directory name # ignore "lib/templates/*.rb" # library "pathname", "set" # Standard libraries # library "strong_json" # Gems # library "instance_storage" library "monitor" library "set" end # target :spec do # signature "sig", "sig-private" # # check "spec" # # # library "pathname", "set" # Standard libraries # # library "rspec" # end delayer-1.2.1/LICENSE.txt0000644000004100000410000000205614052603426015020 0ustar www-datawww-dataCopyright (c) 2013 Toshiaki Asai MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.