.NET 6 长时间繁忙等待 IO 操作在进行内存转储时立即完成

.NET 6 long busy wait on IO operations instantly completes when memory dump is taken

提问人:bearwithme 提问时间:8/21/2023 更新时间:8/21/2023 访问量:36

问:

该程序使用 Faster 库来存储一些 kv 并溢出到磁盘。

代码尝试等待 Faster 在写入所有超时条目后完成任何挂起的操作。它调用 CompletePending 并使用 Thread.Yield() 忙碌等待,直到 CompletePending() 返回 true。

bool done = false;
while(!done) {
    done = this.fasterSession.CompletePending(wait: false); // this tells Faster to complete all pending operations
    if (timeElapsed > timeout) {
       // .. throw timeout exception
    }
    Thread.Yield();
}

这在框架 471 上运行良好,通常在几秒钟内完成(最多在一分钟内)。但是,在 NET 6 上,这开始需要超过 5 分钟才能完成。

完全删除超时和产量解决了这个问题,但是在代码的后面有

this.faster.log.DisposeFromMemory(); // internally busy waits with Thread.Yield()

过去最多也需要几秒钟,但现在大约需要 10-20 分钟才能完成。这两者唯一的共同点是它们都调用了 Thread.Yield()。

计算机上的 IO 指标看起来不错。该问题始终如一地重现。

内存转储显示的最奇怪的事情是,操作(CompletePending 或 DisposeFromMemory)在进行内存转储后立即完成。据我了解,进行内存转储会暂停所有线程,然后恢复它们。

内存转储本身没有任何有趣的东西显示。!clrstack 仅显示正在等待的 Thread.Yield() 或其他父线程。其中一个 IOCP 线程上存在 ObjectDisposedException。堆栈跟踪不在我们的代码中,似乎涉及一些 SslStream。

0:058> !PrintException /d 000001e8488d1d10
Exception object: 000001e8488d1d10
Exception type:   System.ObjectDisposedException
Message:          Cannot access a disposed object.
InnerException:   <none>
StackTrace (generated):
SP               IP               Function
    000000A6B3A7EED0 00007FF8AE0EEB25 System_Net_Security!System.Net.Security.SslStream.<ThrowIfExceptional>g__ThrowExceptional|138_0(System.Runtime.ExceptionServices.ExceptionDispatchInfo)+0x55
    000000A6B3A7EF00 00007FF625647910 System_Net_Security!System.Net.Security.SslStream.DecryptData(Int32)+0x130
    000000A6B3A7EFE0 00007FF62560EFCC System_Net_Security!System.Net.Security.SslStream+<ReadAsyncInternal>d__188`1[[System.Net.Security.AsyncReadWriteAdapter, System.Net.Security]].MoveNext()+0x66c

!dlk没有任何值得注意的内容。 显示线程运行产量需要 5 分钟,这或多或少是预期的。!runaway

在这个阶段,我尝试了不同的配置,似乎 Thread.Yield() 似乎是罪魁祸首。更改它将导致操作在一分钟内完成。 或使用不起作用。将 Faster 库升级到最新版本也无济于事。我正在努力理解为什么 Thread.Yield() 会导致延迟,以及为什么进行内存转储可以解决它。Thread.Sleep(100)Thread.Sleep(0)SpinWait

C# .NET IO NET-6.0 IOCP

评论

1赞 JonasH 8/21/2023
当你大概在等待方法完成时,你为什么要使用?我会担心使用 ,因为它应该将线程标记为准备好执行而不会有任何延迟,在某些情况下可能会使优先级较低的线程挨饿。等待 waitHandle 或类似的东西应该更可靠。wait: falseThread.Yield
0赞 bearwithme 8/22/2023
虽然我可以删除 CompletePending 的 Yield,但我不能对内部使用 yield 的 DisposeFromMemory 执行相同的操作。如果线程匮乏是原因,那么转储中是否有任何可以确认它的内容?
0赞 JonasH 8/22/2023
但是你为什么不打电话.我假设这会阻止线程,直到所有操作完成。这不正是你想要的吗?CompletePending(wait: true)

答: 暂无答案