1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
# frozen_string_literal: false
module Dalli
module Protocol
class Meta
##
# Class that encapsulates logic for formatting meta protocol requests
# to memcached.
##
class RequestFormatter
# 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
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
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
# 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.
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
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
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/ParameterLists
# rubocop:enable Metrics/PerceivedComplexity
def self.meta_noop
"mn#{TERMINATOR}"
end
def self.version
"version#{TERMINATOR}"
end
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
ALLOWED_STATS_ARGS = [nil, '', 'items', 'slabs', 'settings', 'reset'].freeze
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
def self.mode_to_token(mode)
case mode
when :add
'E'
when :replace
'R'
when :append
'A'
when :prepend
'P'
else
'S'
end
end
def self.cas_string(cas)
cas = parse_to_64_bit_int(cas, nil)
cas.nil? || cas.zero? ? '' : " C#{cas}"
end
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
end
end
end
end
|