public static ListenerTask Create(ListenerContext context, ConnectionState state, Func <Task> start, Func <ConnectionState> continuation) { Func <Task <object> > func = async() => { await start().ConfigureAwait(false); return(null); }; return(new ListenerTask <object> (context, state, func, r => continuation())); }
internal void AssignContext(ListenerContext context) { hasInstrumentation = true; AssignedContext = context; context.AssignContext(this); contextTask.TrySetResult(context); }
protected override void Close() { if (targetContext != null) { targetContext.Dispose(); targetContext = null; } base.Close(); }
internal async Task <HttpResponse> HandleRequest( TestContext ctx, ListenerContext context, HttpConnection connection, HttpRequest request, CancellationToken cancellationToken) { var me = $"{ME} HANDLE REQUEST"; ctx.LogDebug(2, $"{me} {connection.ME} {request}"); OnInit(); HttpResponse response; try { cancellationToken.ThrowIfCancellationRequested(); if (!Operation.HasAnyFlags(HttpOperationFlags.DontReadRequestBody)) { await request.Read(ctx, cancellationToken).ConfigureAwait(false); ctx.LogDebug(2, $"{me} REQUEST FULLY READ"); } else { await request.ReadHeaders(ctx, cancellationToken).ConfigureAwait(false); ctx.LogDebug(2, $"{me} REQUEST HEADERS READ"); } response = await Handler.HandleRequest( ctx, Operation, connection, request, cancellationToken).ConfigureAwait(false); ctx.LogDebug(2, $"{me} HANDLE REQUEST DONE: {response}"); } catch (OperationCanceledException) { OnCanceled(); throw; } catch (Exception ex) { OnError(ex); throw; } if (response.Redirect == null) { OnFinished(); return(response); } response.Redirect.parentOperation = this; return(response); }
internal ListenerOperation Redirect(ListenerContext newContext) { if (State == ConnectionState.NeedContextForRedirect) { redirectContext = newContext; State = ConnectionState.RequestComplete; return(currentResponse.Redirect); } if (State == ConnectionState.CannotReuseConnection) { State = ConnectionState.Closed; return(assignedOperation); } throw new InvalidOperationException(); }
public ListenerContext ReuseConnection() { disposed = true; var oldConnection = Interlocked.Exchange(ref connection, null); if (oldConnection == null) { throw new InvalidOperationException(); } var newContext = new ListenerContext(Listener, oldConnection, true) { State = Listener.UsingInstrumentation ? ConnectionState.WaitingForContext : ConnectionState.WaitingForRequest }; assignedOperation = null; currentOperation = null; State = ConnectionState.Closed; return(newContext); }
internal ListenerOperation GetOperation(ListenerContext context, HttpRequest request) { ListenerOperation operation; lock (this) { var me = $"{nameof (GetOperation)}({context.Connection.ME})"; Debug($"{me} {request.Method} {request.Path} {request.Protocol}"); if (!registry.ContainsKey(request.Path)) { Debug($"{me} INVALID PATH: {request.Path}!"); return(null); } operation = registry[request.Path]; registry.Remove(request.Path); Server.BumpRequestCount(); } ParentListener?.UnregisterOperation(operation); return(operation); }
public async Task <Response> RunWithContext(TestContext ctx, ListenerOperation operation, Request request, ClientFunc clientFunc, CancellationToken cancellationToken) { var me = $"{ME}({operation.Operation.ME}) RUN WITH CONTEXT"; ListenerContext context = null; ListenerContext targetContext = null; var reusing = !operation.Operation.HasAnyFlags(HttpOperationFlags.DontReuseConnection); var delayedContext = operation.Operation.HasAnyFlags(HttpOperationFlags.DelayedListenerContext); /* * When using HttpOperationFlags.DelayedListenerContext, then we call the `clientFunc` before we * actually start listening. */ if (UsingInstrumentation && !delayedContext) { context = await FindContext(ctx, operation, reusing); reusing = context.ReusingConnection; ctx.LogDebug(2, $"{me} - CREATE CONTEXT: {reusing} {context.ME}"); await context.ServerStartTask.ConfigureAwait(false); ctx.LogDebug(2, $"{me} - CREATE CONTEXT #1: {reusing} {context.ME}"); } if (TargetListener?.UsingInstrumentation ?? false) { targetContext = await TargetListener.FindContext(ctx, operation.TargetOperation, false); ctx.LogDebug(2, $"{me} - CREATE TARGET CONTEXT: {reusing} {targetContext.ME}"); try { await targetContext.ServerStartTask.ConfigureAwait(false); } catch { context?.Dispose(); throw; } } var clientTask = clientFunc(ctx, request, cancellationToken); var serverInitTask = operation.ServerInitTask; var serverFinishedTask = operation.ServerFinishedTask; ExceptionDispatchInfo throwMe = null; bool initDone = false, serverDone = false, clientDone = false; while (!initDone || !serverDone || !clientDone) { ctx.LogDebug(2, $"{me} LOOP: init={initDone} server={serverDone} client={clientDone}"); if (clientDone) { if (operation.Operation.HasAnyFlags( HttpOperationFlags.AbortAfterClientExits, HttpOperationFlags.ServerAbortsHandshake, HttpOperationFlags.ClientAbortsHandshake)) { ctx.LogDebug(2, $"{me} - ABORTING"); break; } if (!initDone) { ctx.LogDebug(2, $"{me} - ERROR: {clientTask.Result}"); throwMe = ExceptionDispatchInfo.Capture(new ConnectionException( $"{me} client exited before server accepted connection.")); break; } } var tasks = new List <Task> (); if (!initDone) { tasks.Add(serverInitTask); } if (!serverDone) { tasks.Add(serverFinishedTask); } if (!clientDone) { tasks.Add(clientTask); } var finished = await Task.WhenAny(tasks).ConfigureAwait(false); string which; if (finished == serverInitTask) { which = "init"; initDone = true; } else if (finished == serverFinishedTask) { which = "server"; serverDone = true; } else if (finished == clientTask) { which = "client"; clientDone = true; } else { throwMe = ExceptionDispatchInfo.Capture(new InvalidOperationException()); break; } ctx.LogDebug(2, $"{me} #4: {which} exited - {finished.Status}"); if (finished.Status == TaskStatus.Faulted || finished.Status == TaskStatus.Canceled) { if (operation.Operation.HasAnyFlags(HttpOperationFlags.ExpectServerException) && (finished == serverFinishedTask || finished == serverInitTask)) { ctx.LogDebug(2, $"{me} EXPECTED EXCEPTION {finished.Exception.GetType ()}"); } else if (finished.Status == TaskStatus.Canceled) { ctx.LogDebug(2, $"{me} CANCELED"); throwMe = ExceptionDispatchInfo.Capture(new OperationCanceledException()); break; } else { ctx.LogDebug(2, $"{me} FAILED: {finished.Exception.Message}"); throwMe = ExceptionDispatchInfo.Capture(finished.Exception); break; } } } if (throwMe != null) { ctx.LogDebug(2, $"{me} THROWING {throwMe.SourceException.Message}"); lock (this) { operation.OnError(throwMe.SourceException); if (context != null) { context.Dispose(); } if (targetContext != null) { targetContext.Dispose(); } mainLoopEvent.Set(); } throwMe.Throw(); } return(clientTask.Result); }
void RunScheduler() { do { Cleanup(); if (!UsingInstrumentation) { CreateConnections(); } } while (!StartTasks()); void Cleanup() { var iter = connections.First; while (iter != null) { var node = iter; var context = node.Value; iter = iter.Next; if (context.State == ConnectionState.Closed) { connections.Remove(node); context.Dispose(); } else if (context.State == ConnectionState.ReuseConnection) { connections.Remove(node); var newContext = context.ReuseConnection(); connections.AddLast(newContext); } } } void CreateConnections() { while (connections.Count < requestParallelConnections) { Debug($"RUN SCHEDULER: {connections.Count}"); var connection = Backend.CreateConnection(); connections.AddLast(new ListenerContext(this, connection, false)); Debug($"RUN SCHEDULER #1: {connection.ME}"); } } bool StartTasks() { var listening = false; ListenerContext listeningContext = null; ListenerContext redirectContext = null; ListenerContext idleContext = null; var me = $"RUN SCHEDULER - TASKS"; Debug($"{me}"); var idx = 0; var iter = connections.First; while (iter != null) { var node = iter; var context = node.Value; iter = iter.Next; Debug($"{me} ({++idx}/{connections.Count}): {context.State} {context.CurrentTask != null} {context.ME}"); if (!listening && context.State == ConnectionState.Listening) { listening = true; } if (false && UsingInstrumentation && listening && context.State == ConnectionState.Listening) { continue; } if (context.CurrentTask != null) { continue; } switch (context.State) { case ConnectionState.Idle: case ConnectionState.WaitingForContext: if (!UsingInstrumentation) { throw new InvalidOperationException(); } if (idleContext == null && listeningContext == null) { idleContext = context; } continue; case ConnectionState.NeedContextForRedirect: case ConnectionState.CannotReuseConnection: if (redirectContext == null) { redirectContext = context; } continue; case ConnectionState.Listening: if (idleContext == null && listeningContext == null) { listeningContext = context; } break; } var task = context.MainLoopListenerTask(TestContext, cts.Token); listenerTasks.AddLast(task); if (false && context.State == ConnectionState.Listening && !listening) { listeningContext = context; listening = true; } } Debug($"{me} DONE: instrumentation={currentOperation?.ID} listening={listening} " + $"redirect={redirectContext != null} idleContext={idleContext != null} " + $"listeningContext={listeningContext != null} " + $"assigned={currentOperation?.AssignedContext != null}"); var availableContext = idleContext ?? listeningContext; if (currentOperation != null) { if (currentOperation.AssignedContext != null) { return(true); } var operation = currentOperation.Operation; var forceNewCnc = operation.HasAnyFlags(HttpOperationFlags.ForceNewConnection); if (listening && !forceNewCnc) { return(true); } if (availableContext == null || forceNewCnc) { var connection = Backend.CreateConnection(); availableContext = new ListenerContext(this, connection, false); connections.AddLast(availableContext); } Debug($"{me} ASSIGN CONTEXT: {availableContext.ME} {currentOperation.ME}"); currentOperation.AssignContext(availableContext); return(false); } if (redirectContext == null) { return(true); } if (availableContext == null) { var connection = Backend.CreateConnection(); availableContext = new ListenerContext(this, connection, false); connections.AddLast(availableContext); } currentOperation = redirectContext.Redirect(availableContext); currentOperation.AssignContext(availableContext); Debug($"{me} DONE #1: {availableContext.ME} {currentOperation?.ME}"); return(false); } }
public static ListenerTask Create <T, U> (ListenerContext context, ConnectionState state, Func <Task <(T, U)> > start, Func <T, U, ConnectionState> continuation)
public static ListenerTask Create <T> (ListenerContext context, ConnectionState state, Func <Task <T> > start, Func <T, ConnectionState> continuation) { return(new ListenerTask <T> (context, state, start, continuation)); }