1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-22 07:42:37 +00:00

perf: revert e065565cd to restore async dns lookups (#4182)

This commit is contained in:
Charles Kerr 2022-11-15 11:16:49 -06:00 committed by GitHub
parent befeafbcfe
commit d2125ee965
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 578 additions and 82 deletions

View file

@ -11,6 +11,8 @@
0A6169A80FE5C9A200C66CE6 /* bitfield.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A6169A60FE5C9A200C66CE6 /* bitfield.h */; };
0A89346B736DBCF81F3A4850 /* torrent-metainfo.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0A89346B736DBCF81F3A4851 /* torrent-metainfo.cc */; };
0A89346B736DBCF81F3A4852 /* torrent-metainfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A89346B736DBCF81F3A4853 /* torrent-metainfo.h */; };
11524394C75E57E52CD9ADF0 /* dns.h in Headers */ = {isa = PBXBuildFile; fileRef = 11524394C75E57E52CD9ADF1 /* dns.h */; settings = {ATTRIBUTES = (Public, ); }; };
11524394C75E57E52CD9ADF2 /* dns-ev.h in Headers */ = {isa = PBXBuildFile; fileRef = 11524394C75E57E52CD9ADF3 /* dns-ev.h */; settings = {ATTRIBUTES = (Private, ); }; };
1BB44E07B1B52E28291B4E32 /* file-piece-map.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BB44E07B1B52E28291B4E30 /* file-piece-map.cc */; };
1BB44E07B1B52E28291B4E33 /* file-piece-map.h in Headers */ = {isa = PBXBuildFile; fileRef = 1BB44E07B1B52E28291B4E31 /* file-piece-map.h */; };
2856E0656A49F2665D69E760 /* benc.h in Headers */ = {isa = PBXBuildFile; fileRef = 2856E0656A49F2665D69E761 /* benc.h */; };
@ -602,6 +604,8 @@
0A89346B736DBCF81F3A4851 /* torrent-metainfo.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "torrent-metainfo.cc"; sourceTree = "<group>"; };
0A89346B736DBCF81F3A4853 /* torrent-metainfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "torrent-metainfo.h"; sourceTree = "<group>"; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
11524394C75E57E52CD9ADF1 /* dns.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = dns.h; sourceTree = "<group>"; };
11524394C75E57E52CD9ADF3 /* dns-ev.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "dns-ev.h"; sourceTree = "<group>"; };
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
1BB44E07B1B52E28291B4E30 /* file-piece-map.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "file-piece-map.cc"; sourceTree = "<group>"; };
1BB44E07B1B52E28291B4E31 /* file-piece-map.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "file-piece-map.h"; sourceTree = "<group>"; };
@ -1707,6 +1711,8 @@
C11DEA151FCD31C0009E22B9 /* subprocess.h */,
E975121263DD973CAF4AEBA5 /* timer-ev.cc */,
E975121263DD973CAF4AEBA3 /* timer-ev.h */,
11524394C75E57E52CD9ADF1 /* dns.h */,
11524394C75E57E52CD9ADF3 /* dns-ev.h */,
E975121263DD973CAF4AEBA1 /* timer.h */,
A20152790D1C26EB0081714F /* torrent-ctor.cc */,
A47A7C87B8B57BE50DF0D411 /* torrent-files.cc */,
@ -2223,6 +2229,8 @@
2856E0656A49F2665D69E760 /* benc.h in Headers */,
E975121263DD973CAF4AEBA0 /* timer.h in Headers */,
E975121263DD973CAF4AEBA2 /* timer-ev.h in Headers */,
11524394C75E57E52CD9ADF0 /* dns.h in Headers */,
11524394C75E57E52CD9ADF2 /* dns-ev.h in Headers */,
C1077A4F183EB29600634C22 /* error.h in Headers */,
A2679295130E00A000CB7464 /* tr-utp.h in Headers */,
A263C6B1F6718E2486DB20E0 /* tr-buffer.h in Headers */,

View file

