11#include < storages/postgres/detail/cancel.hpp>
22
3+ // Internal structures of libpq to send cancel packet using async non blocking socket
4+
35#include < array>
46#include < cstdint>
57
8+ #include < sys/socket.h>
9+
610#include < userver/engine/io/sockaddr.hpp>
711#include < userver/engine/io/socket.hpp>
812#include < userver/storages/postgres/exceptions.hpp>
913#include < userver/tracing/span.hpp>
1014
11- #ifdef __clang__
12- // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
13- #define USERVER_IMPL_DISABLE_MSAN __attribute__ ((no_sanitize_memory))
15+ #include < storages/postgres/detail/pg_version.hpp>
16+
17+ // NOLINTNEXTLINE(modernize-use-using)
18+ typedef struct {
19+ struct sockaddr_storage addr;
20+ socklen_t salen;
21+ } SockAddr;
22+
23+ #if USERVER_LIBPQ_VERSION >= 180000
24+
25+ struct pg_cancel {
26+ SockAddr raddr; /* Remote address */
27+ int be_pid; /* PID of to-be-canceled backend */
28+ int pgtcp_user_timeout; /* tcp user timeout */
29+ int keepalives; /* use TCP keepalives? */
30+ int keepalives_idle; /* time between TCP keepalives */
31+ int keepalives_interval; /* time between TCP keepalive
32+ * retransmits */
33+ int keepalives_count; /* maximum number of TCP keepalive
34+ * retransmits */
35+
36+ /* Pre-constructed cancel request packet starts here */
37+ int32_t cancel_pkt_len; /* in network byte order */
38+ char cancel_req[/* FLEXIBLE_ARRAY_MEMBER*/ ]; /* CancelRequestPacket */
39+ };
40+
1441#else
15- // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
16- #define USERVER_IMPL_DISABLE_MSAN
17- #endif
1842
1943// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
2044#define PG_PROTOCOL_MAJOR (v ) ((v) >> 16 )
2145// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
2246#define PG_PROTOCOL_MINOR (v ) ((v) & 0x0000ffff )
2347// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
2448#define PG_PROTOCOL (m, n ) (((m) << 16 ) | (n))
25-
2649// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
2750#define CANCEL_REQUEST_CODE PG_PROTOCOL (1234 , 5678 )
2851
29- using ACCEPT_TYPE_ARG3 = socklen_t;
30-
31- struct PgSockaddrStorage {
32- union {
33- struct sockaddr sa; /* get the system-dependent fields */
34- int64_t ss_align; /* ensures struct is properly aligned */
35- char ss_pad[128 ]; /* ensures struct has desired size */
36- } ss_stuff;
37- };
38-
3952// NOLINTNEXTLINE(modernize-use-using)
40- typedef struct {
41- struct PgSockaddrStorage addr;
42- ACCEPT_TYPE_ARG3 salen;
43- } SockAddr;
53+ typedef struct CancelRequestPacket {
54+ /* Note that each field is stored in network byte order! */
55+ uint32_t cancelRequestCode; /* code to identify a cancel request */
56+ uint32_t backendPID; /* PID of client's backend */
57+ uint32_t cancelAuthCode; /* secret key to authorize cancel */
58+ } CancelRequestPacket;
4459
4560struct pg_cancel {
4661 SockAddr raddr;
4762 int be_pid;
4863 int be_key;
4964};
5065
51- // NOLINTNEXTLINE(modernize-use-using)
52- typedef struct CancelRequestPacket {
53- /* Note that each field is stored in network byte order! */
54- uint32_t cancel_request_code; /* code to identify a cancel request */
55- uint32_t backend_pid; /* PID of client's backend */
56- uint32_t cancel_auth_code; /* secret key to authorize cancel */
57- } CancelRequestPacket;
58-
59- struct CancelPacket {
60- uint32_t packetlen;
61- CancelRequestPacket cp;
62- };
66+ #endif
6367
6468USERVER_NAMESPACE_BEGIN
6569
6670namespace storages ::postgres::detail {
6771
6872namespace {
6973
70- // TODO(TAXICOMMON-11213) investigate why memory sanitizer complains.
71- USERVER_IMPL_DISABLE_MSAN CancelPacket MakeCancelPacket (const PGcancel& cn) noexcept {
72- CancelPacket cp{};
73- cp.packetlen = sizeof (cp);
74- cp.cp .cancel_request_code = static_cast <uint32_t >(htonl (CANCEL_REQUEST_CODE));
75- cp.cp .backend_pid = htonl (cn.be_pid );
76- cp.cp .cancel_auth_code = htonl (cn.be_key );
77- return cp;
74+ void SendCancelPacket (engine::io::Socket& tmp_sock, engine::Deadline deadline, const PGcancel& cn) {
75+ #if USERVER_LIBPQ_VERSION >= 180000
76+ // reference code: libpq/fe-cancel.c
77+ // In libpq cancel packet is stored directly in pg_cancel struct
78+ // starting from cancel_pkt_len field
79+ // After this field pg_cancel contains flexible array with binary prepared packet
80+ // NOTE: cancel_pkt_len in pg_cancel stored in network byte order
81+ size_t cancel_pkt_len = ntohl (cn.cancel_pkt_len );
82+ auto ret = tmp_sock.SendAll (reinterpret_cast <const char *>(&cn.cancel_pkt_len ), cancel_pkt_len, deadline);
83+ if (ret != cancel_pkt_len) {
84+ throw CommandError (fmt::format (
85+ " SendCancelPacket: Failed to call SendAll(), expected bytes to send {}, actual bytes sent {}" ,
86+ cancel_pkt_len,
87+ ret
88+ ));
89+ }
90+ #else
91+ struct {
92+ uint32_t packetlen;
93+ CancelRequestPacket cp;
94+ } crp;
95+
96+ crp.packetlen = htonl (sizeof (crp));
97+ crp.cp .cancelRequestCode = htonl (CANCEL_REQUEST_CODE);
98+ crp.cp .backendPID = htonl (cn.be_pid );
99+ crp.cp .cancelAuthCode = htonl (cn.be_key );
100+
101+ auto ret = tmp_sock.SendAll (&crp, sizeof (crp), deadline);
102+ if (ret != sizeof (crp)) {
103+ throw CommandError (" SendAll()" );
104+ }
105+ #endif
78106}
79107
80108} // namespace
81109
82- USERVER_IMPL_DISABLE_MSAN void Cancel (PGcancel* cn, engine::Deadline deadline) {
110+ void Cancel (PGcancel* cn, engine::Deadline deadline) {
83111 tracing::Span span{" pg_cancel" };
84112 if (!cn) {
85113 return ;
86114 }
87115
88- engine::io::Sockaddr addr;
89- memcpy (addr.Data (), &cn->raddr , std::min<size_t >(sizeof (cn->raddr ), addr.Size ()));
90-
116+ engine::io::Sockaddr addr (&cn->raddr .addr );
91117 engine::io::Socket tmp_sock (addr.Domain (), engine::io::SocketType::kStream );
92118
93119 tmp_sock.Connect (addr, deadline);
120+ LOG_DEBUG () << " Connected to " << addr.PrimaryAddressString () << " on port " << addr.Port ();
94121
95- CancelPacket cp = MakeCancelPacket (*cn);
96- auto ret = tmp_sock.SendAll (&cp, sizeof (cp), deadline);
97- if (ret != sizeof (cp)) {
98- throw CommandError (" SendAll()" );
99- }
122+ SendCancelPacket (tmp_sock, deadline, *cn);
100123
101124 /*
102125 * Comment from libpq's sources, fe-connect.c, inside internal_cancel():
@@ -107,8 +130,14 @@ USERVER_IMPL_DISABLE_MSAN void Cancel(PGcancel* cn, engine::Deadline deadline) {
107130 * one we thought we were canceling. Note we don't actually expect this
108131 * read to obtain any data, we are just waiting for EOF to be signaled.
109132 */
110- std::array<char , 1024 > c{};
111- [[maybe_unused]] auto ret2 = tmp_sock.RecvAll (c.data (), c.size (), deadline);
133+ try {
134+ std::array<char , 1024 > c{};
135+ [[maybe_unused]] auto ret = tmp_sock.RecvAll (c.data (), c.size (), deadline);
136+ } catch (const engine::io::IoSystemError& ex) {
137+ LOG_LIMITED_INFO () << " Got exception during RecvAll after pg_cancel: " << ex;
138+ // Do not propagate exception here, because postmaster sends no response
139+ // for cancel packets, just silently close socket
140+ }
112141}
113142
114143} // namespace storages::postgres::detail
0 commit comments