mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-07-31 01:40:42 +00:00
Rate Policy
This commit is contained in:
@@ -454,6 +454,7 @@ public partial class EpConnection : NetworkConnection, IStore
|
||||
|
||||
public override void Destroy()
|
||||
{
|
||||
TerminateInvocations();
|
||||
UnsubscribeAll();
|
||||
this.OnReady = null;
|
||||
this.OnError = null;
|
||||
@@ -477,7 +478,9 @@ public partial class EpConnection : NetworkConnection, IStore
|
||||
{
|
||||
offset += (uint)rt;
|
||||
|
||||
if (_packet.Tdu == null)
|
||||
if (_packet.Tdu == null &&
|
||||
_packet.Method != EpPacketMethod.Reply &&
|
||||
_packet.Method != EpPacketMethod.Extension)
|
||||
return offset;
|
||||
|
||||
#if VERBOSE
|
||||
@@ -514,6 +517,9 @@ public partial class EpConnection : NetworkConnection, IStore
|
||||
}
|
||||
else if (_packet.Method == EpPacketMethod.Request)
|
||||
{
|
||||
if (IsRateControlBlocked)
|
||||
return offset;
|
||||
|
||||
var dt = _packet.Tdu.Value;
|
||||
|
||||
switch (_packet.Request)
|
||||
@@ -582,37 +588,58 @@ public partial class EpConnection : NetworkConnection, IStore
|
||||
case EpPacketRequest.StaticCall:
|
||||
EpRequestStaticCall(_packet.CallbackId, dt);
|
||||
break;
|
||||
case EpPacketRequest.PullStream:
|
||||
EpRequestPullStream(_packet.CallbackId, dt);
|
||||
break;
|
||||
case EpPacketRequest.TerminateExecution:
|
||||
EpRequestTerminateExecution(_packet.CallbackId, dt);
|
||||
break;
|
||||
case EpPacketRequest.HaltExecution:
|
||||
EpRequestHaltExecution(_packet.CallbackId, dt);
|
||||
break;
|
||||
case EpPacketRequest.ResumeExecution:
|
||||
EpRequestResumeExecution(_packet.CallbackId, dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (_packet.Method == EpPacketMethod.Reply)
|
||||
{
|
||||
var dt = _packet.Tdu.Value;
|
||||
var dt = _packet.Tdu;
|
||||
|
||||
switch (_packet.Reply)
|
||||
{
|
||||
case EpPacketReply.Completed:
|
||||
EpReplyCompleted(_packet.CallbackId, dt);
|
||||
break;
|
||||
case EpPacketReply.Stream:
|
||||
EpReplyStream(_packet.CallbackId);
|
||||
break;
|
||||
case EpPacketReply.Propagated:
|
||||
EpReplyPropagated(_packet.CallbackId, dt);
|
||||
if (dt.HasValue)
|
||||
EpReplyPropagated(_packet.CallbackId, dt.Value);
|
||||
break;
|
||||
case EpPacketReply.PermissionError:
|
||||
EpReplyError(_packet.CallbackId, dt, ErrorType.Management);
|
||||
if (dt.HasValue)
|
||||
EpReplyError(_packet.CallbackId, dt.Value, ErrorType.Management);
|
||||
break;
|
||||
case EpPacketReply.ExecutionError:
|
||||
EpReplyError(_packet.CallbackId, dt, ErrorType.Exception);
|
||||
if (dt.HasValue)
|
||||
EpReplyError(_packet.CallbackId, dt.Value, ErrorType.Exception);
|
||||
break;
|
||||
|
||||
case EpPacketReply.Progress:
|
||||
EpReplyProgress(_packet.CallbackId, dt);
|
||||
if (dt.HasValue)
|
||||
EpReplyProgress(_packet.CallbackId, dt.Value);
|
||||
break;
|
||||
|
||||
case EpPacketReply.Chunk:
|
||||
EpReplyChunk(_packet.CallbackId, dt);
|
||||
if (dt.HasValue)
|
||||
EpReplyChunk(_packet.CallbackId, dt.Value);
|
||||
break;
|
||||
|
||||
case EpPacketReply.Warning:
|
||||
EpReplyWarning(_packet.Extension, dt);
|
||||
if (dt.HasValue)
|
||||
EpReplyWarning(_packet.CallbackId, dt.Value);
|
||||
break;
|
||||
|
||||
}
|
||||
@@ -2102,6 +2129,7 @@ public partial class EpConnection : NetworkConnection, IStore
|
||||
protected override void Disconnected()
|
||||
{
|
||||
// clean up
|
||||
TerminateInvocations();
|
||||
_authenticated = false;
|
||||
Status = EpConnectionStatus.Closed;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ using Esiur.Net.Packets;
|
||||
using Esiur.Resource;
|
||||
using Esiur.Security.Authority;
|
||||
using Esiur.Security.Permissions;
|
||||
using Esiur.Security.RateLimiting;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System;
|
||||
using System.Collections;
|
||||
@@ -47,6 +48,140 @@ namespace Esiur.Protocol;
|
||||
|
||||
partial class EpConnection
|
||||
{
|
||||
readonly object _rateControlDenialsLock = new object();
|
||||
DateTime _rateControlDenialWindowStarted;
|
||||
int _rateControlDenials;
|
||||
int _rateControlBlocked;
|
||||
|
||||
internal bool IsRateControlBlocked => Volatile.Read(ref _rateControlBlocked) != 0;
|
||||
|
||||
bool TryApplyRateControl(
|
||||
MemberDef member,
|
||||
IResource? resource,
|
||||
ActionType action,
|
||||
uint callback,
|
||||
out TimeSpan delay)
|
||||
{
|
||||
delay = TimeSpan.Zero;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(member.RatePolicyName))
|
||||
return true;
|
||||
|
||||
if (IsRateControlBlocked)
|
||||
return false;
|
||||
|
||||
var warehouse = Instance.Warehouse;
|
||||
var policy = warehouse.TryGetRatePolicy(member.RatePolicyName);
|
||||
|
||||
if (policy == null)
|
||||
{
|
||||
DenyRateControlledRequest(
|
||||
callback,
|
||||
$"Rate policy `{member.RatePolicyName}` is not registered.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var context = new RateControlContext(
|
||||
warehouse,
|
||||
this,
|
||||
_session,
|
||||
resource,
|
||||
member,
|
||||
action);
|
||||
|
||||
if (policy.Applicable(context) == Ruling.Denied)
|
||||
{
|
||||
DenyRateControlledRequest(
|
||||
callback,
|
||||
$"Rate policy `{member.RatePolicyName}` denied `{member.Fullname}`.");
|
||||
return false;
|
||||
}
|
||||
|
||||
delay = context.Delay > TimeSpan.Zero ? context.Delay : TimeSpan.Zero;
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
DenyRateControlledRequest(
|
||||
callback,
|
||||
$"Rate policy `{member.RatePolicyName}` failed: {exception.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DenyRateControlledRequest(uint callback, string message)
|
||||
{
|
||||
var configuration = Instance.Warehouse.Configuration.RateControl;
|
||||
var now = DateTime.UtcNow;
|
||||
var shouldBlock = false;
|
||||
|
||||
lock (_rateControlDenialsLock)
|
||||
{
|
||||
var window = configuration.DenialWindow > TimeSpan.Zero
|
||||
? configuration.DenialWindow
|
||||
: TimeSpan.FromMinutes(1);
|
||||
|
||||
if (_rateControlDenialWindowStarted == default ||
|
||||
now - _rateControlDenialWindowStarted > window)
|
||||
{
|
||||
_rateControlDenialWindowStarted = now;
|
||||
_rateControlDenials = 0;
|
||||
}
|
||||
|
||||
_rateControlDenials++;
|
||||
shouldBlock = configuration.DenialsBeforeConnectionBlock > 0 &&
|
||||
_rateControlDenials >= configuration.DenialsBeforeConnectionBlock &&
|
||||
Interlocked.Exchange(ref _rateControlBlocked, 1) == 0;
|
||||
}
|
||||
|
||||
SendError(
|
||||
ErrorType.Management,
|
||||
callback,
|
||||
(ushort)ExceptionCode.RateLimitExceeded,
|
||||
message);
|
||||
|
||||
if (shouldBlock)
|
||||
_ = CloseRateControlledConnectionAsync(configuration.ConnectionBlockDelay);
|
||||
}
|
||||
|
||||
async Task CloseRateControlledConnectionAsync(TimeSpan delay)
|
||||
{
|
||||
if (delay > TimeSpan.Zero)
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
void ExecuteRateControlled(uint callback, TimeSpan delay, Action action)
|
||||
{
|
||||
if (delay <= TimeSpan.Zero)
|
||||
{
|
||||
action();
|
||||
return;
|
||||
}
|
||||
|
||||
_ = ExecuteRateControlledAsync(callback, delay, action);
|
||||
}
|
||||
|
||||
async Task ExecuteRateControlledAsync(uint callback, TimeSpan delay, Action action)
|
||||
{
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
|
||||
if (!IsConnected || IsRateControlBlocked)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
SendError(ErrorType.Exception, callback, (ushort)ExceptionCode.GeneralFailure, exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
KeyList<ulong, RemoteTypeDef> _neededTypeDefs = new KeyList<ulong, RemoteTypeDef>();
|
||||
KeyList<ulong, RemoteTypeDef> _cachedTypeDefs = new KeyList<ulong, RemoteTypeDef>();
|
||||
KeyList<ulong, FetchRequestInfo<RemoteTypeDef, ulong>> _typeDefRequests = new KeyList<ulong, FetchRequestInfo<RemoteTypeDef, ulong>>();
|
||||
@@ -94,6 +229,9 @@ partial class EpConnection
|
||||
|
||||
KeyList<uint, AsyncReply> _requests = new KeyList<uint, AsyncReply>();
|
||||
|
||||
readonly object _invocationsLock = new object();
|
||||
readonly Dictionary<uint, InvocationContext> _invocations = new Dictionary<uint, InvocationContext>();
|
||||
|
||||
volatile int _callbackCounter = 0;
|
||||
|
||||
Dictionary<IResource, List<byte>> _subscriptions = new Dictionary<IResource, List<byte>>();
|
||||
@@ -125,18 +263,25 @@ partial class EpConnection
|
||||
//callbackCounter++; // avoid thread racing
|
||||
_requests.Add(c, reply);
|
||||
|
||||
SendRequestPacket(action, c, args);
|
||||
return reply;
|
||||
}
|
||||
|
||||
void SendRequestPacket(EpPacketRequest action, uint callbackId, object[] args)
|
||||
{
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
var bl = new BinaryList();
|
||||
bl.AddUInt8((byte)(0x40 | (byte)action))
|
||||
.AddUInt32(c);
|
||||
.AddUInt32(callbackId);
|
||||
Send(bl.ToArray());
|
||||
}
|
||||
if (args.Length == 1)
|
||||
{
|
||||
var bl = new BinaryList();
|
||||
bl.AddUInt8((byte)(0x60 | (byte)action))
|
||||
.AddUInt32(c)
|
||||
.AddUInt32(callbackId)
|
||||
.AddUInt8Array(Codec.Compose(args[0], this.Instance?.Warehouse ?? _serverWarehouse, this));
|
||||
Send(bl.ToArray());
|
||||
}
|
||||
@@ -144,11 +289,39 @@ partial class EpConnection
|
||||
{
|
||||
var bl = new BinaryList();
|
||||
bl.AddUInt8((byte)(0x60 | (byte)action))
|
||||
.AddUInt32(c)
|
||||
.AddUInt32(callbackId)
|
||||
.AddUInt8Array(Codec.Compose(args, this.Instance?.Warehouse ?? _serverWarehouse, this));
|
||||
Send(bl.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
AsyncStreamReply SendStreamRequest(StreamMode streamMode, EpPacketRequest action, params object[] args)
|
||||
{
|
||||
var callbackId = (uint)Interlocked.Increment(ref _callbackCounter);
|
||||
var reply = new AsyncStreamReply(
|
||||
streamMode,
|
||||
() => SendRequest(EpPacketRequest.PullStream, callbackId),
|
||||
() => SendRequest(EpPacketRequest.TerminateExecution, callbackId),
|
||||
() => SendRequest(EpPacketRequest.HaltExecution, callbackId),
|
||||
() => SendRequest(EpPacketRequest.ResumeExecution, callbackId));
|
||||
|
||||
_requests.Add(callbackId, reply);
|
||||
SendRequestPacket(action, callbackId, args);
|
||||
return reply;
|
||||
}
|
||||
|
||||
AsyncStreamReply<T> SendStreamRequest<T>(StreamMode streamMode, EpPacketRequest action, params object[] args)
|
||||
{
|
||||
var callbackId = (uint)Interlocked.Increment(ref _callbackCounter);
|
||||
var reply = new AsyncStreamReply<T>(
|
||||
streamMode,
|
||||
() => SendRequest(EpPacketRequest.PullStream, callbackId),
|
||||
() => SendRequest(EpPacketRequest.TerminateExecution, callbackId),
|
||||
() => SendRequest(EpPacketRequest.HaltExecution, callbackId),
|
||||
() => SendRequest(EpPacketRequest.ResumeExecution, callbackId));
|
||||
|
||||
_requests.Add(callbackId, reply);
|
||||
SendRequestPacket(action, callbackId, args);
|
||||
return reply;
|
||||
}
|
||||
|
||||
@@ -304,6 +477,16 @@ partial class EpConnection
|
||||
return SendRequest(EpPacketRequest.StaticCall, typeId, index, parameters);
|
||||
}
|
||||
|
||||
public AsyncStreamReply<T> StaticStreamCall<T>(ulong typeId, byte index, object parameters, StreamMode streamMode)
|
||||
{
|
||||
return SendStreamRequest<T>(streamMode, EpPacketRequest.StaticCall, typeId, index, parameters);
|
||||
}
|
||||
|
||||
public AsyncStreamReply StaticStreamCall(ulong typeId, byte index, object parameters, StreamMode streamMode)
|
||||
{
|
||||
return SendStreamRequest(streamMode, EpPacketRequest.StaticCall, typeId, index, parameters);
|
||||
}
|
||||
|
||||
public AsyncReply Call(string procedureCall, params object[] parameters)
|
||||
{
|
||||
//var args = new Map<byte, object>();
|
||||
@@ -324,6 +507,16 @@ partial class EpConnection
|
||||
return SendRequest(EpPacketRequest.InvokeFunction, instanceId, index, parameters);
|
||||
}
|
||||
|
||||
internal AsyncStreamReply SendStreamInvoke(uint instanceId, byte index, object parameters, StreamMode streamMode)
|
||||
{
|
||||
return SendStreamRequest(streamMode, EpPacketRequest.InvokeFunction, instanceId, index, parameters);
|
||||
}
|
||||
|
||||
internal AsyncStreamReply<T> SendStreamInvoke<T>(uint instanceId, byte index, object parameters, StreamMode streamMode)
|
||||
{
|
||||
return SendStreamRequest<T>(streamMode, EpPacketRequest.InvokeFunction, instanceId, index, parameters);
|
||||
}
|
||||
|
||||
internal AsyncReply SendSetProperty(uint instanceId, byte index, object value)
|
||||
{
|
||||
return SendRequest(EpPacketRequest.SetProperty, instanceId, index, value);
|
||||
@@ -383,7 +576,7 @@ partial class EpConnection
|
||||
SendReply(EpPacketReply.Chunk, callbackId, chunk);
|
||||
}
|
||||
|
||||
void EpReplyCompleted(uint callbackId, PlainTdu tdu)
|
||||
void EpReplyCompleted(uint callbackId, PlainTdu? tdu)
|
||||
{
|
||||
var req = _requests.Take(callbackId);
|
||||
|
||||
@@ -395,7 +588,19 @@ partial class EpConnection
|
||||
return;
|
||||
}
|
||||
|
||||
var pr = Codec.Parse(tdu, this, null);
|
||||
if (req is AsyncStreamReply streamReply)
|
||||
{
|
||||
streamReply.TriggerStreamCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
if (tdu == null)
|
||||
{
|
||||
req.Trigger(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var pr = Codec.Parse(tdu.Value, this, null);
|
||||
|
||||
if (pr is AsyncReply asyncReply)
|
||||
{
|
||||
@@ -421,6 +626,12 @@ partial class EpConnection
|
||||
//}).Error(req.TriggerError);
|
||||
}
|
||||
|
||||
void EpReplyStream(uint callbackId)
|
||||
{
|
||||
if (_requests[callbackId] is AsyncStreamReply streamReply)
|
||||
streamReply.TriggerStreamStarted();
|
||||
}
|
||||
|
||||
void EpExtensionAction(byte actionId, PlainTdu? tdu)
|
||||
{
|
||||
// nothing is supported now
|
||||
@@ -1419,6 +1630,22 @@ partial class EpConnection
|
||||
|
||||
|
||||
void InvokeFunction(FunctionDef ft, uint callback, object arguments, EpPacketRequest actionType, object target = null)
|
||||
{
|
||||
if (!TryApplyRateControl(
|
||||
ft,
|
||||
target as IResource,
|
||||
ActionType.Execute,
|
||||
callback,
|
||||
out var delay))
|
||||
return;
|
||||
|
||||
ExecuteRateControlled(
|
||||
callback,
|
||||
delay,
|
||||
() => InvokeFunctionCore(ft, callback, arguments, actionType, target));
|
||||
}
|
||||
|
||||
void InvokeFunctionCore(FunctionDef ft, uint callback, object arguments, EpPacketRequest actionType, object target = null)
|
||||
{
|
||||
|
||||
// cast arguments
|
||||
@@ -1559,59 +1786,44 @@ partial class EpConnection
|
||||
return;
|
||||
}
|
||||
|
||||
if (rt is IAsyncEnumerable<object>)
|
||||
if (ft.StreamMode != StreamMode.None)
|
||||
{
|
||||
var enu = rt as IAsyncEnumerable<object>;
|
||||
var enumerator = enu.GetAsyncEnumerator();
|
||||
Task.Run(async () =>
|
||||
context ??= new InvocationContext(this, callback);
|
||||
context.InitializeStream(ft.StreamMode, ft.Pausable);
|
||||
|
||||
if (ft.StreamMode == StreamMode.Pull && context.SetAsyncEnumerable(rt))
|
||||
{
|
||||
try
|
||||
{
|
||||
while (await enumerator.MoveNextAsync())
|
||||
RegisterInvocation(callback, context);
|
||||
SendReply(EpPacketReply.Stream, callback);
|
||||
}
|
||||
else if (ft.StreamMode == StreamMode.Push && rt is System.Collections.IEnumerable enumerable)
|
||||
{
|
||||
context.SetEnumerable(enumerable);
|
||||
RegisterInvocation(callback, context);
|
||||
SendReply(EpPacketReply.Stream, callback);
|
||||
PumpEnumerable(callback, context);
|
||||
}
|
||||
else if (ft.StreamMode == StreamMode.Push && rt is AsyncReply streamSource)
|
||||
{
|
||||
RegisterInvocation(callback, context);
|
||||
SendReply(EpPacketReply.Stream, callback);
|
||||
|
||||
streamSource.Then(_ => CompleteInvocation(callback, context))
|
||||
.Error(ex => FailInvocation(callback, context, ex))
|
||||
.Progress((pt, pv, pm) => SendProgress(callback, pv, pm))
|
||||
.Chunk(value =>
|
||||
{
|
||||
var v = enumerator.Current;
|
||||
SendChunk(callback, v);
|
||||
}
|
||||
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
|
||||
if (context != null)
|
||||
context.Ended = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (context != null)
|
||||
context.Ended = true;
|
||||
|
||||
var (code, msg) = SummerizeException(ex);
|
||||
SendError(ErrorType.Exception, callback, code, msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (rt is System.Collections.IEnumerable && !(rt is Array || rt is Map<string, object> || rt is string))
|
||||
{
|
||||
var enu = rt as System.Collections.IEnumerable;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var v in enu)
|
||||
SendChunk(callback, v);
|
||||
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
|
||||
if (context != null)
|
||||
context.Ended = true;
|
||||
|
||||
if (!context.Ended)
|
||||
context.Chunk(value);
|
||||
})
|
||||
.Warning((level, message) => SendWarning(callback, level, message));
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
if (context != null)
|
||||
context.Ended = true;
|
||||
|
||||
var (code, msg) = SummerizeException(ex);
|
||||
SendError(ErrorType.Exception, callback, code, msg);
|
||||
context.Ended = true;
|
||||
SendError(ErrorType.Exception, callback, (ushort)ExceptionCode.NotSupported,
|
||||
$"Stream mode `{ft.StreamMode}` is not compatible with `{rt?.GetType()}`.");
|
||||
}
|
||||
|
||||
}
|
||||
else if (rt is Task)
|
||||
{
|
||||
@@ -1663,6 +1875,217 @@ partial class EpConnection
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterInvocation(uint callback, InvocationContext context)
|
||||
{
|
||||
lock (_invocationsLock)
|
||||
_invocations[callback] = context;
|
||||
}
|
||||
|
||||
InvocationContext GetInvocation(uint callback)
|
||||
{
|
||||
lock (_invocationsLock)
|
||||
return _invocations.TryGetValue(callback, out var context) ? context : null;
|
||||
}
|
||||
|
||||
void TerminateInvocations()
|
||||
{
|
||||
InvocationContext[] contexts;
|
||||
|
||||
lock (_invocationsLock)
|
||||
{
|
||||
contexts = _invocations.Values.ToArray();
|
||||
_invocations.Clear();
|
||||
}
|
||||
|
||||
foreach (var context in contexts)
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await context.TerminateAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// The connection is already closing; disposal is best effort.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool TakeInvocation(uint callback, InvocationContext expected, out InvocationContext context)
|
||||
{
|
||||
lock (_invocationsLock)
|
||||
{
|
||||
if (!_invocations.TryGetValue(callback, out context) ||
|
||||
(expected != null && !ReferenceEquals(expected, context)))
|
||||
return false;
|
||||
|
||||
_invocations.Remove(callback);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async void CompleteInvocation(uint callback, InvocationContext expected)
|
||||
{
|
||||
if (!TakeInvocation(callback, expected, out var context))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
await context.EndAsync();
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var (code, message) = SummerizeException(ex);
|
||||
SendError(ErrorType.Exception, callback, code, message);
|
||||
}
|
||||
}
|
||||
|
||||
async void FailInvocation(uint callback, InvocationContext expected, Exception exception)
|
||||
{
|
||||
if (!TakeInvocation(callback, expected, out var context))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
await context.TerminateAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Preserve the exception that ended the stream.
|
||||
}
|
||||
|
||||
var (code, message) = SummerizeException(exception);
|
||||
SendError(ErrorType.Exception, callback, code, message);
|
||||
}
|
||||
|
||||
void PumpEnumerable(uint callback, InvocationContext context)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var next = await context.MoveNextAsync();
|
||||
if (!next.HasValue)
|
||||
{
|
||||
CompleteInvocation(callback, context);
|
||||
return;
|
||||
}
|
||||
|
||||
context.Chunk(next.Value);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// TerminateExecution owns completion when it cancels the pump.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
FailInvocation(callback, context, ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uint ParseExecutionCallback(PlainTdu tdu)
|
||||
=> Convert.ToUInt32(Codec.ParseSync(tdu, Instance.Warehouse));
|
||||
|
||||
void EpRequestPullStream(uint callback, PlainTdu tdu)
|
||||
{
|
||||
var executionCallback = ParseExecutionCallback(tdu);
|
||||
var context = GetInvocation(executionCallback);
|
||||
|
||||
if (context == null || context.StreamMode != StreamMode.Pull)
|
||||
{
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.NotAllowed,
|
||||
"The target execution is not an active pull stream.");
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var next = await context.PullAsync();
|
||||
if (next.HasValue)
|
||||
SendChunk(executionCallback, next.Value);
|
||||
else
|
||||
CompleteInvocation(executionCallback, context);
|
||||
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
FailInvocation(executionCallback, context, ex);
|
||||
var (code, message) = SummerizeException(ex);
|
||||
SendError(ErrorType.Exception, callback, code, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EpRequestTerminateExecution(uint callback, PlainTdu tdu)
|
||||
{
|
||||
var executionCallback = ParseExecutionCallback(tdu);
|
||||
|
||||
if (!TakeInvocation(executionCallback, null, out var context))
|
||||
{
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.NotAllowed,
|
||||
"The target execution is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await context.TerminateAsync();
|
||||
SendReply(EpPacketReply.Completed, executionCallback);
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var (code, message) = SummerizeException(ex);
|
||||
SendError(ErrorType.Exception, executionCallback, code, message);
|
||||
SendError(ErrorType.Exception, callback, code, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EpRequestHaltExecution(uint callback, PlainTdu tdu)
|
||||
{
|
||||
var executionCallback = ParseExecutionCallback(tdu);
|
||||
var context = GetInvocation(executionCallback);
|
||||
|
||||
if (context == null || !context.Halt())
|
||||
{
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.NotAllowed,
|
||||
"The target execution is not pausable or is already halted.");
|
||||
return;
|
||||
}
|
||||
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}
|
||||
|
||||
void EpRequestResumeExecution(uint callback, PlainTdu tdu)
|
||||
{
|
||||
var executionCallback = ParseExecutionCallback(tdu);
|
||||
var context = GetInvocation(executionCallback);
|
||||
|
||||
if (context == null || !context.Resume())
|
||||
{
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.NotAllowed,
|
||||
"The target execution is not pausable or is not halted.");
|
||||
return;
|
||||
}
|
||||
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}
|
||||
|
||||
void EpRequestSubscribe(uint callback, PlainTdu tdu)
|
||||
{
|
||||
|
||||
@@ -1683,7 +2106,7 @@ partial class EpConnection
|
||||
|
||||
var et = r.Instance.Definition.GetEventDefByIndex(index);
|
||||
|
||||
if (et != null)
|
||||
if (et == null)
|
||||
{
|
||||
// et not found
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.MethodNotFound);
|
||||
@@ -1789,7 +2212,7 @@ partial class EpConnection
|
||||
var (offset, length, args) = DataDeserializer.LimitedCountListParser(tdu.Data, tdu.PayloadOffset,
|
||||
tdu.PayloadLength, Instance.Warehouse, 2);
|
||||
|
||||
var rid = (uint)args[0];
|
||||
var rid = Convert.ToUInt32(args[0]);
|
||||
var index = (byte)args[1];
|
||||
|
||||
// un hold the socket to send data immediately
|
||||
@@ -1806,13 +2229,27 @@ partial class EpConnection
|
||||
|
||||
var pt = r.Instance.Definition.GetPropertyDefByIndex(index);
|
||||
|
||||
if (pt != null)
|
||||
if (pt == null)
|
||||
{
|
||||
// property not found
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.PropertyNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (r.Instance.Applicable(_session, ActionType.SetProperty, pt, this) == Ruling.Denied)
|
||||
{
|
||||
SendError(ErrorType.Exception, callback, (ushort)ExceptionCode.SetPropertyDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryApplyRateControl(
|
||||
pt,
|
||||
r,
|
||||
ActionType.SetProperty,
|
||||
callback,
|
||||
out var rateControlDelay))
|
||||
return;
|
||||
|
||||
|
||||
if (r is IDynamicResource dynamicResource)
|
||||
{
|
||||
@@ -1823,25 +2260,27 @@ partial class EpConnection
|
||||
asyncReply.Then((value) =>
|
||||
{
|
||||
// propagation
|
||||
dynamicResource.SetResourcePropertyAsync(index, value).Then((x) =>
|
||||
{
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}).Error(x =>
|
||||
{
|
||||
SendError(x.Type, callback, (ushort)x.Code, x.Message);
|
||||
});
|
||||
ExecuteRateControlled(callback, rateControlDelay, () =>
|
||||
dynamicResource.SetResourcePropertyAsync(index, value).Then((x) =>
|
||||
{
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}).Error(x =>
|
||||
{
|
||||
SendError(x.Type, callback, (ushort)x.Code, x.Message);
|
||||
}));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// propagation
|
||||
dynamicResource.SetResourcePropertyAsync(index, pr.Value).Then((x) =>
|
||||
{
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}).Error(x =>
|
||||
{
|
||||
SendError(x.Type, callback, (ushort)x.Code, x.Message);
|
||||
});
|
||||
ExecuteRateControlled(callback, rateControlDelay, () =>
|
||||
dynamicResource.SetResourcePropertyAsync(index, pr.Value).Then((x) =>
|
||||
{
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}).Error(x =>
|
||||
{
|
||||
SendError(x.Type, callback, (ushort)x.Code, x.Message);
|
||||
}));
|
||||
}
|
||||
}).Error(x => SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ParseError)); ;
|
||||
|
||||
@@ -1856,12 +2295,6 @@ partial class EpConnection
|
||||
return;
|
||||
}
|
||||
|
||||
if (r.Instance.Applicable(_session, ActionType.SetProperty, pt, this) == Ruling.Denied)
|
||||
{
|
||||
SendError(ErrorType.Exception, callback, (ushort)ExceptionCode.SetPropertyDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pi.CanWrite)
|
||||
{
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ReadOnlyProperty);
|
||||
@@ -1876,6 +2309,36 @@ partial class EpConnection
|
||||
if (pr.Value is AsyncReply asyncReply)
|
||||
{
|
||||
asyncReply.Then((value) =>
|
||||
{
|
||||
ExecuteRateControlled(callback, rateControlDelay, () =>
|
||||
{
|
||||
if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition()
|
||||
== typeof(PropertyContext<>))
|
||||
{
|
||||
value = Activator.CreateInstance(pi.PropertyType, this, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = RuntimeCaster.Cast(value, pi.PropertyType);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
pi.SetValue(r, value);
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SendError(ErrorType.Exception, callback, 0, ex.Message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = pr.Value;
|
||||
|
||||
ExecuteRateControlled(callback, rateControlDelay, () =>
|
||||
{
|
||||
if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition()
|
||||
== typeof(PropertyContext<>))
|
||||
@@ -1884,7 +2347,6 @@ partial class EpConnection
|
||||
}
|
||||
else
|
||||
{
|
||||
// cast new value type to property type
|
||||
value = RuntimeCaster.Cast(value, pi.PropertyType);
|
||||
}
|
||||
|
||||
@@ -1899,32 +2361,6 @@ partial class EpConnection
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = pr.Value;
|
||||
|
||||
if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition()
|
||||
== typeof(PropertyContext<>))
|
||||
{
|
||||
value = Activator.CreateInstance(pi.PropertyType, this, value);
|
||||
//value = new DistributedPropertyContext(this, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// cast new value type to property type
|
||||
value = RuntimeCaster.Cast(value, pi.PropertyType);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
pi.SetValue(r, value);
|
||||
SendReply(EpPacketReply.Completed, callback);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SendError(ErrorType.Exception, callback, 0, ex.Message);
|
||||
}
|
||||
}
|
||||
}).Error(x => SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ParseError));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -289,9 +289,33 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
throw new Exception("Function definition not found.");
|
||||
|
||||
if (ft.IsStatic)
|
||||
return _connection.StaticCall(Instance.Definition.Id, index, args);
|
||||
else
|
||||
return _connection.SendInvoke(_instanceId, index, args);
|
||||
return ft.StreamMode == StreamMode.None
|
||||
? _connection.StaticCall(Instance.Definition.Id, index, args)
|
||||
: _connection.StaticStreamCall(Instance.Definition.Id, index, args, ft.StreamMode);
|
||||
|
||||
return ft.StreamMode == StreamMode.None
|
||||
? _connection.SendInvoke(_instanceId, index, args)
|
||||
: _connection.SendStreamInvoke(_instanceId, index, args, ft.StreamMode);
|
||||
}
|
||||
|
||||
public AsyncStreamReply<T> _InvokeStream<T>(byte index, object args)
|
||||
{
|
||||
if (_status == ResourceStatus.Destroyed)
|
||||
throw new Exception("Trying to access a destroyed object.");
|
||||
|
||||
if (_status == ResourceStatus.Suspended)
|
||||
throw new Exception("Trying to access a suspended object.");
|
||||
|
||||
var function = Instance.Definition.GetFunctionDefByIndex(index)
|
||||
?? throw new Exception("Function definition not found.");
|
||||
|
||||
if (function.StreamMode == StreamMode.None)
|
||||
throw new Exception("Function is not a stream.");
|
||||
|
||||
if (function.IsStatic)
|
||||
return _connection.StaticStreamCall<T>(Instance.Definition.Id, index, args, function.StreamMode);
|
||||
|
||||
return _connection.SendStreamInvoke<T>(_instanceId, index, args, function.StreamMode);
|
||||
}
|
||||
|
||||
public AsyncReply Subscribe(EventDef et)
|
||||
@@ -634,7 +658,8 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
// this only happens if the programmer forgot to emit in property setter
|
||||
_properties[index] = value;
|
||||
reply.Trigger(null);
|
||||
});
|
||||
})
|
||||
.Error(reply.TriggerError);
|
||||
|
||||
return reply;
|
||||
|
||||
@@ -655,4 +680,4 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user