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
irc_coroutine.cpp
ircmsg.cpp
registration_thread.cpp
registration.cpp
sasl_mechanism.cpp
self_thread.cpp
client.cpp
settings.cpp
snote.cpp
)

35
bot.cpp
View File

@ -2,7 +2,7 @@
#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));
@ -22,18 +22,38 @@ auto Bot::process_command(std::string_view message, const IrcMsg &msg) -> void
message = message.substr(cmdstart);
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 =
cmdend == message.npos ? message : message.substr(0, cmdend);
std::string_view arguments =
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
@ -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
#include "self_thread.hpp"
#include "client.hpp"
#include <boost/signals2.hpp>
@ -18,15 +18,19 @@ struct Command
struct Bot : std::enable_shared_from_this<Bot>
{
std::shared_ptr<SelfThread> self_;
std::shared_ptr<Client> self_;
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)}
, command_prefix_{'!'}
{}
auto on_ircmsg(IrcCommand, const IrcMsg &) -> 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"
@ -9,17 +9,17 @@
using namespace std::literals;
auto SelfThread::on_welcome(const IrcMsg &irc) -> void
auto Client::on_welcome(const IrcMsg &irc) -> void
{
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))
{
@ -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];
}
auto SelfThread::on_join(const IrcMsg &irc) -> void
auto Client::on_join(const IrcMsg &irc) -> void
{
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]))
{
@ -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))
{
@ -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]))
{
@ -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;
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) {
switch (cmd)
@ -163,38 +163,38 @@ auto SelfThread::start(Connection &connection) -> std::shared_ptr<SelfThread>
return thread;
}
auto SelfThread::get_my_nickname() const -> const std::string &
auto Client::get_my_nickname() const -> const std::string &
{
return nickname_;
}
auto SelfThread::get_my_mode() const -> const std::string &
auto Client::get_my_mode() const -> const std::string &
{
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_;
}
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;
}
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('!');
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;
}
auto SelfThread::on_authenticate(const std::string_view body) -> void
auto Client::on_authenticate(const std::string_view body) -> void
{
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_)
{
@ -248,7 +248,7 @@ static auto casemap_impl(std::string_view str) -> std::string
return result;
}
auto SelfThread::casemap(std::string_view str) const -> std::string
auto Client::casemap(std::string_view str) const -> std::string
{
switch (casemap_) {
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;
}
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_) {
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);
}
}
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.
*
*/
class SelfThread
class Client
{
Connection &connection_;
@ -47,13 +47,16 @@ class SelfThread
auto on_registered() -> void;
public:
SelfThread(Connection &connection)
boost::signals2::signal<void()> sig_registered;
Client(Connection &connection)
: connection_{connection}
, casemap_{Casemap::Rfc1459}
, 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;
@ -72,4 +75,6 @@ public:
auto casemap(std::string_view) const -> std::string;
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();
// 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 "bot.hpp"
#include "registration_thread.hpp"
#include "self_thread.hpp"
#include "client.hpp"
#include "registration.hpp"
using namespace std::chrono_literals;
using namespace std::literals;
auto start(boost::asio::io_context &io, const Settings &settings) -> void
{
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);
RegistrationThread::start(*connection, settings, selfThread);
Bot::start(selfThread);
const auto bot = Bot::start(client);
connection->sig_snote.connect([](auto &match) {
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(
[&io, &settings](auto &slot) {
slot.disconnect();
client->sig_registered.connect_extended([connection](auto &slot) {
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);
timer->expires_after(5s);
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,
.host = settings.host,
.port = settings.service,

View File

@ -1,4 +1,4 @@
#include "registration_thread.hpp"
#include "registration.hpp"
#include "connection.hpp"
#include "ircmsg.hpp"
@ -8,10 +8,10 @@
#include <unordered_map>
#include <unordered_set>
RegistrationThread::RegistrationThread(
Registration::Registration(
Connection &connection,
const Settings &settings,
std::shared_ptr<SelfThread> self
std::shared_ptr<Client> self
)
: connection_{connection}
, settings_{settings}
@ -19,9 +19,11 @@ RegistrationThread::RegistrationThread(
{
}
auto RegistrationThread::on_connect() -> void
auto Registration::on_connect() -> void
{
connection_.send_cap_ls();
listen_for_cap_ls();
if (not settings_.password.empty())
{
connection_.send_pass(settings_.password);
@ -30,7 +32,7 @@ auto RegistrationThread::on_connect() -> void
connection_.send_nick(settings_.nickname);
}
auto RegistrationThread::send_req() -> void
auto Registration::send_req() -> void
{
std::string request;
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]}};
std::for_each(
@ -89,16 +91,16 @@ auto RegistrationThread::on_msg_cap_ack(const IrcMsg &msg) -> void
);
if (outstanding.empty())
{
message_handle_.disconnect();
if (settings_.sasl_mechanism.empty())
{
slot_.disconnect();
connection_.send_cap_end();
}
else
{
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)
{
default:
@ -106,14 +108,14 @@ auto RegistrationThread::on_msg_cap_ack(const IrcMsg &msg) -> void
case IrcCommand::RPL_SASLSUCCESS:
case IrcCommand::ERR_SASLFAIL:
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;
bool last;
@ -153,32 +155,30 @@ auto RegistrationThread::on_msg_cap_ls(const IrcMsg &msg) -> void
if (last)
{
message_handle_.disconnect();
slot_.disconnect();
send_req();
}
}
auto RegistrationThread::start(
auto Registration::start(
Connection &connection,
const Settings &settings,
std::shared_ptr<SelfThread> self
) -> std::shared_ptr<RegistrationThread>
std::shared_ptr<Client> self
) -> 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->connect_handle_ = connection.sig_connect.connect([thread]() {
thread->connect_handle_.disconnect();
thread->slot_ = connection.sig_connect.connect([thread]() {
thread->slot_.disconnect();
thread->on_connect();
});
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])
{
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])
{
thread->on_msg_cap_ls(msg);
@ -196,7 +196,7 @@ auto RegistrationThread::listen_for_cap_ls() -> void
else if (IrcCommand::RPL_WELCOME == cmd)
{
// Server doesn't support CAP negotiation
thread->message_handle_.disconnect();
thread->slot_.disconnect();
}
});
}

View File

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