File: request_formatter.rb

package info (click to toggle)
ruby-dalli 5.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 992 kB
  • sloc: ruby: 9,447; sh: 19; makefile: 4
file content (153 lines) | stat: -rw-r--r-- 6,077 bytes parent folder | download
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