mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
32e7ff00b5
* feat: Add support for Workflow-as-Executor * Fixes routing of 'object' compile-typed variables to properly take in type information * Fixes a concurrency issue in StepTracer * fix: Make Subworkflow ExternalRequests work properly * fix: Threading and Concurrency fixes; prep for OffThread Mode * refactor: Remove dead code around OffStreamRunEventStream Currently not used, and will be replaced with a rewrite when brought back, so having it in the change is not valuable. * ci: Work around issues with dotnet-format not properly analyzing the source * fix: Fix the logic of AsyncCoordinator and AsyncBarrier * Prevent individual wait cancellations from canceling the entire barrier * Propagate information about whether the wait was completed or cancelled, and whether any waiters were present when released * fix: Remove superfluous acces to .Keys in InProcStepTracer * refactor: Clean up AsyncCoordinator's use of AsyncBarrier
158 lines
6.1 KiB
C#
158 lines
6.1 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Agents.AI.Workflows.Checkpointing;
|
|
using Microsoft.Shared.Diagnostics;
|
|
|
|
namespace Microsoft.Agents.AI.Workflows.Execution;
|
|
|
|
internal sealed class AsyncRunHandle : ICheckpointingHandle, IAsyncDisposable, IInputCoordinator
|
|
{
|
|
private readonly AsyncCoordinator _waitForResponseCoordinator = new();
|
|
private readonly ISuperStepRunner _stepRunner;
|
|
private readonly ICheckpointingHandle _checkpointingHandle;
|
|
|
|
private readonly LockstepRunEventStream _eventStream;
|
|
private readonly CancellationTokenSource _endRunSource = new();
|
|
private int _isDisposed;
|
|
private int _isEventStreamTaken;
|
|
|
|
internal AsyncRunHandle(ISuperStepRunner stepRunner, ICheckpointingHandle checkpointingHandle, ExecutionMode mode)
|
|
{
|
|
this._stepRunner = Throw.IfNull(stepRunner);
|
|
this._checkpointingHandle = Throw.IfNull(checkpointingHandle);
|
|
|
|
this._eventStream = mode switch
|
|
{
|
|
//ExecutionMode.OffThread => Not supported yet
|
|
ExecutionMode.Lockstep => new LockstepRunEventStream(stepRunner),
|
|
_ => throw new ArgumentOutOfRangeException(nameof(mode), $"Unknown execution mode {mode}")
|
|
};
|
|
this._eventStream.Start();
|
|
}
|
|
|
|
public ValueTask<bool> WaitForNextInputAsync(CancellationToken cancellation = default)
|
|
=> this._waitForResponseCoordinator.WaitForCoordinationAsync(cancellation);
|
|
|
|
public void ReleaseResponseWaiter() => this._waitForResponseCoordinator.MarkCoordinationPoint();
|
|
|
|
public string RunId => this._stepRunner.RunId;
|
|
|
|
public IReadOnlyList<CheckpointInfo> Checkpoints => this._checkpointingHandle.Checkpoints;
|
|
|
|
public ValueTask<RunStatus> GetStatusAsync(CancellationToken cancellation = default)
|
|
=> this._eventStream.GetStatusAsync(cancellation);
|
|
|
|
public async IAsyncEnumerable<WorkflowEvent> TakeEventStreamAsync(bool breakOnHalt, [EnumeratorCancellation] CancellationToken cancellation = default)
|
|
{
|
|
// Create a linked cancellation token that combines the provided token with the end-run token
|
|
using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellation, this._endRunSource.Token);
|
|
|
|
// Only one enumerator of this is allowed at a time
|
|
if (Interlocked.CompareExchange(ref this._isEventStreamTaken, 1, 0) != 0)
|
|
{
|
|
throw new InvalidOperationException("The event stream has already been taken. Only one enumerator is allowed at a time.");
|
|
}
|
|
|
|
try
|
|
{
|
|
await foreach (WorkflowEvent @event in this._eventStream.TakeEventStreamAsync(linkedSource.Token)
|
|
.ConfigureAwait(false))
|
|
{
|
|
yield return @event;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
Volatile.Write(ref this._isEventStreamTaken, 0);
|
|
}
|
|
}
|
|
|
|
public ValueTask<bool> IsValidInputTypeAsync<T>(CancellationToken cancellation = default)
|
|
=> this._stepRunner.IsValidInputTypeAsync<T>(cancellation);
|
|
|
|
public async ValueTask<bool> EnqueueMessageAsync<T>(T message, CancellationToken cancellation = default)
|
|
{
|
|
if (message is ExternalResponse response)
|
|
{
|
|
// EnqueueResponseAsync marks the coordination point itself
|
|
await this.EnqueueResponseAsync(response, cancellation)
|
|
.ConfigureAwait(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool result = await this._stepRunner.EnqueueMessageAsync(message, cancellation)
|
|
.ConfigureAwait(false);
|
|
|
|
this._waitForResponseCoordinator.MarkCoordinationPoint();
|
|
|
|
return result;
|
|
}
|
|
|
|
public async ValueTask<bool> EnqueueMessageUntypedAsync([NotNull] object message, Type? declaredType = null, CancellationToken cancellation = default)
|
|
{
|
|
if (declaredType?.IsInstanceOfType(message) == false)
|
|
{
|
|
throw new ArgumentException($"Message is not of the declared type {declaredType}. Actual type: {message.GetType()}", nameof(message));
|
|
}
|
|
|
|
if (declaredType != null && typeof(ExternalResponse).IsAssignableFrom(declaredType))
|
|
{
|
|
// EnqueueResponseAsync marks the coordination point itself
|
|
await this.EnqueueResponseAsync((ExternalResponse)message, cancellation)
|
|
.ConfigureAwait(false);
|
|
|
|
return true;
|
|
}
|
|
else if (declaredType == null && message is ExternalResponse response)
|
|
{
|
|
// EnqueueResponseAsync marks the coordination point itself
|
|
await this.EnqueueResponseAsync(response, cancellation)
|
|
.ConfigureAwait(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool result = await this._stepRunner.EnqueueMessageUntypedAsync(message, declaredType ?? message.GetType(), cancellation)
|
|
.ConfigureAwait(false);
|
|
|
|
this._waitForResponseCoordinator.MarkCoordinationPoint();
|
|
|
|
return result;
|
|
}
|
|
|
|
public async ValueTask EnqueueResponseAsync(ExternalResponse response, CancellationToken cancellation = default)
|
|
{
|
|
await this._stepRunner.EnqueueResponseAsync(response, cancellation).ConfigureAwait(false);
|
|
|
|
this._waitForResponseCoordinator.MarkCoordinationPoint();
|
|
}
|
|
|
|
public ValueTask RequestEndRunAsync()
|
|
{
|
|
this._endRunSource.Cancel();
|
|
return this._stepRunner.RequestEndRunAsync();
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (Interlocked.Exchange(ref this._isDisposed, 1) == 0)
|
|
{
|
|
this._endRunSource.Cancel();
|
|
await this.RequestEndRunAsync().ConfigureAwait(false);
|
|
this._endRunSource.Dispose();
|
|
|
|
await this._eventStream.DisposeAsync().ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
public ValueTask RestoreCheckpointAsync(CheckpointInfo checkpointInfo, CancellationToken cancellationToken = default)
|
|
=> this._checkpointingHandle.RestoreCheckpointAsync(checkpointInfo, cancellationToken);
|
|
}
|