1 // Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 #include <config.h>
8
9 #include <util/watched_thread.h>
10
11 #include <gtest/gtest.h>
12
13 #include <atomic>
14 #include <functional>
15 #include <signal.h>
16 #include <unistd.h>
17
18 using namespace std;
19 using namespace isc;
20 using namespace isc::util;
21
22 namespace {
23
24 /// @brief Test Fixture for testing @c isc::util::WatchedThread
25 class WatchedThreadTest : public ::testing::Test {
26 public:
27 /// @brief Maximum number of passes allowed in worker event loop
28 static const int WORKER_MAX_PASSES;
29
30 /// @brief Constructor.
WatchedThreadTest()31 WatchedThreadTest() {}
32
33 /// @brief Destructor.
~WatchedThreadTest()34 ~WatchedThreadTest() {
35 }
36
37 /// @brief Sleeps for a given number of event periods sleep
38 /// Each period is 50 ms.
nap(int periods)39 void nap(int periods) {
40 usleep(periods * 50 * 1000);
41 };
42
43 /// @brief Worker function to be used by the WatchedThread's thread
44 ///
45 /// The function runs 10 passes through an "event" loop.
46 /// On each pass:
47 /// - check terminate command
48 /// - instigate the desired event (second pass only)
49 /// - naps for 1 period (50ms)
50 ///
51 /// @param watch_type type of event that should occur
worker(WatchedThread::WatchType watch_type)52 void worker(WatchedThread::WatchType watch_type) {
53 sigset_t nsset;
54 pthread_sigmask(SIG_SETMASK, 0, &nsset);
55 EXPECT_EQ(1, sigismember(&nsset, SIGCHLD));
56 EXPECT_EQ(1, sigismember(&nsset, SIGINT));
57 EXPECT_EQ(1, sigismember(&nsset, SIGHUP));
58 EXPECT_EQ(1, sigismember(&nsset, SIGTERM));
59 for (passes_ = 1; passes_ < WORKER_MAX_PASSES; ++passes_) {
60
61 // Stop if we're told to do it.
62 if (wthread_->shouldTerminate()) {
63 return;
64 }
65
66 // On the second pass, set the event.
67 if (passes_ == 2) {
68 switch (watch_type) {
69 case WatchedThread::ERROR:
70 wthread_->setError("we have an error");
71 break;
72 case WatchedThread::READY:
73 wthread_->markReady(watch_type);
74 break;
75 case WatchedThread::TERMINATE:
76 default:
77 // Do nothing, we're waiting to be told to stop.
78 break;
79 }
80 }
81
82 // Take a nap.
83 nap(1);
84 }
85
86 // Indicate why we stopped.
87 wthread_->setError("thread expired");
88 }
89
90 /// @brief Current WatchedThread instance.
91 WatchedThreadPtr wthread_;
92
93 /// @brief Counter used to track the number of passes made
94 /// within the thread worker function.
95 std::atomic<int> passes_;
96 };
97
98 const int WatchedThreadTest::WORKER_MAX_PASSES = 10;
99
100 /// Verifies the basic operation of the WatchedThread class.
101 /// It checks that a WatchedThread can be created, can be stopped,
102 /// and that in set and clear sockets.
TEST_F(WatchedThreadTest,watchedThreadClassBasics)103 TEST_F(WatchedThreadTest, watchedThreadClassBasics) {
104
105 /// We'll create a WatchedThread and let it run until it expires. (Note this is more
106 /// of a test of WatchedThreadTest itself and ensures that the assumptions made in
107 /// our other tests as to why threads have finished are sound.
108 wthread_.reset(new WatchedThread());
109 ASSERT_FALSE(wthread_->isRunning());
110 wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::TERMINATE));
111 ASSERT_TRUE(wthread_->isRunning());
112
113 // Wait more long enough (we hope) for the thread to expire.
114 nap(WORKER_MAX_PASSES * 4);
115
116 // It should have done the maximum number of passes.
117 EXPECT_EQ(passes_, WORKER_MAX_PASSES);
118
119 // Error should be ready and error text should be "thread expired".
120 ASSERT_TRUE(wthread_->isReady(WatchedThread::ERROR));
121 ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
122 ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
123 EXPECT_EQ("thread expired", wthread_->getLastError());
124
125 // Thread is technically still running, so let's stop it.
126 EXPECT_TRUE(wthread_->isRunning());
127 ASSERT_NO_THROW(wthread_->stop());
128 ASSERT_FALSE(wthread_->isRunning());
129
130 /// Now we'll test stopping a thread.
131 /// Start the WatchedThread, let it run a little and then tell it to stop.
132 wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::TERMINATE));
133 ASSERT_TRUE(wthread_->isRunning());
134
135 // No watches should be ready.
136 ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
137 ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
138 ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
139
140 // Wait a little while.
141 nap(3);
142
143 // Tell it to stop.
144 wthread_->stop();
145 ASSERT_FALSE(wthread_->isRunning());
146
147 // It should have done less than the maximum number of passes.
148 EXPECT_LT(passes_, WORKER_MAX_PASSES);
149
150 // No watches should be ready. Error text should be "thread stopped".
151 ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
152 ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
153 ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
154 EXPECT_EQ("thread stopped", wthread_->getLastError());
155
156
157 // Next we'll test error notification.
158 // Start the WatchedThread with a thread that sets an error on the second pass.
159 wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::ERROR));
160 ASSERT_TRUE(wthread_->isRunning());
161
162 // No watches should be ready.
163 ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
164 ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
165 ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
166
167 // Wait a little while.
168 nap(6);
169
170 // It should now indicate an error.
171 ASSERT_TRUE(wthread_->isReady(WatchedThread::ERROR));
172 EXPECT_EQ("we have an error", wthread_->getLastError());
173
174 // Tell it to stop.
175 wthread_->stop();
176 ASSERT_FALSE(wthread_->isRunning());
177
178 // It should have done less than the maximum number of passes.
179 EXPECT_LT(passes_, WORKER_MAX_PASSES);
180
181 // No watches should be ready. Error text should be "thread stopped".
182 ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
183 ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
184 ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
185 EXPECT_EQ("thread stopped", wthread_->getLastError());
186
187
188 // Finally, we'll test data ready notification.
189 // We'll start the WatchedThread with a thread that indicates data ready on its second pass.
190 wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::READY));
191 ASSERT_TRUE(wthread_->isRunning());
192
193 // No watches should be ready.
194 ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
195 ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
196 ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
197
198 // Wait a little while.
199 nap(6);
200
201 // It should now indicate data ready.
202 ASSERT_TRUE(wthread_->isReady(WatchedThread::READY));
203
204 // Tell it to stop.
205 wthread_->stop();
206 ASSERT_FALSE(wthread_->isRunning());
207
208 // It should have done less than the maximum number of passes.
209 EXPECT_LT(passes_, WORKER_MAX_PASSES);
210
211 // No watches should be ready. Error text should be "thread stopped".
212 ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
213 ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
214 ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
215 EXPECT_EQ("thread stopped", wthread_->getLastError());
216 }
217
218 }
219