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 class ConfigInfoReleaser : public Continuation
33 {
34 public:
ConfigInfoReleaser(unsigned int id,ConfigInfo * info)35   ConfigInfoReleaser(unsigned int id, ConfigInfo *info) : Continuation(new_ProxyMutex()), m_id(id), m_info(info)
36   {
37     SET_HANDLER(&ConfigInfoReleaser::handle_event);
38   }
39 
40   int
handle_event(int,void *)41   handle_event(int /* event ATS_UNUSED */, void * /* edata ATS_UNUSED */)
42   {
43     configProcessor.release(m_id, m_info);
44     delete this;
45     return EVENT_DONE;
46   }
47 
48 public:
49   unsigned int m_id;
50   ConfigInfo *m_info;
51 };
52 
53 unsigned int
set(unsigned int id,ConfigInfo * info,unsigned timeout_secs)54 ConfigProcessor::set(unsigned int id, ConfigInfo *info, unsigned timeout_secs)
55 {
56   ConfigInfo *old_info;
57   int idx;
58 
59   if (id == 0) {
60     id = ++ninfos;
61     ink_assert(id != 0);
62     ink_assert(id <= MAX_CONFIGS);
63   }
64 
65   // Don't be an idiot and use a zero timeout ...
66   ink_assert(timeout_secs > 0);
67 
68   // New objects *must* start with a zero refcount. The config
69   // processor holds it's own refcount. We should be the only
70   // refcount holder at this point.
71   ink_release_assert(info->refcount_inc() == 1);
72 
73   if (id > MAX_CONFIGS) {
74     // invalid index
75     Error("[ConfigProcessor::set] invalid index");
76     return 0;
77   }
78 
79   idx      = id - 1;
80   old_info = infos[idx].exchange(info);
81 
82   Debug("config", "Set for slot %d 0x%" PRId64 " was 0x%" PRId64 " with ref count %d", id, (int64_t)info, (int64_t)old_info,
83         (old_info) ? old_info->refcount() : 0);
84 
85   if (old_info) {
86     // The ConfigInfoReleaser now takes our refcount, but
87     // some other thread might also have one ...
88     ink_assert(old_info->refcount() > 0);
89     eventProcessor.schedule_in(new ConfigInfoReleaser(id, old_info), HRTIME_SECONDS(timeout_secs));
90   }
91 
92   return id;
93 }
94 
95 ConfigInfo *
get(unsigned int id)96 ConfigProcessor::get(unsigned int id)
97 {
98   ConfigInfo *info;
99   int idx;
100 
101   ink_assert(id <= MAX_CONFIGS);
102 
103   if (id == 0 || id > MAX_CONFIGS) {
104     // because of an invalid index
105     return nullptr;
106   }
107 
108   idx  = id - 1;
109   info = infos[idx];
110 
111   // Hand out a refcount to the caller. We should still have out
112   // own refcount, so it should be at least 2.
113   ink_release_assert(info->refcount_inc() > 1);
114   return info;
115 }
116 
117 void
release(unsigned int id,ConfigInfo * info)118 ConfigProcessor::release(unsigned int id, ConfigInfo *info)
119 {
120   int idx;
121 
122   if (id == 0 || id > MAX_CONFIGS) {
123     // nothing to delete since we have an invalid index
124     ink_abort("released an invalid id '%u'", id);
125   }
126 
127   idx = id - 1;
128 
129   if (info && info->refcount_dec() == 0) {
130     // When we release, we should already have replaced this object in the index.
131     Debug("config", "Release config %d 0x%" PRId64, id, (int64_t)info);
132     ink_release_assert(info != this->infos[idx]);
133     delete info;
134   }
135 }
136 
137 #if TS_HAS_TESTS
138 
139 enum {
140   REGRESSION_CONFIG_FIRST  = 1, // last config in a sequence
141   REGRESSION_CONFIG_LAST   = 2, // last config in a sequence
142   REGRESSION_CONFIG_SINGLE = 4, // single-owner config
143 };
144 
145 struct RegressionConfig : public ConfigInfo {
146   static int nobjects; // count of outstanding RegressionConfig objects (not-reentrant)
147 
148   // DeferredCall is a simple function call wrapper that defers itself until the RegressionConfig
149   // object count drops below the specified count.
150   template <typename CallType> struct DeferredCall : public Continuation {
DeferredCallRegressionConfig::DeferredCall151     DeferredCall(int _r, CallType _c) : remain(_r), call(_c) { SET_HANDLER(&DeferredCall::handleEvent); }
152     int
handleEventRegressionConfig::DeferredCall153     handleEvent(int event ATS_UNUSED, Event *e)
154     {
155       if (RegressionConfig::nobjects > this->remain) {
156         e->schedule_in(HRTIME_MSECONDS(500));
157         return EVENT_CONT;
158       }
159 
160       call();
161       delete this;
162       return EVENT_DONE;
163     }
164 
165     int remain; // Number of remaining RegressionConfig objects to wait for.
166     CallType call;
167   };
168 
169   template <typename CallType>
170   static void
deferRegressionConfig171   defer(int count, CallType call)
172   {
173     eventProcessor.schedule_in(new RegressionConfig::DeferredCall<CallType>(count, call), HRTIME_MSECONDS(500));
174   }
175 
RegressionConfigRegressionConfig176   RegressionConfig(RegressionTest *r, int *ps, unsigned f) : test(r), pstatus(ps), flags(f)
177   {
178     if (this->flags & REGRESSION_CONFIG_SINGLE) {
179       TestBox box(this->test, this->pstatus);
180       box.check(this->refcount() == 1, "invalid refcount %d (should be 1)", this->refcount());
181     }
182 
183     ink_atomic_increment(&nobjects, 1);
184   }
185 
~RegressionConfigRegressionConfig186   ~RegressionConfig() override
187   {
188     TestBox box(this->test, this->pstatus);
189 
190     box.check(this->refcount() == 0, "invalid refcount %d (should be 0)", this->refcount());
191 
192     // If we are the last config to be scheduled, pass the test.
193     // Otherwise, verify that the test is still running ...
194     if (REGRESSION_CONFIG_LAST & flags) {
195       *this->pstatus = REGRESSION_TEST_PASSED;
196     } else {
197       box.check(*this->pstatus == REGRESSION_TEST_INPROGRESS, "intermediate config out of sequence, *pstatus is %d", *pstatus);
198     }
199 
200     ink_atomic_increment(&nobjects, -1);
201   }
202 
203   RegressionTest *test;
204   int *pstatus;
205   unsigned flags;
206 };
207 
208 int RegressionConfig::nobjects = 0;
209 
210 struct ProxyConfig_Set_Completion {
ProxyConfig_Set_CompletionProxyConfig_Set_Completion211   ProxyConfig_Set_Completion(int _id, RegressionConfig *_c) : configid(_id), config(_c) {}
212   void
operator ()ProxyConfig_Set_Completion213   operator()() const
214   {
215     // Push one more RegressionConfig to force the LAST-tagged one to get destroyed.
216     rprintf(config->test, "setting LAST config object %p\n", config);
217     configProcessor.set(configid, config, 1);
218   }
219 
220   int configid;
221   RegressionConfig *config;
222 };
223 
224 // Test that ConfigProcessor::set() correctly releases the old ConfigInfo after a timeout.
EXCLUSIVE_REGRESSION_TEST(ProxyConfig_Set)225 EXCLUSIVE_REGRESSION_TEST(ProxyConfig_Set)(RegressionTest *test, int /* atype ATS_UNUSED */, int *pstatus)
226 {
227   int configid = 0;
228 
229   *pstatus                   = REGRESSION_TEST_INPROGRESS;
230   RegressionConfig::nobjects = 0;
231 
232   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
233   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
234   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
235   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
236   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
237   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
238   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_LAST), 1);
239 
240   // Wait until there's only 2 objects remaining, the one in ConfigProcessor, and the one we make here.
241   RegressionConfig::defer(2, ProxyConfig_Set_Completion(configid, new RegressionConfig(test, pstatus, 0)));
242 }
243 
244 struct ProxyConfig_Release_Completion {
ProxyConfig_Release_CompletionProxyConfig_Release_Completion245   ProxyConfig_Release_Completion(int _id, RegressionConfig *_c) : configid(_id), config(_c) {}
246   void
operator ()ProxyConfig_Release_Completion247   operator()() const
248   {
249     // Release the reference count. Since we were keeping this alive, it should be the last to die.
250     configProcessor.release(configid, config);
251   }
252 
253   int configid;
254   RegressionConfig *config;
255 };
256 
257 // Test that ConfigProcessor::release() correctly releases the old ConfigInfo across an implicit
258 // release timeout.
EXCLUSIVE_REGRESSION_TEST(ProxyConfig_Release)259 EXCLUSIVE_REGRESSION_TEST(ProxyConfig_Release)(RegressionTest *test, int /* atype ATS_UNUSED */, int *pstatus)
260 {
261   int configid = 0;
262   RegressionConfig *config;
263 
264   *pstatus                   = REGRESSION_TEST_INPROGRESS;
265   RegressionConfig::nobjects = 0;
266 
267   // Set an initial config, then get it back to hold a reference count.
268   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_LAST), 1);
269   config   = (RegressionConfig *)configProcessor.get(configid);
270 
271   // Now update the config a few times.
272   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
273   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
274   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, REGRESSION_CONFIG_FIRST), 1);
275 
276   configid = configProcessor.set(configid, new RegressionConfig(test, pstatus, 0), 1);
277 
278   // Defer the release of the object that we held back until there are only 2 left. The one we are holding
279   // and the one in the ConfigProcessor. Then releasing the one we hold will trigger the LAST check
280   RegressionConfig::defer(2, ProxyConfig_Release_Completion(configid, config));
281 }
282 
283 #endif /* TS_HAS_TESTS */
284