🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

ztimer

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ztimer - rubygems Package Compare versions

Comparing version
0.6.0
to
1.0.0
+93
-22
lib/ztimer.rb

@@ -1,6 +0,9 @@

require "ztimer/version"
require "ztimer/slot"
require "ztimer/sorted_store"
require "ztimer/watcher"
# frozen_string_literal: true
require 'ztimer/version'
require 'ztimer/slot'
require 'ztimer/sorted_store'
require 'ztimer/watcher'
# Implements a timer which allows to execute a block with a delay, recurrently or asynchronously.
class Ztimer

@@ -13,3 +16,3 @@ @default_instance = nil

@concurrency = concurrency
@watcher = Ztimer::Watcher.new(){|slot| execute(slot) }
@watcher = Ztimer::Watcher.new { |slot| execute(slot) }
@workers_lock = Mutex.new

@@ -22,2 +25,3 @@ @count_lock = Mutex.new

# Execute the code block asyncrhonously right now
def async(&callback)

@@ -30,5 +34,6 @@ enqueued_at = utc_microseconds

return slot
slot
end
# Execute the code block after the specified delay
def after(milliseconds, &callback)

@@ -41,35 +46,100 @@ enqueued_at = utc_microseconds

return slot
slot
end
def every(milliseconds, &callback)
enqueued_at = utc_microseconds
expires_at = enqueued_at + milliseconds * 1000
slot = Slot.new(enqueued_at, expires_at, milliseconds * 1000, &callback)
# Execute the code block at a specific datetime
def at(datetime, &callback)
enqueued_at = datetime.to_f * 1_000_000
slot = Slot.new(enqueued_at, enqueued_at, -1, &callback)
add(slot)
return slot
slot
end
# Execute the code block every N milliseconds.
# When :start_at is specified, the first execution will start at specified date/time
def every(milliseconds, start_at: nil, &callback)
enqueued_at = start_at ? start_at.to_f * 1_000_000 : utc_microseconds
expires_at = enqueued_at + milliseconds * 1000
slot = Slot.new(enqueued_at, expires_at, milliseconds * 1000, &callback)
add(slot)
slot
end
# Run ztimer every N seconds, starting with the nearest time slot (ex. secondly(5)
# will run at second 0, 5, 10, 15, etc.)
def secondly(seconds, offset: 0, &callback)
start_time = utc_microseconds
milliseconds = (seconds.to_f * 1000).to_i
enqueued_at = start_time - (start_time % (milliseconds * 1000)) + offset * 1_000_000
expires_at = enqueued_at + milliseconds * 1000
slot = Slot.new(enqueued_at, expires_at, milliseconds * 1000, &callback)
add(slot)
slot
end
# Run ztimer every N minutes, starting at the nearest time slot (ex. minutely(2) will run at minute 0, 2, 4, 6, etc.)
def minutely(minutes, offset: 0, &callback)
secondly(minutes.to_f * 60, offset: offset.to_f * 60, &callback)
end
# Run ztimer every N hours, starting at the nearest time slot (ex. hourly(2) will run at hour 0, 2, 4, 6, etc.)
def hourly(hours, offset: 0, &callback)
minutely(hours.to_f * 60, offset: offset.to_f * 60, &callback)
end
def daily(days, offset: 0, &callback)
raise ArgumentError, "Days number should be > 0: #{days.inspect}" if days.to_f <= 0
hourly(days.to_f * 24, offset: offset.to_f * 24, &callback)
end
def day_of_week(day, &callback)
days = %w[sun mon tue thu wen fri sat]
current_day = Time.now.wday
index = day.to_i
if day.is_a?(String)
# Find day number by day name
index = days.index { |day_name| day.strip.downcase == day_name }
raise ArgumentError, "Invalid week day: #{day.inspect}" if index.nil?
elsif index.negative? || index > 6
raise ArgumentError, "Invalid week day: #{day.inspect}"
end
offset = 0
offset = (current_day > index ? index - current_day : current_day - index) if current_day != index
daily(7, offset: offset, &callback)
end
def days_of_week(*args, &callback)
args.map { |day| day_of_week(day, &callback) }
end
def jobs_count
return @watcher.jobs
@watcher.jobs
end
def concurrency=(new_value)
raise ArgumentError.new("Invalid concurrency value: #{new_value}") unless new_value.is_a?(Fixnum) && new_value >= 1
value_is_integer = new_value.is_a?(Integer)
raise ArgumentError, "Invalid concurrency value: #{new_value}" unless value_is_integer && new_value >= 1
@concurrency = new_value
end
def stats
{
running: @running,
running: @running,
scheduled: @watcher.jobs,
executing: @queue.size,
total: @count
total: @count
}
end
def self.method_missing(name, *args, &block)

