Skip to content

Commit 3eea40a

Browse files
authored
Merge pull request #980 from dkorolev/clean_http_init
Introduced a clean way to catch that the HTTP port is taken.
2 parents 5725a09 + 1425e62 commit 3eea40a

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed

bricks/net/tcp/impl/posix.h

+19
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,25 @@ class ReserveLocalPortImpl final {
380380
hint, nagle_algorithm_policy, max_connections);
381381
}
382382

383+
// Acquires the local port with the explicltly provided number, or throws the exception.
384+
// Used to cleanly construct the HTTP server on the specific port number,
385+
// or fail early, from the thread that is attempting to construct this HTTP server.
386+
// The alternative way used before has the issue that the port is acquired in the thread, not outside it,
387+
// and catching the exception that might occur during its construction might not do what the user expected it to do.
388+
// TODO(dkorolev): Maybe rewrite the default logic as well and retire the `safe_http_construction` example?
389+
[[nodiscard]] inline ReservedLocalPort AcquireLocalPort(
390+
uint16_t port,
391+
NagleAlgorithm nagle_algorithm_policy = kDefaultNagleAlgorithmPolicy,
392+
MaxServerQueuedConnectionsValue max_connections = kMaxServerQueuedConnections) {
393+
auto hold_port_or_throw = current::net::SocketHandle(current::net::SocketHandle::BindAndListen(),
394+
current::net::BarePort(port),
395+
nagle_algorithm_policy,
396+
max_connections);
397+
return current::net::ReservedLocalPort(current::net::ReservedLocalPort::Construct(),
398+
port,
399+
std::move(hold_port_or_throw));
400+
}
401+
383402
class Connection : public SocketHandle {
384403
public:
385404
Connection(SocketHandle&& rhs, IPAndPort&& local_ip_and_port, IPAndPort&& remote_ip_and_port)
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../scripts/Makefile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#include "../../blocks/http/api.h"
2+
#include"../../bricks/dflags/dflags.h"
3+
4+
DEFINE_uint16(port, 8080, "The local port to use.");
5+
DEFINE_bool(unsafe, false, "Set to illustrate the unsafe behavior.");
6+
7+
inline void RunUnsafe() {
8+
// This code does not do what the user expects, since listening on the port happens on a separate thread,
9+
// which means it is done asynchronously, so that a) the exception will be thrown in a different thread, and
10+
// 2) it will be thrown too late to be caught regardless.
11+
//
12+
// The output of the 2nd concurrent run of `./.current/safe_http_server --unsafe` would likely be:
13+
// $ ./.current/safe_http_server --unsafe
14+
// unsafe ok, listening
15+
// terminate called after throwing an instance of 'current::net::SocketBindException'
16+
// what(): ... SocketBindException(static_cast<uint16_t>(bare_port)) 8080
17+
// zsh: abort (core dumped) ./.current/safe_http_server --unsafe
18+
//
19+
// This is why the recommended way to acquire the port in a try/catch block for the HTTP server
20+
// is via `current::net::AcquireLocalPort`, the return value of which is `std::move()`-d into `HTTP()`
21+
try {
22+
auto& http = HTTP(current::net::BarePort(FLAGS_port));
23+
auto scope = http.Register("/", [](Request r) { r("unsafe ok\n"); });
24+
std::cerr << "unsafe ok, listening" << std::endl;
25+
http.Join();
26+
} catch (current::net::SocketBindException const&) {
27+
// NOTE(dkorolev): This `catch` block will *not* be executed!
28+
// TODO(dkorolev): Maybe even remove this old `HTTP()` constructor, or at least mark it as deprecated.
29+
std::cerr << "unsafe can not listen, the local port is taken" << std::endl;
30+
}
31+
}
32+
33+
inline void RunSafe() {
34+
// This is the correct way to ensure the `current::net::SocketBindException` is caught.
35+
// TODO(dkorolev): Now that I think about it, we may well make `RunUnsafe()` do the same thing under the hood.
36+
try {
37+
auto safe_port = current::net::AcquireLocalPort(FLAGS_port);
38+
auto& http = HTTP(std::move(safe_port));
39+
40+
// Of course, the commented line below would also work. The above two are just for illustrative purposes.
41+
// auto& http = HTTP(current::net::AcquireLocalPort(FLAGS_port));
42+
43+
auto scope = http.Register("/", [](Request r) { r("safe ok\n"); });
44+
std::cerr << "safe ok, listening" << std::endl;
45+
http.Join();
46+
} catch (current::net::SocketBindException const&) {
47+
// NOTE(dkorolev): This `catch` block will *not* be executed!
48+
// TODO(dkorolev): Maybe even remove this old `HTTP()` constructor, or at least mark it as deprecated.
49+
std::cerr << "safe can not listen, the local port is taken" << std::endl;
50+
}
51+
}
52+
53+
int main(int argc, char** argv) {
54+
ParseDFlags(&argc, &argv);
55+
56+
if (!FLAGS_unsafe) {
57+
RunSafe();
58+
} else {
59+
RunUnsafe();
60+
}
61+
}

0 commit comments

Comments
 (0)