2017-05-26 04:48:44 +08:00
|
|
|
/*
|
|
|
|
* ReplicationPolicy.h
|
|
|
|
*
|
|
|
|
* This source file is part of the FoundationDB open source project
|
|
|
|
*
|
|
|
|
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
|
2018-02-22 02:25:11 +08:00
|
|
|
*
|
2017-05-26 04:48:44 +08:00
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
2018-02-22 02:25:11 +08:00
|
|
|
*
|
2017-05-26 04:48:44 +08:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2018-02-22 02:25:11 +08:00
|
|
|
*
|
2017-05-26 04:48:44 +08:00
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef FLOW_REPLICATION_POLICY_H
|
|
|
|
#define FLOW_REPLICATION_POLICY_H
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include "flow/flow.h"
|
2018-10-20 01:30:13 +08:00
|
|
|
#include "fdbrpc/ReplicationTypes.h"
|
2017-05-26 04:48:44 +08:00
|
|
|
|
|
|
|
template <class Ar>
|
2019-03-14 04:14:39 +08:00
|
|
|
void serializeReplicationPolicy(Ar& ar, Reference<IReplicationPolicy>& policy);
|
2017-05-26 04:48:44 +08:00
|
|
|
extern void testReplicationPolicy(int nTests);
|
|
|
|
|
|
|
|
struct IReplicationPolicy : public ReferenceCounted<IReplicationPolicy> {
|
2019-04-10 05:29:21 +08:00
|
|
|
IReplicationPolicy() {}
|
|
|
|
virtual ~IReplicationPolicy() {}
|
|
|
|
virtual std::string name() const = 0;
|
|
|
|
virtual std::string info() const = 0;
|
|
|
|
virtual void addref() { ReferenceCounted<IReplicationPolicy>::addref(); }
|
|
|
|
virtual void delref() { ReferenceCounted<IReplicationPolicy>::delref(); }
|
|
|
|
virtual int maxResults() const = 0;
|
|
|
|
virtual int depth() const = 0;
|
2021-03-11 02:06:03 +08:00
|
|
|
virtual bool selectReplicas(Reference<LocalitySet>& fromServers,
|
|
|
|
std::vector<LocalityEntry> const& alsoServers,
|
2019-04-10 05:29:21 +08:00
|
|
|
std::vector<LocalityEntry>& results) = 0;
|
|
|
|
virtual void traceLocalityRecords(Reference<LocalitySet> const& fromServers);
|
|
|
|
virtual void traceOneLocalityRecord(Reference<LocalityRecord> record, Reference<LocalitySet> const& fromServers);
|
|
|
|
virtual bool validate(std::vector<LocalityEntry> const& solutionSet,
|
|
|
|
Reference<LocalitySet> const& fromServers) const = 0;
|
|
|
|
|
|
|
|
bool operator==(const IReplicationPolicy& r) const { return info() == r.info(); }
|
|
|
|
bool operator!=(const IReplicationPolicy& r) const { return info() != r.info(); }
|
|
|
|
|
|
|
|
template <class Ar>
|
|
|
|
void serialize(Ar& ar) {
|
2019-04-13 05:30:46 +08:00
|
|
|
static_assert(!is_fb_function<Ar>);
|
2019-04-10 05:29:21 +08:00
|
|
|
Reference<IReplicationPolicy> refThis(this);
|
|
|
|
serializeReplicationPolicy(ar, refThis);
|
|
|
|
refThis->delref_no_destroy();
|
|
|
|
}
|
|
|
|
virtual void deserializationDone() = 0;
|
|
|
|
|
|
|
|
// Utility functions
|
|
|
|
bool selectReplicas(Reference<LocalitySet>& fromServers, std::vector<LocalityEntry>& results);
|
|
|
|
bool validate(Reference<LocalitySet> const& solutionSet) const;
|
2021-03-11 02:06:03 +08:00
|
|
|
bool validateFull(bool solved,
|
|
|
|
std::vector<LocalityEntry> const& solutionSet,
|
|
|
|
std::vector<LocalityEntry> const& alsoServers,
|
|
|
|
Reference<LocalitySet> const& fromServers);
|
2019-04-10 05:29:21 +08:00
|
|
|
|
|
|
|
// Returns a set of the attributes that this policy uses in selection and validation.
|
|
|
|
std::set<std::string> attributeKeys() const {
|
|
|
|
std::set<std::string> keys;
|
|
|
|
this->attributeKeys(&keys);
|
|
|
|
return keys;
|
|
|
|
}
|
|
|
|
virtual void attributeKeys(std::set<std::string>*) const = 0;
|
2017-05-26 04:48:44 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
template <class Archive>
|
2019-04-10 05:29:21 +08:00
|
|
|
inline void load(Archive& ar, Reference<IReplicationPolicy>& value) {
|
2019-03-16 01:34:57 +08:00
|
|
|
bool present;
|
2017-05-26 04:48:44 +08:00
|
|
|
ar >> present;
|
|
|
|
if (present) {
|
|
|
|
serializeReplicationPolicy(ar, value);
|
2019-04-10 05:29:21 +08:00
|
|
|
} else {
|
2017-05-26 04:48:44 +08:00
|
|
|
value.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
2019-04-10 05:29:21 +08:00
|
|
|
inline void save(Archive& ar, const Reference<IReplicationPolicy>& value) {
|
2019-03-16 01:34:57 +08:00
|
|
|
bool present = (value.getPtr() != nullptr);
|
2017-05-26 04:48:44 +08:00
|
|
|
ar << present;
|
|
|
|
if (present) {
|
2019-04-10 05:29:21 +08:00
|
|
|
serializeReplicationPolicy(ar, (Reference<IReplicationPolicy>&)value);
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-08 10:55:05 +08:00
|
|
|
struct PolicyOne final : IReplicationPolicy, public ReferenceCounted<PolicyOne> {
|
2019-04-10 05:29:21 +08:00
|
|
|
PolicyOne(){};
|
2019-01-29 11:38:13 +08:00
|
|
|
explicit PolicyOne(const PolicyOne& o) {}
|
2020-10-08 10:55:05 +08:00
|
|
|
std::string name() const override { return "One"; }
|
|
|
|
std::string info() const override { return "1"; }
|
|
|
|
int maxResults() const override { return 1; }
|
|
|
|
int depth() const override { return 1; }
|
|
|
|
bool validate(std::vector<LocalityEntry> const& solutionSet,
|
|
|
|
Reference<LocalitySet> const& fromServers) const override;
|
2021-03-11 02:06:03 +08:00
|
|
|
bool selectReplicas(Reference<LocalitySet>& fromServers,
|
|
|
|
std::vector<LocalityEntry> const& alsoServers,
|
2020-10-08 10:55:05 +08:00
|
|
|
std::vector<LocalityEntry>& results) override;
|
2017-05-26 04:48:44 +08:00
|
|
|
template <class Ar>
|
2019-04-13 05:30:46 +08:00
|
|
|
void serialize(Ar& ar) {
|
|
|
|
static_assert(!is_fb_function<Ar>);
|
|
|
|
}
|
2020-10-08 10:55:05 +08:00
|
|
|
void deserializationDone() override {}
|
|
|
|
void attributeKeys(std::set<std::string>* set) const override { return; }
|
2017-05-26 04:48:44 +08:00
|
|
|
};
|
|
|
|
|
2020-10-08 10:55:05 +08:00
|
|
|
struct PolicyAcross final : IReplicationPolicy, public ReferenceCounted<PolicyAcross> {
|
2019-01-29 11:38:13 +08:00
|
|
|
friend struct serializable_traits<PolicyAcross*>;
|
2019-04-10 05:29:21 +08:00
|
|
|
PolicyAcross(int count, std::string const& attribKey, Reference<IReplicationPolicy> const policy);
|
2019-01-29 11:38:13 +08:00
|
|
|
explicit PolicyAcross();
|
|
|
|
explicit PolicyAcross(const PolicyAcross& other) : PolicyAcross(other._count, other._attribKey, other._policy) {}
|
2021-01-26 09:55:43 +08:00
|
|
|
~PolicyAcross() override;
|
2020-10-08 10:55:05 +08:00
|
|
|
std::string name() const override { return "Across"; }
|
2020-10-09 00:31:19 +08:00
|
|
|
std::string embeddedPolicyName() const { return _policy->name(); }
|
2020-02-20 01:25:57 +08:00
|
|
|
int getCount() const { return _count; }
|
2020-10-08 10:55:05 +08:00
|
|
|
std::string info() const override { return format("%s^%d x ", _attribKey.c_str(), _count) + _policy->info(); }
|
|
|
|
int maxResults() const override { return _count * _policy->maxResults(); }
|
|
|
|
int depth() const override { return 1 + _policy->depth(); }
|
|
|
|
bool validate(std::vector<LocalityEntry> const& solutionSet,
|
|
|
|
Reference<LocalitySet> const& fromServers) const override;
|
2021-03-11 02:06:03 +08:00
|
|
|
bool selectReplicas(Reference<LocalitySet>& fromServers,
|
|
|
|
std::vector<LocalityEntry> const& alsoServers,
|
2020-10-08 10:55:05 +08:00
|
|
|
std::vector<LocalityEntry>& results) override;
|
2017-05-26 04:48:44 +08:00
|
|
|
|
|
|
|
template <class Ar>
|
|
|
|
void serialize(Ar& ar) {
|
2019-04-13 05:30:46 +08:00
|
|
|
static_assert(!is_fb_function<Ar>);
|
2018-12-29 02:49:26 +08:00
|
|
|
serializer(ar, _attribKey, _count);
|
2017-05-26 04:48:44 +08:00
|
|
|
serializeReplicationPolicy(ar, _policy);
|
|
|
|
}
|
|
|
|
|
2020-10-08 10:55:05 +08:00
|
|
|
void deserializationDone() override {}
|
2017-05-26 04:48:44 +08:00
|
|
|
|
2019-01-29 11:38:13 +08:00
|
|
|
static bool compareAddedResults(const std::pair<int, int>& rhs, const std::pair<int, int>& lhs) {
|
|
|
|
return (rhs.first < lhs.first) || (!(lhs.first < rhs.first) && (rhs.second < lhs.second));
|
|
|
|
}
|
|
|
|
|
2020-10-08 10:55:05 +08:00
|
|
|
void attributeKeys(std::set<std::string>* set) const override {
|
2019-01-29 11:38:13 +08:00
|
|
|
set->insert(_attribKey);
|
|
|
|
_policy->attributeKeys(set);
|
|
|
|
}
|
2017-11-16 13:05:10 +08:00
|
|
|
|
2021-04-27 01:16:18 +08:00
|
|
|
Reference<IReplicationPolicy> embeddedPolicy() const { return _policy; }
|
2021-04-21 15:22:33 +08:00
|
|
|
|
2021-04-27 01:16:18 +08:00
|
|
|
const std::string& attributeKey() const { return _attribKey; }
|
2021-04-21 15:22:33 +08:00
|
|
|
|
2017-05-26 04:48:44 +08:00
|
|
|
protected:
|
2019-04-10 05:29:21 +08:00
|
|
|
int _count;
|
|
|
|
std::string _attribKey;
|
|
|
|
Reference<IReplicationPolicy> _policy;
|
2017-05-26 04:48:44 +08:00
|
|
|
|
|
|
|
// Cache temporary members
|
2019-04-10 05:29:21 +08:00
|
|
|
std::vector<AttribValue> _usedValues;
|
|
|
|
std::vector<LocalityEntry> _newResults;
|
|
|
|
Reference<LocalitySet> _selected;
|
|
|
|
VectorRef<std::pair<int, int>> _addedResults;
|
|
|
|
Arena _arena;
|
2017-05-26 04:48:44 +08:00
|
|
|
};
|
|
|
|
|
2020-10-08 10:55:05 +08:00
|
|
|
struct PolicyAnd final : IReplicationPolicy, public ReferenceCounted<PolicyAnd> {
|
2019-01-29 11:38:13 +08:00
|
|
|
friend struct serializable_traits<PolicyAnd*>;
|
2019-04-10 05:29:21 +08:00
|
|
|
PolicyAnd(std::vector<Reference<IReplicationPolicy>> policies) : _policies(policies), _sortedPolicies(policies) {
|
2017-05-26 04:48:44 +08:00
|
|
|
// Sort the policy array
|
|
|
|
std::sort(_sortedPolicies.begin(), _sortedPolicies.end(), PolicyAnd::comparePolicy);
|
|
|
|
}
|
2019-01-29 11:38:13 +08:00
|
|
|
explicit PolicyAnd(const PolicyAnd& other) : _policies(other._policies), _sortedPolicies(other._sortedPolicies) {}
|
|
|
|
explicit PolicyAnd() {}
|
2020-10-08 10:55:05 +08:00
|
|
|
std::string name() const override { return "And"; }
|
|
|
|
std::string info() const override {
|
2019-04-10 05:29:21 +08:00
|
|
|
std::string infoText;
|
2017-05-26 04:48:44 +08:00
|
|
|
for (auto& policy : _policies) {
|
2019-04-10 05:29:21 +08:00
|
|
|
infoText += ((infoText.length()) ? " & (" : "(") + policy->info() + ")";
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
2021-03-11 02:06:03 +08:00
|
|
|
if (_policies.size())
|
|
|
|
infoText = "(" + infoText + ")";
|
2017-05-26 04:48:44 +08:00
|
|
|
return infoText;
|
|
|
|
}
|
2020-10-08 10:55:05 +08:00
|
|
|
int maxResults() const override {
|
2017-05-26 04:48:44 +08:00
|
|
|
int resultsMax = 0;
|
|
|
|
for (auto& policy : _policies) {
|
|
|
|
resultsMax += policy->maxResults();
|
|
|
|
}
|
|
|
|
return resultsMax;
|
|
|
|
}
|
2020-10-08 10:55:05 +08:00
|
|
|
int depth() const override {
|
2017-05-26 04:48:44 +08:00
|
|
|
int policyDepth, depthMax = 0;
|
|
|
|
for (auto& policy : _policies) {
|
|
|
|
policyDepth = policy->depth();
|
|
|
|
if (policyDepth > depthMax) {
|
|
|
|
depthMax = policyDepth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return depthMax;
|
|
|
|
}
|
2020-10-08 10:55:05 +08:00
|
|
|
bool validate(std::vector<LocalityEntry> const& solutionSet,
|
|
|
|
Reference<LocalitySet> const& fromServers) const override;
|
2017-05-26 04:48:44 +08:00
|
|
|
|
2021-03-11 02:06:03 +08:00
|
|
|
bool selectReplicas(Reference<LocalitySet>& fromServers,
|
|
|
|
std::vector<LocalityEntry> const& alsoServers,
|
2020-10-08 10:55:05 +08:00
|
|
|
std::vector<LocalityEntry>& results) override;
|
2017-05-26 04:48:44 +08:00
|
|
|
|
2019-04-10 05:29:21 +08:00
|
|
|
static bool comparePolicy(const Reference<IReplicationPolicy>& rhs, const Reference<IReplicationPolicy>& lhs) {
|
|
|
|
return (lhs->maxResults() < rhs->maxResults()) ||
|
|
|
|
(!(rhs->maxResults() < lhs->maxResults()) && (lhs->depth() < rhs->depth()));
|
|
|
|
}
|
2017-05-26 04:48:44 +08:00
|
|
|
|
|
|
|
template <class Ar>
|
|
|
|
void serialize(Ar& ar) {
|
2019-04-13 05:30:46 +08:00
|
|
|
static_assert(!is_fb_function<Ar>);
|
2017-05-26 04:48:44 +08:00
|
|
|
int count = _policies.size();
|
2018-12-29 02:49:26 +08:00
|
|
|
serializer(ar, count);
|
2017-05-26 04:48:44 +08:00
|
|
|
_policies.resize(count);
|
2019-04-10 05:29:21 +08:00
|
|
|
for (int i = 0; i < count; i++) {
|
2017-05-26 04:48:44 +08:00
|
|
|
serializeReplicationPolicy(ar, _policies[i]);
|
|
|
|
}
|
2019-04-10 05:29:21 +08:00
|
|
|
if (Ar::isDeserializing) {
|
2017-05-26 04:48:44 +08:00
|
|
|
_sortedPolicies = _policies;
|
|
|
|
std::sort(_sortedPolicies.begin(), _sortedPolicies.end(), PolicyAnd::comparePolicy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-08 10:55:05 +08:00
|
|
|
void deserializationDone() override {
|
2019-01-29 11:38:13 +08:00
|
|
|
_sortedPolicies = _policies;
|
|
|
|
std::sort(_sortedPolicies.begin(), _sortedPolicies.end(), PolicyAnd::comparePolicy);
|
|
|
|
}
|
|
|
|
|
2020-10-08 10:55:05 +08:00
|
|
|
void attributeKeys(std::set<std::string>* set) const override {
|
2019-04-10 05:29:21 +08:00
|
|
|
for (const Reference<IReplicationPolicy>& r : _policies) {
|
|
|
|
r->attributeKeys(set);
|
|
|
|
}
|
|
|
|
}
|
2017-11-16 13:05:10 +08:00
|
|
|
|
2017-05-26 04:48:44 +08:00
|
|
|
protected:
|
2019-04-10 05:29:21 +08:00
|
|
|
std::vector<Reference<IReplicationPolicy>> _policies;
|
|
|
|
std::vector<Reference<IReplicationPolicy>> _sortedPolicies;
|
2017-05-26 04:48:44 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
template <class Ar>
|
2019-03-14 04:14:39 +08:00
|
|
|
void serializeReplicationPolicy(Ar& ar, Reference<IReplicationPolicy>& policy) {
|
2021-03-11 02:06:03 +08:00
|
|
|
// To change this serialization, ProtocolVersion::ReplicationPolicy must be updated, and downgrades need to be
|
|
|
|
// considered
|
2019-04-10 05:29:21 +08:00
|
|
|
if (Ar::isDeserializing) {
|
2017-05-26 04:48:44 +08:00
|
|
|
StringRef name;
|
2018-12-29 02:49:26 +08:00
|
|
|
serializer(ar, name);
|
2017-05-26 04:48:44 +08:00
|
|
|
|
2019-04-10 05:29:21 +08:00
|
|
|
if (name == LiteralStringRef("One")) {
|
2017-05-26 04:48:44 +08:00
|
|
|
PolicyOne* pointer = new PolicyOne();
|
|
|
|
pointer->serialize(ar);
|
2019-03-14 04:14:39 +08:00
|
|
|
policy = Reference<IReplicationPolicy>(pointer);
|
2019-04-10 05:29:21 +08:00
|
|
|
} else if (name == LiteralStringRef("Across")) {
|
2019-03-14 04:14:39 +08:00
|
|
|
PolicyAcross* pointer = new PolicyAcross(0, "", Reference<IReplicationPolicy>());
|
2017-05-26 04:48:44 +08:00
|
|
|
pointer->serialize(ar);
|
2019-03-14 04:14:39 +08:00
|
|
|
policy = Reference<IReplicationPolicy>(pointer);
|
2019-04-10 05:29:21 +08:00
|
|
|
} else if (name == LiteralStringRef("And")) {
|
2019-01-31 05:53:23 +08:00
|
|
|
PolicyAnd* pointer = new PolicyAnd{};
|
2017-05-26 04:48:44 +08:00
|
|
|
pointer->serialize(ar);
|
2019-03-14 04:14:39 +08:00
|
|
|
policy = Reference<IReplicationPolicy>(pointer);
|
2019-04-10 05:29:21 +08:00
|
|
|
} else if (name == LiteralStringRef("None")) {
|
2019-03-14 04:14:39 +08:00
|
|
|
policy = Reference<IReplicationPolicy>();
|
2019-04-10 05:29:21 +08:00
|
|
|
} else {
|
2019-04-14 01:05:59 +08:00
|
|
|
TraceEvent(SevError, "SerializingInvalidPolicyType").detail("PolicyName", name);
|
2018-02-18 05:51:17 +08:00
|
|
|
}
|
2019-04-10 05:29:21 +08:00
|
|
|
} else {
|
2018-02-18 05:51:17 +08:00
|
|
|
std::string name = policy ? policy->name() : "None";
|
2017-05-26 04:48:44 +08:00
|
|
|
Standalone<StringRef> nameRef = StringRef(name);
|
2018-12-29 02:49:26 +08:00
|
|
|
serializer(ar, nameRef);
|
2019-04-10 05:29:21 +08:00
|
|
|
if (name == "One") {
|
2017-05-26 04:48:44 +08:00
|
|
|
((PolicyOne*)policy.getPtr())->serialize(ar);
|
2019-04-10 05:29:21 +08:00
|
|
|
} else if (name == "Across") {
|
2017-05-26 04:48:44 +08:00
|
|
|
((PolicyAcross*)policy.getPtr())->serialize(ar);
|
2019-04-10 05:29:21 +08:00
|
|
|
} else if (name == "And") {
|
2017-05-26 04:48:44 +08:00
|
|
|
((PolicyAnd*)policy.getPtr())->serialize(ar);
|
2019-04-10 05:29:21 +08:00
|
|
|
} else if (name == "None") {
|
|
|
|
} else {
|
|
|
|
TraceEvent(SevError, "SerializingInvalidPolicyType").detail("PolicyName", name);
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-13 05:30:46 +08:00
|
|
|
template <>
|
|
|
|
struct dynamic_size_traits<Reference<IReplicationPolicy>> : std::true_type {
|
2019-07-02 07:32:25 +08:00
|
|
|
private:
|
|
|
|
using T = Reference<IReplicationPolicy>;
|
|
|
|
|
|
|
|
public:
|
2019-07-16 03:58:31 +08:00
|
|
|
template <class Context>
|
|
|
|
static size_t size(const T& value, Context& context) {
|
2019-07-12 06:32:14 +08:00
|
|
|
// size gets called multiple times. If this becomes a performance problem, we can perform the
|
|
|
|
// serialization once and cache the result as a mutable member of IReplicationPolicy
|
2019-07-16 03:58:31 +08:00
|
|
|
BinaryWriter writer{ AssumeVersion(context.protocolVersion()) };
|
2019-07-12 06:32:14 +08:00
|
|
|
::save(writer, value);
|
|
|
|
return writer.getLength();
|
2019-06-20 02:30:35 +08:00
|
|
|
}
|
|
|
|
|
2019-07-02 07:32:25 +08:00
|
|
|
// Guaranteed to be called only once during serialization
|
2019-07-16 03:58:31 +08:00
|
|
|
template <class Context>
|
|
|
|
static void save(uint8_t* out, const T& value, Context& context) {
|
|
|
|
BinaryWriter writer{ AssumeVersion(context.protocolVersion()) };
|
2019-07-12 06:32:14 +08:00
|
|
|
::save(writer, value);
|
|
|
|
memcpy(out, writer.getData(), writer.getLength());
|
2019-04-13 05:30:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Context is an arbitrary type that is plumbed by reference throughout the
|
|
|
|
// load call tree.
|
|
|
|
template <class Context>
|
2019-07-16 03:58:31 +08:00
|
|
|
static void load(const uint8_t* buf, size_t sz, Reference<IReplicationPolicy>& value, Context& context) {
|
2019-04-13 05:30:46 +08:00
|
|
|
StringRef str(buf, sz);
|
2019-07-16 03:58:31 +08:00
|
|
|
BinaryReader reader(str, AssumeVersion(context.protocolVersion()));
|
2019-07-12 06:32:14 +08:00
|
|
|
::load(reader, value);
|
2019-04-13 05:30:46 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-05-26 04:48:44 +08:00
|
|
|
#endif
|