Skip to content
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

增加3.0子协程功能 #309

Open
wants to merge 2 commits into
base: 3.0
Choose a base branch
from
Open

增加3.0子协程功能 #309

wants to merge 2 commits into from

Conversation

batcom
Copy link

@batcom batcom commented Jul 6, 2022

修复和功能描述:
1.合并4.0的沙盒获取方式
2.创建子协程造成获取Container失败
3.子协程逃逸后沙盒内存泄漏的处理
问题描述:

  1. 3.0不支持在如控制器等地方直接go(),自己创建子协程,如直接使用会出现The app object has not been initialized。合并4.0的相关代码
protected function getSnapshotId($init=false)
    {
        if ($fd = Context::getData('_fd')) {
            return 'fd_' . $fd;
        }
        if ($init) {
            Coroutine::getContext()->offsetSet('#root', true);
            return Coroutine::getCid();
        } else {
            $cid = Coroutine::getCid();
            while (!(Coroutine::getContext($cid)->offsetExists('#root'))) {
                $cid = Coroutine::getPcid($cid);
                if (Coroutine::getContext($cid) == null) {
                    throw new Exception("发现逃逸协程:$cid");
                }
                if ($cid < 1) {
                    break;
                }
            }
            return $cid;
        }

    }
  1. 创建一个TestController 执行go([new Test1(),'run']) 然后再Test1类里面执行go([new Test2(),'run']); Test2 run 方法里面new 一个model 然后调用Log::info('xxx'); 即可重现协程逃逸现象,会出现offsetExists on null 的错误,因为自动产生了协程切换。
  2. 对这种情况,修改getSnapshotId,然后修改initialize 里面清理下即可。4.0依旧有此问题。

2.创建子协程造成获取Container失败
3.子协程逃逸后沙盒内存泄漏的处理
$this->clear();
}
}

});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里协程逃逸后,重建了一个app对象也是有隐患的,之前app对象已有的数据就丢失了

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

目前想到的方案 就是在自定义一个go方法来代替swoole自带的 这样可以在协程创建时就把父协程的数据写入到子协程里去,而不用反过来一级一级的查找(因为swoole协程本身并没有父子关系,只要中间有一个协程执行完毕退出了,就找不到最顶级的数据了)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.这个问题是比较绕,比较底层,我看了些其它的框架,好像都没解决这个问题。至于你谈到的方案,我没试过,但我感觉可能会有问题,因为协程逃逸是swoole系统底层自动产生的调度,不一定会有入口点供数据插入。
按照这种思路,我感觉需要用堆栈数据结构defer代替数组会比较好,这样能保证每一次调用自动入栈和出栈,不需要去清理,在最顶层的一定会最后被出队。但这种做法的确比较tricky
2.第二种比较简单的方法就是提供一种垃圾回收算法,因为协程逃逸主要是因为顶级协程完成退出的原因,而此时快照已经被unset,其实就协程逃逸的描述来说这是正常的,因为既然是逃逸就相当于也指针和僵尸进程,快照内存既然已经释放,后面逃逸的协程就理应不应该在获取以前的app对象了,这也是这个问题绕的原因所在。
故提供几个算法解决:
算法
1.设置一个超时时间,可以提供这样一个最简单的内置回收算法,或提供一个接口,给用户自己实现.
2.类似引用计数和堆栈的思路,每次获取snap就入栈或者引用计数,然后用defer去解引用或出队。
3.增加一个traceid对应一个请求,然后设置时间,当有逃逸发生的时候,更新这个时间,因为一般逃逸跟清理时间正常应该间隔很短,这样判断,可以更快进行垃圾回收。

最后,这个问题总的来说也是一个代价问题。如果底层处理,很难有完美的方案,甚至需要用户自己去实现垃圾回收,比如如果应用并发量很大,算法2就不太适合了,可能还需要增加一个snapshots数量加上时间来回收了。会比较难。而用户层用重写go的方法,或者不创建子协程,用队列方式替代,通过规范写法的途径也能避免,毕竟研究到这个层面的用户比较少。
具体算法,可以参考其中一个实现。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已按算法1简单实现了一个

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrk -c 10000 -d 120s 压测了下,稳定,没什么问题。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我的意思是此扩展内提供一个\think\go函数 代替\swoole\go内置的

此\think\go方法内调用\swoole\go前 获取snapshot并记录id到当前的context,
同时在snapshot上做一个snapshot->go++ 的计数,
且定义一个defer回收 执行snapshot->go-- ;
当启动垃圾回收时先检查snapshot->go是不是为0,如果是0则执行\co\yield() 让出执行权,并记录当前正在等待回收的状态
同时在刚才定义的那个defer里检查当前是不是等待回收的状态,如果时则执行\co\resume(); 重新进行上面的回收检查,直到snapshot->go=0时完成回收

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

明白,你可以抽时间实现下这个,不过正如我上面所说,这个是动用户层空间,对用户不太友好。如果用户代码已经是go,就得批量替换和测试,用户也必须先看文档或代码,才能知道该如何正确使用。这就是代价问题。
我刚刚实现的一个,就增加2行代码,用chan超时特性也能解决这个问题,不用动用户空间。你可以看下。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

所以我一直没动,在等swoole什么时候给协程提供一个类似于defer这样的前置操作注册函数

其实一般用户用tp的话根本没必要使用go去自己开一个协程能执行什么东西,原来tp在fpm下怎么开发的还怎么开发,不需要额外使用swoole里提供的东西

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants