ASP.NET Core middleware with unbounded output - asp.net-core

I have a piece of ASP.NET Core middleware that produces an unbounded amount of data when it takes over processing a request. It goes into a loop that calls await context.Response.Body.WriteAsync as long as it is allowed to. If the caller stays connected, it expects to keep receiving data. This service is being hosted with Kestrel, and it seems to be working properly as described so far. But, what I am finding is that when the caller disconnects, Kestrel doesn't seem to notice and continues pumping output from the middleware. That output isn't going anywhere, because the memory usage of the process isn't going up, and at the same time netstat -an doesn't show the connection any more. But, the middleware just keeps on chugging away.
For typical HTTP requests, this wouldn't be a terribly serious issue, because most of the time the client doesn't disconnect when it has only read part of the request, and in those cases where it does, the response is finite in size anyway. But the pattern with this endpoint is that the data is conceptually infinite in length, and the caller stays connected for as long as it wants and then signals that it no longer wants further data by disconnecting.
These images illustrate the problem:
https://imgur.com/a/9Qp7VV3
How can I make it so that the middleware notices when the client disconnects?

I also posted this as an issue on the aspnetcore GitHub repo, and I got a reply that explained the problem and provided a solution:
https://github.com/dotnet/aspnetcore/issues/22156
Basically, for better or worse, the ASP.NET Core infrastructure suppresses all errors from write operations, and so if the request has been aborted, calling context.Response.Body.WriteAsync fails silently. I personally think there's a mistake in there somewhere, but the rationale given is that this reduces exception/log spam from behaviours over which the server has no control.
Because of this, if you're writing a loop like mine, you have to explicitly check for the request having been aborted. The context provides a CancellationToken that is used to capture aborts. You can also use this CancellationToken on other actions the handler is doing that aren't within the scope of the request context.
My data pump looks like this now:
while (true)
{
int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, context.RequestAborted);
if (bytesRead < 0)
throw new Exception("I/O error");
if (bytesRead == 0)
break;
await context.Response.Body.WriteAsync(buffer, 0, bytesRead);
if (context.RequestAborted.IsCancellationRequested)
break;
_statusConsoleSender.NotifyRequestProgress(requestID, bytesRead);
}

Related

Can I send an API response before successful persistence of data?

I am currently developing a Microservice that is interacting with other microservices.
The problem now is that those interactions are really time-consuming. I already implemented concurrent calls via Uni and uses caching where useful. Now I still have some calls that still need some seconds in order to respond and now I thought of another thing, which I could do, in order to improve the performance:
Is it possible to send a response before the sucessfull persistence of data? I send requests to the other microservices where they have to persist the results of my methods. Can I already send the user the result in a first response and make a second response if the persistence process was sucessfull?
With that, the front-end could already begin working even though my API is not 100% finished.
I saw that there is a possible status-code 207 but it's rather used with streams where someone wants to split large files. Is there another possibility? Thanks in advance.
"Is it possible to send a response before the sucessfull persistence of data? Can I already send the user the result in a first response and make a second response if the persistence process was sucessfull? With that, the front-end could already begin working even though my API is not 100% finished."
You can and should, but it is a philosophy change in your API and possibly you have to consider some edge cases and techniques to deal with them.
In case of a long running API call, you can issue an "ack" response, a traditional 200 one, only the answer would just mean the operation is asynchronous and will complete in the future, something like { id:49584958, apicall:"create", status:"queued", result:true }
Then you can
poll your API with the returned ID to see if the operation that is still ongoing, has succeeded or failed.
have a SSE channel (realtime server side events) where your server can issue status messages as pending operations finish
maybe using persistent connections and keepalives, or flushing the response in the middle, you can achieve what you point out, ie. like a segmented response. I am not familiar with that approach as I normally go for the suggesions above.
But in any case, edge cases apply exactly the same: For example, what happens if then through your API a user issues calls dependent on the success of an ongoing or not even started previous command? like for example, get information about something still being persisted?
You will have to deal with these situations with mechanisms like:
Reject related operations until pending call is resolved "server side": Api could return ie. a BUSY error informing that operations are still ongoing when you want to, for example, delete something that still is being created.
Queue all operations so the server executes all them sequentially.
Allow some simulatenous operations if you find they will not collide (ie. create 2 unrelated items)

Is it allowed to use Task.Run in an ASP.NET Core controller?

