From 12e76cdbe4722c0c9fe15608319f23925c78c3a2 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 25 Jun 2021 14:33:37 +0200 Subject: [PATCH] Concurrent.monotonic_time accept an optional unit parameter This is a direct transposition of MRI's `clock_gettime` unit option. --- .../concurrent/utility/monotonic_time.rb | 102 ++++++++++++------ spec/concurrent/monotonic_time_spec.rb | 30 ++++++ 2 files changed, 97 insertions(+), 35 deletions(-) create mode 100644 spec/concurrent/monotonic_time_spec.rb diff --git a/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb b/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb index c9f4b369a..0fa5c5094 100644 --- a/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +++ b/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb @@ -2,26 +2,64 @@ module Concurrent - class_definition = Class.new(Synchronization::LockableObject) do - def initialize - @last_time = Time.now.to_f - super() + # @!macro monotonic_get_time + # + # Returns the current time a tracked by the application monotonic clock. + # + # @param [Symbol] unit the time unit to be returned, can be either + # :float_second, :float_millisecond, :float_microsecond, :second, + # :millisecond, :microsecond, or :nanosecond default to :float_second. + # + # @return [Float] The current monotonic time since some unspecified + # starting point + # + # @!macro monotonic_clock_warning + if defined?(Process::CLOCK_MONOTONIC) + + def monotonic_time(unit = :float_second) + Process.clock_gettime(Process::CLOCK_MONOTONIC, unit) end - if defined?(Process::CLOCK_MONOTONIC) - # @!visibility private - def get_time - Process.clock_gettime(Process::CLOCK_MONOTONIC) - end - elsif Concurrent.on_jruby? - # @!visibility private - def get_time - java.lang.System.nanoTime() / 1_000_000_000.0 + elsif Concurrent.on_jruby? + + # @!visibility private + TIME_UNITS = Hash.new { |_hash, key| raise ArgumentError, "unexpected unit: #{key}" }.compare_by_identity + TIME_UNITS.merge!( + second: 1_000_000_000, + millisecond: 1_000_000, + microsecond: 1_000, + nanosecond: 1, + float_second: 1_000_000_000.0, + float_millisecond: 1_000_000.0, + float_microsecond: 1_000.0, + ) + TIME_UNITS.freeze + private_constant :TIME_UNITS + + def monotonic_time(unit = :float_second) + java.lang.System.nanoTime() / TIME_UNITS[unit] + end + + else + + class_definition = Class.new(Synchronization::LockableObject) do + def initialize + @last_time = Time.now.to_f + @time_units = Hash.new { |_hash, key| raise ArgumentError, "unexpected unit: #{key}" }.compare_by_identity + @time_units.merge!( + second: [nil, true], + millisecond: [1_000, true], + microsecond: [1_000_000, true], + nanosecond: [1_000_000_000, true], + float_second: [nil, false], + float_millisecond: [1_000.0, false], + float_microsecond: [1_000_000.0, false], + ) + super() end - else # @!visibility private - def get_time + def get_time(unit) synchronize do now = Time.now.to_f if @last_time < now @@ -29,30 +67,24 @@ def get_time else # clock has moved back in time @last_time += 0.000_001 end + scale, to_int = @time_units[unit] + now *= scale if scale + now = now.to_i if to_int + now end end - end - end - # Clock that cannot be set and represents monotonic time since - # some unspecified starting point. - # - # @!visibility private - GLOBAL_MONOTONIC_CLOCK = class_definition.new - private_constant :GLOBAL_MONOTONIC_CLOCK - - # @!macro monotonic_get_time - # - # Returns the current time a tracked by the application monotonic clock. - # - # @return [Float] The current monotonic time since some unspecified - # starting point - # - # @!macro monotonic_clock_warning - def monotonic_time - GLOBAL_MONOTONIC_CLOCK.get_time + # Clock that cannot be set and represents monotonic time since + # some unspecified starting point. + # + # @!visibility private + GLOBAL_MONOTONIC_CLOCK = class_definition.new + private_constant :GLOBAL_MONOTONIC_CLOCK + + def monotonic_time(unit = :float_second) + GLOBAL_MONOTONIC_CLOCK.get_time(unit) + end end - module_function :monotonic_time end diff --git a/spec/concurrent/monotonic_time_spec.rb b/spec/concurrent/monotonic_time_spec.rb new file mode 100644 index 000000000..674c19f7f --- /dev/null +++ b/spec/concurrent/monotonic_time_spec.rb @@ -0,0 +1,30 @@ +module Concurrent + + RSpec.describe :monotonic_time do + context 'behavior' do + + it 'returns seconds as float' do + expect(Concurrent.monotonic_time).to be_a(Float) + end + + [:float_second, :float_millisecond, :float_microsecond].each do |unit| + it "returns a Float when unit = #{unit.inspect}" do + expect(Concurrent.monotonic_time(unit)).to be_a(Float) + end + end + + [:second, :millisecond, :microsecond, :nanosecond].each do |unit| + it "returns an Integer when unit = #{unit.inspect}" do + expect(Concurrent.monotonic_time(unit)).to be_an(Integer) + end + end + + it 'raises ArgumentError on unknown units' do + expect { + Concurrent.monotonic_time(:foo) + }.to raise_error(ArgumentError) + end + + end + end +end