explicitly tear down slots

This commit is contained in:
Eric Mertens 2025-01-26 19:35:56 -08:00
parent 135f5aa47d
commit a49389d508
9 changed files with 137 additions and 81 deletions

View File

@ -46,9 +46,9 @@ add_executable(xbot
connection.cpp connection.cpp
irc_coroutine.cpp irc_coroutine.cpp
ircmsg.cpp ircmsg.cpp
registration_thread.cpp registration.cpp
sasl_mechanism.cpp sasl_mechanism.cpp
self_thread.cpp client.cpp
settings.cpp settings.cpp
snote.cpp snote.cpp
) )

35
bot.cpp
View File

@ -2,7 +2,7 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
auto Bot::start(std::shared_ptr<SelfThread> self) -> std::shared_ptr<Bot> auto Bot::start(std::shared_ptr<Client> self) -> std::shared_ptr<Bot>
{ {
const auto thread = std::make_shared<Bot>(std::move(self)); const auto thread = std::make_shared<Bot>(std::move(self));
@ -22,18 +22,38 @@ auto Bot::process_command(std::string_view message, const IrcMsg &msg) -> void
message = message.substr(cmdstart); message = message.substr(cmdstart);
if (not message.starts_with(command_prefix_)) return; if (not message.starts_with(command_prefix_)) return;
message = message.substr(1); // discard the prefix
auto cmdend = message.find_first_of(' ', 1); auto cmdend = message.find(' ');
std::string_view command = std::string_view command =
cmdend == message.npos ? message : message.substr(0, cmdend); cmdend == message.npos ? message : message.substr(0, cmdend);
std::string_view arguments = std::string_view arguments =
cmdend == message.npos ? std::string_view{} : message.substr(cmdend + 1); cmdend == message.npos ? std::string_view{} : message.substr(cmdend + 1);
BOOST_LOG_TRIVIAL(debug) << "COMMAND: " << command << " -- [" << arguments << "]"; std::string_view oper;
std::string_view account;
for (auto [key, value] : msg.tags)
{
if (key == "account")
{
account = value;
}
else if (key == "operator")
{
oper = value;
}
}
sig_command({
.source = msg.args[0],
.target = msg.args[1],
.oper = oper,
.account = account,
.command = command,
.arguments = arguments
});
} }
auto Bot::on_ircmsg(IrcCommand cmd, const IrcMsg &msg) -> void auto Bot::on_ircmsg(IrcCommand cmd, const IrcMsg &msg) -> void
@ -53,3 +73,8 @@ auto Bot::on_ircmsg(IrcCommand cmd, const IrcMsg &msg) -> void
} }
} }
} }
auto Bot::shutdown() -> void
{
sig_command.disconnect_all_slots();
}

12
bot.hpp
View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "self_thread.hpp" #include "client.hpp"
#include <boost/signals2.hpp> #include <boost/signals2.hpp>
@ -18,15 +18,19 @@ struct Command
struct Bot : std::enable_shared_from_this<Bot> struct Bot : std::enable_shared_from_this<Bot>
{ {
std::shared_ptr<SelfThread> self_; std::shared_ptr<Client> self_;
char command_prefix_; char command_prefix_;
Bot(std::shared_ptr<SelfThread> self) boost::signals2::signal<void(const Command &)> sig_command;
Bot(std::shared_ptr<Client> self)
: self_{std::move(self)} : self_{std::move(self)}
, command_prefix_{'!'} , command_prefix_{'!'}
{} {}
auto on_ircmsg(IrcCommand, const IrcMsg &) -> void; auto on_ircmsg(IrcCommand, const IrcMsg &) -> void;
auto process_command(std::string_view message, const IrcMsg &msg) -> void; auto process_command(std::string_view message, const IrcMsg &msg) -> void;
static auto start(std::shared_ptr<SelfThread>) -> std::shared_ptr<Bot>; static auto start(std::shared_ptr<Client>) -> std::shared_ptr<Bot>;
auto shutdown() -> void;
}; };

