| 3012 | |
| 3013 | |
| 3014 | void TCPHandler::trySendExceptionWithoutConnectionBuffers(const Exception & e) |
| 3015 | { |
| 3016 | try |
| 3017 | { |
| 3018 | /// The connection failed to initialize most likely because the server memory limit |
| 3019 | /// is reached. In this state every tracked allocation throws, so the error handling |
| 3020 | /// path must be exempt from the memory limit: it allocates a small bounded amount |
| 3021 | /// (the strings of the exception message). |
| 3022 | LockMemoryExceptionInThread lock_memory_tracker(VariableContext::Global); |
| 3023 | |
| 3024 | /// The client has not received 'Hello' yet, so the communication is not chunked, |
| 3025 | /// and the client is able to receive an exception packet instead of the 'Hello' |
| 3026 | /// response (this is also how authentication errors are delivered). The stack |
| 3027 | /// trace is not sent: it is of little use, while it noticeably grows the packet. |
| 3028 | char stack_memory[4096]; |
| 3029 | WriteBufferFromPocoSocket socket_out(socket(), sizeof(stack_memory), stack_memory); |
| 3030 | writeVarUInt(Protocol::Server::Exception, socket_out); |
| 3031 | writeException(e, socket_out, false /*with_stack_trace*/); |
| 3032 | socket_out.finalize(); |
| 3033 | |
| 3034 | /// The client's 'Hello' packet is still unread in the socket receive queue. |
| 3035 | /// Closing the socket with pending unread data makes the kernel send RST, |
| 3036 | /// which can discard the exception packet written above before the client |
| 3037 | /// reads it. Read the pending data out, so the connection is terminated |
| 3038 | /// gracefully with FIN. There is no need to wait for more data: the client |
| 3039 | /// sends 'Hello' in a single packet before reading the response. |
| 3040 | /// The amount of drained data is limited in case the client keeps sending: |
| 3041 | /// such a connection is closed with RST, and that is fine. |
| 3042 | ssize_t max_bytes_to_drain = 65536; |
| 3043 | if (socket().secure()) |
| 3044 | { |
| 3045 | /// For secure sockets available is `SSL_pending`, which only reports decrypted |
| 3046 | /// data buffered inside OpenSSL and misses the data in the socket receive queue, |
| 3047 | /// so the readiness of the underlying fd is polled instead. receiveBytes can |
| 3048 | /// block on a partially received TLS record, so the receive timeout is limited. |
| 3049 | socket().setReceiveTimeout(Poco::Timespan(0, 100'000 /*microseconds*/)); |
| 3050 | while (max_bytes_to_drain > 0 && socket().poll(Poco::Timespan(), Poco::Net::Socket::SELECT_READ)) |
| 3051 | { |
| 3052 | int bytes_received = socket().receiveBytes(stack_memory, sizeof(stack_memory)); |
| 3053 | if (bytes_received <= 0) |
| 3054 | break; |
| 3055 | max_bytes_to_drain -= bytes_received; |
| 3056 | } |
| 3057 | } |
| 3058 | else |
| 3059 | { |
| 3060 | /// For plain sockets available reflects the socket receive queue, |
| 3061 | /// and receiveBytes never blocks. |
| 3062 | while (max_bytes_to_drain > 0 && socket().available() > 0) |
| 3063 | { |
| 3064 | int bytes_received = socket().receiveBytes(stack_memory, sizeof(stack_memory)); |
| 3065 | if (bytes_received <= 0) |
| 3066 | break; |
| 3067 | max_bytes_to_drain -= bytes_received; |
| 3068 | } |
| 3069 | } |
| 3070 | } |
| 3071 | catch (...) |
nothing calls this directly
no test coverage detected