]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | /* |
2 | * Licensed to the Apache Software Foundation (ASF) under one | |
3 | * or more contributor license agreements. See the NOTICE file | |
4 | * distributed with this work for additional information | |
5 | * regarding copyright ownership. The ASF licenses this file | |
6 | * to you under the Apache License, Version 2.0 (the | |
7 | * "License"); you may not use this file except in compliance | |
8 | * with the License. You may obtain a copy of the License at | |
9 | * | |
10 | * http://www.apache.org/licenses/LICENSE-2.0 | |
11 | * | |
12 | * Unless required by applicable law or agreed to in writing, | |
13 | * software distributed under the License is distributed on an | |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
15 | * KIND, either express or implied. See the License for the | |
16 | * specific language governing permissions and limitations | |
17 | * under the License. | |
18 | */ | |
19 | ||
20 | #include <thrift/concurrency/TimerManager.h> | |
21 | #include <thrift/concurrency/ThreadFactory.h> | |
22 | #include <thrift/concurrency/Monitor.h> | |
23 | ||
24 | #include <assert.h> | |
25 | #include <chrono> | |
26 | #include <thread> | |
27 | #include <iostream> | |
28 | ||
29 | namespace apache { | |
30 | namespace thrift { | |
31 | namespace concurrency { | |
32 | namespace test { | |
33 | ||
34 | using namespace apache::thrift::concurrency; | |
35 | ||
36 | class TimerManagerTests { | |
37 | ||
38 | public: | |
39 | class Task : public Runnable { | |
40 | public: | |
41 | Task(Monitor& monitor, uint64_t timeout) | |
42 | : _timeout(timeout), | |
43 | _startTime(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count()), | |
44 | _endTime(0), | |
45 | _monitor(monitor), | |
46 | _success(false), | |
47 | _done(false) {} | |
48 | ||
49 | ~Task() override { std::cerr << this << std::endl; } | |
50 | ||
51 | void run() override { | |
52 | ||
53 | _endTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count(); | |
54 | _success = (_endTime - _startTime) >= _timeout; | |
55 | ||
56 | { | |
57 | Synchronized s(_monitor); | |
58 | _done = true; | |
59 | _monitor.notifyAll(); | |
60 | } | |
61 | } | |
62 | ||
63 | int64_t _timeout; | |
64 | int64_t _startTime; | |
65 | int64_t _endTime; | |
66 | Monitor& _monitor; | |
67 | bool _success; | |
68 | bool _done; | |
69 | }; | |
70 | ||
71 | /** | |
72 | * This test creates two tasks and waits for the first to expire within 10% | |
73 | * of the expected expiration time. It then verifies that the timer manager | |
74 | * properly clean up itself and the remaining orphaned timeout task when the | |
75 | * manager goes out of scope and its destructor is called. | |
76 | */ | |
77 | bool test00(uint64_t timeout = 1000LL) { | |
78 | ||
79 | shared_ptr<TimerManagerTests::Task> orphanTask | |
80 | = shared_ptr<TimerManagerTests::Task>(new TimerManagerTests::Task(_monitor, 10 * timeout)); | |
81 | ||
82 | { | |
83 | TimerManager timerManager; | |
84 | timerManager.threadFactory(shared_ptr<ThreadFactory>(new ThreadFactory())); | |
85 | timerManager.start(); | |
86 | if (timerManager.state() != TimerManager::STARTED) { | |
87 | std::cerr << "timerManager is not in the STARTED state, but should be" << std::endl; | |
88 | return false; | |
89 | } | |
90 | ||
91 | // Don't create task yet, because its constructor sets the expected completion time, and we | |
92 | // need to delay between inserting the two tasks into the run queue. | |
93 | shared_ptr<TimerManagerTests::Task> task; | |
94 | ||
95 | { | |
96 | Synchronized s(_monitor); | |
97 | timerManager.add(orphanTask, 10 * timeout); | |
98 | ||
99 | std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); | |
100 | ||
101 | task.reset(new TimerManagerTests::Task(_monitor, timeout)); | |
102 | timerManager.add(task, timeout); | |
103 | _monitor.wait(); | |
104 | } | |
105 | ||
106 | if (!task->_done) { | |
107 | std::cerr << "task is not done, but it should have executed" << std::endl; | |
108 | return false; | |
109 | } | |
110 | ||
111 | std::cout << "\t\t\t" << (task->_success ? "Success" : "Failure") << "!" << std::endl; | |
112 | } | |
113 | ||
114 | if (orphanTask->_done) { | |
115 | std::cerr << "orphan task is done, but it should not have executed" << std::endl; | |
116 | return false; | |
117 | } | |
118 | ||
119 | return true; | |
120 | } | |
121 | ||
122 | /** | |
123 | * This test creates two tasks, removes the first one then waits for the second one. It then | |
124 | * verifies that the timer manager properly clean up itself and the remaining orphaned timeout | |
125 | * task when the manager goes out of scope and its destructor is called. | |
126 | */ | |
127 | bool test01(uint64_t timeout = 1000LL) { | |
128 | TimerManager timerManager; | |
129 | timerManager.threadFactory(shared_ptr<ThreadFactory>(new ThreadFactory())); | |
130 | timerManager.start(); | |
131 | assert(timerManager.state() == TimerManager::STARTED); | |
132 | ||
133 | Synchronized s(_monitor); | |
134 | ||
135 | // Setup the two tasks | |
136 | shared_ptr<TimerManagerTests::Task> taskToRemove | |
137 | = shared_ptr<TimerManagerTests::Task>(new TimerManagerTests::Task(_monitor, timeout / 2)); | |
138 | timerManager.add(taskToRemove, taskToRemove->_timeout); | |
139 | ||
140 | shared_ptr<TimerManagerTests::Task> task | |
141 | = shared_ptr<TimerManagerTests::Task>(new TimerManagerTests::Task(_monitor, timeout)); | |
142 | timerManager.add(task, task->_timeout); | |
143 | ||
144 | // Remove one task and wait until the other has completed | |
145 | timerManager.remove(taskToRemove); | |
146 | _monitor.wait(timeout * 2); | |
147 | ||
148 | assert(!taskToRemove->_done); | |
149 | assert(task->_done); | |
150 | ||
151 | return true; | |
152 | } | |
153 | ||
154 | /** | |
155 | * This test creates two tasks with the same callback and another one, then removes the two | |
156 | * duplicated then waits for the last one. It then verifies that the timer manager properly | |
157 | * clean up itself and the remaining orphaned timeout task when the manager goes out of scope | |
158 | * and its destructor is called. | |
159 | */ | |
160 | bool test02(uint64_t timeout = 1000LL) { | |
161 | TimerManager timerManager; | |
162 | timerManager.threadFactory(shared_ptr<ThreadFactory>(new ThreadFactory())); | |
163 | timerManager.start(); | |
164 | assert(timerManager.state() == TimerManager::STARTED); | |
165 | ||
166 | Synchronized s(_monitor); | |
167 | ||
168 | // Setup the one tasks and add it twice | |
169 | shared_ptr<TimerManagerTests::Task> taskToRemove | |
170 | = shared_ptr<TimerManagerTests::Task>(new TimerManagerTests::Task(_monitor, timeout / 3)); | |
171 | timerManager.add(taskToRemove, taskToRemove->_timeout); | |
172 | timerManager.add(taskToRemove, taskToRemove->_timeout * 2); | |
173 | ||
174 | shared_ptr<TimerManagerTests::Task> task | |
175 | = shared_ptr<TimerManagerTests::Task>(new TimerManagerTests::Task(_monitor, timeout)); | |
176 | timerManager.add(task, task->_timeout); | |
177 | ||
178 | // Remove the first task (e.g. two timers) and wait until the other has completed | |
179 | timerManager.remove(taskToRemove); | |
180 | _monitor.wait(timeout * 2); | |
181 | ||
182 | assert(!taskToRemove->_done); | |
183 | assert(task->_done); | |
184 | ||
185 | return true; | |
186 | } | |
187 | ||
188 | /** | |
189 | * This test creates two tasks, removes the first one then waits for the second one. It then | |
190 | * verifies that the timer manager properly clean up itself and the remaining orphaned timeout | |
191 | * task when the manager goes out of scope and its destructor is called. | |
192 | */ | |
193 | bool test03(uint64_t timeout = 1000LL) { | |
194 | TimerManager timerManager; | |
195 | timerManager.threadFactory(shared_ptr<ThreadFactory>(new ThreadFactory())); | |
196 | timerManager.start(); | |
197 | assert(timerManager.state() == TimerManager::STARTED); | |
198 | ||
199 | Synchronized s(_monitor); | |
200 | ||
201 | // Setup the two tasks | |
202 | shared_ptr<TimerManagerTests::Task> taskToRemove | |
203 | = shared_ptr<TimerManagerTests::Task>(new TimerManagerTests::Task(_monitor, timeout / 2)); | |
204 | TimerManager::Timer timer = timerManager.add(taskToRemove, taskToRemove->_timeout); | |
205 | ||
206 | shared_ptr<TimerManagerTests::Task> task | |
207 | = shared_ptr<TimerManagerTests::Task>(new TimerManagerTests::Task(_monitor, timeout)); | |
208 | timerManager.add(task, task->_timeout); | |
209 | ||
210 | // Remove one task and wait until the other has completed | |
211 | timerManager.remove(timer); | |
212 | _monitor.wait(timeout * 2); | |
213 | ||
214 | assert(!taskToRemove->_done); | |
215 | assert(task->_done); | |
216 | ||
217 | // Verify behavior when removing the removed task | |
218 | try { | |
219 | timerManager.remove(timer); | |
220 | assert(nullptr == "ERROR: This remove should send a NoSuchTaskException exception."); | |
221 | } catch (NoSuchTaskException&) { | |
222 | } | |
223 | ||
224 | return true; | |
225 | } | |
226 | ||
227 | /** | |
228 | * This test creates one task, and tries to remove it after it has expired. | |
229 | */ | |
230 | bool test04(uint64_t timeout = 1000LL) { | |
231 | TimerManager timerManager; | |
232 | timerManager.threadFactory(shared_ptr<ThreadFactory>(new ThreadFactory())); | |
233 | timerManager.start(); | |
234 | assert(timerManager.state() == TimerManager::STARTED); | |
235 | ||
236 | Synchronized s(_monitor); | |
237 | ||
238 | // Setup the task | |
239 | shared_ptr<TimerManagerTests::Task> task | |
240 | = shared_ptr<TimerManagerTests::Task>(new TimerManagerTests::Task(_monitor, timeout / 10)); | |
241 | TimerManager::Timer timer = timerManager.add(task, task->_timeout); | |
242 | task.reset(); | |
243 | ||
244 | // Wait until the task has completed | |
245 | _monitor.wait(timeout); | |
246 | ||
247 | // Verify behavior when removing the expired task | |
248 | // notify is called inside the task so the task may still | |
249 | // be running when we get here, so we need to loop... | |
250 | for (;;) { | |
251 | try { | |
252 | timerManager.remove(timer); | |
253 | assert(nullptr == "ERROR: This remove should throw NoSuchTaskException, or UncancellableTaskException."); | |
254 | } catch (const NoSuchTaskException&) { | |
255 | break; | |
256 | } catch (const UncancellableTaskException&) { | |
257 | // the thread was still exiting; try again... | |
258 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); | |
259 | } | |
260 | } | |
261 | ||
262 | return true; | |
263 | } | |
264 | ||
265 | friend class TestTask; | |
266 | ||
267 | Monitor _monitor; | |
268 | }; | |
269 | ||
270 | } | |
271 | } | |
272 | } | |
273 | } // apache::thrift::concurrency |