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