@ -136,6 +136,8 @@ endif()
set(${PROJECT_NAME}_PUBLIC_HEADERS
${PROJECT_BINARY_DIR}/version.h
dns-ev.h
dns.h
error-types.h
error.h
file.h

View file

@ -5,17 +5,13 @@
#include <algorithm> // for std::find_if()
#include <cerrno> // for errno, EAFNOSUPPORT
#include <cstring> // for memcpy()
#include <cstring> // for memset()
#include <ctime>
#include <list>
#include <memory>
#include <string_view>
#include <vector>
#ifdef _WIN32
#include <ws2tcpip.h>
#endif
#include <fmt/core.h>
#include <fmt/format.h>
@ -27,7 +23,6 @@
#include "announcer-common.h"
#include "crypto-utils.h" /* tr_rand_buffer() */
#include "log.h"
#include "error.h"
#include "peer-io.h"
#include "peer-mgr.h" // for tr_pex::fromCompact4()
#include "session.h"
@ -36,11 +31,6 @@
#include "utils.h"
#include "web-utils.h"
#ifdef _WIN32
#undef gai_strerror
#define gai_strerror gai_strerrorA
#endif
#define logwarn(interned, msg) tr_logAddWarn(msg, (interned).sv())
#define logdbg(interned, msg) tr_logAddDebug(msg, (interned).sv())
#define logtrace(interned, msg) tr_logAddTrace(msg, (interned).sv())
@ -341,19 +331,9 @@ struct tau_tracker
{
}
tau_tracker(tau_tracker&&) = delete;
tau_tracker(tau_tracker const&) = delete;
tau_tracker& operator=(tau_tracker&&) = delete;
tau_tracker& operator=(tau_tracker const&) = delete;
~tau_tracker()
{
tr_error_clear(&addr_error_);
}
[[nodiscard]] auto isIdle() const noexcept
{
return std::empty(announces) && std::empty(scrapes);
return std::empty(announces) && std::empty(scrapes) && (dns_request_ == 0U);
}
void failAll(bool did_connect, bool did_timeout, std::string_view errmsg)
@ -400,7 +380,7 @@ struct tau_tracker
tr_interned_string const host;
tr_port const port;
tr_error* addr_error_ = nullptr;
libtransmission::Dns::Tag dns_request_ = {};
std::optional<std::pair<sockaddr_storage, socklen_t>> addr_;
time_t addr_expires_at_ = 0;
@ -417,43 +397,30 @@ struct tau_tracker
std::list<tau_scrape_request> scrapes;
};
static std::optional<std::pair<sockaddr_storage, socklen_t>> host2sockaddr(
std::string_view host,
tr_port port,
tr_error** error)
{
auto const szhost = tr_urlbuf{ host };
auto szport = std::array<char, 16>{};
*fmt::format_to(std::data(szport), FMT_STRING("{:d}"), port.host()) = '\0';
auto hints = addrinfo{};
hints.ai_family = AF_UNSPEC;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_socktype = SOCK_DGRAM;
addrinfo* info = nullptr;
if (int const rc = getaddrinfo(szhost, std::data(szport), &hints, &info); rc != 0)
{
tr_logAddWarn(fmt::format(
_("Couldn't look up '{address}:{port}': {error} ({error_code})"),
fmt::arg("address", host),
fmt::arg("port", port.host()),
fmt::arg("error", gai_strerror(rc)),
fmt::arg("error_code", rc)));
tr_error_set(error, rc, gai_strerror(rc));
return {};
}
auto ss = sockaddr_storage{};
auto const len = info->ai_addrlen;
memcpy(&ss, info->ai_addr, len);
freeaddrinfo(info);
return std::make_pair(ss, len);
}
static void tau_tracker_upkeep(struct tau_tracker* /*tracker*/);
static void tau_tracker_on_dns(tau_tracker* const tracker, sockaddr const* sa, socklen_t salen, time_t expires_at)
{
tracker->dns_request_ = {};
if (sa == nullptr)
{
auto const errmsg = fmt::format(_("Couldn't find address of tracker '{host}'"), fmt::arg("host", tracker->host));
logwarn(tracker->key, errmsg);
tracker->failAll(false, false, errmsg.c_str());
tracker->addr_expires_at_ = tr_time() + tau_tracker::DnsRetryIntervalSecs;
}
else
{
logdbg(tracker->key, "DNS lookup succeeded");
auto ss = sockaddr_storage{};
memcpy(&ss, sa, salen);
tracker->addr_.emplace(ss, salen);
tracker->addr_expires_at_ = expires_at;
tau_tracker_upkeep(tracker);
}
}
static void tau_tracker_send_request(struct tau_tracker* tracker, void const* payload, size_t payload_len)
{
logdbg(tracker->key, fmt::format("sending request w/connection id {}", tracker->connection_id));
@ -498,6 +465,7 @@ static void tau_tracker_send_requests(tau_tracker* tracker, std::list<T>& reqs)
static void tau_tracker_send_reqs(tau_tracker* tracker)
{
TR_ASSERT(!tracker->dns_request_);
TR_ASSERT(tracker->addr_);
TR_ASSERT(tracker->connecting_at == 0);
TR_ASSERT(tracker->connection_expiration_time > tr_time());
@ -600,20 +568,20 @@ static void tau_tracker_upkeep_ex(struct tau_tracker* tracker, bool timeout_reqs
return;
}
// if we don't have an address yet, try & get one now
if (!closing && !tracker->addr_)
/* if we don't have an address yet, try & get one now. */
if (!closing && !tracker->addr_ && (tracker->dns_request_ == 0U))
{
if (tracker->addr_expires_at_ <= now)
{
tr_error_clear(&tracker->addr_error_);
tracker->addr_ = host2sockaddr(tracker->host, tracker->port, &tracker->addr_error_);
tracker->addr_expires_at_ = now + tau_tracker::DnsRetryIntervalSecs;
}
if (tracker->addr_error_ != nullptr)
{
tracker->failAll(false, false, tracker->addr_error_->message);
return;
}
auto hints = libtransmission::Dns::Hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
logtrace(tracker->host, "Trying a new DNS lookup");
tracker->dns_request_ = tracker->mediator_.dns().lookup(
tracker->host.sv(),
[tracker](sockaddr const* sa, socklen_t len, time_t expires_at)
{ tau_tracker_on_dns(tracker, sa, len, expires_at); },
hints);
return;
}
logtrace(
@ -722,6 +690,12 @@ public:
for (auto& tracker : trackers_)
{
// if there's a pending DNS request, cancel it
if (tracker.dns_request_ != 0U)
{
mediator_.dns().cancel(tracker.dns_request_);
}
tracker.close_at = now + 3;
tau_tracker_upkeep(&tracker);
}

View file

@ -301,6 +301,7 @@ public:
public:
virtual ~Mediator() noexcept = default;
virtual void sendto(void const* buf, size_t buflen, sockaddr const* addr, socklen_t addrlen) = 0;
[[nodiscard]] virtual libtransmission::Dns& dns() = 0;
[[nodiscard]] virtual std::optional<tr_address> announceIP() const = 0;
};

210
libtransmission/dns-ev.h Normal file
View file

@ -0,0 +1,210 @@
// This file Copyright 2022 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#pragma once
#ifndef __TRANSMISSION__
#error only libtransmission should #include this header.
#endif
#include <cstring> // for std::memcpy()
#include <ctime>
#include <list>
#include <map>
#include <memory>
#include <utility>
#include <event2/dns.h>
#include <event2/event.h>
#include "dns.h"
#include "utils.h" // for tr_strlower()
namespace libtransmission
{
class EvDns final : public Dns
{
private:
using Key = std::pair<std::string, Hints>;
struct CacheEntry
{
sockaddr_storage ss_ = {};
socklen_t sslen_ = {};
time_t expires_at_ = {};
};
struct CallbackArg
{
Key key;
EvDns* self;
};
struct Request
{
evdns_getaddrinfo_request* request;
struct CallbackInfo
{
CallbackInfo(Tag tag, Callback callback)
: tag_{ tag }
, callback_{ std::move(callback) }
{
}
Tag tag_;
Callback callback_;
};
std::list<CallbackInfo> callbacks;
};
public:
using TimeFunc = time_t (*)();
EvDns(struct event_base* event_base, TimeFunc time_func)
: time_func_{ time_func }
, evdns_base_{ evdns_base_new(event_base, EVDNS_BASE_INITIALIZE_NAMESERVERS),
[](evdns_base* dns)
{
// if zero, active requests will be aborted
evdns_base_free(dns, 0);
} }
{
}
~EvDns() override
{
for (auto& [key, request] : requests_)
{
evdns_getaddrinfo_cancel(request.request);
}
}
std::optional<std::pair<sockaddr const*, socklen_t>> cached(std::string_view address, Hints hints = {}) const override
{
if (auto const* entry = cached(makeKey(address, hints)); entry != nullptr)
{
return std::make_pair(reinterpret_cast<sockaddr const*>(&entry->ss_), entry->sslen_);
}
return {};
}
Tag lookup(std::string_view address, Callback&& callback, Hints hints = {}) override
{
auto const key = makeKey(address, hints);
if (auto const* entry = cached(key); entry)
{
callback(reinterpret_cast<sockaddr const*>(&entry->ss_), entry->sslen_, entry->expires_at_);
return {};
}
auto& request = requests_[key];
auto const tag = next_tag_;
++next_tag_;
request.callbacks.emplace_back(tag, std::move(callback));
if (request.request == nullptr)
{
auto evhints = evutil_addrinfo{};
evhints.ai_family = hints.ai_family;
evhints.ai_socktype = hints.ai_socktype;
evhints.ai_protocol = hints.ai_protocol;
void* const arg = new CallbackArg{ key, this };
request.request = evdns_getaddrinfo(evdns_base_.get(), key.first.c_str(), nullptr, &evhints, evcallback, arg);
}
return tag;
}
void cancel(Tag tag) override
{
for (auto& [key, request] : requests_)
{
for (auto iter = std::begin(request.callbacks), end = std::end(request.callbacks); iter != end; ++iter)
{
if (iter->tag_ != tag)
{
continue;
}
iter->callback_(nullptr, 0, 0);
request.callbacks.erase(iter);
// if this was the last pending request for `key`, cancel the evdns request
if (std::empty(request.callbacks))
{
evdns_getaddrinfo_cancel(request.request);
requests_.erase(key);
}
return;
}
}
}
private:
[[nodiscard]] static Key makeKey(std::string_view address, Hints hints)
{
return Key{ tr_strlower(address), hints };
}
[[nodiscard]] CacheEntry const* cached(Key const& key) const
{
if (auto iter = cache_.find(key); iter != std::end(cache_))
{
auto const& entry = iter->second;
if (auto const now = time_func_(); entry.expires_at_ > now)
{
return &entry;
}
cache_.erase(iter); // expired
}
return nullptr;
}
static void evcallback(int /*result*/, struct evutil_addrinfo* res, void* varg)
{
auto* const arg = static_cast<CallbackArg*>(varg);
auto [key, self] = *arg;
delete arg;
auto& cache_entry = self->cache_[key];
if (res != nullptr)
{
cache_entry.expires_at_ = self->time_func_() + CacheTtlSecs;
cache_entry.sslen_ = res->ai_addrlen;
std::memcpy(&cache_entry.ss_, res->ai_addr, res->ai_addrlen);
evutil_freeaddrinfo(res);
}
if (auto request_entry = self->requests_.extract(key); request_entry)
{
for (auto& callback : request_entry.mapped().callbacks)
{
callback.callback_(
reinterpret_cast<sockaddr const*>(&cache_entry.ss_),
cache_entry.sslen_,
cache_entry.expires_at_);
}
}
}
TimeFunc const time_func_;
static time_t constexpr CacheTtlSecs = 3600U;
std::unique_ptr<evdns_base, void (*)(evdns_base*)> const evdns_base_;
mutable std::map<Key, CacheEntry> cache_;
std::map<Key, Request> requests_;
unsigned int next_tag_ = 1;
};
} // namespace libtransmission

72
libtransmission/dns.h Normal file
View file

@ -0,0 +1,72 @@
// This file Copyright 2022 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#pragma once
#include <functional>
#include <string_view>
#include "transmission.h"
#include "net.h"
namespace libtransmission
{
class Dns
{
public:
virtual ~Dns() = default;
using Callback = std::function<void(struct sockaddr const*, socklen_t salen, time_t expires_at)>;
using Tag = unsigned int;
class Hints
{
public:
Hints()
{
}
int ai_family = AF_UNSPEC;
int ai_socktype = SOCK_DGRAM;
int ai_protocol = IPPROTO_UDP;
[[nodiscard]] constexpr int compare(Hints const& that) const noexcept // <=>
{
if (ai_family != that.ai_family)
{
return ai_family < that.ai_family ? -1 : 1;
}
if (ai_socktype != that.ai_socktype)
{
return ai_socktype < that.ai_socktype ? -1 : 1;
}
if (ai_protocol != that.ai_protocol)
{
return ai_protocol < that.ai_protocol ? -1 : 1;
}
return 0;
}
[[nodiscard]] constexpr bool operator<(Hints const& that) const noexcept
{
return compare(that) < 0;
}
};
[[nodiscard]] virtual std::optional<std::pair<struct sockaddr const*, socklen_t>> cached(
std::string_view address,
Hints hints = {}) const = 0;
virtual Tag lookup(std::string_view address, Callback&& callback, Hints hints = {}) = 0;
virtual void cancel(Tag) = 0;
};
} // namespace libtransmission

View file

@ -26,6 +26,7 @@
#include <sys/stat.h> /* umask() */
#endif
#include <event2/dns.h>
#include <event2/event.h>
#include <fmt/chrono.h>
@ -39,6 +40,7 @@
#include "blocklist.h"
#include "cache.h"
#include "crypto-utils.h"
#include "dns-ev.h"
#include "error-types.h"
#include "error.h"
#include "file.h"
@ -2173,6 +2175,7 @@ tr_session::tr_session(std::string_view config_dir, tr_variant* settings_dict)
, blocklist_dir_{ makeBlocklistDir(config_dir) }
, session_thread_{ tr_session_thread::create() }
, timer_maker_{ std::make_unique<libtransmission::EvTimerMaker>(eventBase()) }
, dns_{ std::make_unique<libtransmission::EvDns>(eventBase(), tr_time) }
, settings_{ settings_dict }
, session_id_{ tr_time }
, peer_mgr_{ tr_peerMgrNew(this), tr_peerMgrFree }

View file

@ -31,6 +31,7 @@
#include "bandwidth.h"
#include "bitfield.h"
#include "cache.h"
#include "dns.h"
#include "interned-string.h"
#include "net.h" // tr_socket_t
#include "open-files.h"
@ -147,6 +148,11 @@ private:
return tr_address::fromString(session_.announceIP());
}
[[nodiscard]] libtransmission::Dns& dns() override
{
return *session_.dns_.get();
}
private:
tr_session& session_;
};
@ -1034,6 +1040,9 @@ private:
// depends-on: session_thread_
std::unique_ptr<libtransmission::TimerMaker> const timer_maker_;
// depends-on: event_base_
std::unique_ptr<libtransmission::Dns> const dns_;
/// trivial type fields
tr_session_settings settings_;
@ -1140,7 +1149,7 @@ private:
// depends-on: lpd_mediator_
std::unique_ptr<tr_lpd> lpd_;
// depends-on: udp_core_
// depends-on: dns_, udp_core_
AnnouncerUdpMediator announcer_udp_mediator_{ *this };
// depends-on: timer_maker_, torrents_, peer_mgr_

