Rate Policy

This commit is contained in:
2026-07-13 17:15:00 +03:00
parent 31bda58460
commit 4f6d2d0801
46 changed files with 3142 additions and 468 deletions
+1 -1
View File
@@ -271,7 +271,7 @@ public class AsyncReply
return;
}
public void TriggerError(Exception exception)
public virtual void TriggerError(Exception exception)
{
//timeout?.Dispose();
+409
View File
@@ -0,0 +1,409 @@
/*
Copyright (c) 2017-2026 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using Esiur.Data;
using Esiur.Data.Types;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Esiur.Core;
/// <summary>
/// Represents a remotely executing stream and exposes its lifecycle controls.
/// </summary>
public class AsyncStreamReply : AsyncReply
{
readonly Func<AsyncReply> _pull;
readonly Func<AsyncReply> _terminate;
readonly Func<AsyncReply> _halt;
readonly Func<AsyncReply> _resume;
readonly object _streamLock = new object();
readonly TaskCompletionSource<bool> _started = NewCompletionSource();
Exception _streamException;
bool _streamStarted;
bool _streamCompleted;
bool _terminationSent;
/// <summary>
/// Gets the delivery mode declared by the remote function.
/// </summary>
public StreamMode StreamMode { get; }
/// <summary>
/// Gets whether the peer acknowledged that the stream has started.
/// </summary>
public bool Started
{
get
{
lock (_streamLock)
return _streamStarted;
}
}
/// <summary>
/// Gets whether the stream completed or was terminated.
/// </summary>
public bool Completed
{
get
{
lock (_streamLock)
return _streamCompleted;
}
}
internal AsyncStreamReply(
StreamMode streamMode,
Func<AsyncReply> pull,
Func<AsyncReply> terminate,
Func<AsyncReply> halt,
Func<AsyncReply> resume)
{
StreamMode = streamMode;
_pull = pull;
_terminate = terminate;
_halt = halt;
_resume = resume;
}
/// <summary>
/// Requests the next item of a pull stream.
/// </summary>
public AsyncReply Pull()
{
if (StreamMode != StreamMode.Pull)
throw new InvalidOperationException("Pull is only valid for a pull stream.");
lock (_streamLock)
{
if (_streamCompleted)
return new AsyncReply(null);
}
return _pull();
}
/// <summary>
/// Terminates the remote stream execution and releases its enumerator.
/// </summary>
public AsyncReply Terminate()
{
lock (_streamLock)
{
if (_terminationSent || _streamCompleted)
return new AsyncReply(null);
_terminationSent = true;
_streamCompleted = true;
_started.TrySetResult(true);
}
OnStreamCompleted();
var reply = _terminate();
reply.Error(TriggerStreamError);
return reply;
}
/// <summary>
/// Halts a pausable remote stream execution.
/// </summary>
public AsyncReply Halt()
{
lock (_streamLock)
{
if (_streamCompleted)
return new AsyncReply(null);
}
var reply = _halt();
reply.Error(TriggerStreamError);
return reply;
}
/// <summary>
/// Resumes a halted remote stream execution.
/// </summary>
public AsyncReply Resume()
{
lock (_streamLock)
{
if (_streamCompleted)
return new AsyncReply(null);
}
var reply = _resume();
reply.Error(TriggerStreamError);
return reply;
}
internal void TriggerStreamStarted()
{
lock (_streamLock)
{
if (_streamCompleted || _streamException != null)
return;
_streamStarted = true;
_started.TrySetResult(true);
}
OnStreamStarted();
}
internal void TriggerStreamCompleted()
{
lock (_streamLock)
{
if (_streamCompleted)
return;
_streamCompleted = true;
_started.TrySetResult(true);
}
OnStreamCompleted();
}
internal void TriggerStreamError(AsyncException exception)
{
lock (_streamLock)
{
if (_streamCompleted || _streamException != null)
return;
_streamException = exception;
_streamCompleted = true;
_started.TrySetException(exception);
}
OnStreamError(exception);
if (errorCallbacks != null)
base.TriggerError(exception);
}
/// <inheritdoc />
public override void TriggerError(Exception exception)
{
TriggerStreamError(exception as AsyncException ?? new AsyncException(exception));
}
internal Task WaitUntilStartedAsync() => _started.Task;
internal Exception GetStreamException()
{
lock (_streamLock)
return _streamException;
}
internal bool IsStreamCompleted()
{
lock (_streamLock)
return _streamCompleted;
}
protected virtual void OnStreamStarted() { }
protected virtual void OnStreamCompleted() { }
protected virtual void OnStreamError(Exception exception) { }
protected static TaskCompletionSource<bool> NewCompletionSource()
=> new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
}
/// <summary>
/// A typed remote stream that can be consumed with <c>await foreach</c>.
/// Pull streams request exactly one remote item for each <c>MoveNextAsync</c> call.
/// </summary>
public sealed class AsyncStreamReply<T> : AsyncStreamReply, IAsyncEnumerable<T>
{
readonly object _itemsLock = new object();
readonly Queue<T> _items = new Queue<T>();
TaskCompletionSource<bool> _itemAvailable;
bool _enumeratorCreated;
bool _movePending;
internal AsyncStreamReply(
StreamMode streamMode,
Func<AsyncReply> pull,
Func<AsyncReply> terminate,
Func<AsyncReply> halt,
Func<AsyncReply> resume)
: base(streamMode, pull, terminate, halt, resume)
{
Chunk(value => ReceiveItem((T)RuntimeCaster.Cast(value, typeof(T))));
}
/// <inheritdoc />
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
lock (_itemsLock)
{
if (_enumeratorCreated)
throw new InvalidOperationException("A remote stream can only be enumerated once.");
_enumeratorCreated = true;
}
return new Enumerator(this, cancellationToken);
}
void ReceiveItem(T item)
{
TaskCompletionSource<bool> itemAvailable;
lock (_itemsLock)
{
if (IsStreamCompleted() || GetStreamException() != null)
return;
_items.Enqueue(item);
itemAvailable = _itemAvailable;
_itemAvailable = null;
}
itemAvailable?.TrySetResult(true);
}
protected override void OnStreamCompleted()
{
TaskCompletionSource<bool> itemAvailable;
lock (_itemsLock)
{
itemAvailable = _itemAvailable;
_itemAvailable = null;
}
itemAvailable?.TrySetResult(true);
}
protected override void OnStreamError(Exception exception)
{
TaskCompletionSource<bool> itemAvailable;
lock (_itemsLock)
{
itemAvailable = _itemAvailable;
_itemAvailable = null;
}
itemAvailable?.TrySetException(exception);
}
async Task<(bool HasValue, T Value)> MoveNextAsync(CancellationToken cancellationToken)
{
lock (_itemsLock)
{
if (_movePending)
throw new InvalidOperationException("Concurrent MoveNextAsync calls are not supported.");
_movePending = true;
}
try
{
cancellationToken.ThrowIfCancellationRequested();
await WaitUntilStartedAsync();
while (true)
{
TaskCompletionSource<bool> itemAvailable;
lock (_itemsLock)
{
var exception = GetStreamException();
if (exception != null)
throw exception;
if (_items.Count > 0)
return (true, _items.Dequeue());
if (IsStreamCompleted())
return (false, default);
_itemAvailable = NewCompletionSource();
itemAvailable = _itemAvailable;
}
if (StreamMode == StreamMode.Pull)
{
var pull = Pull();
_ = pull.Error(TriggerStreamError);
}
using (cancellationToken.Register(() =>
{
itemAvailable.TrySetCanceled();
_ = Terminate();
}))
{
await itemAvailable.Task;
}
}
}
finally
{
lock (_itemsLock)
_movePending = false;
}
}
async Task DisposeAsync()
{
if (IsStreamCompleted())
return;
await Terminate();
}
sealed class Enumerator : IAsyncEnumerator<T>
{
readonly AsyncStreamReply<T> _owner;
readonly CancellationToken _cancellationToken;
public T Current { get; private set; }
internal Enumerator(AsyncStreamReply<T> owner, CancellationToken cancellationToken)
{
_owner = owner;
_cancellationToken = cancellationToken;
}
public async ValueTask<bool> MoveNextAsync()
{
var result = await _owner.MoveNextAsync(_cancellationToken);
Current = result.Value;
return result.HasValue;
}
public ValueTask DisposeAsync() => new ValueTask(_owner.DisposeAsync());
}
}
+2 -1
View File
@@ -46,5 +46,6 @@ public enum ExceptionCode : ushort
Timeout,
NotSupported,
NotImplemented,
NotAllowed
NotAllowed,
RateLimitExceeded
}
+219 -8
View File
@@ -1,15 +1,47 @@
using Esiur.Protocol;
using Esiur.Data.Types;
using Esiur.Protocol;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Esiur.Core
{
public class InvocationContext
{
private uint CallbackId;
readonly uint CallbackId;
readonly object _stateLock = new object();
readonly CancellationTokenSource _cancellation = new CancellationTokenSource();
readonly SemaphoreSlim _moveLock = new SemaphoreSlim(1, 1);
TaskCompletionSource<bool> _resumeSignal = CompletedSignal();
internal bool Ended;
IAsyncEnumeratorAdapter _asyncEnumerator;
IEnumerator _enumerator;
bool _halted;
internal StreamMode StreamMode { get; private set; }
internal bool Pausable { get; private set; }
internal volatile bool Ended;
/// <summary>
/// Gets a token that is cancelled when the caller terminates this execution.
/// </summary>
public CancellationToken CancellationToken => _cancellation.Token;
/// <summary>
/// Gets whether a pausable execution is currently halted.
/// </summary>
public bool IsHalted
{
get
{
lock (_stateLock)
return _halted;
}
}
public void Chunk(object value)
{
@@ -19,8 +51,8 @@ namespace Esiur.Core
Connection.SendChunk(CallbackId, value);
}
public void Progress(uint value, uint max) {
public void Progress(uint value, uint max)
{
if (Ended)
throw new Exception("Execution has ended.");
@@ -29,7 +61,6 @@ namespace Esiur.Core
public void Warning(byte level, string message)
{
if (Ended)
throw new Exception("Execution has ended.");
@@ -38,12 +69,192 @@ namespace Esiur.Core
public EpConnection Connection { get; internal set; }
internal InvocationContext(EpConnection connection, uint callbackId)
{
Connection = connection;
CallbackId = callbackId;
}
internal void InitializeStream(StreamMode streamMode, bool pausable)
{
StreamMode = streamMode;
Pausable = pausable;
}
internal bool SetAsyncEnumerable(object enumerable)
{
var asyncEnumerableInterface = enumerable.GetType()
.GetInterfaces()
.Concat(new[] { enumerable.GetType() })
.FirstOrDefault(x => x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>));
if (asyncEnumerableInterface == null)
return false;
var itemType = asyncEnumerableInterface.GetGenericArguments()[0];
var adapterType = typeof(AsyncEnumeratorAdapter<>).MakeGenericType(itemType);
_asyncEnumerator = (IAsyncEnumeratorAdapter)Activator.CreateInstance(
adapterType, enumerable, _cancellation.Token);
return true;
}
internal void SetEnumerable(IEnumerable enumerable)
{
_enumerator = enumerable.GetEnumerator();
}
internal async Task<(bool HasValue, object Value)> PullAsync()
{
if (StreamMode != StreamMode.Pull || _asyncEnumerator == null)
throw new InvalidOperationException("Execution is not an asynchronous pull stream.");
await _moveLock.WaitAsync(_cancellation.Token);
try
{
await WaitWhileHaltedAsync();
_cancellation.Token.ThrowIfCancellationRequested();
var hasValue = await _asyncEnumerator.MoveNextAsync();
return (hasValue, hasValue ? _asyncEnumerator.Current : null);
}
finally
{
_moveLock.Release();
}
}
internal async Task<(bool HasValue, object Value)> MoveNextAsync()
{
if (_enumerator == null)
throw new InvalidOperationException("Execution is not a synchronous stream.");
await WaitWhileHaltedAsync();
_cancellation.Token.ThrowIfCancellationRequested();
var hasValue = _enumerator.MoveNext();
return (hasValue, hasValue ? _enumerator.Current : null);
}
internal bool Halt()
{
lock (_stateLock)
{
if (Ended || !Pausable || _halted)
return false;
_halted = true;
_resumeSignal = NewSignal();
return true;
}
}
internal bool Resume()
{
TaskCompletionSource<bool> resumeSignal;
lock (_stateLock)
{
if (Ended || !Pausable || !_halted)
return false;
_halted = false;
resumeSignal = _resumeSignal;
}
resumeSignal.TrySetResult(true);
return true;
}
/// <summary>
/// Asynchronously waits until a halted execution is resumed.
/// </summary>
public async Task WaitWhileHaltedAsync()
{
Task resumeTask;
lock (_stateLock)
resumeTask = _resumeSignal.Task;
await resumeTask;
_cancellation.Token.ThrowIfCancellationRequested();
}
internal async Task TerminateAsync()
{
TaskCompletionSource<bool> resumeSignal;
lock (_stateLock)
{
if (Ended)
return;
Ended = true;
_halted = false;
resumeSignal = _resumeSignal;
}
resumeSignal.TrySetResult(true);
_cancellation.Cancel();
if (_asyncEnumerator != null)
await _asyncEnumerator.DisposeAsync();
(_enumerator as IDisposable)?.Dispose();
}
internal async Task EndAsync()
{
TaskCompletionSource<bool> resumeSignal;
lock (_stateLock)
{
if (Ended)
return;
Ended = true;
_halted = false;
resumeSignal = _resumeSignal;
}
resumeSignal.TrySetResult(true);
if (_asyncEnumerator != null)
await _asyncEnumerator.DisposeAsync();
(_enumerator as IDisposable)?.Dispose();
}
static TaskCompletionSource<bool> NewSignal()
=> new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
static TaskCompletionSource<bool> CompletedSignal()
{
var signal = NewSignal();
signal.SetResult(true);
return signal;
}
}
interface IAsyncEnumeratorAdapter
{
object Current { get; }
ValueTask<bool> MoveNextAsync();
ValueTask DisposeAsync();
}
sealed class AsyncEnumeratorAdapter<T> : IAsyncEnumeratorAdapter
{
readonly IAsyncEnumerator<T> _enumerator;
public object Current => _enumerator.Current;
public AsyncEnumeratorAdapter(IAsyncEnumerable<T> enumerable, CancellationToken cancellationToken)
{
_enumerator = enumerable.GetAsyncEnumerator(cancellationToken);
}
public ValueTask<bool> MoveNextAsync() => _enumerator.MoveNextAsync();
public ValueTask DisposeAsync() => _enumerator.DisposeAsync();
}
}