- Initial commit

This commit is contained in:
Timur Kozanov
2026-07-03 05:02:26 +03:00
commit 374ff1e689
4080 changed files with 388870 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ecb01125967f0e44988bb3d7e893be7f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,519 @@
#if !UNITY_WEBGL || UNITY_EDITOR
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.Logger;
using UnityEngine;
using Best.HTTP.Shared.PlatformSupport.Text;
using System.Net;
using System.Net.Sockets;
using Best.HTTP.Shared.PlatformSupport.Network.Tcp;
using Best.HTTP.Shared.PlatformSupport.Threading;
namespace Best.HTTP.Shared.PlatformSupport.Network.DNS.Cache
{
/// <summary>
/// Represents the result of a DNS query, including the original host name, resolved IP addresses, and any error.
/// </summary>
public readonly struct DNSQueryResult
{
/// <summary>
/// The host name used in the DNS query.
/// </summary>
public readonly string HostName;
/// <summary>
/// The resolved IP addresses associated with the host name.
/// </summary>
public readonly DNSIPAddress[] Addresses;
/// <summary>
/// Any error that occurred during the DNS query.
/// </summary>
public readonly Exception Error;
internal DNSQueryResult(string hostName, DNSIPAddress[] addresses, Exception error)
{
this.HostName = hostName;
this.Addresses = addresses;
this.Error = error;
}
public override string ToString()
{
if (this.Error != null)
return $"[{nameof(DNSQueryResult)}(\"{HostName}\", {this.Addresses?.Length}, Error: \"{this.Error?.Message}\")]";
else
return $"[{nameof(DNSQueryResult)}(\"{HostName}\", {this.Addresses?.Length})]";
}
}
/// <summary>
/// Represents an IP address obtained from DNS resolution.
/// </summary>
public sealed class DNSIPAddress
{
/// <summary>
/// The resolved IP address.
/// </summary>
public IPAddress IPAddress { get; private set; }
/// <summary>
/// Indicates whether this IP address worked during the last connection attempt.
/// </summary>
public bool IsWorkedLastTime { get; internal set; }
internal DNSIPAddress(IPAddress iPAddress)
{
this.IPAddress = iPAddress;
// By default, assumme it's a working IP address
this.IsWorkedLastTime = true;
}
public override string ToString() => $"[{nameof(DNSIPAddress)}({this.IPAddress}, Working: {this.IsWorkedLastTime})]";
}
/// <summary>
/// Represents options for configuring the DNS cache behavior.
/// </summary>
public sealed class DNSCacheOptions
{
/// <summary>
/// The time interval after which DNS cache entries should be refreshed.
/// </summary>
public TimeSpan RefreshAfter = TimeSpan.FromSeconds(30);
/// <summary>
/// The time interval after which DNS cache entries should be removed if not used.
/// </summary>
public TimeSpan RemoveAfter = TimeSpan.FromSeconds(70);
/// <summary>
/// The granularity of cancellation checks for DNS queries.
/// </summary>
public TimeSpan CancellationCheckGranularity = TimeSpan.FromMilliseconds(100);
/// <summary>
/// The frequency of cache maintenance.
/// </summary>
public TimeSpan MaintenanceFrequency = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Represents parameters for a DNS query, including the host name, address, cancellation token, logging context, callback, and tag.
/// </summary>
public sealed class DNSQueryParameters
{
/// <summary>
/// The hash key associated with the DNS query.
/// </summary>
public Hash128 Key { get; private set; }
/// <summary>
/// The host name used in the DNS query.
/// </summary>
public string Hostname { get => this.Address.Host; }
/// <summary>
/// The URI address used in the DNS query.
/// </summary>
public Uri Address { get; private set; }
/// <summary>
/// The cancellation token used to cancel the DNS query.
/// </summary>
public CancellationToken Token;
/// <summary>
/// Optional logging context.
/// </summary>
public LoggingContext Context;
/// <summary>
/// The callback to be invoked upon completion of the DNS query.
/// </summary>
public Action<DNSQueryParameters, DNSQueryResult> Callback;
/// <summary>
/// An optional object reference associated with the DNS query.
/// </summary>
public object Tag;
/// <summary>
/// Indicates whether the DNS query is a prefetch query.
/// </summary>
public bool IsPrefetch { get => this.Context == null; }
public DNSQueryParameters(Uri address)
{
this.Address = address;
this.Key = Hash128.Compute(this.Hostname);
}
public override string ToString()
{
return $"{Key} => {Address}";
}
}
/// <summary>
/// The DNSCache class is a static utility that manages DNS caching and queries within the Best HTTP library.
/// It helps improve network efficiency by caching DNS query results, reducing the need for redundant DNS resolutions.
/// </summary>
/// <remarks>
/// <para>By utilizing the DNSCache class and its associated features, you can optimize DNS resolution in your network communication, leading to improved performance and reduced latency in your applications.</para>
/// <para>
/// Its key features include:
/// <list type="bullet">
/// <item>
/// <term>Improving Network Efficiency</term>
/// <description>The DNSCache class is designed to enhance network efficiency by caching DNS query results.
/// When your application needs to resolve hostnames to IP addresses for making network requests, the DNSCache stores previously resolved results.
/// This reduces the need for redundant DNS resolutions, making network communication faster and more efficient.
/// </description>
/// </item>
///
/// <item>
/// <term>DNS Prefetching</term>
/// <description>You can use the DNSCache to initiate DNS prefetch operations.
/// Prefetching allows you to resolve and cache DNS records for hostnames in advance, reducing latency for future network requests.
/// This is particularly useful when you expect to make multiple network requests to the same hostnames, as it helps to avoid DNS resolution delays.
/// </description>
/// </item>
///
/// <item>
/// <term>Marking IP Addresses as Non-Working</term>
/// <description>In cases where a previously resolved IP address is determined to be non-functional (e.g., due to network issues), you can use the DNSCache to report IP addresses as non-working.
/// This information helps the cache make better decisions about which IP addresses to use for future network connections. <see cref="Best.HTTP.Shared.PlatformSupport.Network.Tcp.TCPRingmaster"/> gives higher priority for adresses not marked as non-working.
/// </description>
/// </item>
///
/// <item>
/// <term>Clearing the DNS Cache</term>
/// <description>If you need to reset the DNS cache and remove all stored DNS resolutions, you can use the Clear method provided by the DNSCache class.
/// This operation can be useful in scenarios where you want to start with a fresh cache.
/// </description>
/// </item>
///
/// <item>
/// <term>Performing DNS Queries</term>
/// <description>The primary function of the DNSCache class is to perform DNS queries with specified parameters.
/// It resolves DNS records for a given hostname and caches the results. This can be called directly or used internally by the Best HTTP library for resolving hostnames.
/// </description>
/// </item>
///
/// <item>
/// <term>Configuring Cache Behavior</term>
/// <description>You can configure the behavior of the DNS cache using the DNSCacheOptions class.
/// This includes setting refresh intervals for cache entries, defining the granularity of cancellation checks for DNS queries, and specifying the frequency of cache maintenance.
/// </description>
/// </item>
/// </list>
/// </para>
/// </remarks>
[Best.HTTP.Shared.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstruction]
public static class DNSCache
{
/// <summary>
/// Options for configuring the DNS cache behavior, including refresh intervals and maintenance frequency.
/// </summary>
public static DNSCacheOptions Options = new DNSCacheOptions();
private static ConcurrentDictionary<Hash128, DNSCacheEntry> _cache = new ConcurrentDictionary<Hash128, DNSCacheEntry>();
private static int _isMaintenanceScheduled = 0;
/// <summary>
/// Initiates a DNS prefetch operation for the specified host name. DNS prefetching is used to resolve and cache
/// DNS records for host names in advance, reducing latency for future network requests.
/// </summary>
/// <param name="hostName">The host name to prefetch.</param>
public static void Prefetch(string hostName)
{
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(Prefetch)}(\"{hostName}\")");
Query(new DNSQueryParameters(new Uri($"prefetch://{hostName}")));
}
/// <summary>
/// Reports an IP address as non-working for the specified host name. In cases where a previously resolved IP address
/// is determined to be non-functional, this method updates the cache to mark the IP address as non-working.
/// </summary>
/// <param name="hostName">The host name associated with the IP address.</param>
/// <param name="address">The <see cref="IPAddress"/> to report as non-working.</param>
/// <param name="context">Optional logging context for debugging purposes.</param>
public static void ReportAsNonWorking(string hostName, IPAddress address, LoggingContext context)
{
var key = Hash128.Compute(hostName);
if (_cache.TryGetValue(key, out var entry))
{
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(ReportAsNonWorking)}(\"{hostName}\", {address}) - CacheEntry found", context);
entry.ReportNonWorking(address, context);
}
else
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(ReportAsNonWorking)}(\"{hostName}\", {address}) - CacheEntry not found!", context);
}
/// <summary>
/// Clears the DNS cache, removing all cached DNS records. This operation can be used to reset the cache
/// and remove all stored DNS resolutions.
/// </summary>
public static void Clear()
{
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(Clear)}()");
_cache.Clear();
#if BESTHTTP_PROFILE && UNITY_2021_2_OR_NEWER
Profiler.Network.NetworkStats.TotalDNSCacheMissCounter.Value = 0;
Profiler.Network.NetworkStats.TotalDNSCacheHitsCounter.Value = 0;
#endif
}
/// <summary>
/// Performs a DNS query with the specified parameters. It resolves DNS records for a given host name,
/// caching the results to reduce the need for redundant DNS resolutions.
/// </summary>
/// <param name="parameters">The parameters for the DNS query.</param>
public static void Query(DNSQueryParameters parameters)
{
// First check whether it's already an IP address. If so, call the callback without touching any DNS query.
if (IPAddress.TryParse(parameters.Hostname, out var ip) &&
(ip.AddressFamily == AddressFamily.InterNetwork || ip.AddressFamily == AddressFamily.InterNetworkV6))
{
try
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(Query)}(\"{parameters.Hostname}\") - It's already an IP address, skipping DNS query...", parameters.Context);
parameters.Callback?.Invoke(parameters, new DNSQueryResult(parameters.Hostname, new DNSIPAddress[] { new DNSIPAddress(ip) }, null));
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(DNSCache), $"{nameof(Query)} - Callback", ex, parameters.Context);
}
return;
}
// When context is null, it's a call to refresh cached entry, so it must skip the cache check and have to go straight for the DNS query
if (!parameters.IsPrefetch && _cache.TryGetValue(parameters.Key, out var entry))
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(Query)}(\"{parameters.Hostname}\", \"{parameters.Key}\") - Cache hit: {entry}", parameters.Context);
var addresses = entry.GetAddresses();
if (addresses != null && addresses.Length > 0)
{
#if BESTHTTP_PROFILE && UNITY_2021_2_OR_NEWER
Profiler.Network.NetworkStats.TotalDNSCacheHitsCounter.Value++;
#endif
try
{
var result = new DNSQueryResult(parameters.Hostname, addresses, null);
ThreadedRunner.RunShortLiving<DNSQueryParameters, DNSQueryResult>((par, res) => par?.Callback?.Invoke(par, res), parameters, result);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(DNSCache), $"{nameof(Query)}(\"{parameters.Hostname}\", \"{parameters.Key}\") - Cache hit - QueryImpl", ex, parameters.Context);
}
// Return now, if it's a stalled entry, the regular maintenance call will do its job.
return;
}
else if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(Query)}(\"{parameters.Hostname}\", \"{parameters.Key}\") - CacheEntry found, but no addresses returned!", parameters.Context);
}
// TODO: Try to combine callbacks for the same query
// - Doing a new query while there<72>s an active query (from prefetch for example) creates two queries. If we could combine callbacks safely, or steal active queries' callback it would be even faster.
// Cases to handle:
// - Thread-safe update of the list of callbacks
// - Query finishing while we try to update it
// - Query is in the middle of dispatching
var ar = Dns.BeginGetHostAddresses(parameters.Hostname, OnGetHostAddresses, /*query*/ parameters);
// Apply a timer only when there's a context == it's in the context of a request where it can be cancelled.
// If it's a refresh/prefech request, it would be a waste of resources.
if (!parameters.IsPrefetch)
{
#if BESTHTTP_PROFILE && UNITY_2021_2_OR_NEWER
Profiler.Network.NetworkStats.TotalDNSCacheMissCounter.Value++;
#endif
if (!ar.CompletedSynchronously && parameters.Token != CancellationToken.None)
Extensions.Timer.Add(new TimerData(Options.CancellationCheckGranularity, /*query*/ parameters, CheckForCanceled));
}
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(Query)}(\"{parameters.Hostname}\", \"{parameters.Key}\") - Cache miss/Prefetch! {nameof(Dns.BeginGetHostAddresses)} called!", parameters.Context);
}
private static void OnGetHostAddresses(IAsyncResult ar)
{
var parameters = ar.AsyncState as /*DNSQuery*/DNSQueryParameters;
var callback = parameters.Callback;
callback = Interlocked.CompareExchange<Action<DNSQueryParameters, DNSQueryResult>>(ref parameters.Callback, null, callback);
try
{
// If something went wrong, this will throw an exception.
var addresses = Dns.EndGetHostAddresses(ar);
if (HTTPManager.Logger.IsDiagnostic)
{
var sb = StringBuilderPool.Get(1);
sb.Append('[');
for (int i = 0; i < addresses.Length; ++i)
{
sb.Append(addresses[i]);
if (i < addresses.Length - 1)
sb.Append(", ");
}
sb.Append(']');
var ips = StringBuilderPool.ReleaseAndGrab(sb);
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(OnGetHostAddresses)}({parameters}) - {nameof(Dns.EndGetHostAddresses)} returned with {addresses?.Length} IPs: {ips}", parameters.Context);
}
DNSCacheEntry AddCacheEntry(Hash128 key)
{
var resolved = new List<DNSIPAddress>(addresses.Length);
foreach (var address in addresses)
if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
resolved.Add(new DNSIPAddress(address));
var entry = new DNSCacheEntry(key, parameters.Hostname, resolved);
return entry;
}
DNSCacheEntry UpdateCacheEntry(Hash128 key, DNSCacheEntry oldEntry)
{
var resolved = new List<DNSIPAddress>(addresses.Length);
foreach (var address in addresses)
if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
resolved.Add(new DNSIPAddress(address));
var entry = oldEntry.DeriveWith(resolved);
return entry;
}
// Store/update cach entry
var entry = _cache.AddOrUpdate(parameters.Key, AddCacheEntry, UpdateCacheEntry);
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(OnGetHostAddresses)}({parameters}) - entry added to/updated in the cache: {entry}. Cache size: {_cache.Count}", parameters.Context);
if (entry != null && Interlocked.CompareExchange(ref _isMaintenanceScheduled, 1, 0) == 0)
{
Extensions.Timer.Add(new TimerData(Options.MaintenanceFrequency, null, Maintenance));
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(OnGetHostAddresses)}({parameters}) - Scheduled maintenance rutin", parameters.Context);
}
try
{
callback?.Invoke(parameters, new DNSQueryResult(parameters.Hostname, entry.GetAddresses(), null));
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(DNSCache), $"{nameof(OnGetHostAddresses)}({parameters}) - callback", ex, parameters.Context);
}
}
catch (Exception ex)
{
// If there's an error (like DNS couldn't resolve the host) old entries are will remain in the cache.
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(OnGetHostAddresses)}({parameters}) calling callback with no addresses!", parameters.Context);
try
{
callback?.Invoke(parameters, new DNSQueryResult(parameters.Hostname, null, ex));
}
catch (Exception e)
{
HTTPManager.Logger.Exception(nameof(DNSCache), $"{nameof(OnGetHostAddresses)}({parameters}) - callback", e, parameters.Context);
}
}
}
/// <summary>
/// It's plan-b for the case where BeginGetHostAddresses take too long and no reply in time. If the query's Token is canceled it will call the callback if it's still available.
/// </summary>
private static bool CheckForCanceled(DateTime now, object context)
{
var query = context as /*DNSQuery*/ DNSQueryParameters;
if (query.Token.IsCancellationRequested)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(CheckForCanceled)}({query}) - Token.IsCancellationRequested!", query.Context);
var callback = query.Callback;
callback = Interlocked.CompareExchange<Action<DNSQueryParameters, DNSQueryResult>>(ref query.Callback, null, callback);
try
{
callback?.Invoke(query, new DNSQueryResult(query.Hostname, null, new TimeoutException("DNS Query Timed Out")));
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(DNSCache), $"{nameof(CheckForCanceled)}({query}) - callback", ex, query.Context);
}
return false;
}
return query.Callback != null;
}
private static bool Maintenance(DateTime now, object context)
{
using var __ = new Unity.Profiling.ProfilerMarker(nameof(DNSCache)).Auto();
foreach (var kvp in _cache)
{
Hash128 key = kvp.Key;
DNSCacheEntry entry = kvp.Value;
if (entry.IsReadyToRemove(now))
{
if (_cache.TryRemove(key, out _))
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(Maintenance)}.{nameof(DNSCacheEntry.IsReadyToRemove)}: Removed entry from cache: {entry}");
}
else if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(Maintenance)}.{nameof(DNSCacheEntry.IsReadyToRemove)}: Couldn't remove entry from cache: {entry}");
}
else if (entry.IsStalled(now))
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(Maintenance)}.{nameof(DNSCacheEntry.IsStalled)}: Refreshing entry: {entry}");
entry.Refresh();
}
}
// return true to repeat. So return false if there's no more entries in the cache and we could change _isMaintenanceScheduled.
bool removeShedule = _cache.Count == 0 && Interlocked.CompareExchange(ref _isMaintenanceScheduled, 0, 1) == 1;
if (removeShedule && HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(DNSCache), $"{nameof(Maintenance)} - Remove scheduled rutin");
return !removeShedule; // Timer expects false to remove the timer, so here we actually have to negate removeSchedule to remove.
}
}
}
#endif

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7dffa1d4a664f694faa02c068034ce74
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267636
packageName: Best HTTP
packageVersion: 3.0.17
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/DNS/Cache/DNSCache.cs
uploadId: 783279

