Line data Source code
1 : #ifndef THINGER_UTIL_CLOSE_NOTIFIER_HPP
2 : #define THINGER_UTIL_CLOSE_NOTIFIER_HPP
3 :
4 : #include <optional>
5 : #include <tuple>
6 : #include <utility>
7 : #include <boost/asio/any_io_executor.hpp>
8 : #include <boost/asio/post.hpp>
9 : #include "logger.hpp"
10 : #include "types.hpp"
11 :
12 : namespace thinger::util {
13 :
14 : // The close callback of a connection (see asio::websocket::on_close(),
15 : // http::websocket_connection::on_close() and http::sse_connection::on_close()), with what the
16 : // connection tells it (`Args`): called once, the connection closed, always posted to its
17 : // executor, so never from within the call that closed it (e.g. a send from a loop over the
18 : // connections, which may then remove this one), and what it throws is logged. Set before or
19 : // after the close: set once closed, it is posted at once. Used on the thread of the connection,
20 : // as the connection itself.
21 : template<typename... Args>
22 : class close_notifier {
23 : public:
24 : using callback_type = sync_callback<void(Args...)>;
25 :
26 7549 : explicit close_notifier(boost::asio::any_io_executor executor) : executor_(std::move(executor)) {}
27 :
28 702 : void set(callback_type callback) {
29 702 : if (released_) return;
30 702 : if (closed_) {
31 27 : if (callback) post(std::move(callback), *closed_);
32 27 : return;
33 : }
34 675 : callback_ = std::move(callback);
35 : }
36 :
37 : // The connection closed (once: later calls are ignored)
38 6509 : void notify(Args... args) {
39 6509 : if (closed_ || released_) return;
40 5030 : closed_.emplace(std::move(args)...);
41 : // released here: it may own the connection
42 5030 : auto callback = std::exchange(callback_, nullptr);
43 5030 : if (callback) post(std::move(callback), *closed_);
44 5030 : }
45 :
46 : // Drop the callback: the connection will never be told closed (e.g. its socket was
47 : // released, or it is destroyed open), so neither any callback set afterwards. Once told
48 : // closed, a callback set afterwards is still posted
49 7546 : void release() {
50 7546 : if (!closed_) released_ = true;
51 7546 : callback_ = nullptr;
52 7546 : }
53 :
54 : private:
55 681 : void post(callback_type callback, std::tuple<Args...> args) {
56 1303 : boost::asio::post(executor_, [callback = std::move(callback), args = std::move(args)]() mutable {
57 : try {
58 622 : std::apply(callback, std::move(args));
59 54 : } catch (const std::exception& e) {
60 27 : LOG_ERROR("error in the close callback of a connection: {}", e.what());
61 0 : } catch (...) {
62 0 : LOG_ERROR("unknown error in the close callback of a connection");
63 : }
64 : });
65 681 : }
66 :
67 : boost::asio::any_io_executor executor_;
68 : callback_type callback_;
69 : // What the connection told once closed
70 : std::optional<std::tuple<Args...>> closed_;
71 : bool released_ = false;
72 : };
73 :
74 : } // namespace thinger::util
75 :
76 : #endif // THINGER_UTIL_CLOSE_NOTIFIER_HPP
|