KumoKyaku/kcp

关于UnsafeSegManager疑似错误分配内存的问题

Closed this issue · 4 comments

由于我上次来提issue的时候似乎没有做好工作,因此这里先叠个甲:
我自己的上层协议栈与Kcp内部(Recv,循环调用Update)都保证了正确的单线程调用,保证这部分没有出现问题。
SingleThreadAssert.cs.txt


我正在做一点协议栈的测试工作,但是观察到了在测试发包压力巨大(10ms一次发包)的时候出现了严重的丢包。更确切地说,是包可能出现中间内容异位,或是包被掐头去尾(比较形象的描述)。

下面是两张现象截图(图中监视的是rcv_queue):

屏幕截图 2023-06-09 223519

屏幕截图 2023-06-09 232508

经过亿点点调试工作,最终确认了大概是使用了UnsafeSegManager的问题。

image

观察这幅图高亮的部分。Send 所使用的 KcpSegment 与 Input 中的一个 Segment 竟然共享一个 ptr,我不知道这是否是预期之内的情况。
这个包在 Flush 出口的时候被 Debug.Assert 拦下了,还没有 Flush 出去,应该不存在可能内存释放掉重用了的情况。再放一张 NEED_SEND 的日志证明一下:

image

切换到 SimpleSegManager 后没有出现过类似问题。PoolSegManager 没有测试过,主要因为它换了一个 Segment 类型,暂时不想做更多调试了。

不好意思,最近比较忙,今天才看见。
我初步理解了你描述的问题。

观察这幅图高亮的部分。Send 所使用的 KcpSegment 与 Input 中的一个 Segment 竟然共享一个 ptr,我不知道这是否是预期之内的情况。

UnsafeSegManager约等于非托管内存的内存池。共享一个 ptr 绝对是非预期的。
多线程情况下,池出现问题的可能性还是非常大的。
但是没有测试环境,仅分析代码,没有办法想出哪里可能导致这种这种情况。
我自己的测试用例,没有复现这个bug。

如果你有时间,可以尝试在free函数加锁,看看bug会不会消失。
image

另外,如果不执着于GC alloc,PoolSegManager 使用dotnet内置的内存池,可靠性比我手写的内存管理要可靠的多。
PoolSegManager 也不见得会多分配多少内存。

非托管内存更多用于GC洁癖的用户。

刚刚看见,试着上 Github Actions 跑了几次测试。先进行了几次测试:

  1. SimpleSegManager
  2. 原版 UnSafeSegManager
  3. Free() 加锁的 UnSafeSegManager

测试的标准都是 5ms 一发包,重复 10000 次,包大小 3500 字节。Windows 平台上和 Ubuntu 平台上各跑一次。
从宏观上来看,Windows 下 1、3都完全没有出现问题,2 出现了丢包(或是错包)。Ubuntu 这里都没有出现问题,可能因为速度比较快减少了并发压力。

(好像是说本地网络通信都是直接内存拷贝,因此速度极快,项目同一份代码放 Windows 下平均延迟 22ms,放到 Ubuntu 下可以达到 4ms)

然后改了一下标准,10ms 一发包,但是包大小上调到了 15000 字节。重复上面的 2、3 试验。

加锁后 3 仍然没有出现问题。2 在 Ubuntu 下似乎没有问题,但是在 Windows 下出现了更为严重的坏包,程序记录的丢包有大约 80%-90%(在我提 issue 的时候测试出来异常确实会积累,但是更有可能是我自己的代码写炸了解包出异常没有处理)

image

现阶段比较忙,并没有把 merge 之前带一堆测试符号的代码拿回来跑(应该连改个包大小都不一定适配了),但是就 Actions 上跑的这些数据我认为改完应该就没有什么问题了。

多谢。下个版本我会在free上加锁。