-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
It is impossible to shut down the server safely, concurrently and cleanly #1645
Comments
Use svr1.wait_until_ready();
svr1.stop(); |
Fixes yhirose#1645. `Server::stop` does nothing if the server is not running. This can cause a program to hang it it calls stop before the server processing thread has had time to initialize. It would make sense for a server that has been stopped to refuse to start, but that would be backwards incompatible. Instead, we introduce a new method: `decommission`. It sets the server object to stop and refuse to start. This means it does not matter in which order concurrent threads end up calling `listen` and `decommission`, the result is always that the listening thread will exit.
That is handy. But it has two problems:
Number 2 is the big issue here. Yes, this can be worked around by moving all initializations that could have errors to the main thread, and making the server thread only contain the call to Please consider my implementation for the proposed |
How about this way? Server svr;
std::thread t;
try {
t = std::thread { [&] { svr.listen(HOST, PORT); } }
svr.wait_until_ready();
} catch (...) {}
svr.stop();
if (t.joinable())
t.join() |
I was referring to a case where an exception occurs in the server thread. Like this: #include "httplib.h"
#include <thread>
#include <iostream>
int main()
{
std::cout << "Starting..." << std::endl;
httplib::Server svr;
std::thread t { [&] {
try {
throw std::runtime_error("Some thing that happens to go wrong.");
svr.listen("localhost", 3000 );
} catch (...) {
std::cout << "[Thread] Exception. Aborting." << std::endl;
}
} };
std::cout << "Before wait" << std::endl;
svr.wait_until_ready();
std::cout << "After wait" << std::endl;
svr.stop();
if (t.joinable())
t.join();
std::cout << "Reached the end successfully." << std::endl;
return 0;
} This program will hang forever. |
How about this? int main() {
std::cout << "Starting..." << std::endl;
httplib::Server svr;
auto server_started = std::atomic<bool>{false};
std::thread t{[&] {
try {
throw std::runtime_error("Some thing that happens to go wrong.");
server_started = true;
svr.listen("localhost", 3000);
} catch (...) { std::cout << "[Thread] Exception. Aborting." << std::endl; }
}};
if (server_started) {
std::cout << "Before wait" << std::endl;
svr.wait_until_ready();
std::cout << "After wait" << std::endl;
svr.stop();
}
if (t.joinable()) t.join();
std::cout << "Reached the end successfully." << std::endl;
return 0;
} |
Nothing in your example stops the main thread from reading That being said, you are correct that the problem can probably be solved by a sufficiently precise combination of at least two signal booleans. One for the server thread to signal its intent to inevitably start the server (making waiting for it safe), the other for the main thread to signal it already decided to shut down the server (allowing the server thread to not start when the call to stop is in the past). I'm not sure even this would work, I'd have to write the code and perform a thorough analysis. But if you merge #1646, a simple call to |
@OskiKervinen-MF thanks for the comment and identifying the bug in my example. I corrected it, and the following code should work either with int main() {
std::cout << "Starting..." << std::endl;
auto ok = std::atomic<bool>{true};
httplib::Server svr;
svr.Get("/hi", [&](const httplib::Request &, httplib::Response &res) {
res.body = "hi...";
});
std::thread t{[&] {
try {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// throw std::runtime_error("Some thing that happens to go wrong.");
svr.listen("localhost", 3000);
} catch (...) {
std::cout << "[Thread] Exception. Aborting." << std::endl;
ok = false;
}
}};
std::cout << "Before wait" << std::endl;
while (ok && !svr.is_running())
;
std::cout << "After wait" << std::endl;
if (ok) {
std::cout << "Ready to make HTTP requests." << std::endl;
httplib::Client cli("localhost", 3000);
auto res = cli.Get("/hi");
std::cout << res->body << std::endl;
svr.stop();
}
if (t.joinable()) t.join();
std::cout << "Reached the end successfully." << std::endl;
return 0;
} Regarding |
Okay, I'm impressed. You got it working with a single signal boolean. int main(int argc, char** argv) {
std::cout << "Starting..." << std::endl;
httplib::Server svr;
svr.Get("/hi", [&](const httplib::Request &, httplib::Response &res) {
res.body = "hi...";
});
std::thread t{[&] {
try {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << "Starting server." << std::endl;
if( argc > 1 )
throw std::runtime_error("Some thing that happens to go wrong.");
svr.listen("localhost", 3000);
} catch (...) {
std::cout << "[Thread] Exception. Aborting." << std::endl;
}
}};
std::cout << "Press enter to stop." << std::endl;
std::cin.get();
std::cout << "Stopping server" << std::endl;
svr.decommission();
t.join();
std::cout << "Reached the end successfully." << std::endl;
return 0;
} For a server you start once and stop once, this gives you an entirely worry-free way to shut it down. After this discussion, I think I can solve my specific problem without the change, so it's no longer vital for us, but I still think it would be useful for users of the library. In the end, it's your call. Thanks for the discussion in any case. |
Thanks for the update. I'll close this issue. By the way, I tested the code with your branch, but I didn't get "Reached the end successfully.". Also I still don't fully understand how
|
Did you press enter to stop? I was trying to simulate the arbitrary timing of the shutdown by letting it depend on user input. How to use decommission? You use it like stop. If this library was just being published, |
@OskiKervinen-MF that was my silly mistake... Yes, I confirmed that your pull request worked. I'll put my comment regarding the |
I'm running into a similar problem now and it does not seem like the server is currently honoring stop(). When I call stop() on the svr it just hangs until systemd finally kills that process. Is this in the latest version? |
@Nitecon at least I know, all the unit tests for client stop and server stop in the latest version are working file on GitHub Action CI. |
@Nitecon You can make @yhirose Well, the the tests work because there is no test for this case. If you replace |
The thing that tends to show up for me is this kills a single thread from a stop perspective but not all threads int he pool so it's not really a "stop" for the server which should effectively handle all of the child threads appropriately by killing them. I'm still testing to make sure it's not anything else on my side though. |
Hello,
There seems to be no way to call
Server.stop()
from another thread without the possibility that the server will never shut down. The root of the problem is thatServer.stop()
does nothing if the server is not running. Because of the nature of concurrency you can not easily guarantee the server has started if the server runs in a different thread.Here is code to illustrate the problem. This program hangs forever:
The real use case is a program that actually used the server, but sometimes there is an error in start-up. The error will cause the program to shut down immediately, before the server is running, causing the stop to be ignored because of the race condition.
All work-arounds boil down to the main thread waiting in a loop until the server thread has initialized the Server. That works, I guess, but it's pretty ugly.
This is essentially the problem mentioned in #1371, but that was closed only with a few work-arounds mentioned. Also commenters in #43 seem to suffer from the same problem.
I think the correct solution would be for
Server.stop()
to set the server to a "shutting down" state, from which anylisten_*
calls would return immediately. This would automatically just work. The downside is that this would make a Server object usable only once. You couldn't call start and listen multiple times on the same object. So the change may not be backwards-compatible. Perhaps instead add a separate method nameddecommission
that moves the server to a state whence it can never be started again.The text was updated successfully, but these errors were encountered: