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
|
# frozen_string_literal: true
module Dalli
module Protocol
##
# Dalli::Protocol::ValueSerializer compartmentalizes the logic for managing
# serialization and deserialization of stored values. It manages interpreting
# relevant options from both client and request, determining whether to
# serialize/deserialize on store/retrieve, and processes bitflags as necessary.
##
class ValueSerializer
DEFAULTS = {
serializer: Marshal
}.freeze
OPTIONS = DEFAULTS.keys.freeze
# https://www.hjp.at/zettel/m/memcached_flags.rxml
# Looks like most clients use bit 0 to indicate native language serialization
FLAG_SERIALIZED = 0x1
attr_accessor :serialization_options
def initialize(protocol_options)
@serialization_options =
DEFAULTS.merge(protocol_options.select { |k, _| OPTIONS.include?(k) })
end
def store(value, req_options, bitflags)
do_serialize = !(req_options && req_options[:raw])
store_value = do_serialize ? serialize_value(value) : value.to_s
bitflags |= FLAG_SERIALIZED if do_serialize
[store_value, bitflags]
end
# TODO: Some of these error messages need to be validated. It's not obvious
# that all of them are actually generated by the invoked code
# in current systems
# rubocop:disable Layout/LineLength
TYPE_ERR_REGEXP = %r{needs to have method `_load'|exception class/object expected|instance of IO needed|incompatible marshal file format}.freeze
ARGUMENT_ERR_REGEXP = /undefined class|marshal data too short/.freeze
NAME_ERR_STR = 'uninitialized constant'
# rubocop:enable Layout/LineLength
def retrieve(value, bitflags)
serialized = (bitflags & FLAG_SERIALIZED) != 0
serialized ? serializer.load(value) : value
rescue TypeError => e
filter_type_error(e)
rescue ArgumentError => e
filter_argument_error(e)
rescue NameError => e
filter_name_error(e)
end
def filter_type_error(err)
raise err unless TYPE_ERR_REGEXP.match?(err.message)
raise UnmarshalError, "Unable to unmarshal value: #{err.message}"
end
def filter_argument_error(err)
raise err unless ARGUMENT_ERR_REGEXP.match?(err.message)
raise UnmarshalError, "Unable to unmarshal value: #{err.message}"
end
def filter_name_error(err)
raise err unless err.message.include?(NAME_ERR_STR)
raise UnmarshalError, "Unable to unmarshal value: #{err.message}"
end
def serializer
@serialization_options[:serializer]
end
def serialize_value(value)
serializer.dump(value)
rescue Timeout::Error => e
raise e
rescue StandardError => e
# Serializing can throw several different types of generic Ruby exceptions.
# Convert to a specific exception so we can special case it higher up the stack.
exc = Dalli::MarshalError.new(e.message)
exc.set_backtrace e.backtrace
raise exc
end
end
end
end
|