cuteant / SpanNetty

Port of Netty(v4.1.51.Final) for .NET
MIT License
301 stars 47 forks source link

Stackoverflow in PoolChunkList.Move() when `io.netty.allocator.maxOrder`=8 #29

Closed yyjdelete closed 3 years ago

yyjdelete commented 3 years ago

https://github.com/cuteant/SpanNetty/blob/34a24e358bf03178075cd90c7d7a319660232481/src/DotNetty.Buffers/PoolChunkList.cs#L77-L78

进一步调试发现这里的转换在java和C#版本中无法得到一致的结果,

    public static void main(String[] args){

        int pageSize = 8192;
        int maxOrder = 8;//11
        int chunkSize = pageSize << maxOrder;
        int maxUsage = Integer.MAX_VALUE;
        int minUsage = Integer.MIN_VALUE;
        //int minUsage = int.MinValue
        XXX(chunkSize, maxUsage);
        XXX(chunkSize, minUsage);
    }

    private static void XXX(int chunkSize, int usage)
    {
        //int freeThreshold = (usage == 100) ? 0 : (int)(uint)(chunkSize * (100.0d - usage + 0.99999999d) / 100L);//C# version
        int freeThreshold = (usage == 100) ? 0 : (int) (chunkSize * (100.0 - usage + 0.99999999) / 100L);//Java version
        System.out.println(freeThreshold);
        //Console.WriteLine(freeThreshold);
    }

对于q100和qInit的int.MaxValue和int.MinValue 在Java中分别得到-2147483648,2147483647(maxOrder=8或11) 在C#版本(仅在netcoreapp3.1/net5.0 x64下测试, 不同版本返回值可能存在差异)中分别得到 1032931247,-1028674028(maxOrder=8), -326484623,360542371(maxOrder=11), 如果不先转换为uint则均是-2147483648,-2147483648

对于PoolArena._qInit, minUsage始终为int.MinValue,_prevList为其自身. 因此在调用qInit.Free时会由于_freeMaxThreshold为负, 条件恒true(而非Java的恒false), 进入一条死循环的逻辑(_prevList.Move(), 但_prevList==this)从而出现堆栈溢出

对于q100, 是否会出现问题未测试发现. 但也可能会造成一些其他逻辑上的问题.

根据 https://github.com/dotnet/runtime/issues/461#issuecomment-560955673 , 如果当从浮点型转换为整型时出现溢出, 转换的结果是未定义的任何值(并且好像已知在x64和arm上不一致, 但找不到原文了) 这里可能需要将double的结果手动做溢出检查并转换为int.MaxValue,int.MinValue

cuteant commented 3 years ago

稍等两天,我再看,到时再交流

cuteant commented 3 years ago

@yyjdelete 晕菜,真的是这样,难道只能这样吗?

        private static void XXX(int chunkSize, int usage)
        {
            int freeThreshold;
            if (usage == 100)
            {
                freeThreshold = 0;
            }
            else
            {
                var tmp = chunkSize * (100.0d - usage + 0.99999999d) / 100L;
                if (tmp < int.MinValue)
                {
                    freeThreshold = int.MinValue;
                }
                else if (tmp > int.MaxValue)
                {
                    freeThreshold = int.MaxValue;
                }
                else
                {
                    freeThreshold = (int)(uint)tmp;
                }
            }
            //int freeThreshold = (usage == 100) ? 0 : (int)(uint)(chunkSize * (100.0d - usage + 0.99999999d) / 100L);

            Console.WriteLine(freeThreshold);
        }
yyjdelete commented 3 years ago

感觉可能是,不过还好,好像就这一个地方会出这个问题.

呃, 搞错了, double无所谓,float才是由于精度问题必须取等. 另外下面那行的(int)(uint)的两步转换在先检查了溢出之后不是必须的

        private static int CalcWithOverflow(int chunkSize, int usage)
        {
            int freeThreshold;
            if (usage == 100)
            {
                freeThreshold = 0;
            }
            else
            {
                var tmp = chunkSize * (100.0d - usage + 0.99999999d) / 100L;
                if (tmp <= int.MinValue)
                {
                    freeThreshold = int.MinValue;
                }
                else if (tmp >= int.MaxValue)
                {
                    freeThreshold = int.MaxValue;
                }
                else
                {
                    freeThreshold = (int)tmp;
                }
            }

            return freeThreshold;
        }