@@ -115,8 +185,9 @@ @default_instance ||= Ztimer.new(concurrency: 20)

current_slot.callback.call(current_slot) unless current_slot.callback.nil? || current_slot.canceled?
rescue => e
STDERR.puts e.inspect + (e.backtrace ? "\n" + e.backtrace.join("\n") : "")
rescue StandardError => e
backtrace = e.backtrace ? "\n#{e.backtrace.join("\n")}" : ''
warn e.inspect + backtrace
end
end
rescue ThreadError
puts "queue is empty"
puts 'queue is empty'
end

@@ -129,4 +200,4 @@ @workers_lock.synchronize { @running -= 1 }

def utc_microseconds
return Time.now.to_f * 1_000_000
Time.now.to_f * 1_000_000
end
end
+7
-7

@@ -0,3 +1,5 @@

# frozen_string_literal: true
class Ztimer
# Implements a slot, which represents a block of code to be executed at specified time slot.
class Slot

@@ -7,3 +9,3 @@ attr_reader :enqueued_at, :expires_at, :recurrency, :callback

def initialize(enqueued_at, expires_at,recurrency = -1, &callback)
def initialize(enqueued_at, expires_at, recurrency = -1, &callback)
@enqueued_at = enqueued_at

@@ -19,13 +21,11 @@ @expires_at = expires_at

def recurrent?
return @recurrency > 0
@recurrency.positive?
end
def reset!
if recurrent?
@expires_at += recurrency
end
@expires_at += recurrency if recurrent?
end
def canceled?
return @canceled
@canceled
end

@@ -38,5 +38,5 @@

def <=>(other)
return @expires_at <=> other.expires_at
@expires_at <=> other.expires_at
end
end
end

@@ -0,5 +1,7 @@

# frozen_string_literal: true
class Ztimer
# Implements a performant sorted store for time slots, which uses binary search to optimize
# new items insertion and items retrievement.
class SortedStore
def initialize

@@ -11,3 +13,4 @@ @store = []

@store.insert(position_for(value), value)
return self
self
end

@@ -17,41 +20,36 @@

index = index_of(value)
if index
@store.delete_at(index)
else
return nil
end
index.nil? ? nil : @store.delete_at(index)
end
def [](index)
return @store[index]
@store[index]
end
def first
return @store.first
@store.first
end
def last
return @store.last
@store.last
end
def shift
return @store.shift
@store.shift
end
def pop
return @store.pop
@store.pop
end
def index_of(value, start = 0, stop = [@store.count - 1, 0].max)
if start > stop
return nil
elsif start == stop
return value == @store[start] ? start : nil
else
position = ((stop + start)/ 2).to_i
case value <=> @store[position]
when -1 then return index_of(value, start, position)
when 0 then return position
when 1 then return index_of(value, position + 1, stop)
end
return nil if start > stop
return value == @store[start] ? start : nil if start == stop
position = ((stop + start) / 2).to_i
case value <=> @store[position]
when -1 then index_of(value, start, position)
when 0 then position
when 1 then index_of(value, position + 1, stop)
end

@@ -61,36 +59,35 @@ end

def count
return @store.count
@store.count
end
def size
return @store.size
@store.size
end
def empty?
return @store.empty?
@store.empty?
end
def clear
return @store.clear
@store.clear
end
def to_a
return @store.dup
@store.dup
end
protected
def position_for(item, start = 0, stop = [@store.count - 1, 0].max)
if start > stop
raise "Invalid range (#{start}, #{stop})"
elsif start == stop
raise "Invalid range (#{start}, #{stop})" if start > stop
if start == stop
element = @store[start]
return element.nil? || ((item <=> element) <= 0) ? start : start + 1
element.nil? || ((item <=> element) <= 0) ? start : start + 1
else
position = ((stop + start)/ 2).to_i
position = ((stop + start) / 2).to_i
case item <=> @store[position]
when -1 then return position_for(item, start, position)
when 0 then return position
when 1 then return position_for(item, position + 1, stop)
when -1 then position_for(item, start, position)
when 0 then position
when 1 then position_for(item, position + 1, stop)
end

@@ -97,0 +94,0 @@ end

@@ -0,3 +1,5 @@

# frozen_string_literal: true
class Ztimer
VERSION = "0.6.0"
VERSION = '1.0.0'
end

@@ -0,5 +1,7 @@

# frozen_string_literal: true
class Ztimer
# Implements a watcher which allows to enqueue Ztimer::Slot items, that will be executed
# as soon as the time of Ztimer::Slot is reached.
class Watcher
def initialize(&callback)

@@ -13,8 +15,6 @@ @thread = nil

def << (slot)
def <<(slot)
@mutex.synchronize do
@slots << slot
if @slots.first == slot
run
end
run if @slots.first == slot
end

