bool io_service::async_write(_sock_t fd, const std::vector<char>& buffer, std::size_t write_size, const write_callback_t& callback) { std::lock_guard<std::recursive_mutex> lock(m_fds_mutex); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service is requested async_write for fd #" + std::to_string(fd)); auto reg_fd_it = m_fds.find(fd); if (reg_fd_it == m_fds.end()) { __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service does not track fd #" + std::to_string(fd)); return false; } auto& reg_fd = reg_fd_it->second; bool expected = false; if (!reg_fd.async_write.compare_exchange_strong(expected, true)) { __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service already doing async_write for fd #" + std::to_string(fd)); return false; } reg_fd.write_buffer = buffer; reg_fd.write_size = write_size; reg_fd.write_callback = callback; notify_poll(); return true; }
void redis_connection::connect(const std::string &host, std::size_t port, const disconnection_handler_t &client_disconnection_handler, const reply_callback_t &client_reply_callback, std::uint32_t timeout_ms) { try { __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection attempts to connect"); /** * connect client */ m_client->connect(host, (uint32_t) port, timeout_ms); m_client->set_on_disconnection_handler(std::bind(&redis_connection::tcp_client_disconnection_handler, this)); /** * start to read asynchronously */ tcp_client_iface::read_request request = {__CPP_REDIS_READ_SIZE, std::bind(&redis_connection::tcp_client_receive_handler, this, std::placeholders::_1)}; m_client->async_read(request); __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection connected"); } catch (const std::exception &e) { __CPP_REDIS_LOG(error, std::string("cpp_redis::network::redis_connection ") + e.what()); throw redis_error(e.what()); } m_reply_callback = client_reply_callback; m_disconnection_handler = client_disconnection_handler; }
void io_service::write_fd(int fd) { std::unique_lock<std::recursive_mutex> lock(m_fds_mutex); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service non-blocking write available for fd #" + std::to_string(fd)); auto fd_it = m_fds.find(fd); if (fd_it == m_fds.end()) { __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service does not track fd #" + std::to_string(fd)); return; } __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service writing data for fd #" + std::to_string(fd)); int nb_bytes_written = static_cast<int>(send(fd_it->first, fd_it->second.write_buffer.data(), fd_it->second.write_size, 0)); fd_it->second.async_write = false; if (nb_bytes_written <= 0) { __CPP_REDIS_LOG(error, "cpp_redis::network::io_service write error for fd #" + std::to_string(fd)); fd_it->second.disconnection_handler(*this); m_fds.erase(fd_it); } else { __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service calling write callback for fd #" + std::to_string(fd)); fd_it->second.callback_running = true; lock.unlock(); fd_it->second.write_callback(nb_bytes_written); fd_it->second.callback_running = false; fd_it->second.callback_notification.notify_all(); } }
io_service::~io_service(void) { m_should_stop = true; notify_poll(); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service waiting for worker completion"); m_worker.join(); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service finished to wait for worker completion"); if (m_notif_pipe_fds[0] != -1) close(m_notif_pipe_fds[0]); if (m_notif_pipe_fds[1] != -1) close(m_notif_pipe_fds[1]); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service destroyed"); }
void redis_connection::call_disconnection_handler() { if (m_disconnection_handler) { __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection calls disconnection handler"); m_disconnection_handler(*this); } }
redis_connection & redis_connection::send(const std::vector<std::string> &redis_cmd) { std::lock_guard<std::mutex> lock(m_buffer_mutex); m_buffer += build_command(redis_cmd); __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection stored new command in the send buffer"); return *this; }
io_service::io_service(std::size_t nb_workers) : network::io_service(nb_workers) , m_should_stop(false) , m_notif_pipe_fds{1, 1} { if (pipe(m_notif_pipe_fds) == -1) { __CPP_REDIS_LOG(error, "cpp_redis::network::io_service could not create pipe"); throw cpp_redis::redis_error("Could not init cpp_redis::io_service, pipe() failure"); } int flags = fcntl(m_notif_pipe_fds[1], F_GETFL, 0); if (flags == -1 || fcntl(m_notif_pipe_fds[1], F_SETFL, flags | O_NONBLOCK) == -1) { __CPP_REDIS_LOG(error, "cpp_redis::network::io_service could not configure pipe"); throw cpp_redis::redis_error("Could not init cpp_redis::io_service, fcntl() failure"); } m_worker = std::thread(&io_service::process_io, this); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service created"); }
void io_service::process_io(void) { struct pollfd fds[_CPP_REDIS_MAX_NB_FDS]; __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service starts poll loop in worker thread"); while (!m_should_stop) { std::size_t nfds = init_sets(fds); if (poll(fds, static_cast<nfds_t>(nfds), -1) > 0) { __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service woke up by poll"); process_sets(fds, nfds); } else { __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service woke up by poll, but nothing to process"); } } __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service ends poll loop in worker thread"); }
void redis_connection::disconnect(bool wait_for_removal) { __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection attempts to disconnect"); /** * close connection */ m_client->disconnect(wait_for_removal); /** * clear buffer */ m_buffer.clear(); /** * clear builder */ m_builder.reset(); __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection disconnected"); }
void io_service::untrack(_sock_t fd) { std::unique_lock<std::recursive_mutex> lock(m_fds_mutex); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service requests to untrack fd #" + std::to_string(fd)); auto fd_it = m_fds.find(fd); if (fd_it == m_fds.end()) { __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service does not track fd #" + std::to_string(fd)); return; } if (fd_it->second.callback_running) { __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service waits for callbacks to complete before untracking fd #" + std::to_string(fd)); fd_it->second.callback_notification.wait(lock, [=] { return !fd_it->second.callback_running; }); } m_fds.erase(fd_it); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service now untracks fd #" + std::to_string(fd)); }
void redis_connection::tcp_client_receive_handler(const tcp_client_iface::read_result &result) { if (!result.success) { return; } try { __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection receives packet, attempts to build reply"); m_builder << std::string(result.buffer.begin(), result.buffer.end()); } catch (const redis_error &) { __CPP_REDIS_LOG(error, "cpp_redis::network::redis_connection could not build reply (invalid format), disconnecting"); call_disconnection_handler(); return; } while (m_builder.reply_available()) { __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection reply fully built"); auto reply = m_builder.get_front(); m_builder.pop_front(); if (m_reply_callback) { __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection executes reply callback"); m_reply_callback(*this, reply); } } try { tcp_client_iface::read_request request = {__CPP_REDIS_READ_SIZE, std::bind(&redis_connection::tcp_client_receive_handler, this, std::placeholders::_1)}; m_client->async_read(request); } catch (const std::exception &) { /** * Client disconnected in the meantime */ } }
void io_service::track(_sock_t fd, const disconnection_handler_t& handler) { std::lock_guard<std::recursive_mutex> lock(m_fds_mutex); auto& info = m_fds[fd]; info.async_read = false; info.async_write = false; info.disconnection_handler = handler; __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service now tracks fd #" + std::to_string(fd)); notify_poll(); }
void io_service::read_fd(int fd) { std::unique_lock<std::recursive_mutex> lock(m_fds_mutex); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service non-blocking read available for fd #" + std::to_string(fd)); auto fd_it = m_fds.find(fd); if (fd_it == m_fds.end()) { __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service does not track fd #" + std::to_string(fd)); return; } auto& buffer = *fd_it->second.read_buffer; int original_buffer_size = static_cast<int>(buffer.size()); buffer.resize(original_buffer_size + fd_it->second.read_size); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service reading data for fd #" + std::to_string(fd)); int nb_bytes_read = static_cast<int>(recv(fd_it->first, buffer.data() + original_buffer_size, fd_it->second.read_size, 0)); fd_it->second.async_read = false; if (nb_bytes_read <= 0) { __CPP_REDIS_LOG(error, "cpp_redis::network::io_service read error for fd #" + std::to_string(fd)); buffer.resize(original_buffer_size); fd_it->second.disconnection_handler(*this); m_fds.erase(fd_it); } else { buffer.resize(original_buffer_size + nb_bytes_read); __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service calling read callback for fd #" + std::to_string(fd)); fd_it->second.callback_running = true; lock.unlock(); fd_it->second.read_callback(nb_bytes_read); fd_it->second.callback_running = false; fd_it->second.callback_notification.notify_all(); } }
/** * commit pipelined transaction */ redis_connection & redis_connection::commit() { std::lock_guard<std::mutex> lock(m_buffer_mutex); /** * ensure buffer is cleared */ __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection attempts to send pipelined commands"); std::string buffer = std::move(m_buffer); try { tcp_client_iface::write_request request = {std::vector<char>{buffer.begin(), buffer.end()}, nullptr}; m_client->async_write(request); } catch (const std::exception &e) { __CPP_REDIS_LOG(error, std::string("cpp_redis::network::redis_connection ") + e.what()); throw redis_error(e.what()); } __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection sent pipelined commands"); return *this; }
void redis_connection::tcp_client_disconnection_handler() { __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection has been disconnected"); /** * clear buffer */ m_buffer.clear(); /** * clear builder */ m_builder.reset(); /** * call disconnection handler */ call_disconnection_handler(); }
void io_service::notify_poll(void) { __CPP_REDIS_LOG(debug, "cpp_redis::network::io_service notifies poll to wake up"); (void) write(m_notif_pipe_fds[1], "a", 1); }
redis_connection::redis_connection(const std::shared_ptr<tcp_client_iface> &client) : m_client(client), m_reply_callback(nullptr), m_disconnection_handler(nullptr) { __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection created"); }
redis_connection::~redis_connection() { m_client->disconnect(true); __CPP_REDIS_LOG(debug, "cpp_redis::network::redis_connection destroyed"); }