View File

@ -0,0 +1,192 @@
#if !UNITY_WEBGL || UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Threading;
using Best.HTTP.Shared.PlatformSupport.Text;
using Best.HTTP.Shared.Logger;
using UnityEngine;
namespace Best.HTTP.Shared.PlatformSupport.Network.DNS.Cache
{
/// <summary>
/// Represents a cached entry for DNS query results, including resolved IP addresses and metadata.
/// </summary>
/// <remarks>
/// Almost immutable, all changes are done in-class in a thread-safe manner.
/// </remarks>
internal class DNSCacheEntry
{
/// <summary>
/// Gets the 128-bit hash derived from the host name.
/// </summary>
public readonly Hash128 Key;
/// <summary>
/// Gets the host name this entry stores the IP addresses for.
/// </summary>
public readonly string Host;
/// <summary>
/// Gets the timestamp when the entry was last resolved.
/// </summary>
public readonly DateTime ResolvedAt;
/// <summary>
/// Gets the timestamp when the entry was last used by calling <see cref="GetAddresses()"/>.
/// </summary>
public DateTime LastUsed { get => new DateTime(this._lastUsedTicks); }
private long _lastUsedTicks;
/// <summary>
/// Resolved IP addresses. It's private, accesible through the <see cref="GetAddresses()"/> call only.
/// </summary>
private readonly List<DNSIPAddress> _resolvedAddresses;
/// <summary>
/// Flag that is set to <c>true</c> when the cache is refreshing this host.
/// </summary>
/// <remarks>
/// When set to <c>true</c>, <see cref="IsStalled(DateTime)"/> will always return as non-stalled.
/// </remarks>
private int _isRefreshing;
public DNSCacheEntry(Hash128 key, string host, List<DNSIPAddress> resolvedAddresses)
: this(key, host, DateTime.UtcNow.Ticks, resolvedAddresses) { }
/// <summary>
/// Initializes a new instance of the DNSCacheEntry class.
/// </summary>
/// <param name="key">The 128-bit hash key derived from the host name.</param>
/// <param name="host">The host name associated with this entry.</param>
/// <param name="resolvedAddresses">The list of <see cref="DNSIPAddress"/> containing the resolved IP addresses.</param>
private DNSCacheEntry(Hash128 key, string host, long lastUsedTicks, List<DNSIPAddress> resolvedAddresses)
{
this.Key = key;
this.Host = host;
this.ResolvedAt = DateTime.UtcNow;
this._lastUsedTicks = lastUsedTicks;
this._resolvedAddresses = resolvedAddresses;
}
/// <summary>
/// Called to clone the entry. The new entry will inherit the last used timestamp.
/// </summary>
/// <param name="resolvedAddresses">The list of <see cref="DNSIPAddress"/> containing the resolved IP addresses.</param>
/// <returns>A new DNSCacheEntry instance with updated resolved addresses.</returns>
public DNSCacheEntry DeriveWith(List<DNSIPAddress> resolvedAddresses) => new DNSCacheEntry(this.Key, this.Host, this._lastUsedTicks, resolvedAddresses);
/// <summary>
/// Checks if the entry is stalled and needs to be refreshed.
/// </summary>
/// <param name="now">The current timestamp.</param>
/// <returns><c>true</c> if the entry is stalled; otherwise, <c>false</c>.</returns>
/// <remarks>
/// The entry is considered stalled if it is not currently being refreshed (i.e., <see cref="_isRefreshing"/> is false)
/// and the time since the last resolution exceeds the refresh interval specified in <see cref="DNSCacheOptions.RefreshAfter"/>.
/// </remarks>
public bool IsStalled(DateTime now) => Volatile.Read(ref this._isRefreshing) == 0 && this.ResolvedAt + DNSCache.Options.RefreshAfter < now;
/// <summary>
/// Checks if the entry is ready to be removed from the cache.
/// </summary>
/// <param name="now">The current timestamp.</param>
/// <returns><c>true</c> if the entry is ready for removal; otherwise, <c>false</c>.</returns>
/// <remarks>
/// The entry is considered ready for removal if the time since it was last used exceeds the removal interval specified in <see cref="DNSCacheOptions.RemoveAfter"/>.
/// </remarks>
public bool IsReadyToRemove(DateTime now) => this.LastUsed + DNSCache.Options.RemoveAfter < now;
/// <summary>
/// Refreshes the entry by initiating a DNS prefetch (by calling <see cref="DNSCache.Prefetch(string)"/>) for the associated host name.
/// </summary>
/// <remarks>
/// This method initiates a DNS prefetch operation for the host name associated with this entry.
/// DNS prefetching is used to resolve and cache DNS records for host names in advance, reducing latency for future network requests.
/// </remarks>
public void Refresh()
{
if (Volatile.Read(ref this._isRefreshing) != 0)
return;
// isRefreshing - we set it only to true, when the fresh records are in a new CacheEntry is created with isRefreshing beeing false.
if (Interlocked.CompareExchange(ref this._isRefreshing, 1, 0) == 0)
DNSCache.Prefetch(this.Host);
}
/// <summary>
/// Gets the resolved IP addresses associated with this entry.
/// </summary>
/// <returns>An array of <see cref="DNSIPAddress"/> representing resolved IP addresses.</returns>
/// <remarks>
/// This method returns the resolved IP addresses associated with this entry and updates the last used timestamp.
/// </remarks>
public DNSIPAddress[] GetAddresses()
{
Interlocked.Exchange(ref this._lastUsedTicks, DateTime.UtcNow.Ticks);
return this._resolvedAddresses.ToArray();
}
/// <summary>
/// Reports an IP address as non-working for the specified host name. In cases where a previously resolved IP address
/// is determined to be non-functional, this method updates the cache to mark the IP address as non-working.
/// </summary>
/// <param name="nonWorking">The non-working IP address to report.</param>
/// <param name="context">Optional logging context for debugging purposes.</param>
/// <remarks>
/// This method is used to report an IP address associated with a host name as non-working.
/// When a previously resolved IP address is determined to be non-functional, this method updates the cache to mark the IP address as non-working.
/// It can be useful in situations where network errors or issues with specific IP addresses need to be recorded and managed.
/// </remarks>
public void ReportNonWorking(System.Net.IPAddress nonWorking, LoggingContext context)
{
var address = this._resolvedAddresses.Find(adr => adr.IPAddress == nonWorking);
if (address != null)
{
// Because the TCP ringmaster not just probes an address, but a port of on that address, setting IsWorkedLastTime to false here
// means, that the address will be tried last even for a different port too.
address.IsWorkedLastTime = false;
}
else
{
// It could happen if a refresh query is started while a tcp race probing the previous addresses and when the refresh finished with different addresses the
// tcp race will try report non-working and now non-existing addresses.
//HTTPManager.Logger.Warning(nameof(DNSCacheEntry), $"{nameof(ReportNonWorking)}({nonWorking}) - couldn't find IP address in resolved addresses!", context);
}
}
public override string ToString()
{
var sb = StringBuilderPool.Get(1);
sb.Append('[');
sb.Append(nameof(DNSCacheEntry));
sb.Append(" Key: ");
sb.Append(this.Key.ToString());
sb.Append(" ResolvedAt: ");
sb.Append(this.ResolvedAt.ToString());
sb.Append(" LastUsed: ");
sb.Append(this.LastUsed.ToString());
sb.Append(" IsRefreshing: ");
sb.Append(this._isRefreshing.ToString());
sb.Append(" IsStalled: ");
sb.Append(this.IsStalled(DateTime.UtcNow));
sb.Append(" IsReadyToRemove: ");
sb.Append(this.IsReadyToRemove(DateTime.UtcNow));
sb.Append(" Resolved: [");
for (int i = 0; i < this._resolvedAddresses?.Count; ++i)
{
sb.Append(this._resolvedAddresses[i].ToString());
if (i < this._resolvedAddresses.Count - 1)
sb.Append(", ");
}
sb.Append(']');
return StringBuilderPool.ReleaseAndGrab(sb);
}
}
}
#endif

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: bf38c03c8439341498f03965f5313f67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267636
packageName: Best HTTP
packageVersion: 3.0.17
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/DNS/Cache/DNSCacheEntry.cs
uploadId: 783279