ruby-concurrency/concurrent-ruby · error · ArgumentError

must be greater than zero

Error message

must be greater than zero

What it means

TimerTask#execution_interval= rejects values that are <= 0 after to_f (unlike ScheduledTask, zero is not allowed here), raising ArgumentError('must be greater than zero'). The same setter runs during TimerTask.new via the :execution / :execution_interval option, so bad intervals surface at construction. Note nil.to_f == 0.0, so a missing config value raises this error. The companion timeout_interval= is deprecated and only warns.

Source

Thrown at lib/concurrent-ruby/concurrent/timer_task.rb:270

    #   task = Concurrent::TimerTask.execute(execution_interval: 10){ print "Hello World\n" }
    #   task.running? #=> true
    def self.execute(opts = {}, &task)
      TimerTask.new(opts, &task).execute
    end

    # @!attribute [rw] execution_interval
    # @return [Fixnum] Number of seconds after the task completes before the
    #   task is performed again.
    def execution_interval
      synchronize { @execution_interval }
    end

    # @!attribute [rw] execution_interval
    # @return [Fixnum] Number of seconds after the task completes before the
    #   task is performed again.
    def execution_interval=(value)
      if (value = value.to_f) <= 0.0
        raise ArgumentError.new('must be greater than zero')
      else
        synchronize { @execution_interval = value }
      end
    end

    # @!attribute [r] interval_type
    # @return [Symbol] method to calculate the interval between executions
    attr_reader :interval_type

    # @!attribute [rw] timeout_interval
    # @return [Fixnum] Number of seconds the task can run before it is
    #   considered to have failed.
    def timeout_interval
      warn 'TimerTask timeouts are now ignored as these were not able to be implemented correctly'
    end

    # @!attribute [rw] timeout_interval
    # @return [Fixnum] Number of seconds the task can run before it is

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Default the value at the boundary: interval = Float(cfg.fetch('interval', 30))
  2. Treat 0/nil as 'do not start the timer' instead of passing it through: skip TimerTask.new unless interval && interval > 0
  3. Validate once at config load: raise unless interval.is_a?(Numeric) && interval > 0

Example fix

# before (nil from missing config becomes 0.0 and raises)
task = Concurrent::TimerTask.new(execution_interval: cfg['interval']) { sync }

# after (explicit default + explicit disable semantics)
interval = Float(cfg.fetch('interval', 30))
task = interval > 0 ? Concurrent::TimerTask.new(execution_interval: interval) { sync } : nil
Defensive patterns

Strategy: validation

Validate before calling

interval = Float(interval)
raise ArgumentError, "interval #{interval} must be > 0" unless interval > 0
Concurrent::TimerTask.new(execution_interval: interval) { work }

Type guard

def valid_interval?(v)
  v.is_a?(Numeric) && v.to_f > 0
end

Try / catch

begin
  timer.execution_interval = new_interval
rescue ArgumentError
  logger.warn("ignoring invalid interval #{new_interval.inspect}")
end

Prevention

When it happens

Trigger: TimerTask.new(execution_interval: 0) or execution_interval: -5; timer_task.execution_interval = 0; interval sourced from config/ENV that is nil, absent, or the string '0' ('0'.to_f == 0.0).

Common situations: Intervals read from YAML/ENV where 'disable the timer' was encoded as 0 or left blank; ops-configured intervals with typos or negative numbers; dividing to produce a Float that lands on 0.0.

Related errors


AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21). Data as JSON: /api/errors/c965dd1e87be9a2c. Report an issue: GitHub.