View file

@ -2331,7 +2331,7 @@ void tr_torrentSetQueuePosition(tr_torrent* tor, size_t queue_position)
size_t current = 0;
auto const old_pos = tor->queuePosition;
tor->queuePosition = static_cast<size_t>(-1);
tor->queuePosition = -1;
for (auto* const walk : tor->session->torrents())
{

View file

@ -14,9 +14,9 @@
#include "announcer.h"
#include "crypto-utils.h"
#include "dns.h"
#include "peer-mgr.h" // for tr_pex
#include "tr-buffer.h"
#include "utils.h" // for tr_net_init()
#include "test-fixtures.h"
@ -28,15 +28,43 @@ private:
void SetUp() override
{
::testing::Test::SetUp();
tr_net_init();
tr_timeUpdate(time(nullptr));
}
protected:
class MockDns final : public libtransmission::Dns
{
public:
[[nodiscard]] std::optional<std::pair<struct sockaddr const*, socklen_t>> cached(
std::string_view /*address*/,
Hints /*hints*/ = {}) const override
{
return {};
}
Tag lookup(std::string_view address, Callback&& callback, Hints /*hints*/) override
{
auto const addr = tr_address::fromString(address); // mock has no actual DNS, just parsing e.g. inet_pton
auto [ss, sslen] = addr->toSockaddr(Port);
callback(reinterpret_cast<sockaddr const*>(&ss), sslen, tr_time() + 3600); // 1hr ttl
return {};
}
void cancel(Tag /*tag*/) override
{
}
static auto constexpr Port = tr_port::fromHost(443);
};
class MockMediator final : public tr_announcer_udp::Mediator
{
public:
MockMediator()
: event_base_{ event_base_new(), event_base_free }
{
}
void sendto(void const* buf, size_t buflen, sockaddr const* sa, socklen_t salen) override
{
auto target = tr_address::fromSockaddr(sa);
@ -44,6 +72,16 @@ protected:
sent_.emplace_back(static_cast<char const*>(buf), buflen, sa, salen);
}
[[nodiscard]] auto* eventBase()
{
return event_base_.get();
}
[[nodiscard]] libtransmission::Dns& dns() override
{
return dns_;
}
[[nodiscard]] std::optional<tr_address> announceIP() const override
{
return {};
@ -66,6 +104,10 @@ protected:
};
std::deque<Sent> sent_;
std::unique_ptr<event_base, void (*)(event_base*)> const event_base_;
MockDns dns_;
};
static void expectEqual(tr_scrape_response const& expected, tr_scrape_response const& actual)
@ -158,7 +200,7 @@ protected:
[[nodiscard]] static auto waitForAnnouncerToSendMessage(MockMediator& mediator)
{
EXPECT_FALSE(std::empty(mediator.sent_));
libtransmission::test::waitFor([&mediator]() { return !std::empty(mediator.sent_); }, 5s);
libtransmission::test::waitFor(mediator.eventBase(), [&mediator]() { return !std::empty(mediator.sent_); });
auto buf = libtransmission::Buffer(mediator.sent_.back().buf_);
mediator.sent_.pop_back();
return buf;
@ -221,8 +263,8 @@ protected:
EXPECT_EQ(expected.up, actual.uploaded);
// EXPECT_EQ(foo, actual.event); ; // 0: none; 1: completed; 2: started; 3: stopped // FIXME
// EXPECT_EQ(foo, actual.ip_address); // FIXME
EXPECT_EQ(expected.key, static_cast<decltype(expected.key)>(actual.key));
EXPECT_EQ(expected.numwant, static_cast<decltype(expected.numwant)>(actual.num_want));
EXPECT_EQ(expected.key, actual.key);
EXPECT_EQ(expected.numwant, actual.num_want);
EXPECT_EQ(expected.port.host(), actual.port);
}
@ -368,7 +410,7 @@ TEST_F(AnnouncerUdpTest, canDestructCleanlyEvenWhenBusy)
// Inspect that request for validity.
auto sent = waitForAnnouncerToSendMessage(mediator);
auto const connect_transaction_id = parseConnectionRequest(sent);
EXPECT_NE(0U, connect_transaction_id);
EXPECT_NE(0, connect_transaction_id);
// now just end the test before responding to the request.
// the announcer and mediator will go out-of-scope & be destroyed.

View file

@ -0,0 +1,175 @@
// This file Copyright (C) 2022 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include <chrono>
#include <memory>
#include <event2/event.h>
#include "transmission.h"
#include "dns-ev.h"
#include "dns.h"
#include "trevent.h" // for tr_evthread_init();
#include "gtest/gtest.h"
#include "test-fixtures.h"
using namespace std::literals;
namespace libtransmission::test
{
class EvDnsTest : public ::testing::Test
{
protected:
void SetUp() override
{
::testing::Test::SetUp();
tr_evthread_init();
event_base_ = event_base_new();
}
void TearDown() override
{
event_base_free(event_base_);
event_base_ = nullptr;
::testing::Test::TearDown();
}
struct event_base* event_base_ = nullptr;
};
TEST_F(EvDnsTest, canLookup)
{
auto dns = EvDns{ event_base_, tr_time };
auto done = false;
dns.lookup(
"example.com",
[&done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
{
EXPECT_NE(nullptr, ai);
EXPECT_GT(ailen, 0);
EXPECT_GT(expires_at, tr_time());
done = true;
});
waitFor(event_base_, [&done]() { return done; });
EXPECT_TRUE(done);
}
TEST_F(EvDnsTest, canRequestWhilePending)
{
auto dns = EvDns{ event_base_, tr_time };
auto n_done = size_t{ 0 };
dns.lookup(
"example.com",
[&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
{
EXPECT_NE(nullptr, ai);
EXPECT_GT(ailen, 0);
EXPECT_GT(expires_at, tr_time());
++n_done;
});
dns.lookup(
"example.com",
[&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
{
EXPECT_NE(nullptr, ai);
EXPECT_GT(ailen, 0);
EXPECT_GT(expires_at, tr_time());
++n_done;
});
// wait for both callbacks to be called
waitFor(event_base_, [&n_done]() { return n_done >= 2U; });
EXPECT_EQ(2U, n_done);
}
TEST_F(EvDnsTest, canCancel)
{
auto dns = EvDns{ event_base_, tr_time };
auto n_done = size_t{ 0 };
static auto constexpr Name = "example.com"sv;
auto tag = dns.lookup(
Name,
[&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
{
++n_done;
// we cancelled this req, so `ai` and `ailen` should be zeroed out
EXPECT_EQ(nullptr, ai);
EXPECT_EQ(0, ailen);
EXPECT_EQ(0, expires_at);
});
dns.lookup(
Name,
[&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
{
++n_done;
// this one did _not_ get cancelled so it should be OK
EXPECT_NE(nullptr, ai);
EXPECT_GT(ailen, 0);
EXPECT_GT(expires_at, tr_time());
});
dns.cancel(tag);
// wait for both callbacks to be called
waitFor(event_base_, [&n_done]() { return n_done >= 2U; });
EXPECT_EQ(2U, n_done);
}
TEST_F(EvDnsTest, doesCacheEntries)
{
auto dns = EvDns{ event_base_, tr_time };
static auto constexpr Name = "example.com"sv;
struct sockaddr const* ai_addr = nullptr;
dns.lookup(
Name,
[&ai_addr](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
{
EXPECT_NE(nullptr, ai);
EXPECT_GT(ailen, 0);
EXPECT_GT(expires_at, tr_time());
ai_addr = ai;
});
// wait for the lookup
waitFor(event_base_, [&ai_addr]() { return ai_addr != nullptr; });
ASSERT_NE(nullptr, ai_addr);
auto second_callback_called = false;
dns.lookup(
Name,
[&ai_addr, &second_callback_called](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
{
EXPECT_NE(nullptr, ai);
EXPECT_GT(ailen, 0);
EXPECT_EQ(ai_addr, ai);
EXPECT_GT(expires_at, tr_time());
second_callback_called = true;
});
// since it's cached, the callback should have been invoked
// without waiting for the event loop
EXPECT_TRUE(second_callback_called);
// confirm that `cached()` returns the cached value immediately
auto res = dns.cached(Name);
EXPECT_TRUE(res);
EXPECT_EQ(ai_addr, res->first);
EXPECT_GT(res->second, 0);
}
} // namespace libtransmission::test

View file

@ -61,7 +61,7 @@ TEST_F(NetTest, compact4)
auto compact4 = std::array<std::byte, 6>{};
auto out = std::data(compact4);
out = addr.toCompact4(out, port);
EXPECT_EQ(std::size(Compact4), static_cast<size_t>(out - std::data(compact4)));
EXPECT_EQ(std::size(Compact4), out - std::data(compact4));
EXPECT_EQ(Compact4, compact4);
/// sockaddr --> compact

View file

@ -327,7 +327,7 @@ TEST_F(SettingsTest, canSaveSizeT)
settings.save(&dict);
auto val = int64_t{};
EXPECT_TRUE(tr_variantDictFindInt(&dict, Key, &val));
EXPECT_EQ(expected_value, static_cast<size_t>(val));
EXPECT_EQ(expected_value, val);
tr_variantClear(&dict);
}