diff --git a/.github/workflows/testbuild.yml b/.github/workflows/testbuild.yml index 7b2897949..a5d5ac9ff 100644 --- a/.github/workflows/testbuild.yml +++ b/.github/workflows/testbuild.yml @@ -59,6 +59,11 @@ jobs: - name: HTTP/Headers test run: make test.headers +# Instance restart test doesn't really show anything unless the instance gets some requests first, +# and setting that up is not on my list right now +# - name: Instance restart test +# run: make test.instance_restart + build_examples: needs: build_lib runs-on: ubuntu-latest diff --git a/core/server/instance.cpp b/core/server/instance.cpp index d97b57494..c5bae273b 100644 --- a/core/server/instance.cpp +++ b/core/server/instance.cpp @@ -63,18 +63,6 @@ LambdaInstance::LambdaInstance(RequestCallback handlerCallback, ServerConfig ini this->m_connections.remove_if(workerJoinFilter); } } - - // Request all workers to exit - for (auto& worker : this->m_connections) { - worker.shutdownFlag = true; - } - - // Wait untill all workers done - for (auto& item : this->m_connections) { - if (item.worker.joinable()) { - item.worker.join(); - } - } }); if (config.loglevel.startMessage) { @@ -88,18 +76,52 @@ void LambdaInstance::shutdownn() { } void LambdaInstance::terminate() { + + // reqeust service worker to exit this->m_terminated = true; + + // close listen socket this->listener.stop(); - this->awaitFinished(); + + // Request all connection workers to exit + for (auto& worker : this->m_connections) { + worker.shutdownFlag = true; + } } void LambdaInstance::awaitFinished() { + + // wait until service worker exits if (this->serviceWorker.valid()) this->serviceWorker.get(); + + // Wait until all connection workers done + for (auto& item : this->m_connections) { + if (item.worker.joinable()) { + item.worker.join(); + } + } } LambdaInstance::~LambdaInstance() { + + // send terminate "signals" this->terminate(); + + // Wait until service worker exits. + // Copypasted a bit but it's better than adding arguments to awaitFinished() + // just to accound for exception suppression + if (this->serviceWorker.valid()) { + try { this->serviceWorker.get(); } + catch(...) {} + } + + // Wait until all connection workers exited + for (auto& item : this->m_connections) { + if (item.worker.joinable()) { + item.worker.join(); + } + } } const ServerConfig& LambdaInstance::getConfig() const noexcept { diff --git a/core/server/server.hpp b/core/server/server.hpp index fcf9b4bf8..b200b3945 100644 --- a/core/server/server.hpp +++ b/core/server/server.hpp @@ -20,18 +20,31 @@ namespace Lambda { std::future serviceWorker; std::forward_list m_connections; - std::atomic m_connections_count {0}; + std::atomic m_connections_count = 0; bool m_terminated = false; + /** + * Internal call that signals all workers to exit + */ void terminate(); public: LambdaInstance(RequestCallback handlerCallback, ServerConfig init); ~LambdaInstance(); + /** + * Stop server + */ void shutdownn(); + + /** + * Block current thread until server exits + */ void awaitFinished(); + /** + * Return current instance config + */ const ServerConfig& getConfig() const noexcept; }; }; diff --git a/examples/reply/main.cpp b/examples/reply/main.cpp index d1f93ec5b..b4f2139f2 100644 --- a/examples/reply/main.cpp +++ b/examples/reply/main.cpp @@ -12,7 +12,7 @@ using namespace Lambda; -auto requestHandler = [](const Request& req, const Context& context) { +auto requestHandlerA = [](const Request& req, const Context& context) { const auto uaHeader = req.headers.get("user-agent"); const auto uaString = uaHeader.size() ? uaHeader : "Unknown"; return Response("Your user-agent is: " + uaString); @@ -22,7 +22,7 @@ int main(int argc, char const *argv[]) { ServerConfig initparams; initparams.loglevel.requests = true; - auto server = LambdaInstance(requestHandler, initparams); + auto server = LambdaInstance(requestHandlerA, initparams); server.awaitFinished(); diff --git a/tests/instance_restart/instance_restart.test.cpp b/tests/instance_restart/instance_restart.test.cpp new file mode 100644 index 000000000..aec02b796 --- /dev/null +++ b/tests/instance_restart/instance_restart.test.cpp @@ -0,0 +1,36 @@ + +#include "../../lambda.hpp" +using namespace Lambda; + +const auto requestHandlerA = [](const Request& req, const Context& context) { + return Response("Response from instance A"); +}; + +const auto requestHandlerB = [](const Request& req, const Context& context) { + return Response("Response from instance B"); +}; + +int main(int argc, char const *argv[]) { + + { + puts("Starting instance A..."); + auto serverA = LambdaInstance(requestHandlerA, {}); + std::thread([&](){ + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + serverA.shutdownn(); + }).detach(); + serverA.awaitFinished(); + } + + { + puts("Starting instance B..."); + auto serverB = LambdaInstance(requestHandlerB, {}); + std::thread([&](){ + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + serverB.shutdownn(); + }).detach(); + serverB.awaitFinished(); + } + + return 0; +} diff --git a/tests/instance_restart/instance_restart.test.mk b/tests/instance_restart/instance_restart.test.mk new file mode 100644 index 000000000..9adf52120 --- /dev/null +++ b/tests/instance_restart/instance_restart.test.mk @@ -0,0 +1,11 @@ + +# instance restart test +TEST_INSTANCE_RESTART_TARGET = instance_restart.test$(EXEEXT) +test.instance_restart: $(TEST_INSTANCE_RESTART_TARGET) + $(TEST_INSTANCE_RESTART_TARGET) + +$(TEST_INSTANCE_RESTART_TARGET): tests/instance_restart/instance_restart.test.o $(LAMBDA_LIBSHARED) + g++ $(CFLAGS) tests/instance_restart/instance_restart.test.o $(LAMBDA_LIBSHARED) $(EXTERNAL_LIBS) $(LINK_SYSTEM_LIBS) -o $(TEST_INSTANCE_RESTART_TARGET) + +tests/instance_restart/instance_restart.test.o: tests/instance_restart/instance_restart.test.cpp + g++ -c $(CFLAGS) tests/instance_restart/instance_restart.test.cpp -o tests/instance_restart/instance_restart.test.o diff --git a/tests/tests.mk b/tests/tests.mk index fc43f9597..f3bfdd626 100644 --- a/tests/tests.mk +++ b/tests/tests.mk @@ -10,3 +10,4 @@ include tests/tcp/tcp.test.mk include tests/vfs/vfs.test.mk include tests/cookie/cookie.test.mk include tests/headers/headers.test.mk +include tests/instance_restart/instance_restart.test.mk