View File

@ -1,4 +1,4 @@
#include "self_thread.hpp" #include "client.hpp"
#include "connection.hpp" #include "connection.hpp"
@ -9,17 +9,17 @@
using namespace std::literals; using namespace std::literals;
auto SelfThread::on_welcome(const IrcMsg &irc) -> void auto Client::on_welcome(const IrcMsg &irc) -> void
{ {
nickname_ = irc.args[0]; nickname_ = irc.args[0];
} }
auto SelfThread::on_registered() -> void auto Client::on_registered() -> void
{ {
connection_.send_join("#lobby"); sig_registered();
} }
auto SelfThread::on_nick(const IrcMsg &irc) -> void auto Client::on_nick(const IrcMsg &irc) -> void
{ {
if (is_my_mask(irc.source)) if (is_my_mask(irc.source))
{ {
@ -27,12 +27,12 @@ auto SelfThread::on_nick(const IrcMsg &irc) -> void
} }
} }
auto SelfThread::on_umodeis(const IrcMsg &irc) -> void auto Client::on_umodeis(const IrcMsg &irc) -> void
{ {
mode_ = irc.args[1]; mode_ = irc.args[1];
} }
auto SelfThread::on_join(const IrcMsg &irc) -> void auto Client::on_join(const IrcMsg &irc) -> void
{ {
if (is_my_mask(irc.source)) if (is_my_mask(irc.source))
{ {
@ -40,7 +40,7 @@ auto SelfThread::on_join(const IrcMsg &irc) -> void
} }
} }
auto SelfThread::on_kick(const IrcMsg &irc) -> void auto Client::on_kick(const IrcMsg &irc) -> void
{ {
if (is_my_nick(irc.args[1])) if (is_my_nick(irc.args[1]))
{ {
@ -48,7 +48,7 @@ auto SelfThread::on_kick(const IrcMsg &irc) -> void
} }
} }
auto SelfThread::on_part(const IrcMsg &irc) -> void auto Client::on_part(const IrcMsg &irc) -> void
{ {
if (is_my_mask(irc.source)) if (is_my_mask(irc.source))
{ {
@ -56,7 +56,7 @@ auto SelfThread::on_part(const IrcMsg &irc) -> void
} }
} }
auto SelfThread::on_mode(const IrcMsg &irc) -> void auto Client::on_mode(const IrcMsg &irc) -> void
{ {
if (is_my_nick(irc.args[0])) if (is_my_nick(irc.args[0]))
{ {
@ -90,7 +90,7 @@ auto SelfThread::on_mode(const IrcMsg &irc) -> void
} }
} }
auto SelfThread::on_isupport(const IrcMsg &msg) -> void auto Client::on_isupport(const IrcMsg &msg) -> void
{ {
const auto hi = msg.args.size() - 1; const auto hi = msg.args.size() - 1;
for (int i = 1; i < hi; ++i) for (int i = 1; i < hi; ++i)
@ -117,9 +117,9 @@ auto SelfThread::on_isupport(const IrcMsg &msg) -> void
} }
} }
auto SelfThread::start(Connection &connection) -> std::shared_ptr<SelfThread> auto Client::start(Connection &connection) -> std::shared_ptr<Client>
{ {
auto thread = std::make_shared<SelfThread>(connection); auto thread = std::make_shared<Client>(connection);
connection.sig_ircmsg.connect([thread](auto cmd, auto &msg) { connection.sig_ircmsg.connect([thread](auto cmd, auto &msg) {
switch (cmd) switch (cmd)
@ -163,38 +163,38 @@ auto SelfThread::start(Connection &connection) -> std::shared_ptr<SelfThread>
return thread; return thread;
} }
auto SelfThread::get_my_nickname() const -> const std::string & auto Client::get_my_nickname() const -> const std::string &
{ {
return nickname_; return nickname_;
} }
auto SelfThread::get_my_mode() const -> const std::string & auto Client::get_my_mode() const -> const std::string &
{ {
return mode_; return mode_;
} }
auto SelfThread::get_my_channels() const -> const std::unordered_set<std::string> & auto Client::get_my_channels() const -> const std::unordered_set<std::string> &
{ {
return channels_; return channels_;
} }
auto SelfThread::is_my_nick(std::string_view nick) const -> bool auto Client::is_my_nick(std::string_view nick) const -> bool
{ {
return casemap_compare(nick, nickname_) == 0; return casemap_compare(nick, nickname_) == 0;
} }
auto SelfThread::is_my_mask(std::string_view mask) const -> bool auto Client::is_my_mask(std::string_view mask) const -> bool
{ {
const auto bang = mask.find('!'); const auto bang = mask.find('!');
return bang != std::string_view::npos && nickname_ == mask.substr(0, bang); return bang != std::string_view::npos && nickname_ == mask.substr(0, bang);
} }
auto SelfThread::is_channel(std::string_view name) const -> bool auto Client::is_channel(std::string_view name) const -> bool
{ {
return not name.empty() && channel_prefix_.find(name[0]) != channel_prefix_.npos; return not name.empty() && channel_prefix_.find(name[0]) != channel_prefix_.npos;
} }
auto SelfThread::on_authenticate(const std::string_view body) -> void auto Client::on_authenticate(const std::string_view body) -> void
{ {
if (not sasl_mechanism_) if (not sasl_mechanism_)
{ {
@ -216,7 +216,7 @@ auto SelfThread::on_authenticate(const std::string_view body) -> void
} }
} }
auto SelfThread::start_sasl(std::unique_ptr<SaslMechanism> mechanism) -> void auto Client::start_sasl(std::unique_ptr<SaslMechanism> mechanism) -> void
{ {
if (sasl_mechanism_) if (sasl_mechanism_)
{ {
@ -248,7 +248,7 @@ static auto casemap_impl(std::string_view str) -> std::string
return result; return result;
} }
auto SelfThread::casemap(std::string_view str) const -> std::string auto Client::casemap(std::string_view str) const -> std::string
{ {
switch (casemap_) { switch (casemap_) {
case Casemap::Ascii: return casemap_impl<tolower>(str); case Casemap::Ascii: return casemap_impl<tolower>(str);
@ -272,7 +272,7 @@ static auto casemap_compare_impl(std::string_view lhs, std::string_view rhs) ->
return 0; return 0;
} }
auto SelfThread::casemap_compare(std::string_view lhs, std::string_view rhs) const -> int auto Client::casemap_compare(std::string_view lhs, std::string_view rhs) const -> int
{ {
switch (casemap_) { switch (casemap_) {
case Casemap::Ascii: return casemap_compare_impl<tolower>(lhs, rhs); case Casemap::Ascii: return casemap_compare_impl<tolower>(lhs, rhs);
@ -280,3 +280,8 @@ auto SelfThread::casemap_compare(std::string_view lhs, std::string_view rhs) con
case Casemap::Rfc1459_Strict: return casemap_compare_impl<tolower_rfc1459_strict>(lhs, rhs); case Casemap::Rfc1459_Strict: return casemap_compare_impl<tolower_rfc1459_strict>(lhs, rhs);
} }
} }
auto Client::shutdown() -> void
{
sig_registered.disconnect_all_slots();
}

View File

@ -20,7 +20,7 @@ enum class Casemap
* @brief Thread to track this connection's identity, and IRC state. * @brief Thread to track this connection's identity, and IRC state.
* *
*/ */
class SelfThread class Client
{ {
Connection &connection_; Connection &connection_;
@ -47,13 +47,16 @@ class SelfThread
auto on_registered() -> void; auto on_registered() -> void;
public: public:
SelfThread(Connection &connection) boost::signals2::signal<void()> sig_registered;
Client(Connection &connection)
: connection_{connection} : connection_{connection}
, casemap_{Casemap::Rfc1459} , casemap_{Casemap::Rfc1459}
, channel_prefix_{"#&"} , channel_prefix_{"#&"}
{ {
} }
static auto start(Connection &) -> std::shared_ptr<SelfThread>;
static auto start(Connection &) -> std::shared_ptr<Client>;
auto start_sasl(std::unique_ptr<SaslMechanism> mechanism) -> void; auto start_sasl(std::unique_ptr<SaslMechanism> mechanism) -> void;
@ -72,4 +75,6 @@ public:
auto casemap(std::string_view) const -> std::string; auto casemap(std::string_view) const -> std::string;
auto casemap_compare(std::string_view, std::string_view) const -> int; auto casemap_compare(std::string_view, std::string_view) const -> int;
auto shutdown() -> void;
}; };

View File

@ -477,5 +477,12 @@ auto Connection::start(ConnectSettings settings) -> void
} }
self->sig_disconnect(); self->sig_disconnect();
// Disconnect all slots to avoid circular references
self->sig_connect.disconnect_all_slots();
self->sig_ircmsg.disconnect_all_slots();
self->sig_disconnect.disconnect_all_slots();
self->sig_snote.disconnect_all_slots();
self->sig_authenticate.disconnect_all_slots();
}); });
} }

