-
Notifications
You must be signed in to change notification settings - Fork 787
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Large Payload Unary Calls: Unable to clean up after use #1267
Comments
Is this measuring memory usage on the server? I suspect what you're seeing is the Kestrel memory pool increasing in size and then never decreasing. |
Kestrel's nonshrinking memory pool (dotnet/aspnetcore#27394) is a possible culprit if we're looking at the server. But looking at the repro, it claims that it's intended to "investigate client memory usage", and the client project doesn't use Kestrel. @efrainsteinbach Did you look types of the objects in gen 2? Or how they're referenced? |
I think I'm right. static async Task Main(object[] args)
{
await RunThingsInScope();
Console.ReadAnyKey();
}
static async Task RunThingsInScope()
{
var result = await client.DoGrpcCallThatReturnsHugeResult();
} Adding static async Task RunThingsInScope()
{
var result = await client.DoGrpcCallThatReturnsHugeResult();
await Task.Yield();
} I'm guessing the task returned by After: |
@stephentoub Hi Stephen, is the behavior described in the comment above expected? I searched for runtime issues and found dotnet/runtime#11189 which could be related, but you added a fix for it. |
@JamesNK |
Hmm that issue was fixed. I'd be curious to see the repro |
When the RunThingsInScope operation completes, its compiler-generated MoveNext method is calling builder.SetResult(result), which is invoking the continuation synchronously, so the stack frame for MoveNext is still on the stack, which means any locals it has are still on the stack. The compiler generated MoveNext has code something like this after an await: TaskAwaiter<TResult> awaiter = <>u__1;
<>u__1 = default;
... = awaiter.GetResult(); I believe what we're seeing is the JIT/GC considering that last |
I know the compiler nulls out fields on exit but things may be stuck in registers (I was debugging this recently). At least this confirms my suspicions. Async unwinding is a bit mind bending 🙃 |
We're using gRPC to transfer large messages over the network, around 150MB each. I've noticed that in the client processes this tends to generate quite large, lasting memory footprints. This matters to us, because we run this on a k8s cluster: So I want to make sure the memory usage after receiving and digesting such a message is minimal. These messages are only exchanged once a day (at night), so we prefer lower memory consumption over speed.
I wrote a small test solution based on the Greeter app that illustrates this: https://github.com/efrainsteinbach/grpc-client-memory-test
It's simply requesting five messages sequentially which should be around ~200MB in size (uncompressed). After the five requests are through, the Gen2 Heap allocation stays at 400MB, even though I'm doing my best to dispose of any references I may still have to any gRPC components:
To me it looks as if some singleton or static components of Grpc.Net.Client or Grpc.Core are somehow holding to the last received message. (?)
Am I doing something wrong? Is there a way to clean this up?
(I tried explicitly shutting down and disposing the channel already)
The text was updated successfully, but these errors were encountered: