Mercurial > irccd
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); };