@@ -24,3 +24,3 @@ end

def jobs
return @slots.size
@slots.size
end

@@ -42,6 +42,7 @@

return if @thread
@thread = Thread.new do
loop do
begin
delay = get_delay
delay = calculate_delay
if delay.nil?

@@ -54,6 +55,6 @@ Thread.stop

while get_first_expired do
while fetch_first_expired
end
rescue => e
puts e.inspect + "\n" + e.backtrace.join("\n")
rescue StandardError => e
puts "#{e.inspect}\n#{e.backtrace.join("\n")}"
end

@@ -66,7 +67,7 @@ end

def get_delay
return @mutex.synchronize { @slots.empty? ? nil : @slots.first.expires_at - utc_microseconds }
def calculate_delay
@mutex.synchronize { @slots.empty? ? nil : @slots.first.expires_at - utc_microseconds }
end
def get_first_expired
def fetch_first_expired
@mutex.synchronize do

@@ -97,5 +98,5 @@ slot = @slots.first

def utc_microseconds
return Time.now.to_f * 1_000_000
Time.now.to_f * 1_000_000
end
end
end

@@ -61,2 +61,16 @@ # Ztimer

| Method | Description |
|--------|-------------|
| `async(&block)` | Execute the block asynchronously. |
| `after(milliseconds, &block)` | Execute the block after the specified amount of milliseconds. |
| `at(datetime, &block)` | Execute the block at the specified timestamp. |
| `every(milliseconds, start_at: nil, &block)` | Execute the block at the specified interval of milliseconds. A custom `:start_at` param could be provided to specify an offset timestamp. |
| `secondly(seconds, offset: 0, &block)` | Executes the block every N seconds. An `:offset` of seconds could be specified to shift the begining of the time slot. By default the block will be exected at the begining of the time slot. Example: `secondly(5)` will run at second `0`, `5`, `10`, `15`, etc. |
| `minutely(minutes, offset: 0, &block)` | Executes the block every N minutes. An `:offset` of minutes could be specified to shift the begining of the time slot. By default the block will be exected at the begining of the time slot. Example: `minutely(5)` will run at minute `0`, `5`, `10`, `15`, etc. |
| `hourly(hours, offset: 0, &block)` | Executes the block every N hours. An `:offset` of hours could be specified to shift the begining of the time slot. By default the block will be exected at the begining of the time slot. Example: `hourly(5)` will run at hour `0`, `5`, `10`, `15`, etc. |
| `daily(days, offset: 0, &block)` | Executes the block every N days. An `:offset` of days could be specified to shift the begining of the time slot. By default the block will be exected at the begining of the time slot. Example: `daily(5)` will run on day `0`, `5`, `10`, `15`, etc. |
| `day_of_week(day, &block)` | Execute the block only on the specified day of week. Valid days are: `"sun", "mon", "tue", "thu", "wen", "fri", "sat"`. |
| `days_of_week(days, &block)` | Execute the block on the specified days of week. |
By default **Ztimer** will run at maximum 20 jobs concurrently, so that if you have 100 jobs to be

@@ -63,0 +77,0 @@ executed at the same time, at most 20 of them will run concurrently. This is necessary in order to prevent uncontrolled threads spawn when many jobs have to be run at the same time.

@@ -1,3 +0,4 @@

# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
# frozen_string_literal: true
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)

@@ -7,20 +8,22 @@ require 'ztimer/version'

Gem::Specification.new do |spec|
spec.name = "ztimer"
spec.name = 'ztimer'
spec.version = Ztimer::VERSION
spec.authors = ["Groza Sergiu"]
spec.email = ["serioja90@gmail.com"]
spec.authors = ['Groza Sergiu']
spec.email = ['serioja90@gmail.com']
spec.summary = %q{An asyncrhonous timer}
spec.description = %q{Ruby asyncrhonous timer that allows you to enqueue tasks to be executed asyncrhonously after a delay}
spec.homepage = "https://github.com/serioja90/ztimer"
spec.license = "MIT"
spec.summary = %(An asyncrhonous timer)
spec.description = %(Ruby asyncrhonous timer that allows you to enqueue tasks to be executed asyncrhonously after a delay)
spec.homepage = 'https://github.com/serioja90/ztimer'
spec.license = 'MIT'
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = "exe"
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.require_paths = ['lib']
spec.add_development_dependency "bundler", "~> 1.11"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.0"
spec.required_ruby_version = '>= 2.5'
spec.add_development_dependency 'bundler', '~>2.2', '>= 2.2.33'
spec.add_development_dependency 'rake', '~>12.3', '>= 12.3.3'
spec.add_development_dependency 'rspec', '~> 3.0'
end