changeset 674:5d0ed41be10c

Irccd: do not own a queue in irc::connection class, closes #791 @1h Since the server is an automatic state machine that recreate a new session after a connection failure, we may have some invalid references in the irc::connection class if there are still handler running. Because server is already a std::shared_ptr that manages itself, it was heavy to do the same in irc::connection class. Therefore, the irc::connection class is now only responsible of connecting/receiving/sending data (and parsing it). Update the server class to use a send queue and allow the user sending command at any time which will be postponed once connected.
author David Demelier <markand@malikania.fr>
date Wed, 11 Apr 2018 21:01:10 +0200
parents f3d1f6f80ace
children 168ea30142d9
files irccd/main.cpp libirccd/irccd/daemon/irc.cpp libirccd/irccd/daemon/irc.hpp libirccd/irccd/daemon/server.cpp libirccd/irccd/daemon/server.hpp
diffstat 5 files changed, 292 insertions(+), 291 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Wed Apr 11 20:11:18 2018 +0200
+++ b/irccd/main.cpp	Wed Apr 11 21:01:10 2018 +0200
@@ -83,7 +83,6 @@
 
 namespace {
 
-std::atomic<bool> running{true};
 std::unique_ptr<irccd> instance;
 
 void usage()
@@ -249,12 +248,15 @@
      * loggers.
      */
     sigs.async_wait([&] (auto, auto) {
-        running = false;
         service.stop();
     });
 
-    while (running)
+    try {
         service.run();
+    } catch (const std::exception& ex) {
+        std::cerr << "abort: " << ex.what() << std::endl;
+        return 1;
+    }
 
     instance = nullptr;
 }
--- a/libirccd/irccd/daemon/irc.cpp	Wed Apr 11 20:11:18 2018 +0200
+++ b/libirccd/irccd/daemon/irc.cpp	Wed Apr 11 21:01:10 2018 +0200
@@ -22,150 +22,14 @@
 
 #include "irc.hpp"
 
