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

ALLOWED_STATS_ARGS =
[nil, '', 'items', 'slabs', 'settings', 'reset'].freeze

Class Method Summary collapse

Class Method Details

.cas_string(cas) ⇒ Object



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

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



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

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

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



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

def self.meta_noop
  "mn#{TERMINATOR}"
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



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

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



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

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)


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

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



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

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