ruby-concurrency/concurrent-ruby · error · ArgumentError
interval_type must be either :fixed_delay or :fixed_rate
Error message
interval_type must be either :fixed_delay or :fixed_rate
What it means
TimerTask supports two interval semantics: :fixed_delay (default; interval counted after each run completes) and :fixed_rate (interval counted from run start). The :interval_type option is compared by identity against these two symbols, and any other value — including the strings 'fixed_delay'/'fixed_rate' — raises ArgumentError.
Source
Thrown at lib/concurrent-ruby/concurrent/timer_task.rb:303
end
# @!attribute [rw] timeout_interval
# @return [Fixnum] Number of seconds the task can run before it is
# considered to have failed.
def timeout_interval=(value)
warn 'TimerTask timeouts are now ignored as these were not able to be implemented correctly'
end
private :post, :<<
private
def ns_initialize(opts, &task)
set_deref_options(opts)
self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
if opts[:interval_type] && ![FIXED_DELAY, FIXED_RATE].include?(opts[:interval_type])
raise ArgumentError.new('interval_type must be either :fixed_delay or :fixed_rate')
end
if opts[:timeout] || opts[:timeout_interval]
warn 'TimeTask timeouts are now ignored as these were not able to be implemented correctly'
end
@run_now = opts[:now] || opts[:run_now]
@interval_type = opts[:interval_type] || DEFAULT_INTERVAL_TYPE
@task = Concurrent::SafeTaskExecutor.new(task)
@executor = opts[:executor] || Concurrent.global_io_executor
@running = Concurrent::AtomicBoolean.new(false)
@age = Concurrent::AtomicFixnum.new(0)
@value = nil
self.observers = Collection::CopyOnNotifyObserverSet.new
end
# @!visibility private
def ns_shutdown_executionView on GitHub (pinned to 0b88d5ff75)
Solutions
- Use the exact symbols: interval_type: :fixed_rate
- Normalize at the config boundary: interval_type: cfg['interval_type']&.to_sym
- Whitelist early: raise ArgumentError, 'bad interval_type' unless %i[fixed_delay fixed_rate].include?(type.to_sym)
Example fix
# before (string from YAML fails the symbol check)
task = Concurrent::TimerTask.new(interval_type: config['interval_type']) { tick }
# after
type = config['interval_type'].to_s.to_sym if config['interval_type']
type = :fixed_delay unless %i[fixed_delay fixed_rate].include?(type)
task = Concurrent::TimerTask.new(interval_type: type) { tick } Defensive patterns
Strategy: validation
Validate before calling
VALID_INTERVAL_TYPES = %i[fixed_delay fixed_rate].freeze
type = opts[:interval_type]&.to_sym
type = nil unless VALID_INTERVAL_TYPES.include?(type)
Concurrent::TimerTask.new(**opts, interval_type: type).tap { |t| t.execute } Type guard
def valid_interval_type?(v) %i[fixed_delay fixed_rate].include?(v.is_a?(String) ? v.to_sym : v) end
Prevention
- Always pass interval_type as a Symbol, never a String
- Normalize with to_sym at the YAML/JSON boundary and whitelist against the two legal values
- Document the accepted set next to your config schema so typos fail review
When it happens
Trigger: TimerTask.new(interval_type: 'fixed_delay') where the string came from YAML/JSON/ENV; typos like :fixed_interval or :fixedrate; forwarding user input unvalidated.
Common situations: Config-driven timer setup where the parser returns strings, not symbols; options copied from documentation written with quotes; version upgrades introducing the option into previously untyped config hashes.
Related errors
- no block given
- must be greater than zero
- no block given
- Not all dependencies are IVars. Dependencies: #{ inputs.insp
- no block given
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/0522da902bce66d1.
Report an issue: GitHub.