Implement deferred core utility parity APIs/tests and refresh tracking artifacts
This commit is contained in:
@@ -25,6 +25,18 @@ public static class AccessTimeService
|
||||
// Mirror Go's init(): nothing to pre-allocate in .NET.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicit init hook for Go parity.
|
||||
/// Mirrors package <c>init()</c> in server/ats/ats.go.
|
||||
/// This method is intentionally idempotent.
|
||||
/// </summary>
|
||||
public static void Init()
|
||||
{
|
||||
// Ensure a non-zero cached timestamp is present.
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
|
||||
Interlocked.CompareExchange(ref _utime, now, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a user. Starts the background timer when the first registrant calls this.
|
||||
/// Each call to <see cref="Register"/> must be paired with a call to <see cref="Unregister"/>.
|
||||
|
||||
@@ -40,6 +40,24 @@ public sealed class IpQueue<T>
|
||||
/// <summary>Default maximum size of the recycled backing-list capacity.</summary>
|
||||
public const int DefaultMaxRecycleSize = 4 * 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Functional option type used by <see cref="NewIPQueue"/>.
|
||||
/// Mirrors Go <c>ipQueueOpt</c>.
|
||||
/// </summary>
|
||||
public delegate void IpQueueOption(IpQueueOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Option bag used by <see cref="NewIPQueue"/>.
|
||||
/// Mirrors Go <c>ipQueueOpts</c>.
|
||||
/// </summary>
|
||||
public sealed class IpQueueOptions
|
||||
{
|
||||
public int MaxRecycleSize { get; set; } = DefaultMaxRecycleSize;
|
||||
public Func<T, ulong>? SizeCalc { get; set; }
|
||||
public ulong MaxSize { get; set; }
|
||||
public int MaxLen { get; set; }
|
||||
}
|
||||
|
||||
private long _inprogress;
|
||||
private readonly object _lock = new();
|
||||
|
||||
@@ -68,6 +86,56 @@ public sealed class IpQueue<T>
|
||||
/// <summary>Notification channel reader — wait on this to learn items were added.</summary>
|
||||
public ChannelReader<bool> Ch => _ch.Reader;
|
||||
|
||||
/// <summary>
|
||||
/// Option helper that configures maximum recycled backing-list size.
|
||||
/// Mirrors Go <c>ipqMaxRecycleSize</c>.
|
||||
/// </summary>
|
||||
public static IpQueueOption IpqMaxRecycleSize(int max) =>
|
||||
options => options.MaxRecycleSize = max;
|
||||
|
||||
/// <summary>
|
||||
/// Option helper that enables size accounting for queue elements.
|
||||
/// Mirrors Go <c>ipqSizeCalculation</c>.
|
||||
/// </summary>
|
||||
public static IpQueueOption IpqSizeCalculation(Func<T, ulong> calc) =>
|
||||
options => options.SizeCalc = calc;
|
||||
|
||||
/// <summary>
|
||||
/// Option helper that limits queue pushes by total accounted size.
|
||||
/// Mirrors Go <c>ipqLimitBySize</c>.
|
||||
/// </summary>
|
||||
public static IpQueueOption IpqLimitBySize(ulong max) =>
|
||||
options => options.MaxSize = max;
|
||||
|
||||
/// <summary>
|
||||
/// Option helper that limits queue pushes by element count.
|
||||
/// Mirrors Go <c>ipqLimitByLen</c>.
|
||||
/// </summary>
|
||||
public static IpQueueOption IpqLimitByLen(int max) =>
|
||||
options => options.MaxLen = max;
|
||||
|
||||
/// <summary>
|
||||
/// Factory wrapper for Go parity.
|
||||
/// Mirrors <c>newIPQueue</c>.
|
||||
/// </summary>
|
||||
public static IpQueue<T> NewIPQueue(
|
||||
string name,
|
||||
ConcurrentDictionary<string, object>? registry = null,
|
||||
params IpQueueOption[] options)
|
||||
{
|
||||
var opts = new IpQueueOptions();
|
||||
foreach (var option in options)
|
||||
option(opts);
|
||||
|
||||
return new IpQueue<T>(
|
||||
name,
|
||||
registry,
|
||||
opts.MaxRecycleSize,
|
||||
opts.SizeCalc,
|
||||
opts.MaxSize,
|
||||
opts.MaxLen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new queue, optionally registering it in <paramref name="registry"/>.
|
||||
/// Mirrors <c>newIPQueue</c>.
|
||||
|
||||
@@ -38,6 +38,12 @@ public sealed class RateCounter
|
||||
Interval = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory wrapper for Go parity.
|
||||
/// Mirrors <c>newRateCounter</c>.
|
||||
/// </summary>
|
||||
public static RateCounter NewRateCounter(long limit) => new(limit);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the event is within the rate limit for the current window.
|
||||
/// Mirrors <c>rateCounter.allow</c>.
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
// Adapted from server/util.go in the NATS server Go source.
|
||||
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Internal;
|
||||
@@ -268,6 +270,25 @@ public static class ServerUtilities
|
||||
return client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parity wrapper for Go <c>natsDialTimeout</c>.
|
||||
/// Accepts a network label (tcp/tcp4/tcp6) and host:port address.
|
||||
/// </summary>
|
||||
public static Task<System.Net.Sockets.TcpClient> NatsDialTimeout(
|
||||
string network, string address, TimeSpan timeout)
|
||||
{
|
||||
if (!string.Equals(network, "tcp", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(network, "tcp4", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(network, "tcp6", StringComparison.OrdinalIgnoreCase))
|
||||
throw new NotSupportedException($"unsupported network: {network}");
|
||||
|
||||
var (host, port, err) = ParseHostPort(address, defaultPort: 0);
|
||||
if (err != null || port <= 0)
|
||||
throw new InvalidOperationException($"invalid dial address: {address}", err);
|
||||
|
||||
return NatsDialTimeoutAsync(host, port, timeout);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// URL redaction
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -337,6 +358,54 @@ public static class ServerUtilities
|
||||
return result;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// RefCountedUrlSet wrappers (Go parity mapping)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Parity wrapper for <see cref="RefCountedUrlSet.AddUrl"/>.
|
||||
/// Mirrors <c>refCountedUrlSet.addUrl</c>.
|
||||
/// </summary>
|
||||
public static bool AddUrl(RefCountedUrlSet urlSet, string urlStr)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(urlSet);
|
||||
return urlSet.AddUrl(urlStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parity wrapper for <see cref="RefCountedUrlSet.RemoveUrl"/>.
|
||||
/// Mirrors <c>refCountedUrlSet.removeUrl</c>.
|
||||
/// </summary>
|
||||
public static bool RemoveUrl(RefCountedUrlSet urlSet, string urlStr)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(urlSet);
|
||||
return urlSet.RemoveUrl(urlStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parity wrapper for <see cref="RefCountedUrlSet.GetAsStringSlice"/>.
|
||||
/// Mirrors <c>refCountedUrlSet.getAsStringSlice</c>.
|
||||
/// </summary>
|
||||
public static string[] GetAsStringSlice(RefCountedUrlSet urlSet)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(urlSet);
|
||||
return urlSet.GetAsStringSlice();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// INFO helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Serialises <paramref name="info"/> into an INFO line (<c>INFO {...}\r\n</c>).
|
||||
/// Mirrors <c>generateInfoJSON</c>.
|
||||
/// </summary>
|
||||
public static byte[] GenerateInfoJSON(global::ZB.MOM.NatsNet.Server.ServerInfo info)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(info);
|
||||
return Encoding.UTF8.GetBytes($"INFO {json}\r\n");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Copy helpers
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -391,6 +460,13 @@ public static class ServerUtilities
|
||||
|
||||
return channel.Writer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parity wrapper for <see cref="CreateParallelTaskQueue"/>.
|
||||
/// Mirrors <c>parallelTaskQueue</c>.
|
||||
/// </summary>
|
||||
public static System.Threading.Channels.ChannelWriter<Action> ParallelTaskQueue(int maxParallelism = 0) =>
|
||||
CreateParallelTaskQueue(maxParallelism);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -187,6 +187,12 @@ public static class SignalHandler
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(command), $"unknown signal \"{CommandToString(command)}\""),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Go parity alias for <see cref="CommandToUnixSignal"/>.
|
||||
/// Mirrors <c>CommandToSignal</c> in signal.go.
|
||||
/// </summary>
|
||||
public static UnixSignal CommandToSignal(ServerCommand command) => CommandToUnixSignal(command);
|
||||
|
||||
private static Exception? SendSignal(int pid, UnixSignal signal)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user