Class: Dalli::Protocol::Meta::RequestFormatter

Inherits:
Object
  • Object
show all
Defined in:
lib/dalli/protocol/request_formatter.rb

Overview

Class that encapsulates logic for formatting meta protocol requests to memcached.

Constant Summary collapse

META_NOOP =

rubocop:enable Metrics/CyclomaticComplexity rubocop:enable Metrics/ParameterLists rubocop:enable Metrics/PerceivedComplexity

"mn#{TERMINATOR}".freeze
ALLOWED_STATS_ARGS =
[nil, '', 'items', 'slabs', 'settings', 'reset'].freeze

Class Method Summary collapse

Class Method Details

.cas_string(cas) ⇒ Object



140
141
142
143
# File 'lib/dalli/protocol/request_formatter.rb', line 140

def self.cas_string(cas)
  cas = parse_to_64_bit_int(cas, nil)
  cas.nil? || cas.zero? ? '' : " C#{cas}"
end

.flush(delay: nil, quiet: false) ⇒ Object



108
109
110
111
112
113
# File 'lib/dalli/protocol/request_formatter.rb', line 108

def self.flush(delay: nil, quiet: false)
  cmd = +'flush_all'
  cmd << " #{parse_to_64_bit_int(delay, 0)}" if delay
  cmd << ' noreply' if quiet
  cmd << TERMINATOR
end

.meta_arithmetic(key:, delta:, initial:, incr: true, cas: nil, ttl: nil, base64: false, quiet: false) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/dalli/protocol/request_formatter.rb', line 83

def self.meta_arithmetic(key:, delta:, initial:, incr: true, cas: nil, ttl: nil, base64: false, quiet: false)
  cmd = "ma #{key} v"
  cmd << ' b' if base64
  cmd << " D#{delta}" if delta
  cmd << " J#{initial}" if initial
  # Always set a TTL if an initial value is specified
  cmd << " N#{ttl || 0}" if ttl || initial
  cmd << cas_string(cas)
  cmd << ' q' if quiet
  cmd << " M#{incr ? 'I' : 'D'}"
  cmd << TERMINATOR
end

.meta_delete(key:, cas: nil, ttl: nil, base64: false, quiet: false, stale: false) ⇒ Object

Thundering herd protection flag:

  • stale (I flag): Instead of deleting the item, mark it as stale. Other clients using N/R flags will see the X flag and know the item is being regenerated.



73
74
75
76
77
78
79
80
81
# File 'lib/dalli/protocol/request_formatter.rb', line 73

def self.meta_delete(key:, cas: nil, ttl: nil, base64: false, quiet: false, stale: false)
  cmd = "md #{key}"
  cmd << ' b' if base64
  cmd << cas_string(cas)
  cmd << " T#{ttl}" if ttl
  cmd << ' I' if stale # Mark stale instead of deleting
  cmd << ' q' if quiet
  cmd << TERMINATOR
end

.meta_get(key:, value: true, return_cas: false, ttl: nil, base64: false, quiet: false, vivify_ttl: nil, recache_ttl: nil, return_hit_status: false, return_last_access: false, skip_lru_bump: false, skip_flags: false) ⇒ Object

Since these are string construction methods, we’re going to disable these Rubocop directives. We really can’t make this construction much simpler, and introducing an intermediate object seems like overkill.

rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/ParameterLists rubocop:disable Metrics/PerceivedComplexity

Meta get flags:

Thundering herd protection:

  • vivify_ttl (N flag): On miss, create a stub item and return W flag. The TTL specifies how long the stub lives. Other clients see X (stale) and Z (lost race).

  • recache_ttl (R flag): If item’s remaining TTL is below this threshold, return W flag to indicate this client should recache. Other clients get Z (lost race).

Metadata flags:

  • return_hit_status (h flag): Return whether item has been hit before (0 or 1)

  • return_last_access (l flag): Return seconds since item was last accessed

  • skip_lru_bump (u flag): Don’t bump item in LRU, don’t update hit status or last access

Response flags (parsed by response processor):

  • W: Client won the right to recache this item

  • X: Item is stale (another client is regenerating)

  • Z: Client lost the recache race (another client is already regenerating)

  • h0/h1: Hit status (0 = first access, 1 = previously accessed)

  • l<N>: Seconds since last access



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/dalli/protocol/request_formatter.rb', line 38

def self.meta_get(key:, value: true, return_cas: false, ttl: nil, base64: false, quiet: false,
                  vivify_ttl: nil, recache_ttl: nil,
                  return_hit_status: false, return_last_access: false, skip_lru_bump: false,
                  skip_flags: false)
  cmd = "mg #{key}"
  # In raw mode (skip_flags: true), we don't request bitflags since they're not used.
  # This saves 2 bytes per request and skips parsing on response.
  cmd << (skip_flags ? ' v' : ' v f') if value
  cmd << ' c' if return_cas
  cmd << ' b' if base64
  cmd << " T#{ttl}" if ttl
  cmd << ' k q s' if quiet # Return the key in the response if quiet
  cmd << " N#{vivify_ttl}" if vivify_ttl # Thundering herd: vivify on miss
  cmd << " R#{recache_ttl}" if recache_ttl # Thundering herd: win recache if TTL below threshold
  cmd << ' h' if return_hit_status # Return hit status (0 or 1)
  cmd << ' l' if return_last_access # Return seconds since last access
  cmd << ' u' if skip_lru_bump # Don't bump LRU or update access stats
  cmd << TERMINATOR
end

.meta_noopObject



100
101
102
# File 'lib/dalli/protocol/request_formatter.rb', line 100

def self.meta_noop
  META_NOOP
end

.meta_set(key:, value:, bitflags: nil, cas: nil, ttl: nil, mode: :set, base64: false, quiet: false) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/dalli/protocol/request_formatter.rb', line 58

def self.meta_set(key:, value:, bitflags: nil, cas: nil, ttl: nil, mode: :set, base64: false, quiet: false)
  cmd = "ms #{key} #{value.bytesize}"
  cmd << ' c' unless %i[append prepend].include?(mode)
  cmd << ' b' if base64
  cmd << " F#{bitflags}" if bitflags
  cmd << cas_string(cas)
  cmd << " T#{ttl}" if ttl
  cmd << " M#{mode_to_token(mode)}"
  cmd << ' q' if quiet
  cmd << TERMINATOR
end

.mode_to_token(mode) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/dalli/protocol/request_formatter.rb', line 125

def self.mode_to_token(mode)
  case mode
  when :add
    'E'
  when :replace
    'R'
  when :append
    'A'
  when :prepend
    'P'
  else
    'S'
  end
end

.parse_to_64_bit_int(val, default) ⇒ Object



145
146
147
148
149
150
# File 'lib/dalli/protocol/request_formatter.rb', line 145

def self.parse_to_64_bit_int(val, default)
  val.nil? ? nil : Integer(val)
rescue ArgumentError
  # Sanitize to default if it isn't parsable as an integer
  default
end

.stats(arg = nil) ⇒ Object

Raises:

  • (ArgumentError)


117
118
119
120
121
122
123
# File 'lib/dalli/protocol/request_formatter.rb', line 117

def self.stats(arg = nil)
  raise ArgumentError, "Invalid stats argument: #{arg.inspect}" unless ALLOWED_STATS_ARGS.include?(arg)

  cmd = +'stats'
  cmd << " #{arg}" if arg && !arg.empty?
  cmd << TERMINATOR
end

.versionObject



104
105
106
# File 'lib/dalli/protocol/request_formatter.rb', line 104

def self.version
  "version#{TERMINATOR}"
end