xref: /trafficserver/mgmt/ProxyConfig.cc (revision 4cfd5a73)
1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #include "ProxyConfig.h"
25 #include "P_EventSystem.h"
26 #if TS_HAS_TESTS
27 #include "tscore/TestBox.h"
28 #endif
29 
30 ConfigProcessor configProcessor;
31 
32 void *
config_int_cb(void * data,void * value)33 config_int_cb(void *data, void *value)
34 {
35   *static_cast<int *>(data) = *static_cast<int64_t *>(value);
36   return nullptr;
37 }
38 
39 void *
config_float_cb(void * data,void * value)40 config_float_cb(void *data, void *value)
41 {
42   *static_cast<float *>(data) = *static_cast<float *>(value);
43   return nullptr;
44 }
45 
46 void *
config_long_long_cb(void * data,void * value)47 config_long_long_cb(void *data, void *value)
48 {
49   *static_cast<int64_t *>(data) = *static_cast<int64_t *>(value);
50   return nullptr;
51 }
52 
53 /////////////////////////////////////////////////////////////
54 //
55 //  config_string_alloc_cb()
56 //
57 //  configuration callback function. The function is called
58 //  by the manager when a string configuration variable
59 //  changed. It allocates new memory for the new data.
60 //  the old variable is scheduled to be freed using
61 //  ConfigFreerContinuation which will free the memory
62 //  used for this variable after long time, assuming that
63 //  during all this time all the users of this memory will
64 //  disappear.
65 /////////////////////////////////////////////////////////////
66 void *
config_string_alloc_cb(void * data,void * value)67 config_string_alloc_cb(void *data, void *value)
68 {
69   char *_ss        = static_cast<char *>(value);
70   char *_new_value = nullptr;
71 
72 #if defined(DEBUG_CONFIG_STRING_UPDATE)
73   printf("config callback [new, old] = [%s : %s]\n", (_ss) ? (_ss) : (""), (*(char **)data) ? (*(char **)data) : (""));
74 #endif
75 
76   if (_ss) {
77     int len    = strlen(_ss);
78     _new_value = static_cast<char *>(ats_malloc(len + 1));
79     memcpy(_new_value, _ss, len + 1);
80   }
81 
82   char *_temp2                = *static_cast<char **>(data);
83   *static_cast<char **>(data) = _new_value;
84 
85   // free old data
86   if (_temp2 != nullptr) {
87     new_Freer(_temp2, HRTIME_DAY);
88   }
89 
90   return nullptr;
91 }
92 
93 class ConfigInfoReleaser : public Continuation
94 {
95 public:
ConfigInfoReleaser(unsigned int id,ConfigInfo * info)96   ConfigInfoReleaser(unsigned int id, ConfigInfo *info) : Continuation(new_ProxyMutex()), m_id(id), m_info(info)
97   {
98     SET_HANDLER(&ConfigInfoReleaser::handle_event);
99   }
100 
101   int
handle_event(int,void *)102   handle_event(int /* event ATS_UNUSED */, void * /* edata ATS_UNUSED */)
103   {
104     configProcessor.release(m_id, m_info);
105     delete this;
106     return EVENT_DONE;
107   }
108 
109 public:
110   unsigned int m_id;
111   ConfigInfo *m_info;
112 };
113 
114 unsigned int
set(unsigned int id,ConfigInfo * info,unsigned timeout_secs)115 ConfigProcessor::set(unsigned int id, ConfigInfo *info, unsigned timeout_secs)
116 {
117   ConfigInfo *old_info;
118   int idx;
119 
120   if (id == 0) {
121     id = ++ninfos;
122     ink_assert(id != 0);
123     ink_assert(id <= MAX_CONFIGS);
124   }
125 
126   // Don't be an idiot and use a zero timeout ...
127   ink_assert(timeout_secs > 0);
128 
129   // New objects *must* start with a zero refcount. The config
130   // processor holds it's own refcount. We should be the only
131   // refcount holder at this point.
132   ink_release_assert(info->refcount_inc() == 1);
133 
134   if (id > MAX_CONFIGS) {
135     // invalid index
136     Error("[ConfigProcessor::set] invalid index");
137     return 0;
138   }
139 
140   idx      = id - 1;
141   old_info = infos[idx].exchange(info);
142 
143   Debug("config", "Set for slot %d 0x%" PRId64 " was 0x%" PRId64 " with ref count %d", id, (int64_t)info, (int64_t)old_info,
144         (old_info) ? old_info->refcount() : 0);
145 
146   if (old_info) {
147     // The ConfigInfoReleaser now takes our refcount, but
148     // some other thread might also have one ...
149     ink_assert(old_info->refcount() > 0);
150     eventProcessor.schedule_in(new ConfigInfoReleaser(id, old_info), HRTIME_SECONDS(timeout_secs));
151   }
152 
153   return id;
154 }
155 
156 ConfigInfo *
get(unsigned int id)157 ConfigProcessor::get(unsigned int id)
158 {
159   ConfigInfo *info;
160   int idx;
161 
162   ink_assert(id <= MAX_CONFIGS);
163 
164   if (id == 0 || id > MAX_CONFIGS) {
165     // because of an invalid index
166     return nullptr;
167   }
168 
169   idx  = id - 1;
170   info = infos[idx];
171 
172   // Hand out a refcount to the caller. We should still have out
173   // own refcount, so it should be at least 2.
174   ink_release_assert(info->refcount_inc() > 1);
175   return info;
176 }
177 
178 void
release(unsigned int id,ConfigInfo * info)179 ConfigProcessor::release(unsigned int id, ConfigInfo *info)
180 {
181   int idx;
182 
183   if (id == 0 || id > MAX_CONFIGS) {
184     // nothing to delete since we have an invalid index
185     ink_abort("released an invalid id '%u'", id);
186   }
187 
188   idx = id - 1;
189 
190   if (info && info->refcount_dec() == 0) {
191     // When we release, we should already have replaced this object in the index.
192     Debug("config", "Release config %d 0x%" PRId64, id, (int64_t)info);
193     ink_release_assert(info != this->infos[idx]);
194     delete info;
195   }
196 }
197 
198 #if TS_HAS_TESTS
199 
200 enum {
201   REGRESSION_CONFIG_FIRST  = 1, // last config in a sequence
202   REGRESSION_CONFIG_LAST   = 2, // last config in a sequence
203   REGRESSION_CONFIG_SINGLE = 4, // single-owner config
204 };
205 
206 struct RegressionConfig : public ConfigInfo {
207   static int nobjects; // count of outstanding RegressionConfig objects (not-reentrant)
208 
209   // DeferredCall is a simple function call wrapper that defers itself until the RegressionConfig
210   // object count drops below the specified count.
211   template <typename CallType> struct DeferredCall : public Continuation {
DeferredCallRegressionConfig::DeferredCall212     DeferredCall(int _r, CallType _c) : remain(_r), call(_c) { SET_HANDLER(&DeferredCall::handleEvent); }
213     int
handleEventRegressionConfig::DeferredCall214     handleEvent(int event ATS_UNUSED, Event *e)
215     {
216       if (RegressionConfig::nobjects > this->remain) {
217         e->schedule_in(HRTIME_MSECONDS(500));
218         return EVENT_CONT;
219       }
220 
221       call();
222       delete this;
223       return EVENT_DONE;
224     }
225 
226     int remain; // Number of remaining RegressionConfig objects to wait for.
227     CallType call;
228   };
229 
230   template <typename CallType>
231   static void
deferRegressionConfig232   defer(int count, CallType call)
233   {
234     eventProcessor.schedule_in(new RegressionConfig::DeferredCall<CallType>(count, call), HRTIME_MSECONDS(500));
235   }
236 
RegressionConfigRegressionConfig237   RegressionConfig(RegressionTest *r, int *ps, unsigned f) : test(r), pstatus(ps), flags(f)
238   {
239     if (this->flags & REGRESSION_CONFIG_SINGLE) {
240       TestBox box(this->test, this->pstatus);
241       box.check(this->refcount() == 1, "invalid refcount %d (should be 1)", this->refcount());
242     }
243 
244     ink_atomic_increment(&nobjects, 1);
245   }
246 
~RegressionConfigRegressionConfig247   ~RegressionConfig() override
248   {
249     TestBox box(this->test, this->pstatus);
250 
251     box.check(this->refcount() == 0, "invalid refcount %d (should be 0)", this->refcount());
252 
253     // If we are the last config to be scheduled, pass the test.
254     // Otherwise, verify that the test is still running ...
255     if (REGRESSION_CONFIG_LAST & flags) {
256       *this->pstatus = REGRESSION_TEST_PASSED;
257     } else {
258       box.check(*this->pstatus == REGRESSION_TEST_INPROGRESS, "intermediate config out of sequence, *pstatus is %d", *pstatus);
259     }
260 
261     ink_atomic_increment(&nobjects, -1);
262   }
263 
264   RegressionTest *test;
265   int *pstatus;
266   unsigned flags;
267 };
268 
269 int RegressionConfig::nobjects = 0;
270 
271 struct ProxyConfig_Set_Completion {
ProxyConfig_Set_CompletionProxyConfig_Set_Completion272   ProxyConfig_Set_Completion(int _id, RegressionConfig *_c) : configid(_id), config(_c) {}
273   void
operator ()ProxyConfig_Set_Completion274   operator()() const
275   {
276     // Push one more RegressionConfig to force the LAST-tagged one to get destroyed.
277     rprintf(config->test, "setting LAST config object %p\n", config);
278     configProcessor.set(configid, config, 1);
279   }
280 
281   int configid;
282   RegressionConfig *config;
283 };
284 
285 // Test that ConfigProcessor::set() correctly releases the old ConfigInfo after a timeout.
EXCLUSIVE_REGRESSION_TEST(ProxyConfig_Set)286 EXCLUSIVE_REGRESSION_TEST(ProxyConfig_Set)(RegressionTest *test, int /* atype ATS_UNUSED */, int *pstatus)
287 {
288   int configid = 0;
289 
290   *pstatus                   = REGRESSION_TEST_INPROGRESS;
291   RegressionConfig::nobjects = 0;
292 
293   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
294   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
295   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
296   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
297   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
298   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
299   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_LAST), 1);
300 
301   // Wait until there's only 2 objects remaining, the one in ConfigProcessor, and the one we make here.
302   RegressionConfig::defer(2, ProxyConfig_Set_Completion(configid, new RegressionConfig(test, pstatus, 0)));
303 }
304 
305 struct ProxyConfig_Release_Completion {
ProxyConfig_Release_CompletionProxyConfig_Release_Completion306   ProxyConfig_Release_Completion(int _id, RegressionConfig *_c) : configid(_id), config(_c) {}
307   void
operator ()ProxyConfig_Release_Completion308   operator()() const
309   {
310     // Release the reference count. Since we were keeping this alive, it should be the last to die.
311     configProcessor.release(configid, config);
312   }
313 
314   int configid;
315   RegressionConfig *config;
316 };
317 
318 // Test that ConfigProcessor::release() correctly releases the old ConfigInfo across an implicit
319 // release timeout.
EXCLUSIVE_REGRESSION_TEST(ProxyConfig_Release)320 EXCLUSIVE_REGRESSION_TEST(ProxyConfig_Release)(RegressionTest *test, int /* atype ATS_UNUSED */, int *pstatus)
321 {
322   int configid = 0;
323   RegressionConfig *config;
324 
325   *pstatus                   = REGRESSION_TEST_INPROGRESS;
326   RegressionConfig::nobjects = 0;
327 
328   // Set an initial config, then get it back to hold a reference count.
329   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_LAST), 1);
330   config   = (RegressionConfig *)configProcessor.get(configid);
331 
332   // Now update the config a few times.
333   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
334   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
335   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
336 
337   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, 0), 1);
338 
339   // Defer the release of the object that we held back until there are only 2 left. The one we are holding
340   // and the one in the ConfigProcessor. Then releasing the one we hold will trigger the LAST check
341   RegressionConfig::defer(2, ProxyConfig_Release_Completion(configid, config));
342 }
343 
344 #endif /* TS_HAS_TESTS */
345