1/*
2 * Copyright 2018-present Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16#include <folly/experimental/settings/Settings.h>
17
18#include <map>
19
20#include <folly/Synchronized.h>
21
22namespace folly {
23namespace settings {
24namespace detail {
25namespace {
26using SettingsMap = std::map<std::string, SettingCoreBase*>;
27Synchronized<SettingsMap>& settingsMap() {
28 static Indestructible<Synchronized<SettingsMap>> map;
29 return *map;
30}
31} // namespace
32
33void registerSetting(SettingCoreBase& core) {
34 if (core.meta().project.empty() ||
35 core.meta().project.find('_') != std::string::npos) {
36 throw std::logic_error(
37 "Setting project must be nonempty and cannot contain underscores: " +
38 core.meta().project.str());
39 }
40
41 auto fullname = core.meta().project.str() + "_" + core.meta().name.str();
42
43 auto mapPtr = settingsMap().wlock();
44 auto it = mapPtr->find(fullname);
45 if (it != mapPtr->end()) {
46 throw std::logic_error("FOLLY_SETTING already exists: " + fullname);
47 }
48 mapPtr->emplace(std::move(fullname), &core);
49}
50
51} // namespace detail
52
53Optional<SettingMetadata> getSettingsMeta(StringPiece settingName) {
54 auto mapPtr = detail::settingsMap().rlock();
55 auto it = mapPtr->find(settingName.str());
56 if (it == mapPtr->end()) {
57 return none;
58 }
59 return it->second->meta();
60}
61
62bool Snapshot::setFromString(
63 StringPiece settingName,
64 StringPiece newValue,
65 StringPiece reason) {
66 auto mapPtr = detail::settingsMap().rlock();
67 auto it = mapPtr->find(settingName.str());
68 if (it == mapPtr->end()) {
69 return false;
70 }
71 it->second->setFromString(newValue, reason, this);
72 return true;
73}
74
75Optional<Snapshot::SettingsInfo> Snapshot::getAsString(
76 StringPiece settingName) const {
77 auto mapPtr = detail::settingsMap().rlock();
78 auto it = mapPtr->find(settingName.str());
79 if (it == mapPtr->end()) {
80 return none;
81 }
82 return it->second->getAsString(this);
83}
84
85bool Snapshot::resetToDefault(StringPiece settingName) {
86 auto mapPtr = detail::settingsMap().rlock();
87 auto it = mapPtr->find(settingName.str());
88 if (it == mapPtr->end()) {
89 return false;
90 }
91 it->second->resetToDefault(this);
92 return true;
93}
94
95void Snapshot::forEachSetting(
96 const std::function<void(const SettingMetadata&, StringPiece, StringPiece)>&
97 func) const {
98 detail::SettingsMap map;
99 /* Note that this won't hold the lock over the callback, which is
100 what we want since the user might call other settings:: APIs */
101 map = *detail::settingsMap().rlock();
102 for (const auto& kv : map) {
103 auto value = kv.second->getAsString(this);
104 func(kv.second->meta(), value.first, value.second);
105 }
106}
107
108namespace detail {
109std::atomic<SettingCoreBase::Version> gGlobalVersion_;
110
111auto& getSavedValuesMutex() {
112 static SharedMutex gSavedValuesMutex;
113 return gSavedValuesMutex;
114}
115
116/* Version -> (count of outstanding snapshots, saved setting values) */
117auto& getSavedValues() {
118 static std::unordered_map<
119 SettingCoreBase::Version,
120 std::pair<size_t, std::unordered_map<SettingCoreBase::Key, BoxedValue>>>
121 gSavedValues;
122 return gSavedValues;
123}
124
125SettingCoreBase::Version nextGlobalVersion() {
126 return gGlobalVersion_.fetch_add(1) + 1;
127}
128
129void saveValueForOutstandingSnapshots(
130 SettingCoreBase::Key settingKey,
131 SettingCoreBase::Version version,
132 const BoxedValue& value) {
133 SharedMutex::WriteHolder lg(getSavedValuesMutex());
134 for (auto& it : getSavedValues()) {
135 if (version <= it.first) {
136 it.second.second[settingKey] = value;
137 }
138 }
139}
140
141const BoxedValue* FOLLY_NULLABLE
142getSavedValue(SettingCoreBase::Key settingKey, SettingCoreBase::Version at) {
143 SharedMutex::ReadHolder lg(getSavedValuesMutex());
144 auto it = getSavedValues().find(at);
145 if (it != getSavedValues().end()) {
146 auto jt = it->second.second.find(settingKey);
147 if (jt != it->second.second.end()) {
148 return &jt->second;
149 }
150 }
151 return nullptr;
152}
153
154SnapshotBase::SnapshotBase() {
155 SharedMutex::WriteHolder lg(detail::getSavedValuesMutex());
156 at_ = detail::gGlobalVersion_.load();
157 auto it = detail::getSavedValues().emplace(
158 std::piecewise_construct,
159 std::forward_as_tuple(at_),
160 std::forward_as_tuple());
161 ++it.first->second.first;
162}
163
164SnapshotBase::~SnapshotBase() {
165 SharedMutex::WriteHolder lg(detail::getSavedValuesMutex());
166 auto it = detail::getSavedValues().find(at_);
167 assert(it != detail::getSavedValues().end());
168 --it->second.first;
169 if (!it->second.first) {
170 detail::getSavedValues().erase(at_);
171 }
172}
173
174} // namespace detail
175
176void Snapshot::publish() {
177 for (auto& it : snapshotValues_) {
178 it.second.publish();
179 }
180}
181
182} // namespace settings
183} // namespace folly
184