Scenario: I have a web service with a "Delete" ASP.NET Core controller action. The implementation consists of two steps: one cheap after which other operations can already no longer see the deleted data, and a long-running second step which performs the actual deletion.
Is it okay to use Task.Run for the second operation and never await it and so return to the caller very soon? I know that this task could be lost due to application shutdown, but is it even allowed to offload work to the background using Task.Run in ASP.NET Core and never await the resulting task?
I know that this task could be lost due to application shutdown, but is it even allowed to offload work to the background using Task.Run in ASP.NET Core and never await the resulting task?
Oh, sure, nothing will prevent this. .NET and ASP.NET allow you to use Task.Run, and the response will be returned to the user without delay (assuming you ignore the task returned from Task.Run).
Is it okay to use Task.Run for the second operation and never await it and so return to the caller very soon?
You say that you're aware that the task could be lost in an app shutdown. And that's true; so it is entirely possible that the second deletion may never happen, and there will be no error or log message informing you of the fact. Furthermore, since the task is ignored, there will be no notification if that task fails, so if there's something wrong with the deletion code, no logs or anything will notify you of regular failures. If you're OK with that, then using fire-and-forget is fine.
On ASP.NET Core, you don't even need Task.Run; you can just call the (asynchronous) method that does the second delete operation and then ignore the task instead of awaiting it.
Simply ignoring tasks will work (with or without Task.Run), but this does have the negative side effect that ASP.NET isn't aware of the offloaded work at all. So I would recommend registering the task and having something delay the app shutdown until the tasks have a chance to complete. With registration, it is still possible to lose tasks, but registering them reduces the chance of losing tasks.

WCF client causes server to hang until connection fault

The below text is an effort to expand and add color to this question:
How do I prevent a misbehaving client from taking down the entire service?
I have essentially this scenario: a WCF service is up and running with a client callback having a straight forward, simple oneway communication, not very different from this one:
public interface IMyClientContract
{
[OperationContract(IsOneWay = true)]
void SomethingChanged(simpleObject myObj);
}
I'm calling this method potentially thousands of times a second from the service to what will eventually be about 50 concurrently connected clients, with as low latency as possible (<15 ms would be nice). This works fine until I set a break point on one of the client apps connected to the server and then everything hangs after maybe 2-5 seconds the service hangs and none of the other clients receive any data for about 30 seconds or so until the service registers a connection fault event and disconnects the offending client. After this all the other clients continue on their merry way receiving messages.
I've done research on serviceThrottling, concurrency tweaking, setting threadpool minimum threads, WCF secret sauces and the whole 9 yards, but at the end of the day this article MSDN - WCF essentials, One-Way Calls, Callbacks and Events describes exactly the issue I'm having without really making a recommendation.
The third solution that allows the service to safely call back to the client is to have the callback contract operations configured as one-way operations. Doing so enables the service to call back even when concurrency is set to single-threaded, because there will not be any reply message to contend for the lock.
but earlier in the article it describes the issue I'm seeing, only from a client perspective
When one-way calls reach the service, they may not be dispatched all at once and may be queued up on the service side to be dispatched one at a time, all according to the service configured concurrency mode behavior and session mode. How many messages (whether one-way or request-reply) the service is willing to queue up is a product of the configured channel and the reliability mode. If the number of queued messages has exceeded the queue's capacity, then the client will block, even when issuing a one-way call
I can only assume that the reverse is true, the number of queued messages to the client has exceeded the queue capacity and the threadpool is now filled with threads attempting to call this client that are now all blocked.
What is the right way to handle this? Should I research a way to check how many messages are queued at the service communication layer per client and abort their connections after a certain limit is reached?
It almost seems that if the WCF service itself is blocking on a queue filling up then all the async / oneway / fire-and-forget strategies I could ever implement inside the service will still get blocked whenever one client's queue gets full.
Don't know much about the client callbacks, but it sounds similar to generic wcf code blocking issues. I often solve these problems by spawning a BackgroundWorker, and performing the client call in the thread. During that time, the main thread counts how long the child thread is taking. If the child has not finished in a few milliseconds, the main thread just moves on and abandons the thread (it eventually dies by itself, so no memory leak). This is basically what Mr.Graves suggests with the phrase "fire-and-forget".
Update:
I implemented a Fire-and-forget setup to call the client's callback channel and the server no longer blocks once the buffer fills to the client
MyEvent is an event with a delegate that matches one of the methods defined in the WCF client contract, when they connect I'm essentially adding the callback to the event
MyEvent += OperationContext.Current.GetCallbackChannel<IFancyClientContract>().SomethingChanged
etc... and then to send this data to all clients, I'm doing the following
//serialize using protobuff
using (var ms = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(ms, new SpecialDataTransferObject(inputData));
byte[] data = ms.GetBuffer();
Parallel.ForEach(MyEvent.GetInvocationList(), p => ThreadUtil.FireAndForget(p, data));
}
in the ThreadUtil class I made essentially the following change to the code defined in the fire-and-foget article
static void InvokeWrappedDelegate(Delegate d, object[] args)
{
try
{
d.DynamicInvoke(args);
}
catch (Exception ex)
{
//THIS will eventually throw once the client's WCF callback channel has filled up and timed out, and it will throw once for every single time you ever tried sending them a payload, so do some smarter logging here!!
Console.WriteLine("Error calling client, attempting to disconnect.");
try
{
MyService.SingletonServiceController.TerminateClientChannelByHashcode(d.Target.GetHashCode());//this is an IContextChannel object, kept in a dictionary of active connections, cross referenced by hashcode just for this exact occasion
}
catch (Exception ex2)
{
Console.WriteLine("Attempt to disconnect client failed: " + ex2.ToString());
}
}
}
I don't have any good ideas how to go and kill all the pending packets the server is still waiting to see if they'll get delivered on. Once I get the first exception I should in theory be able to go and terminate all the other requests in some queue somewhere, but this setup is functional and meets the objectives.

