Merge pull request #9386 from mehmetbayoglu/perf/netctl-host-query-cache

perf(libSceNetCtl): reuse host interface queries until Windows reports a change
This commit is contained in:
Dean authored and GitHub committed 2026-10-23 14:01:27 +13:00
commit 473b048a19
2 files changed
+90 -2

No files matched your search

+89 -2
View File
@@ -131,7 +131,7 @@ static bool HasIpv4Callback() {
return false;
}
static bool HostHasAddress(int family) {
static bool QueryHostHasAddress(int family) {
#ifdef _WIN32
static const int socket_status = [] {
WSADATA data{};
@@ -188,6 +188,74 @@ struct HostIpv4Interface {
std::vector<in_addr> dns;
};
#ifdef _WIN32
namespace {
struct HostAddressCache {
std::mutex lock;
bool registered = false;
bool tried = false;
std::atomic<std::uint64_t> generation{1};
std::uint64_t seen[3]{};
bool value[3]{};
std::uint64_t interfaceSeen = 0;
bool interfaceFound = false;
HostIpv4Interface interfaceValue;
};
HostAddressCache& AddressCache() {
static HostAddressCache cache;
return cache;
}
void CALLBACK HostAddressChanged(PVOID, PMIB_UNICASTIPADDRESS_ROW, MIB_NOTIFICATION_TYPE) {
AddressCache().generation.fetch_add(1, std::memory_order_acq_rel);
}
void CALLBACK HostInterfaceChanged(PVOID, PMIB_IPINTERFACE_ROW, MIB_NOTIFICATION_TYPE) {
AddressCache().generation.fetch_add(1, std::memory_order_acq_rel);
}
void CALLBACK HostRouteChanged(PVOID, PMIB_IPFORWARD_ROW2, MIB_NOTIFICATION_TYPE) {
AddressCache().generation.fetch_add(1, std::memory_order_acq_rel);
}
void WatchHostAddresses(HostAddressCache& cache) {
if (cache.tried) return;
cache.tried = true;
HANDLE addresses = nullptr;
HANDLE interfaces = nullptr;
HANDLE routes = nullptr;
cache.registered = NotifyUnicastIpAddressChange(AF_UNSPEC, HostAddressChanged, nullptr, FALSE, &addresses) == NO_ERROR &&
NotifyIpInterfaceChange(AF_UNSPEC, HostInterfaceChanged, nullptr, FALSE, &interfaces) == NO_ERROR &&
NotifyRouteChange2(AF_UNSPEC, HostRouteChanged, nullptr, FALSE, &routes) == NO_ERROR;
if (!cache.registered) {
if (addresses != nullptr) CancelMibChangeNotify2(addresses);
if (interfaces != nullptr) CancelMibChangeNotify2(interfaces);
if (routes != nullptr) CancelMibChangeNotify2(routes);
}
}
}
#endif
static bool HostHasAddress(int family) {
#ifdef _WIN32
auto& cache = AddressCache();
const int slot = family == AF_INET ? 0 : family == AF_INET6 ? 1 : 2;
std::lock_guard guard(cache.lock);
WatchHostAddresses(cache);
const auto generation = cache.generation.load(std::memory_order_acquire);
if (cache.registered && cache.seen[slot] == generation) return cache.value[slot];
const bool value = QueryHostHasAddress(family);
cache.value[slot] = value;
cache.seen[slot] = generation;
return value;
#else
return QueryHostHasAddress(family);
#endif
}
static bool UsableIpv4(in_addr address) {
const auto host = ntohl(address.s_addr);
return host != 0 && (host >> 24) != 127;
@@ -310,7 +378,7 @@ static std::vector<in_addr> HostDnsServers() {
}
#endif
static bool FindHostIpv4Interface(HostIpv4Interface& found) {
static bool QueryHostIpv4Interface(HostIpv4Interface& found) {
#ifdef _WIN32
ULONG size = 0;
const ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS;
@@ -371,6 +439,25 @@ static bool FindHostIpv4Interface(HostIpv4Interface& found) {
#endif
}
static bool FindHostIpv4Interface(HostIpv4Interface& found) {
#ifdef _WIN32
auto& cache = AddressCache();
std::lock_guard guard(cache.lock);
WatchHostAddresses(cache);
const auto generation = cache.generation.load(std::memory_order_acquire);
if (!cache.registered || cache.interfaceSeen != generation) {
HostIpv4Interface value;
cache.interfaceFound = QueryHostIpv4Interface(value);
cache.interfaceValue = std::move(value);
cache.interfaceSeen = generation;
}
if (cache.interfaceFound) found = cache.interfaceValue;
return cache.interfaceFound;
#else
return QueryHostIpv4Interface(found);
#endif
}
extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, std::size_t capacity) {
HostIpv4Interface host;
if (!FindHostIpv4Interface(host)) return 0;
+1
View File
@@ -19,3 +19,4 @@
- [libSceNetCtl initialization](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - the library starts initialized, as titles find it: it is the internal sysmodule `0x80000009`, and PPSA11386, PPSA20049, PPSA02530, PPSA14632 and PPSA15878 register and check NetCtl callbacks without importing `sceNetCtlInit`. PPSA11386 executes `int 0x42` at start-up unless `sceNetCtlRegisterCallback` returns 0. So the supplied module's `SCE_NET_CTL_ERROR_NOT_INITIALIZED` answers are unreachable, since Term is a no-op. Whether the console's process start or the module's start sets that state is not measured.
- [sceNetCtlGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) finds the host's default route in `/proc/net/route` on Linux and, on macOS, in the routing table read with `sysctl(NET_RT_FLAGS, RTF_GATEWAY)`, where an interface-scoped default route ranks after the unscoped one
- [sceNetCtlGetIfStat](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) throws on macOS: an unprivileged process reads the interface byte counters (`getifaddrs` and `sysctl(NET_RT_IFLIST2)` alike) reduced to their low 32 bits, so a total would wrap every 4 GiB
- [Host interface cache](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - on Windows, `sceNetCtlGetState`, `sceNetCtlGetStateV6`, `sceNetCtlCheckCallback`, `sceNetCtlGetIfStat` and `sceNetCtlGetInfo` reuse the last host interface query until `NotifyUnicastIpAddressChange`, `NotifyIpInterfaceChange` or `NotifyRouteChange2` reports a change, instead of calling `GetAdaptersAddresses` on every call. A DNS server change with no address, interface or route change is seen only at the next such change. If the notifications cannot be registered, every call queries the host as before.