using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using XFE.SeAgent.Plugin.Protocol;
namespace XFE.SeAgent.Plugin.Transport
{
internal sealed class PendingRequest
{
private readonly object gate = new object();
private readonly long deadline;
private readonly Func<bool> connectionAlive;
private readonly Func<bool> serverStopped;
private readonly TaskCompletionSource<JObject> completion = new TaskCompletionSource<JObject>(TaskCreationOptions.RunContinuationsAsynchronously);
private RequestState state;
internal AgentRequest Request { get; }
internal Task<JObject> Completion => completion.Task;
internal PendingRequest(AgentRequest request, Func<bool> connectionAlive, Func<bool> serverStopped)
{
Request = request;
this.connectionAlive = connectionAlive;
this.serverStopped = serverStopped;
deadline = Stopwatch.GetTimestamp() + (long)Math.Ceiling(request.TimeoutMs * (double)Stopwatch.Frequency / 1000);
}
// This is the execution boundary. Cancellation before this claim prevents dispatch;
// an already executing game operation cannot safely be interrupted by a pipe worker.
internal bool TryStart()
{
lock (gate)
{
if (state != RequestState.Waiting) return false;
if (serverStopped()) return CancelLocked("server_stopped", "Agent server has stopped.");
if (Stopwatch.GetTimestamp() >= deadline) return CancelLocked("timeout", "Request expired before execution.");
if (!connectionAlive()) return CancelLocked("disconnected", "Client disconnected before execution.");
if (serverStopped()) return CancelLocked("server_stopped", "Agent server has stopped.");
if (Stopwatch.GetTimestamp() >= deadline) return CancelLocked("timeout", "Request expired before execution.");
state = RequestState.Executing;
return true;
}
}
internal void Complete(JObject response)
{
lock (gate)
{
if (state != RequestState.Executing) return;
state = RequestState.Finished;
completion.TrySetResult(response);
}
}
internal void Cancel(string code, string message)
{
lock (gate)
{
if (state == RequestState.Finished || state == RequestState.Canceled) return;
if (state == RequestState.Executing && code == "timeout")
message = "Request timed out after execution began; the operation may have completed. Check state before retrying.";
CancelLocked(code, message);
}
}
private bool CancelLocked(string code, string message)
{
state = RequestState.Canceled;
completion.TrySetResult(AgentProtocol.Error(Request.Id, code, message));
return false;
}
private enum RequestState { Waiting, Executing, Finished, Canceled }
}
}
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using XFE.SeAgent.Plugin.Protocol;
namespace XFE.SeAgent.Plugin.Transport
{
internal sealed class PendingRequest
{
private readonly object gate = new object();
private readonly long deadline;
private readonly Func<bool> connectionAlive;
private readonly Func<bool> serverStopped;
private readonly TaskCompletionSource<JObject> completion = new TaskCompletionSource<JObject>(TaskCreationOptions.RunContinuationsAsynchronously);
private RequestState state;
internal AgentRequest Request { get; }
internal Task<JObject> Completion => completion.Task;
internal PendingRequest(AgentRequest request, Func<bool> connectionAlive, Func<bool> serverStopped)
{
Request = request;
this.connectionAlive = connectionAlive;
this.serverStopped = serverStopped;
deadline = Stopwatch.GetTimestamp() + (long)Math.Ceiling(request.TimeoutMs * (double)Stopwatch.Frequency / 1000);
}
// This is the execution boundary. Cancellation before this claim prevents dispatch;
// an already executing game operation cannot safely be interrupted by a pipe worker.
internal bool TryStart()
{
lock (gate)
{
if (state != RequestState.Waiting) return false;
if (serverStopped()) return CancelLocked("server_stopped", "Agent server has stopped.");
if (Stopwatch.GetTimestamp() >= deadline) return CancelLocked("timeout", "Request expired before execution.");
if (!connectionAlive()) return CancelLocked("disconnected", "Client disconnected before execution.");
if (serverStopped()) return CancelLocked("server_stopped", "Agent server has stopped.");
if (Stopwatch.GetTimestamp() >= deadline) return CancelLocked("timeout", "Request expired before execution.");
state = RequestState.Executing;
return true;
}
}
internal void Complete(JObject response)
{
lock (gate)
{
if (state != RequestState.Executing) return;
state = RequestState.Finished;
completion.TrySetResult(response);
}
}
internal void Cancel(string code, string message)
{
lock (gate)
{
if (state == RequestState.Finished || state == RequestState.Canceled) return;
if (state == RequestState.Executing && code == "timeout")
message = "Request timed out after execution began; the operation may have completed. Check state before retrying.";
CancelLocked(code, message);
}
}
private bool CancelLocked(string code, string message)
{
state = RequestState.Canceled;
completion.TrySetResult(AgentProtocol.Error(Request.Id, code, message));
return false;
}
private enum RequestState { Waiting, Executing, Finished, Canceled }
}
}