+using boost::asio::async_connect;
+using boost::asio::async_read_until;
+using boost::asio::async_write;
+
 namespace irccd {
 
 namespace irc {
 
-namespace {
-
-using boost::asio::ip::tcp;
-
-template <typename Socket>
-void wrap_connect(Socket& socket, tcp::resolver::iterator it, connection::connect_t handler)
-{
-    assert(handler);
-
-    socket.close();
-    socket.async_connect(*it, [&socket, it, handler] (auto code) mutable {
-        if (code && ++it != tcp::resolver::iterator())
-            wrap_connect(socket, it, std::move(handler));
-        else
-            handler(code);
-    });
-}
-
-template <typename Socket>
-void wrap_resolve(Socket& socket,
-                tcp::resolver& resolver,
-                const std::string& host,
-                const std::string& port,
-                connection::connect_t handler)
-{
-    assert(handler);
-
-    tcp::resolver::query query(host, port);
-
-    resolver.async_resolve(query, [&socket, handler] (auto code, auto it) {
-        if (code)
-            handler(code);
-        else
-            wrap_connect(socket, it, std::move(handler));
-    });
-}
-
-template <typename Socket>
-void wrap_recv(Socket& socket, boost::asio::streambuf& buffer, connection::recv_t handler)
-{
-    assert(handler);
-
-    boost::asio::async_read_until(socket, buffer, "\r\n", [&socket, &buffer, handler] (auto code, auto xfer) noexcept {
-        if (code || xfer == 0U) {
-            handler(std::move(code), message());
-            return;
-        }
-
-        std::string data;
-
-        try {
-            data = std::string(
-                boost::asio::buffers_begin(buffer.data()),
-                boost::asio::buffers_begin(buffer.data()) + xfer - 2
-            );
-
-            buffer.consume(xfer);
-        } catch (...) {
-            code = make_error_code(boost::system::errc::not_enough_memory);
-        }
-
-        handler(code, code ? message() : message::parse(data));
-    });
-}
-
-template <typename Socket>
-void wrap_send(Socket& socket, const std::string& message, connection::send_t handler)
-{
-    assert(handler);
-
-    boost::asio::async_write(socket, boost::asio::buffer(message), [handler, message] (auto code, auto) noexcept {
-        handler(code);
-    });
-}
-
-} // !namespace
-
-void connection::rflush()
-{
-    if (input_.empty())
-        return;
-
-    auto self = shared_from_this();
-
-    do_recv(buffer_, [this, self] (auto code, auto message) {
-        if (input_.front())
-            input_.front()(code, std::move(message));
-
-        input_.pop_front();
-
-        if (!code)
-            rflush();
-    });
-}
-
-void connection::sflush()
-{
-    if (output_.empty())
-        return;
-
-    auto self = shared_from_this();
-
-    do_send(output_.front().first, [this, self] (auto code) {
-        if (output_.front().second)
-            output_.front().second(code);
-
-        output_.pop_front();
-
-        if (!code)
-            sflush();
-    });
-}
-
-void connection::connect(const std::string& host, const std::string& service, connect_t handler)
-{
-    assert(handler);
-
-    do_connect(host, service, std::move(handler));
-}
-
-void connection::recv(recv_t handler)
-{
-    auto in_progress = !input_.empty();
-
-    input_.push_back(std::move(handler));
-
-    if (!in_progress)
-        rflush();
-}
-
-void connection::send(std::string message, send_t handler)
-{
-    auto in_progress = !output_.empty();
-
-    output_.emplace_back(message + "\r\n", std::move(handler));
-
-    if (!in_progress)
-        sflush();
-}
-
 message message::parse(const std::string& line)
 {
     std::istringstream iss(line);
@@ -212,7 +76,7 @@
 
 bool message::is_ctcp(unsigned index) const noexcept
 {
-    auto a = arg(index);
+    const auto a = arg(index);
 
     if (a.empty())
         return false;
@@ -232,7 +96,7 @@
     if (line.empty())
         return {"", ""};
 
-    auto pos = line.find("!");
+    const auto pos = line.find("!");
 
     if (pos == std::string::npos)
         return {line, ""};
@@ -240,45 +104,168 @@
     return {line.substr(0, pos), line.substr(pos + 1)};
 }
 
-void ip_connection::do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept
+template <typename Socket>
+void connection::wrap_connect(Socket& socket,
+                              const std::string& host,
+                              const std::string& service,
+                              const connect_handler& handler) noexcept
+{
+    using boost::asio::ip::tcp;
+
+    tcp::resolver::query query(host, service);
+
+    resolver_.async_resolve(query, [&socket, handler] (auto code, auto it) {
+        if (code) {
+            handler(code);
+        } else {
+            async_connect(socket, it, [handler] (auto code, auto) {
+                handler(code);
+            });
+        }
+    });
+}
+
+template <typename Socket>
+void connection::wrap_recv(Socket& socket, const recv_handler& handler) noexcept
 {
-    wrap_resolve(socket_, resolver_, host, service, std::move(handler));
+    async_read_until(socket, input_, "\r\n", [this, &socket, handler] (auto code, auto xfer) {
+        if (xfer == 0U)
+            return handler(make_error_code(boost::asio::error::eof), message());
+        else if (code)
+            return handler(std::move(code), message());
+
+        std::string data;
+
+        try {
+            data = std::string(
+                boost::asio::buffers_begin(input_.data()),
+                boost::asio::buffers_begin(input_.data()) + xfer - 2
+            );
+
+            input_.consume(xfer);
+        } catch (...) {
+            code = make_error_code(boost::system::errc::not_enough_memory);
+        }
+
+        handler(code, code ? message() : message::parse(data));
+    });
+}
+
+template <typename Socket>
+void connection::wrap_send(Socket& socket, const send_handler& handler) noexcept
+{
+    boost::asio::async_write(socket, output_, [handler] (auto code, auto xfer) {
+        if (xfer == 0U)
+            return handler(make_error_code(boost::asio::error::eof));
+
+        handler(code);
+    });
 }
 
-void ip_connection::do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept
+void connection::connect(const std::string& host,
+                         const std::string& service,
+                         const connect_handler& handler) noexcept
 {
-    wrap_recv(socket_, buffer, std::move(handler));
+#if !defined(NDEBUG)
+    assert(handler);
+    assert(!is_connecting_);
+
+    is_connecting_ = true;
+#endif
+
+    do_connect(host, service, [this, handler] (auto code) {
+#if !defined(NDEBUG)
+        is_connecting_ = false;
+#endif
+        handler(std::move(code));
+    });
+}
+
+void connection::recv(const recv_handler& handler) noexcept
+{
+#if !defined(NDEBUG)
+    assert(handler);
+    assert(!is_receiving_);
+
+    is_receiving_ = true;
+#endif
+
+    do_recv([this, handler] (auto code, auto message) {
+#if !defined(NDEBUG)
+        is_receiving_ = false;
+#endif
+
+        handler(std::move(code), std::move(message));
+    });
 }
 
-void ip_connection::do_send(const std::string& data, send_t handler) noexcept
+void connection::send(std::string message, const send_handler& handler)
 {
-    wrap_send(socket_, data, std::move(handler));
+#if !defined(NDEBUG)
+    assert(handler);
+    assert(!is_sending_);
+
+    is_sending_ = true;
+#endif
+
+    std::ostream out(&output_);
+
+    out << std::move(message);
+    out << "\r\n";
+
+    do_send([this, handler] (auto code) {
+#if !defined(NDEBUG)
+        is_sending_ = false;
+#endif
+
+        handler(std::move(code));
+    });
+}
+
+void ip_connection::do_connect(const std::string& host,
+                               const std::string& service,
+                               const connect_handler& handler) noexcept
+{
+    wrap_connect(socket_, host, service, handler);
+}
+
+void ip_connection::do_recv(const recv_handler& handler) noexcept
+{
+    wrap_recv(socket_, handler);
+}
+
+void ip_connection::do_send(const send_handler& handler) noexcept
+{
+    wrap_send(socket_, handler);
 }
 
 #if defined(HAVE_SSL)
 
-void tls_connection::do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept
+void tls_connection::do_connect(const std::string& host,
+                                const std::string& service,
+                                const connect_handler& handler) noexcept
 {
     using boost::asio::ssl::stream_base;
 
-    wrap_resolve(socket_.lowest_layer(), resolver_, host, service, [this, handler] (auto code) {
-        if (code)
+    wrap_connect(socket_.lowest_layer(), host, service, [this, handler] (auto code) {
+        if (code) {
             handler(code);
-        else
+        } else {
             socket_.async_handshake(stream_base::client, [this, handler] (auto code) {
                 handler(code);
             });
+        }
     });
 }
 
-void tls_connection::do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept
+void tls_connection::do_recv(const recv_handler& handler) noexcept
 {
-    wrap_recv(socket_, buffer, std::move(handler));
+    wrap_recv(socket_, handler);
 }
 
-void tls_connection::do_send(const std::string& data, send_t handler) noexcept
+void tls_connection::do_send(const send_handler& handler) noexcept
 {
-    wrap_send(socket_, data, std::move(handler));
+    wrap_send(socket_, handler);
 }
 
 #endif // !HAVE_SSL
--- a/libirccd/irccd/daemon/irc.hpp	Wed Apr 11 20:11:18 2018 +0200
+++ b/libirccd/irccd/daemon/irc.hpp	Wed Apr 11 21:01:10 2018 +0200
@@ -356,72 +356,104 @@
 /**
  * \brief Abstract connection to a server.
  */
-class connection : public std::enable_shared_from_this<connection> {
+class connection {
 public:
     /**
      * Handler for connecting.
      */
-    using connect_t = std::function<void (boost::system::error_code)>;
+    using connect_handler = std::function<void (boost::system::error_code)>;
 
     /**
      * Handler for receiving.
      */
-    using recv_t = std::function<void (boost::system::error_code, message)>;
+    using recv_handler = std::function<void (boost::system::error_code, message)>;
 
     /**
      * Handler for sending.
      */
-    using send_t = std::function<void (boost::system::error_code)>;
-
-    /**
-     * Convenient alias.
-     */
-    using ptr = std::shared_ptr<connection>;
+    using send_handler = std::function<void (boost::system::error_code)>;
 
 private:
-    using buffer_t = boost::asio::streambuf;
-    using input_t = std::deque<recv_t>;
-    using output_t = std::deque<std::pair<std::string, send_t>>;
+    boost::asio::ip::tcp::resolver resolver_;
+    boost::asio::streambuf input_;
+    boost::asio::streambuf output_;
 
-    buffer_t buffer_;
-    input_t input_;
-    output_t output_;
-
-    void rflush();
-    void sflush();
+#if !defined(NDEBUG)
+    bool is_connecting_{false};
+    bool is_receiving_{false};
+    bool is_sending_{false};
+#endif
 
 protected:
     /**
+     * Use boost::asio::async_resolve and boost::asio::async_connect on the
+     * given real socket type.
+     *
+     * \param socket the socket
+     * \param host the hostname
+     * \param service the service or port number
+     * \param handler the non-null handler
+     */
+    template <typename Socket>
+    void wrap_connect(Socket& socket,
+                      const std::string& host,
+                      const std::string& service,
+                      const connect_handler& handler) noexcept;
+
+    /**
+     * Use boost::asio::asynd_read_until on the given real socket type.
+     *
+     * \param socket the socket
+     * \param handler the non-null handler
+     */
+    template <typename Socket>
+    void wrap_recv(Socket& socket, const recv_handler& handler) noexcept;
+
+    /**
+     * Use boost::asio::asynd_write on the given real socket type.
+     *
+     * \param socket the socket
+     * \param handler the non-null handler
+     */
+    template <typename Socket>
+    void wrap_send(Socket& socket, const send_handler& handler) noexcept;
+
+    /**
      * Do the connection.
      *
+     * Derived implementation may call wrap_connect on its underlying socket.
+     *
      * \param host the hostname
      * \param service the service or port number
      * \param handler the non-null handler
      */
-    virtual void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept = 0;
+    virtual void do_connect(const std::string& host,
+                            const std::string& service,
+                            const connect_handler& handler) noexcept = 0;
 
     /**
      * Receive some data.
      *
-     * \param buffer the buffer to complete
      * \param handler the non-null handler
      */
-    virtual void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept = 0;
+    virtual void do_recv(const recv_handler& handler) noexcept = 0;
 
     /**
      * Send data.
      *
-     * \param data the data to send
      * \param handler the non-null handler
      */
-    virtual void do_send(const std::string& data, send_t handler) noexcept = 0;
+    virtual void do_send(const send_handler& handler) noexcept = 0;
 
+public:
     /**
      * Default constructor.
      */
-    connection() = default;
+    inline connection(boost::asio::io_service& service)
+        : resolver_(service)
+    {
+    }
 
-public:
     /**
      * Virtual destructor defaulted.
      */
@@ -431,11 +463,14 @@
      * Connect to the host.
      *
      * \pre handler the handler
+     * \pre another connect operation must not be running
      * \param host the host
      * \param service the service or port number
      * \param handler the non-null handler
      */
-    void connect(const std::string& host, const std::string& service, connect_t handler);
+    void connect(const std::string& host,
+                 const std::string& service,
+                 const connect_handler& handler) noexcept;
 
     /**
      * Start receiving data.
@@ -443,9 +478,11 @@
      * The handler must not throw exceptions and `this` must be valid in the
      * lifetime of the handler.
      *
+     * \pre another recv operation must not be running
+     * \pre handler != nullptr
      * \param handler the handler to call
      */
-    void recv(recv_t handler);
+    void recv(const recv_handler& handler) noexcept;
 
     /**
      * Start sending data.
@@ -453,10 +490,12 @@
      * The handler must not throw exceptions and `this` must be valid in the
      * lifetime of the handler.
      *
+     * \pre another send operation must not be running
+     * \pre handler != nullptr
      * \param message the raw message
      * \param handler the handler to call
      */
-    void send(std::string message, send_t handler = nullptr);
+    void send(std::string message, const send_handler& handler);
 };
 
 /**
@@ -465,47 +504,36 @@
 class ip_connection : public connection {
 private:
     boost::asio::ip::tcp::socket socket_;
-    boost::asio::ip::tcp::resolver resolver_;
 
 protected:
     /**
      * \copydoc connection::do_connect
      */
-    void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept override;
+    void do_connect(const std::string& host,
+                    const std::string& service,
+                    const connect_handler& handler) noexcept override;
 
     /**
      * \copydoc connection::do_recv
      */
-    void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept override;
+    void do_recv(const recv_handler& handler) noexcept override;
 
     /**
      * \copydoc connection::do_send
      */
-    void do_send(const std::string& data, send_t handler) noexcept override;
+    void do_send(const send_handler& handler) noexcept override;
 
+public:
     /**
      * Constructor.
      *
      * \param service the io service
      */
-    inline ip_connection(boost::asio::io_service& service) noexcept
-        : socket_(service)
-        , resolver_(service)
+    inline ip_connection(boost::asio::io_service& service)
+        : connection(service)
+        , socket_(service)
     {
     }
-
-public:
-    /**
-     * Wrap the connection as shared ptr.
-     *
-     * \param args the tls_connection constructor arguments
-     * \return the shared_ptr connection
-     */
-    template <typename... Args>
-    static inline std::shared_ptr<ip_connection> create(Args&&... args)
-    {
-        return std::shared_ptr<ip_connection>(new ip_connection(std::forward<Args>(args)...));
-    }
 };
 
 #if defined(HAVE_SSL)
@@ -517,48 +545,37 @@
 private:
     boost::asio::ssl::context context_;
     boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
-    boost::asio::ip::tcp::resolver resolver_;
 
 protected:
     /**
      * \copydoc connection::do_connect
      */
-    void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept override;
+    void do_connect(const std::string& host,
+                    const std::string& service,
+                    const connect_handler& handler) noexcept override;
 
     /**
      * \copydoc connection::do_recv
      */
-    void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept override;
+    void do_recv(const recv_handler& handler) noexcept override;
 
     /**
      * \copydoc connection::do_send
      */
-    void do_send(const std::string& data, send_t handler) noexcept override;
+    void do_send(const send_handler& handler) noexcept override;
 
+public:
     /**
      * Constructor.
      *
      * \param service the io service
      */
     inline tls_connection(boost::asio::io_service& service)
-        : context_(boost::asio::ssl::context::sslv23)
+        : connection(service)
+        , context_(boost::asio::ssl::context::sslv23)
         , socket_(service, context_)
-        , resolver_(service)
     {
     }
-
-public:
-    /**
-     * Wrap the connection as shared ptr.
-     *
-     * \param args the tls_connection constructor arguments
-     * \return the shared_ptr connection
-     */
-    template <typename... Args>
-    static inline std::shared_ptr<tls_connection> create(Args&&... args)
-    {
-        return std::shared_ptr<tls_connection>(new tls_connection(std::forward<Args>(args)...));
-    }
 };
 
 #endif // !HAVE_SSL
--- a/libirccd/irccd/daemon/server.cpp	Wed Apr 11 20:11:18 2018 +0200
+++ b/libirccd/irccd/daemon/server.cpp	Wed Apr 11 21:01:10 2018 +0200
@@ -147,7 +147,7 @@
     if (msg.args().size() < 3 || msg.arg(1) == "")
         return;
 
-    auto it = names_map_.find(msg.arg(1));
+    const auto it = names_map_.find(msg.arg(1));
 
     if (it != names_map_.end()) {
         std::vector<std::string> list(it->second.begin(), it->second.end());
@@ -282,7 +282,7 @@
 {
     assert(msg.command() == "PING");
 
-    conn_->send(string_util::sprintf("PONG %s", msg.arg(0)));
+    send(string_util::sprintf("PONG %s", msg.arg(0)));
 }
 
 void server::dispatch_privmsg(const irc::message& msg)
@@ -395,49 +395,61 @@
         dispatch_whoisuser(message);
 }
 
-void server::handle_recv(boost::system::error_code code, irc::message message)
+void server::recv()
 {
-    if (code) {
-        const auto self = shared_from_this();
-
-        service_.post([this, self] () {
-            state_ = state::disconnected;
-            conn_ = nullptr;
-            reconnect();
-        });
-    } else {
-        dispatch(message);
-        recv();
-    }
+    conn_->recv([this, conn = conn_] (auto code, auto message) {
+        if (code)
+            wait();
+        else {
+            dispatch(message);
+            recv();
+        }
+    });
 }
 
-void server::recv()
+void server::flush()
 {
-    if (state_ != state::connected)
-        throw server_error(server_error::not_connected);
+    if (queue_.empty())
+        return;
 
-    conn_->recv([this] (auto code, auto message) {
-        handle_recv(std::move(code), std::move(message));
+    conn_->send(queue_.front(), [this, conn = conn_] (auto code) {
+        queue_.pop_front();
+
+        if (code)
+            wait();
+        else
+            flush();
     });
 }
 
 void server::identify()
 {
-    assert(state_ == state::identifying);
+    state_ = state::identifying;
+    recocur_ = 0U;
+    jchannels_.clear();
 
     if (!password_.empty())
-        conn_->send(string_util::sprintf("PASS %s", password_));
+        send(string_util::sprintf("PASS %s", password_));
 
-    conn_->send(string_util::sprintf("NICK %s", nickname_));
-    conn_->send(string_util::sprintf("USER %s unknown unknown :%s", username_, realname_));
+    send(string_util::sprintf("NICK %s", nickname_));
+    send(string_util::sprintf("USER %s unknown unknown :%s", username_, realname_));
 }
 
 void server::wait()
 {
-    assert(state_ == state::waiting);
+    /*
+     * This function maybe called from a recv(), send() or even connect() call
+     * so be sure to only wait one at a time.
+     */
+    if (state_ == state::waiting)
+        return;
 
+    state_ = state::waiting;
     timer_.expires_from_now(boost::posix_time::seconds(recodelay_));
-    timer_.async_wait([this] (auto) {
+    timer_.async_wait([this] (auto code) {
+        if (code == boost::asio::error::operation_aborted)
+            return;
+
         recocur_ ++;
         connect();
     });
@@ -445,25 +457,21 @@
 
 void server::handle_connect(boost::system::error_code code)
 {
+    // Cancel connect timer.
+    timer_.cancel();
+
     if (code) {
-        conn_ = nullptr;
-
         // Wait before reconnecting.
         if (recotries_ != 0) {
             if (recotries_ > 0 && recocur_ >= recotries_) {
-                state_ = state::disconnected;
-                on_die({shared_from_this()});
+                disconnect();
             } else {
                 state_ = state::waiting;
                 wait();
             }
         } else
-            state_ = state::disconnected;
+            disconnect();
     } else {
-        state_ = state::identifying;
-        recocur_ = 0U;
-        jchannels_.clear();
-
         identify();
         recv();
     }
@@ -478,7 +486,7 @@
 void server::set_nickname(std::string nickname)
 {
     if (state_ == state::connected)
-        conn_->send(string_util::sprintf("NICK %s", nickname));
+        send(string_util::sprintf("NICK %s", nickname));
     else
         nickname_ = std::move(nickname);
 }
@@ -502,7 +510,7 @@
 
     if (flags_ & ssl) {
 #if defined(HAVE_SSL)
-        conn_ = irc::tls_connection::create(service_);
+        conn_ = std::make_shared<irc::tls_connection>(service_);
 #else
         /*
          * If SSL is not compiled in, the caller is responsible of not setting
@@ -511,10 +519,10 @@
         assert(!(flags_ & ssl));
 #endif
     } else
-        conn_ = irc::ip_connection::create(service_);
+        conn_ = std::make_shared<irc::ip_connection>(service_);
 
     state_ = state::connecting;
-    conn_->connect(host_, std::to_string(port_), [this] (auto code) {
+    conn_->connect(host_, std::to_string(port_), [this, conn = conn_] (auto code) {
         handle_connect(std::move(code));
     });
 }
@@ -643,20 +651,15 @@
 {
     assert(!raw.empty());
 
-    if (state_ != state::connected)
-        throw server_error(server_error::not_connected);
+    if (state_ == state::identifying || state_ == state::connected) {
+        const auto in_progress = queue_.size() > 0;
 
-    conn_->send(std::move(raw), [this] (auto code) {
-        if (code) {
-            const auto self = shared_from_this();
+        queue_.push_back(std::move(raw));
 
-            service_.post([this, self] () {
-                state_ = state::disconnected;
-                conn_ = nullptr;
-                reconnect();
-            });
-        }
-    });
+        if (!in_progress)
+            flush();
+    } else
+        queue_.push_back(std::move(raw));
 }
 
 void server::topic(std::string channel, std::string topic)
--- a/libirccd/irccd/daemon/server.hpp	Wed Apr 11 20:11:18 2018 +0200
+++ b/libirccd/irccd/daemon/server.hpp	Wed Apr 11 21:01:10 2018 +0200
@@ -27,6 +27,7 @@
 #include <irccd/sysconfig.hpp>
 
 #include <cstdint>
+#include <deque>
 #include <map>
 #include <memory>
 #include <set>
@@ -438,6 +439,7 @@
     boost::asio::io_service& service_;
     boost::asio::deadline_timer timer_;
     std::shared_ptr<irc::connection> conn_;
+    std::deque<std::string> queue_;
     std::int8_t recocur_{0};
     std::map<std::string, std::set<std::string>> names_map_;
     std::map<std::string, whois_info> whois_map_;
@@ -463,9 +465,9 @@
     void dispatch_whoisuser(const irc::message&);
     void dispatch(const irc::message&);
 
-    void handle_recv(boost::system::error_code code, irc::message message);
     void handle_connect(boost::system::error_code);
     void recv();
+    void flush();
     void identify();
     void wait();
 
@@ -795,7 +797,6 @@
      *
      * \param target the target nickname
      * \param channel the channel
-     * \throw server_error on errors
      */
     virtual void invite(std::string target, std::string channel);
 
@@ -804,7 +805,6 @@
      *
      * \param channel the channel to join
      * \param password the optional password
-     * \throw server_error on errors
      */
     virtual void join(std::string channel, std::string password = "");
 
@@ -815,7 +815,6 @@
      * \param target the target to kick
      * \param channel from which channel
      * \param reason the optional reason
-     * \throw server_error on errors
      */
     virtual void kick(std::string target, std::string channel, std::string reason = "");
 
@@ -825,7 +824,6 @@
      *
      * \param target the nickname or the channel
      * \param message the message
-     * \throw server_error on errors
      */
     virtual void me(std::string target, std::string message);
 
@@ -834,7 +832,6 @@
      *
      * \param target the target
      * \param message the message
-     * \throw server_error on errors
      */
     virtual void message(std::string target, std::string message);
 
@@ -846,7 +843,6 @@
      * \param limit the optional limit
      * \param user the optional user
      * \param mask the optional ban mask
-     * \throw server_error on errors
      */
     virtual void mode(std::string channel,
                       std::string mode,
@@ -858,7 +854,6 @@
      * Request the list of names.
      *
      * \param channel the channel
-     * \throw server_error on errors
      */
     virtual void names(std::string channel);
 
@@ -867,7 +862,6 @@
      *
      * \param target the target
      * \param message the notice message
-     * \throw server_error on errors
      */
     virtual void notice(std::string target, std::string message);
 
@@ -879,7 +873,6 @@
      *
      * \param channel the channel to leave
      * \param reason the optional reason
-     * \throw server_error on errors
      */
     virtual void part(std::string channel, std::string reason = "");
 
@@ -887,9 +880,10 @@
      * Send a raw message to the IRC server. You don't need to add
      * message terminators.
      *
-     * \pre state() == state::connected
+     * If the server is not yet connected, the command is postponed and will be
+     * ran when ready.
+     *
      * \param raw the raw message (without `\r\n\r\n`)
-     * \throw server_error on errors
      */
     virtual void send(std::string raw);
 
@@ -898,7 +892,6 @@
      *
      * \param channel the channel
      * \param topic the desired topic
-     * \throw server_error on errors
      */
     virtual void topic(std::string channel, std::string topic);
 
@@ -906,7 +899,6 @@
      * Request for whois information.
      *
      * \param target the target nickname
-     * \throw server_error on errors
      */
     virtual void whois(std::string target);
 };