--- a/plugins/ipc/ipc.hpp +++ b/plugins/ipc/ipc.hpp @@ -33,6 +33,18 @@ /** Handle incoming data on the socket */ std::function handle_fd_activity; void handle_fd_incoming(uint32_t); + + /** Outgoing bytes not yet written because the reader is slow/backpressured. */ + std::vector out_buffer; + size_t out_sent = 0; + bool want_writable = false; + + /** Append bytes to the outgoing queue. */ + void queue_output(const char *data, size_t size); + /** Try to drain the outgoing queue; arms a writable watch on EAGAIN. */ + void flush_output(); + /** Toggle WL_EVENT_WRITABLE interest on the client's event source. */ + void set_writable_watch(bool on); }; /** --- a/plugins/ipc/ipc.cpp +++ b/plugins/ipc/ipc.cpp @@ -10,6 +10,7 @@ #include #include #include +#include /** * Handle WL_EVENT_READABLE on the socket. @@ -147,6 +148,7 @@ static constexpr int MAX_MESSAGE_LEN = (1 << 20); static constexpr int HEADER_LEN = 4; +static constexpr size_t MAX_OUTPUT_QUEUE = 16u * 1024 * 1024; wf::ipc::client_t::client_t(server_t *ipc, int fd) { @@ -205,6 +207,11 @@ return; } + if (event_mask & WL_EVENT_WRITABLE) + { + flush_output(); + } + int available = 0; if (ioctl(this->fd, FIONREAD, &available) != 0) { @@ -291,47 +298,95 @@ close(this->fd); } -static bool write_exact(int fd, const char *buf, ssize_t n) +void wf::ipc::client_t::queue_output(const char *data, size_t size) +{ + out_buffer.insert(out_buffer.end(), data, data + size); +} + +void wf::ipc::client_t::set_writable_watch(bool on) +{ + if (on == want_writable) + { + return; + } + + want_writable = on; + wl_event_source_fd_update(source, + WL_EVENT_READABLE | (on ? WL_EVENT_WRITABLE : 0)); +} + +void wf::ipc::client_t::flush_output() { - while (n > 0) + while (out_sent < out_buffer.size()) { - ssize_t w = write(fd, buf, n); - if (w <= 0) + ssize_t w = write(fd, out_buffer.data() + out_sent, out_buffer.size() - out_sent); + if (w > 0) { - return false; + out_sent += (size_t)w; + continue; + } + + if ((w < 0) && (errno == EINTR)) + { + continue; } - n -= w; - buf += w; + if ((w < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) + { + // Reader is slow: keep the remainder queued and flush when writable + // instead of dropping the connection on transient backpressure. + set_writable_watch(true); + return; + } + + LOGE("Error sending json to client!"); + shutdown(fd, SHUT_RDWR); + out_buffer.clear(); + out_sent = 0; + set_writable_watch(false); + return; } - return true; + out_buffer.clear(); + out_sent = 0; + set_writable_watch(false); } bool wf::ipc::client_t::send_json(wf::json_t json) { - bool status = false; + bool status = true; json.map_serialized([&] (const char *buffer, size_t size) { if (size > MAX_MESSAGE_LEN) { LOGE("Error sending json to client: message too long!"); - shutdown(fd, SHUT_RDWR); + status = false; return; } uint32_t len = size; - if (!write_exact(fd, (char*)&len, 4) || !write_exact(fd, buffer, len)) - { - LOGE("Error sending json to client!"); - shutdown(fd, SHUT_RDWR); - return; - } - - status = true; + queue_output((const char*)&len, sizeof(len)); + queue_output(buffer, size); }); - return status; + if (!status) + { + shutdown(fd, SHUT_RDWR); + return false; + } + + if ((out_buffer.size() - out_sent) > MAX_OUTPUT_QUEUE) + { + LOGE("IPC client output backlog too large; dropping connection"); + shutdown(fd, SHUT_RDWR); + out_buffer.clear(); + out_sent = 0; + set_writable_watch(false); + return false; + } + + flush_output(); + return true; } namespace wf