Nservicebus synchronous call

I am using request reply model of NServiceBUs for one of my project.There is a self hosted service bus listening for a request message and reply back with a request message.
So in WCF message code i have coded like
// sent the message to bus.
var synchronousMessageSent =
this._bus.Send(destinationQueueName, requestMessage)
.Register(
(AsyncCallback)delegate(IAsyncResult ar)
{
// process the response from the message.
NServiceBus.CompletionResult completionResult = ar.AsyncState as NServiceBus.CompletionResult;
if (completionResult != null)
{
// set the response messages.
response = completionResult.Messages;
}
},
null);
// block the current thread.
synchronousMessageSent.AsyncWaitHandle.WaitOne(1000);
return response;
The destinaton que will sent the reply.
I am getting the resault one or tweo times afetr that the reply is not coming to the client. Am i doing anything wrong
Why are you trying to turn an a-synchronous framework into a synchronous one? There is a fundamental flaw with what you are trying to do.
You should take a long hard look at your design and appreciate the benefits of a-sync calls. The fact that you are doing
// block the current thread.
synchronousMessageSent.AsyncWaitHandle.WaitOne(1000);
Is highly concerning. What are you trying to achieve with this? Design your system based on a-synchronous messaging communication and you will have a MUCH better system. Otherwise you might as well just use some kind of blocking tcp/ip sockets.

WCF: DuplexSessionChannel, Asynchronous Operations and an Exception

Today I have a WCF question, though it probably pertains to other Networking models in .NET as well.
I have a WCF service that exposes a Send(Message) OperationContract, which is OneWay = true. Now this service has a callback channel to return Messages to the client.
Anyway I am trying (successfully) to call this Send method from my Client asynchronously. On a DuplexSessionChannel I am calling BeginSend(Message, OnSendComplete, null) and I have an OnSendComplete(IAsyncResult) method that calls EndSend(asyncResult) on the DuplexSessionChannel.
The service has a CallbackContract and uses the same BeginSend()/EndSend() pattern for sending back to the client, which is called on the callBack channel I get with OperationContext.Current.GetCallbackChannel.
The client on its DuplexSessionChannel calls BeginReceive()/EndReceive() when receiving messages back from the Services callback channel.
Even though things are working, I dont understand what the End<Operation>() methods actually do and this is what I need to have explained to me.
I ask because I am getting an occasional exception in a call to EndSend() on the Service (sending back to the client) complaining that a collection has been modified (I know what this exception means, but not why it is happening or where exactly...). I am using PollingDuplexHttpBinding with a Silverlight client.
I am not a WCF expert, but don't hold back on the details, I need the knowledge. I have seen these sort of Begin/End patterns before around other async operations during my career thus far but never really understood what was going on.
Thanks in advance.
It sounds like your question is just about the Begin/End APM (async programming model). Briefly, the APM takes a sync method like
R Foo(A a); // R is some result type, A is some argument type
and breaks it into async BeginFoo and EndFoo methods. The main advantage happens when the operation is doing some truly asynchronous system operation (e.g. talking to the network) that may be long-running (at least compared to other functions; e.g. talking to the network may take hundreds of milliseconds or more). This pattern gives you a way to tell the system to start the operation, and then call you back when the result of the operation is ready. The advantage to the pattern is you don't have to have a managed thread blocked while this call is pending (which means e.g. that you can have thousands of pending network reads/writes without needing thousands of threads, hurray, threads are expensive).
So given that, 'BeginFoo' is how you say 'start the method with these arguments', and then when you get called back (as notification that the result is ready), 'EndFoo' is how you get the result. In the general case, if 'Foo' might throw a particular exception, then this exception might come out of either the 'Begin' call or the 'End' call and you have to be prepared to handle it in both places.
In the case of something like Send() (which maybe returns void? I forget) it's a little annoying/weird because since it's one-way you kinda just want to 'fire-and-forget'. But exceptions can still happen (e.g. I tried to send but someone unplugged my network cable), and so this may yield exceptions... and given the Begin/End APM, such an exception might come out of the EndSend call. In effect, the exception is a kind of 'result' of calling Send, and so you calling EndSend provides a way for the system to throw an exception at you to say something went wrong after you called BeginSend.