View File

@ -10,18 +10,18 @@
#include <string> #include <string>
#include "bot.hpp" #include "bot.hpp"
#include "registration_thread.hpp" #include "client.hpp"
#include "self_thread.hpp" #include "registration.hpp"
using namespace std::chrono_literals; using namespace std::literals;
auto start(boost::asio::io_context &io, const Settings &settings) -> void auto start(boost::asio::io_context &io, const Settings &settings) -> void
{ {
const auto connection = std::make_shared<Connection>(io); const auto connection = std::make_shared<Connection>(io);
const auto client = Client::start(*connection);
Registration::start(*connection, settings, client);
const auto selfThread = SelfThread::start(*connection); const auto bot = Bot::start(client);
RegistrationThread::start(*connection, settings, selfThread);
Bot::start(selfThread);
connection->sig_snote.connect([](auto &match) { connection->sig_snote.connect([](auto &match) {
std::cout << "SNOTE " << static_cast<int>(match.get_tag()) << std::endl; std::cout << "SNOTE " << static_cast<int>(match.get_tag()) << std::endl;
@ -31,16 +31,27 @@ auto start(boost::asio::io_context &io, const Settings &settings) -> void
} }
}); });
connection->sig_disconnect.connect_extended( client->sig_registered.connect_extended([connection](auto &slot) {
[&io, &settings](auto &slot) { slot.disconnect();
slot.disconnect(); connection->send_join("##glguy"sv);
});
connection->sig_disconnect.connect(
[&io, &settings, client, bot]() {
client->shutdown();
bot->shutdown();
auto timer = std::make_shared<boost::asio::steady_timer>(io); auto timer = std::make_shared<boost::asio::steady_timer>(io);
timer->expires_after(5s); timer->expires_after(5s);
timer->async_wait([&io, &settings, timer](auto) { start(io, settings); }); timer->async_wait([&io, &settings, timer](auto) { start(io, settings); });
} }
); );
connection->start(ConnectSettings{ bot->sig_command.connect([connection](const Command &cmd) {
std::cout << "COMMAND " << cmd.command << " from " << cmd.account << std::endl;
});
connection->start({
.tls = settings.use_tls, .tls = settings.use_tls,
.host = settings.host, .host = settings.host,
.port = settings.service, .port = settings.service,

View File

@ -1,4 +1,4 @@
#include "registration_thread.hpp" #include "registration.hpp"
#include "connection.hpp" #include "connection.hpp"
#include "ircmsg.hpp" #include "ircmsg.hpp"
@ -8,10 +8,10 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
RegistrationThread::RegistrationThread( Registration::Registration(
Connection &connection, Connection &connection,
const Settings &settings, const Settings &settings,
std::shared_ptr<SelfThread> self std::shared_ptr<Client> self
) )
: connection_{connection} : connection_{connection}
, settings_{settings} , settings_{settings}
@ -19,9 +19,11 @@ RegistrationThread::RegistrationThread(
{ {
} }
auto RegistrationThread::on_connect() -> void auto Registration::on_connect() -> void
{ {
connection_.send_cap_ls(); connection_.send_cap_ls();
listen_for_cap_ls();
if (not settings_.password.empty()) if (not settings_.password.empty())
{ {
connection_.send_pass(settings_.password); connection_.send_pass(settings_.password);
@ -30,7 +32,7 @@ auto RegistrationThread::on_connect() -> void
connection_.send_nick(settings_.nickname); connection_.send_nick(settings_.nickname);
} }
auto RegistrationThread::send_req() -> void auto Registration::send_req() -> void
{ {
std::string request; std::string request;
std::vector<const char *> want{ std::vector<const char *> want{
@ -77,7 +79,7 @@ auto RegistrationThread::send_req() -> void
} }
} }
auto RegistrationThread::on_msg_cap_ack(const IrcMsg &msg) -> void auto Registration::on_msg_cap_ack(const IrcMsg &msg) -> void
{ {
auto in = std::istringstream{std::string{msg.args[2]}}; auto in = std::istringstream{std::string{msg.args[2]}};
std::for_each( std::for_each(
@ -89,16 +91,16 @@ auto RegistrationThread::on_msg_cap_ack(const IrcMsg &msg) -> void
); );
if (outstanding.empty()) if (outstanding.empty())
{ {
message_handle_.disconnect();
if (settings_.sasl_mechanism.empty()) if (settings_.sasl_mechanism.empty())
{ {
slot_.disconnect();
connection_.send_cap_end(); connection_.send_cap_end();
} }
else else
{ {
self_->start_sasl(std::make_unique<SaslPlain>(settings_.sasl_authcid, settings_.sasl_authzid, settings_.sasl_password)); self_->start_sasl(std::make_unique<SaslPlain>(settings_.sasl_authcid, settings_.sasl_authzid, settings_.sasl_password));
connection_.sig_ircmsg.connect_extended([thread = shared_from_this()](auto &slot, auto cmd, auto &msg) { slot_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](auto cmd, auto &msg) {
switch (cmd) switch (cmd)
{ {
default: default:
@ -106,14 +108,14 @@ auto RegistrationThread::on_msg_cap_ack(const IrcMsg &msg) -> void
case IrcCommand::RPL_SASLSUCCESS: case IrcCommand::RPL_SASLSUCCESS:
case IrcCommand::ERR_SASLFAIL: case IrcCommand::ERR_SASLFAIL:
thread->connection_.send_cap_end(); thread->connection_.send_cap_end();
slot.disconnect(); thread->slot_.disconnect();
} }
}); });
} }
} }
} }
auto RegistrationThread::on_msg_cap_ls(const IrcMsg &msg) -> void auto Registration::on_msg_cap_ls(const IrcMsg &msg) -> void
{ {
const std::string_view *kvs; const std::string_view *kvs;
bool last; bool last;
@ -153,32 +155,30 @@ auto RegistrationThread::on_msg_cap_ls(const IrcMsg &msg) -> void
if (last) if (last)
{ {
message_handle_.disconnect(); slot_.disconnect();
send_req(); send_req();
} }
} }
auto RegistrationThread::start( auto Registration::start(
Connection &connection, Connection &connection,
const Settings &settings, const Settings &settings,
std::shared_ptr<SelfThread> self std::shared_ptr<Client> self
) -> std::shared_ptr<RegistrationThread> ) -> std::shared_ptr<Registration>
{ {
const auto thread = std::make_shared<RegistrationThread>(connection, std::move(settings), std::move(self)); const auto thread = std::make_shared<Registration>(connection, std::move(settings), std::move(self));
thread->listen_for_cap_ls(); thread->slot_ = connection.sig_connect.connect([thread]() {
thread->slot_.disconnect();
thread->connect_handle_ = connection.sig_connect.connect([thread]() {
thread->connect_handle_.disconnect();
thread->on_connect(); thread->on_connect();
}); });
return thread; return thread;
} }
auto RegistrationThread::listen_for_cap_ack() -> void auto Registration::listen_for_cap_ack() -> void
{ {
message_handle_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](IrcCommand cmd, const IrcMsg &msg) { slot_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](IrcCommand cmd, const IrcMsg &msg) {
if (IrcCommand::CAP == cmd && msg.args.size() >= 2 && "ACK" == msg.args[1]) if (IrcCommand::CAP == cmd && msg.args.size() >= 2 && "ACK" == msg.args[1])
{ {
thread->on_msg_cap_ack(msg); thread->on_msg_cap_ack(msg);
@ -186,9 +186,9 @@ auto RegistrationThread::listen_for_cap_ack() -> void
}); });
} }
auto RegistrationThread::listen_for_cap_ls() -> void auto Registration::listen_for_cap_ls() -> void
{ {
message_handle_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](IrcCommand cmd, const IrcMsg &msg) { slot_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](IrcCommand cmd, const IrcMsg &msg) {
if (IrcCommand::CAP == cmd && msg.args.size() >= 2 && "LS" == msg.args[1]) if (IrcCommand::CAP == cmd && msg.args.size() >= 2 && "LS" == msg.args[1])
{ {
thread->on_msg_cap_ls(msg); thread->on_msg_cap_ls(msg);
@ -196,7 +196,7 @@ auto RegistrationThread::listen_for_cap_ls() -> void
else if (IrcCommand::RPL_WELCOME == cmd) else if (IrcCommand::RPL_WELCOME == cmd)
{ {
// Server doesn't support CAP negotiation // Server doesn't support CAP negotiation
thread->message_handle_.disconnect(); thread->slot_.disconnect();
} }
}); });
} }

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "connection.hpp" #include "connection.hpp"
#include "self_thread.hpp" #include "client.hpp"
#include "settings.hpp" #include "settings.hpp"
#include <memory> #include <memory>
@ -9,17 +9,16 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
class RegistrationThread : public std::enable_shared_from_this<RegistrationThread> class Registration : public std::enable_shared_from_this<Registration>
{ {
Connection &connection_; Connection &connection_;
const Settings &settings_; const Settings &settings_;
std::shared_ptr<SelfThread> self_; std::shared_ptr<Client> self_;
std::unordered_map<std::string, std::string> caps; std::unordered_map<std::string, std::string> caps;
std::unordered_set<std::string> outstanding; std::unordered_set<std::string> outstanding;
boost::signals2::scoped_connection connect_handle_; boost::signals2::scoped_connection slot_;
boost::signals2::scoped_connection message_handle_;
auto on_connect() -> void; auto on_connect() -> void;
auto send_req() -> void; auto send_req() -> void;
@ -30,15 +29,15 @@ class RegistrationThread : public std::enable_shared_from_this<RegistrationThrea
auto listen_for_cap_ls() -> void; auto listen_for_cap_ls() -> void;
public: public:
RegistrationThread( Registration(
Connection &connection_, Connection &connection_,
const Settings &, const Settings &,
std::shared_ptr<SelfThread> self std::shared_ptr<Client> self
); );
static auto start( static auto start(
Connection &connection, Connection &connection,
const Settings &, const Settings &,
std::shared_ptr<SelfThread> self std::shared_ptr<Client> self
) -> std::shared_ptr<RegistrationThread>; ) -> std::shared_ptr<Registration>;
}; };