Module: Wurk::RedisOptions

Defined in:
lib/wurk/redis_options.rb

Overview

Translates a Sidekiq-shaped config.redis hash into the exact keyword set redis-client accepts.

Sidekiq normalizes the hash itself before handing it over (sidekiq/redis_client_adapter.rb#client_opts), so initializers in the wild carry keys redis-client has never known. Wurk used to splat the hash straight into RedisClient.config, which surfaced as ArgumentError: unknown keyword: :network_timeout — and only inside the forked children, which build their own pools (#283). The parent booted fine, the liveness probe passed, and the swarm respawn loop churned forever processing zero jobs. Hence validate!, called from Configuration#redis= so a bad hash raises in the parent where someone can actually see it.

Reference: sidekiq 7.3 / 8.1 client_opts — namespace rejected, size/pool_timeout dropped, network_timeout → timeout, master_name → name, role/driver symbolized, reconnect_attempts ||= 1.

Constant Summary collapse

POOL_KEYS =

Consumed by the pool layer (RedisPool / Capsule); never a socket concern.

%i[size name pool_name pool_timeout on_error].freeze
IGNORED_KEYS =

Accepted for Sidekiq parity, then dropped: Sidekiq used logger for its own "connecting to Redis with options ..." line and cluster_safe to unlock :nodes. redis-client has no keyword for either.

%i[logger cluster_safe].freeze
TRANSLATED_KEYS =

Sidekiq-only spellings this module rewrites into redis-client keywords.

%i[network_timeout master_name].freeze
UMBRELLA_TIMEOUT_KEYS =

The umbrella socket timeout, under both its names. network_timeout is the redis-rb-era spelling every "widen the timeouts for a slow/remote Redis" snippet still uses; timeout is redis-client's own.

%i[network_timeout timeout].freeze
SPLIT_TIMEOUT_KEYS =

Wurk splits the socket timeouts (#101) and passes all three explicitly, and redis-client lets an explicit read_timeout win over timeout — so forwarding the umbrella verbatim would silently drop the host's value. Fan it out instead; a host-supplied split timeout still wins over the fan-out.

%i[connect_timeout read_timeout write_timeout].freeze
SYMBOLIZED_KEYS =

Symbols in redis-client, strings in plenty of YAML-sourced configs.

%i[driver role].freeze
REJECTED_KEYS =

Keys that mean something in Sidekiq but have no Wurk equivalent. Raise naming the key and its replacement — the alternative is an opaque unknown keyword: from three layers down inside a forked child.

{
  namespace: 'Redis namespacing was dropped in Sidekiq 7 and Wurk never implemented it ' \
             '(docs/migrate-from-sidekiq.md §4). Give Wurk its own Redis database ' \
             '(redis://host:6379/1) or its own instance instead.',
  nodes: 'Wurk does not run on Redis Cluster. Point config.redis at a single server with ' \
         '`url:`, or at a Sentinel set with `sentinels:`.'
}.freeze
KEYWORD_PARAMS =

Keyword parameter kinds in Method#parameters.

%i[key keyreq].freeze

Class Method Summary collapse

Class Method Details

.fiber_blocking_driver?(driver) ⇒ Boolean

driver: :hiredis, the C driver a fiber scheduler cannot switch away from.

Returns:

  • (Boolean)


113
114
115
# File 'lib/wurk/redis_options.rb', line 113

def fiber_blocking_driver?(driver)
  driver.to_s.include?('hiredis')
end

.known_keys ⇒ Object

Every keyword redis-client itself accepts, read off its own signatures so the list can't drift from the installed version. Config#initialize takes a **kwargs rest and forwards to Config::Common, so both have to be walked; SentinelConfig adds the sentinel-only keys.



127
128
129
130
131
# File 'lib/wurk/redis_options.rb', line 127

def known_keys
  @known_keys ||= [
    ::RedisClient::Config, ::RedisClient::Config::Common, ::RedisClient::SentinelConfig
  ].flat_map { |mod| keyword_params(mod) }.union(POOL_KEYS, IGNORED_KEYS, TRANSLATED_KEYS)
end

.normalize(options, defaults: {}) ⇒ Object

The keyword hash for RedisClient.config / RedisClient.sentinel. defaults are Wurk's own socket defaults; everything the host supplied wins over them.



66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/wurk/redis_options.rb', line 66

def normalize(options, defaults: {})
  opts = symbolize(options)
  validate!(opts)
  opts = translate(opts)

  # The default `url` survives next to a sentinel set, as in Sidekiq
  # (`url ||= REDIS_URL`): SentinelConfig reads the db and credentials off
  # it and falls back to its host for the master name, so
  # REDIS_URL=redis://mymaster/2 plus `sentinels:` is a working config.
  # An explicit `name:` still wins over the url's host.
  defaults.merge(split_timeouts(opts), opts.except(*UMBRELLA_TIMEOUT_KEYS))
end

.pool_kwargs(options) ⇒ Object

The keyword set a config.redis-shaped hash hands to RedisPool.new, whose own name: is its telemetry label (Sidekiq's pool_name:). Sidekiq spells the Sentinel master name:, so next to sentinels: it is carried as master_name — otherwise it would collide with the label and never reach RedisClient.sentinel. An explicit master_name wins, as it does in Sidekiq's own translation. Without sentinels name has no redis-client meaning and is dropped; size and pool_name are the caller's to place.



87
88
89
90
91
92
93
# File 'lib/wurk/redis_options.rb', line 87

def pool_kwargs(options)
  opts = symbolize(options)
  name = opts[:name]
  opts = opts.except(:size, :name, :pool_name)
  opts[:master_name] = name if name && sentinel?(opts) && !opts.key?(:master_name)
  opts
end

.sentinel?(client_config) ⇒ Boolean

Sentinel sets go through RedisClient.sentinel — RedisClient.config rejects sentinels: outright. Same routing Sidekiq does.

Returns:

  • (Boolean)


119
120
121
# File 'lib/wurk/redis_options.rb', line 119

def sentinel?(client_config)
  client_config.key?(:sentinels)
end

.validate!(options) ⇒ Object

Raises for anything redis-client would reject. Cheap and pure, so it runs in the parent (Configuration#redis=) as well as at pool-build time.

Raises:

  • (ArgumentError)


97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/wurk/redis_options.rb', line 97

def validate!(options)
  opts = symbolize(options)

  REJECTED_KEYS.each do |key, hint|
    raise ArgumentError, "config.redis[:#{key}] is not supported. #{hint}" if opts.key?(key)
  end

  unknown = opts.keys - known_keys
  return if unknown.empty?

  raise ArgumentError,
        "config.redis: unknown option#{'s' if unknown.size > 1} " \
        "#{unknown.map(&:inspect).join(', ')}. Supported keys: #{known_keys.sort.join(', ')}."
end