From: Thomas Markwalder Date: Fri, 7 May 2021 16:02:25 +0000 (-0400) Subject: [#1818] Added pause and resume to HAService X-Git-Tag: Kea-1.9.8~89 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3824f1f886ea5ca8bcda87690495a122b658c0dc;p=thirdparty%2Fkea.git [#1818] Added pause and resume to HAService HAService defers client start, and can now pause and resume client and listener src/hooks/dhcp/high_availability/ha_service.* HAService::HAService() - client start is now deferred HAService::startClientAndListener() - now starts client too HAService::pauseClientAndListener() HAService::resumeClientAndListener() - new methods src/hooks/dhcp/high_availability/tests/ha_mt_unittest.cc TEST_F(HAMtServiceTest, multiThreadingBasics) - new test that verifies start,pause,resume and stop src/lib/config/cmd_http_listener.* CmdHttpListener: Renamed io_service_ to thread_io_service_ Added run state convenience methods Deleted isListening() method src/lib/config/tests/cmd_http_listener_unittests.cc Updated tests src/lib/http/client.* Clean up Added run state convenience methods src/lib/http/tests/mt_client_unittests.cc Updated tests --- diff --git a/src/hooks/dhcp/high_availability/ha_service.cc b/src/hooks/dhcp/high_availability/ha_service.cc index 8536f3cfb6..55f46d1b75 100644 --- a/src/hooks/dhcp/high_availability/ha_service.cc +++ b/src/hooks/dhcp/high_availability/ha_service.cc @@ -73,9 +73,9 @@ HAService::HAService(const IOServicePtr& io_service, const NetworkStatePtr& netw // Not configured for multi-threading, start a client in ST mode. client_.reset(new HttpClient(*io_service_, 0)); } else { - // Start a client in MT mode. + // Create an MT-mode client. client_.reset(new HttpClient(*io_service_, - config_->getHttpClientThreads())); + config_->getHttpClientThreads(), true)); // If we're configured to use our own listener create and start it. if (config_->getHttpDedicatedListener()) { @@ -2815,11 +2815,37 @@ HAService::getPendingRequestInternal(const QueryPtrType& query) { void HAService::startClientAndListener() { + if (client_) { + client_->start(); + } + if (listener_) { listener_->start(); } } +void +HAService::pauseClientAndListener() { + if (client_) { + client_->pause(); + } + + if (listener_) { + listener_->pause(); + } +} + +void +HAService::resumeClientAndListener() { + if (client_) { + client_->resume(); + } + + if (listener_) { + listener_->resume(); + } +} + void HAService::stopClientAndListener() { if (client_) { diff --git a/src/hooks/dhcp/high_availability/ha_service.h b/src/hooks/dhcp/high_availability/ha_service.h index ff91817d29..9c430056e0 100644 --- a/src/hooks/dhcp/high_availability/ha_service.h +++ b/src/hooks/dhcp/high_availability/ha_service.h @@ -1004,19 +1004,28 @@ public: /// @brief Start the client and(or) listener instances. /// - /// Starts the dedicated listener thread pool, if the listener exists. - /// Nothing is required for the client as it does not currently support - /// a discrete "start" method, rather it is "started" during its - /// construction. + /// When HA+Mt is enabled it starts the client's thread pool + /// and the dedicated listener thread pool, if the listener exists. void startClientAndListener(); + /// @brief Pauses client and(or) listener thread pool operations. + /// + /// Suspends the client and listener thread pool event processing. + /// Has no effect in single-threaded mode or if thread pools are + /// not currently running. + void pauseClientAndListener(); + + /// @brief Resumes client and(or) listener thread pool operations. + /// + /// Resumes the client and listener thread pool event processing. + /// Has no effect in single-threaded mode or if thread pools are + /// not currently paused. + void resumeClientAndListener(); + /// @brief Stop the client and(or) listener instances. /// - /// Closing connections and stops the thread pools for the client + /// Closes all connections and stops the thread pools for the client /// and listener, if they exist. - /// - /// @note Once stopped the service cannot be restarted, it must - /// be recreated. void stopClientAndListener(); protected: diff --git a/src/hooks/dhcp/high_availability/tests/ha_mt_unittest.cc b/src/hooks/dhcp/high_availability/tests/ha_mt_unittest.cc index 5d798a2148..f1f6836cb2 100644 --- a/src/hooks/dhcp/high_availability/tests/ha_mt_unittest.cc +++ b/src/hooks/dhcp/high_availability/tests/ha_mt_unittest.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,7 @@ using namespace isc::dhcp; using namespace isc::ha; using namespace isc::ha::test; +using namespace isc::http; using namespace isc::util; namespace { @@ -147,8 +149,112 @@ public: } }; +// Verifies HA+MT start, pause, resume, and stop. +TEST_F(HAMtServiceTest, multiThreadingBasics) { + + // Build the HA JSON configuration. + std::stringstream ss; + ss << + "[" + " {" + " \"this-server-name\": \"server1\"," + " \"mode\": \"passive-backup\"," + " \"wait-backup-ack\": true," + " \"peers\": [" + " {" + " \"name\": \"server1\"," + " \"url\": \"http://127.0.0.1:8080/\"," + " \"role\": \"primary\"" + " }," + " {" + " \"name\": \"server2\"," + " \"url\": \"http://127.0.0.1:8081/\"," + " \"role\": \"backup\"" + " }" + " ]"; + + // Enable MT, listener, and 3 threads for both client and listener. + ss << "," << makeHAMtJson(true, true, 3, 3) << "}]"; + ConstElementPtr config_json; + ASSERT_NO_THROW_LOG(config_json = Element::fromJSON(ss.str())); + + // Enable DHCP multi-threading configuration in CfgMgr with 3 threads. + setDHCPMultiThreadingConfig(true, 3); + + /// @todo this is a hack... we have chicken-egg... CmdHttpListener won't + /// start if MT is not enabled BUT that happens after config hook point + MultiThreadingMgr::instance().setMode(true); + + // Create the HA configuration + HAConfigPtr ha_config(new HAConfig()); + HAConfigParser parser; + ASSERT_NO_THROW_LOG(parser.parse(ha_config, config_json)); + + // Instantiate the service. + TestHAServicePtr service; + ASSERT_NO_THROW_LOG(service.reset(new TestHAService(io_service_, network_state_, + ha_config))); + // Multi-threading should be enabled. + ASSERT_TRUE(ha_config->getEnableMultiThreading()); + + // Now we'll start, pause, resume and stop a few times. + for (int i = 0; i < 3; ++i) { + // Verify we're stopped. + // Client should exist but be stopped. + ASSERT_TRUE(service->client_); + ASSERT_TRUE(service->client_->isStopped()); + + // Listener should exist but not be stopped.. + ASSERT_TRUE(service->listener_); + ASSERT_TRUE(service->client_->isStopped()); + + // Start client and listener. + ASSERT_NO_THROW_LOG(service->startClientAndListener()); + + // Verify we've started. + // Client should be running. + ASSERT_TRUE(service->client_->isRunning()); + ASSERT_TRUE(service->client_->getThreadIOService()); + EXPECT_FALSE(service->client_->getThreadIOService()->stopped()); + EXPECT_EQ(service->client_->getThreadPoolSize(), 3); + EXPECT_EQ(service->client_->getThreadCount(), 3); + + // Listener should be running. + ASSERT_TRUE(service->listener_->isRunning()); + ASSERT_TRUE(service->listener_->getThreadIOService()); + EXPECT_FALSE(service->listener_->getThreadIOService()->stopped()); + EXPECT_EQ(service->listener_->getThreadPoolSize(), 3); + EXPECT_EQ(service->listener_->getThreadCount(), 3); + + // Pause client and listener. + ASSERT_NO_THROW_LOG(service->pauseClientAndListener()); + + // Client should be paused. + ASSERT_TRUE(service->client_->isPaused()); + EXPECT_TRUE(service->client_->getThreadIOService()->stopped()); + + // Listener should be paused. + ASSERT_TRUE(service->listener_->isPaused()); + EXPECT_TRUE(service->listener_->getThreadIOService()->stopped()); + + // Now resume client and listener. + ASSERT_NO_THROW_LOG(service->resumeClientAndListener()); + + // Client should be running. + ASSERT_TRUE(service->client_->isRunning()); + EXPECT_FALSE(service->client_->getThreadIOService()->stopped()); + + // Listener should be running. + ASSERT_TRUE(service->listener_->isRunning()); + EXPECT_FALSE(service->listener_->getThreadIOService()->stopped()); + + // Stop should succeed. + ASSERT_NO_THROW_LOG(service->stopClientAndListener()); + } +} + // Verifies permutations of HA+MT configuration and start-up. -TEST_F(HAMtServiceTest, multiThreadingStartup) { +TEST_F(HAMtServiceTest, multiThreadingConfigStartup) { // Structure describing a test scenario. struct Scenario { @@ -273,7 +379,7 @@ TEST_F(HAMtServiceTest, multiThreadingStartup) { TestHAServicePtr service; ASSERT_NO_THROW_LOG(service.reset(new TestHAService(io_service_, network_state_, ha_config))); - ASSERT_NO_THROW(service->startClientAndListener()); + ASSERT_NO_THROW_LOG(service->startClientAndListener()); // Verify the configuration is as expected. if (!scenario.exp_ha_mt_enabled_) { @@ -293,6 +399,7 @@ TEST_F(HAMtServiceTest, multiThreadingStartup) { // When HA+MT is enabled, client should be multi-threaded. ASSERT_TRUE(service->client_); + EXPECT_TRUE(service->client_->isRunning()); EXPECT_TRUE(service->client_->getThreadIOService()); EXPECT_EQ(service->client_->getThreadPoolSize(), scenario.exp_client_threads_); // Currently thread count should be the same as thread pool size. This might @@ -305,13 +412,16 @@ TEST_F(HAMtServiceTest, multiThreadingStartup) { continue; } - // We should have a listening listener with the expected number of threads. + // We should have a running listener with the expected number of threads. ASSERT_TRUE(service->listener_); - EXPECT_TRUE(service->listener_->isListening()); + EXPECT_TRUE(service->listener_->isRunning()); EXPECT_EQ(service->listener_->getThreadPoolSize(), scenario.exp_listener_threads_); + // Currently thread count should be the same as thread pool size. This might // change if we go to so some sort of dynamic thread instance management. EXPECT_EQ(service->listener_->getThreadCount(), scenario.exp_listener_threads_); + + ASSERT_NO_THROW_LOG(service->stopClientAndListener()); } } diff --git a/src/lib/config/cmd_http_listener.cc b/src/lib/config/cmd_http_listener.cc index 528c89cc3d..499c3b391f 100644 --- a/src/lib/config/cmd_http_listener.cc +++ b/src/lib/config/cmd_http_listener.cc @@ -28,7 +28,7 @@ namespace config { CmdHttpListener::CmdHttpListener(const IOAddress& address, const uint16_t port, const uint16_t thread_pool_size /* = 1 */) - : address_(address), port_(port), io_service_(), http_listener_(), + : address_(address), port_(port), thread_io_service_(), http_listener_(), thread_pool_size_(thread_pool_size), threads_() { } @@ -44,14 +44,14 @@ CmdHttpListener::start() { " when multi-threading is disabled"); } - // Punt if we're already listening. - if (isListening()) { - isc_throw(InvalidOperation, "CmdHttpListener is already listening!"); + // Punt if we're already started. + if (!isStopped()) { + isc_throw(InvalidOperation, "CmdHttpListener already started!"); } try { // Create a new IOService. - io_service_.reset(new IOService()); + thread_io_service_.reset(new IOService()); // Create the response creator factory first. It will be used to // generate response creators. Each response creator will be @@ -61,13 +61,13 @@ CmdHttpListener::start() { // Create the HTTP listener. It will open up a TCP socket and be // prepared to accept incoming connections. TlsContextPtr tls_context; - http_listener_.reset(new HttpListener(*io_service_, address_, port_, + http_listener_.reset(new HttpListener(*thread_io_service_, address_, port_, tls_context, rcf, HttpListener::RequestTimeout(TIMEOUT_AGENT_RECEIVE_COMMAND), HttpListener::IdleTimeout(TIMEOUT_AGENT_IDLE_CONNECTION_TIMEOUT))); - // Create the thread pooli with immediate start. - threads_.reset(new HttpThreadPool(io_service_, thread_pool_size_)); + // Create the thread pooi with immediate start. + threads_.reset(new HttpThreadPool(thread_io_service_, thread_pool_size_)); // Instruct the HTTP listener to actually open socket, install // callback and start listening. @@ -100,7 +100,7 @@ CmdHttpListener::resume() { void CmdHttpListener::stop() { // Nothing to do. - if (!io_service_) { + if (!thread_io_service_) { return; } @@ -115,7 +115,7 @@ CmdHttpListener::stop() { http_listener_.reset(); // Ditch the IOService. - io_service_.reset(); + thread_io_service_.reset(); LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HTTP_LISTENER_STOPPED) .arg(address_) @@ -132,10 +132,31 @@ CmdHttpListener::getRunState() const { return (threads_->getRunState()); } -bool -CmdHttpListener::isListening() const { - return (threads_ && (threads_->getRunState() == HttpThreadPool::RunState::PAUSED - || threads_->getRunState() == HttpThreadPool::RunState::RUN)); +bool +CmdHttpListener::isRunning() { + if (threads_) { + return (threads_->getRunState() == HttpThreadPool::RunState::RUN); + } + + return (false); +} + +bool +CmdHttpListener::isStopped() { + if (threads_) { + return (threads_->getRunState() == HttpThreadPool::RunState::STOPPED); + } + + return (true); +} + +bool +CmdHttpListener::isPaused() { + if (threads_) { + return (threads_->getRunState() == HttpThreadPool::RunState::PAUSED); + } + + return (false); } } // namespace isc::config diff --git a/src/lib/config/cmd_http_listener.h b/src/lib/config/cmd_http_listener.h index 83115ab0c2..5ba3f8703a 100644 --- a/src/lib/config/cmd_http_listener.h +++ b/src/lib/config/cmd_http_listener.h @@ -55,10 +55,23 @@ public: /// @return Run state of the pool. http::HttpThreadPool::RunState getRunState() const; - /// @brief Checks if we are listening to the HTTP requests. + /// @brief Indicates if the thread pool processing is running. /// - /// @return true if we are listening. - bool isListening() const; + /// @return True if the thread pool exists and is in the RUN state, + /// false otherwise. + bool isRunning(); + + /// @brief Indicates if the thread pool is stopped. + /// + /// @return True if the thread pool does not exist or it + /// is in the STOPPED state, False otherwise. + bool isStopped(); + + /// @brief Indicates if the thread pool processing is running. + /// + /// @return True if the thread pool exists and is in the PAUSED state, + /// false otherwise. + bool isPaused(); /// @brief Fetches the IP address on which to listen. /// @@ -92,8 +105,8 @@ public: return (threads_->getThreadCount()); } - asiolink::IOServicePtr getIOService() const { - return(io_service_); + asiolink::IOServicePtr getThreadIOService() const { + return(thread_io_service_); } private: @@ -104,7 +117,7 @@ private: uint16_t port_; /// @brief IOService instance that drives our IO. - isc::asiolink::IOServicePtr io_service_; + isc::asiolink::IOServicePtr thread_io_service_; /// @brief The HttpListener instance http::HttpListenerPtr http_listener_; diff --git a/src/lib/config/tests/cmd_http_listener_unittests.cc b/src/lib/config/tests/cmd_http_listener_unittests.cc index bb8e50dd99..6d57e1eeff 100644 --- a/src/lib/config/tests/cmd_http_listener_unittests.cc +++ b/src/lib/config/tests/cmd_http_listener_unittests.cc @@ -381,9 +381,9 @@ public: SERVER_PORT, num_threads))); ASSERT_TRUE(listener_); - // Start it and verify it is listening. + // Start it and verify it is running. ASSERT_NO_THROW_LOG(listener_->start()); - ASSERT_TRUE(listener_->isListening()); + ASSERT_TRUE(listener_->isRunning()); EXPECT_EQ(listener_->getThreadCount(), num_threads); // Maps the number of clients served by a given thread-id. @@ -401,7 +401,7 @@ public: // Stop the listener and then verify it has stopped. ASSERT_NO_THROW_LOG(listener_->stop()); - ASSERT_FALSE(listener_->isListening()); + ASSERT_TRUE(listener_->isStopped()); EXPECT_EQ(listener_->getThreadCount(), 0); // Iterate over the clients, checking their outcomes. @@ -574,8 +574,8 @@ TEST_F(CmdHttpListenerTest, basics) { // It should not have an IOService, should not be listening // should have no threads. - ASSERT_FALSE(listener_->getIOService()); - EXPECT_FALSE(listener_->isListening()); + ASSERT_FALSE(listener_->getThreadIOService()); + EXPECT_TRUE(listener_->isStopped()); EXPECT_EQ(listener_->getThreadCount(), 0); ASSERT_THROW_MSG(listener_->getRunState(), InvalidOperation, "CmdHttpListener::getRunState - no thread pool!"); @@ -587,7 +587,7 @@ TEST_F(CmdHttpListenerTest, basics) { " when multi-threading is disabled"); // It should still not be listening and have no threads. - EXPECT_FALSE(listener_->isListening()); + EXPECT_TRUE(listener_->isStopped()); EXPECT_EQ(listener_->getThreadCount(), 0); // Enable multi-threading. @@ -595,33 +595,32 @@ TEST_F(CmdHttpListenerTest, basics) { // Make sure we can start it and it's listening with 1 thread. ASSERT_NO_THROW_LOG(listener_->start()); - ASSERT_TRUE(listener_->isListening()); + ASSERT_TRUE(listener_->isRunning()); EXPECT_EQ(listener_->getThreadCount(), 1); - ASSERT_TRUE(listener_->getIOService()); - EXPECT_FALSE(listener_->getIOService()->stopped()); + ASSERT_TRUE(listener_->getThreadIOService()); + EXPECT_FALSE(listener_->getThreadIOService()->stopped()); EXPECT_EQ(listener_->getRunState(), HttpThreadPool::RunState::RUN); // Trying to start it again should fail. ASSERT_THROW_MSG(listener_->start(), InvalidOperation, - "CmdHttpListener is already listening!"); + "CmdHttpListener already started!"); // Stop it and verify we're no longer listening. ASSERT_NO_THROW_LOG(listener_->stop()); - ASSERT_FALSE(listener_->isListening()); + ASSERT_TRUE(listener_->isStopped()); EXPECT_EQ(listener_->getThreadCount(), 0); EXPECT_EQ(listener_->getRunState(), HttpThreadPool::RunState::STOPPED); - ASSERT_FALSE(listener_->getIOService()); + ASSERT_FALSE(listener_->getThreadIOService()); // Make sure we can call stop again without problems. ASSERT_NO_THROW_LOG(listener_->stop()); // We should be able to restart it. ASSERT_NO_THROW_LOG(listener_->start()); - ASSERT_TRUE(listener_->isListening()); + ASSERT_TRUE(listener_->isRunning()); EXPECT_EQ(listener_->getThreadCount(), 1); - ASSERT_TRUE(listener_->getIOService()); - EXPECT_FALSE(listener_->getIOService()->stopped()); - EXPECT_EQ(listener_->getRunState(), HttpThreadPool::RunState::RUN); + ASSERT_TRUE(listener_->getThreadIOService()); + EXPECT_FALSE(listener_->getThreadIOService()->stopped()); // Destroying it should also stop it. // If the test timeouts we know it didn't! @@ -633,32 +632,30 @@ TEST_F(CmdHttpListenerTest, basics) { EXPECT_EQ(listener_->getAddress(), address); EXPECT_EQ(listener_->getPort(), port); EXPECT_EQ(listener_->getThreadPoolSize(), 4); - ASSERT_TRUE(listener_->isListening()); - ASSERT_TRUE(listener_->getIOService()); - EXPECT_FALSE(listener_->getIOService()->stopped()); + ASSERT_TRUE(listener_->isRunning()); + ASSERT_TRUE(listener_->getThreadIOService()); + EXPECT_FALSE(listener_->getThreadIOService()->stopped()); // Verify we can pause it. We should still be listening, threads intact, // IOservice stopped, state set to PAUSED. ASSERT_NO_THROW_LOG(listener_->pause()); - ASSERT_TRUE(listener_->isListening()); + ASSERT_TRUE(listener_->isPaused()); EXPECT_EQ(listener_->getThreadCount(), 4); - ASSERT_TRUE(listener_->getIOService()); - EXPECT_TRUE(listener_->getIOService()->stopped()); - EXPECT_EQ(listener_->getRunState(), HttpThreadPool::RunState::PAUSED); + ASSERT_TRUE(listener_->getThreadIOService()); + EXPECT_TRUE(listener_->getThreadIOService()->stopped()); // Verify we can resume it. ASSERT_NO_THROW_LOG(listener_->resume()); - ASSERT_TRUE(listener_->isListening()); + ASSERT_TRUE(listener_->isRunning()); EXPECT_EQ(listener_->getThreadCount(), 4); - ASSERT_TRUE(listener_->getIOService()); - EXPECT_FALSE(listener_->getIOService()->stopped()); - EXPECT_EQ(listener_->getRunState(), HttpThreadPool::RunState::RUN); + ASSERT_TRUE(listener_->getThreadIOService()); + EXPECT_FALSE(listener_->getThreadIOService()->stopped()); // Stop it and verify we're no longer listening. ASSERT_NO_THROW_LOG(listener_->stop()); - ASSERT_FALSE(listener_->isListening()); + ASSERT_TRUE(listener_->isStopped()); EXPECT_EQ(listener_->getThreadCount(), 0); - ASSERT_FALSE(listener_->getIOService()); + ASSERT_FALSE(listener_->getThreadIOService()); EXPECT_EQ(listener_->getRunState(), HttpThreadPool::RunState::STOPPED); } @@ -673,7 +670,7 @@ TEST_F(CmdHttpListenerTest, basicListenAndRespond) { // Start the listener and verify it's listening with 1 thread. ASSERT_NO_THROW_LOG(listener_->start()); - ASSERT_TRUE(listener_->isListening()); + ASSERT_TRUE(listener_->isRunning()); EXPECT_EQ(listener_->getThreadCount(), 1); // Now let's send a "foo" command. This should create a client, connect @@ -711,12 +708,12 @@ TEST_F(CmdHttpListenerTest, basicListenAndRespond) { EXPECT_EQ(hr->getBody(), "[ { \"arguments\": [ \"bar\" ], \"result\": 0 } ]"); // Make sure the listener is still listening. - ASSERT_TRUE(listener_->isListening()); + ASSERT_TRUE(listener_->isRunning()); EXPECT_EQ(listener_->getThreadCount(), 1); // Stop the listener then verify it has stopped. ASSERT_NO_THROW_LOG(listener_->stop()); - ASSERT_FALSE(listener_->isListening()); + ASSERT_TRUE(listener_->isStopped()); EXPECT_EQ(listener_->getThreadCount(), 0); } diff --git a/src/lib/http/client.cc b/src/lib/http/client.cc index 7719047c9a..0002a6830e 100644 --- a/src/lib/http/client.cc +++ b/src/lib/http/client.cc @@ -1790,6 +1790,9 @@ public: } } + /// @brief Pauses the thread pool operation. + /// + /// Suspends thread pool event processing. void pause() { if (!threads_) { isc_throw(InvalidOperation, "HttpClient::pause - no thread pool"); @@ -1799,7 +1802,9 @@ public: threads_->pause(); } - /// @brief Pauses the thread pool's worker threads. + /// @brief Resumes the thread pool operation. + /// + /// Resumes thread pool event processing. void resume() { if (!threads_) { isc_throw(InvalidOperation, "HttpClient::resume - no thread pool"); @@ -1809,6 +1814,9 @@ public: threads_->resume(); } + /// @brief Fetches the thread pool's operational state. + /// + /// @return Operational state of the thread pool. HttpThreadPool::RunState getRunState() const { if (!threads_) { isc_throw(InvalidOperation, "HttpClient::getRunState - no thread pool"); @@ -1817,6 +1825,42 @@ public: return (threads_->getRunState()); } + /// @brief Indicates if the thread pool processing is running. + /// + /// @return True if the thread pool exists and is in the RUN state, + /// false otherwise. + bool isRunning() { + if (threads_) { + return (threads_->getRunState() == HttpThreadPool::RunState::RUN); + } + + return (false); + } + + /// @brief Indicates if the thread pool is stopped. + /// + /// @return True if the thread pool exists and is in the STOPPED state, + /// false otherwise + bool isStopped() { + if (threads_) { + return (threads_->getRunState() == HttpThreadPool::RunState::STOPPED); + } + + return (false); + } + + /// @brief Indicates if the thread pool processing is running. + /// + /// @return True if the thread pool exists and is in the PAUSED state, + /// false otherwise. + bool isPaused() { + if (threads_) { + return (threads_->getRunState() == HttpThreadPool::RunState::PAUSED); + } + + return (false); + } + /// @brief Fetches the internal IOService used in multi-threaded mode. /// /// @return A pointer to the IOService, or an empty pointer when @@ -1956,5 +2000,20 @@ HttpClient::getRunState() const { return (impl_->getRunState()); } +bool +HttpClient::isRunning() { + return (impl_->isRunning()); +} + +bool +HttpClient::isStopped() { + return (impl_->isStopped()); +} + +bool +HttpClient::isPaused() { + return (impl_->isPaused()); +} + } // end of namespace isc::http } // end of namespace isc diff --git a/src/lib/http/client.h b/src/lib/http/client.h index 326633177f..92682ec2a6 100644 --- a/src/lib/http/client.h +++ b/src/lib/http/client.h @@ -293,10 +293,42 @@ public: /// @return the number of running threads. uint16_t getThreadCount() const; + /// @brief Pauses the thread pool operation. + /// + /// Suspends thread pool event processing. + /// @throw InvalidOperation if the thread pool does not exist. void pause(); + + /// @brief Resumes the thread pool operation. + /// + /// Resumes thread pool event processing. + /// @throw InvalidOperation if the thread pool does not exist. void resume(); + + /// @brief Fetches the thread pool's operational state. + /// + /// @return Operational state of the thread pool. + /// @throw InvalidOperation if the thread pool does not exist. HttpThreadPool::RunState getRunState() const; + /// @brief Indicates if the thread pool processing is running. + /// + /// @return True if the thread pool exists and is in the RUN state, + /// false otherwise. + bool isRunning(); + + /// @brief Indicates if the thread pool is stopped. + /// + /// @return True if the thread pool exists and is in the STOPPED state, + /// false otherwise. + bool isStopped(); + + /// @brief Indicates if the thread pool processing is running. + /// + /// @return True if the thread pool exists and is in the PAUSED state, + /// false otherwise. + bool isPaused(); + private: /// @brief Pointer to the HTTP client implementation. diff --git a/src/lib/http/tests/mt_client_unittests.cc b/src/lib/http/tests/mt_client_unittests.cc index 630197770e..ece1612cc9 100644 --- a/src/lib/http/tests/mt_client_unittests.cc +++ b/src/lib/http/tests/mt_client_unittests.cc @@ -665,6 +665,11 @@ public: ASSERT_NO_THROW_LOG(client_.reset(new HttpClient(io_service_, num_threads))); ASSERT_TRUE(client_); + // Check convenience functions. + ASSERT_TRUE(client_->isRunning()); + ASSERT_FALSE(client_->isPaused()); + ASSERT_FALSE(client_->isStopped()); + if (num_threads_ == 0) { // If we single-threaded client should not have it's own IOService. ASSERT_FALSE(client_->getThreadIOService()); @@ -704,6 +709,11 @@ public: // Pause the client. ASSERT_NO_THROW(client_->pause()); ASSERT_EQ(HttpThreadPool::RunState::PAUSED, client_->getRunState()); + + // Check convenience functions. + ASSERT_FALSE(client_->isRunning()); + ASSERT_TRUE(client_->isPaused()); + ASSERT_FALSE(client_->isStopped()); } // We should have completed at least the expected number of requests @@ -824,6 +834,11 @@ TEST_F(MtHttpClientTest, basics) { ASSERT_EQ(client->getThreadCount(), 3); ASSERT_EQ(client->getRunState(), HttpThreadPool::RunState::RUN); + // Check convenience functions. + ASSERT_TRUE(client->isRunning()); + ASSERT_FALSE(client->isPaused()); + ASSERT_FALSE(client->isStopped()); + // Verify stop doesn't throw. ASSERT_NO_THROW_LOG(client->stop()); @@ -833,6 +848,11 @@ TEST_F(MtHttpClientTest, basics) { ASSERT_EQ(client->getThreadPoolSize(), 3); ASSERT_EQ(client->getThreadCount(), 0); + // Check convenience functions. + ASSERT_FALSE(client->isRunning()); + ASSERT_FALSE(client->isPaused()); + ASSERT_TRUE(client->isStopped()); + // Verify a second call to stop() doesn't throw. ASSERT_NO_THROW_LOG(client->stop()); @@ -862,6 +882,11 @@ TEST_F(MtHttpClientTest, deferredStart) { ASSERT_EQ(client->getThreadCount(), 0); ASSERT_EQ(client->getRunState(), HttpThreadPool::RunState::STOPPED); + // Check convenience functions. + ASSERT_FALSE(client->isRunning()); + ASSERT_FALSE(client->isPaused()); + ASSERT_TRUE(client->isStopped()); + // We should be able to start it. ASSERT_NO_THROW(client->start()); @@ -871,6 +896,11 @@ TEST_F(MtHttpClientTest, deferredStart) { ASSERT_FALSE(client->getThreadIOService()->stopped()); ASSERT_EQ(client->getRunState(), HttpThreadPool::RunState::RUN); + // Check convenience functions. + ASSERT_TRUE(client->isRunning()); + ASSERT_FALSE(client->isPaused()); + ASSERT_FALSE(client->isStopped()); + // Cannot start it twice. ASSERT_THROW_MSG(client->start(), InvalidOperation, "HttpThreadPool::start already started!"); @@ -975,7 +1005,7 @@ TEST_F(MtHttpClientTest, fourByFourByTwo) { } // Verifies that we can cleanly work, pause, and resume repeatedly. -TEST_F(MtHttpClientTest, workPauseResumee) { +TEST_F(MtHttpClientTest, workPauseResume) { size_t num_threads = 12; size_t num_batches = 12; size_t num_listeners = 12;