walkor / walkor/webman

# [Bug] 协程模式下 `require_once` 加载 controller 存在 race,并发触发 `Cannot redeclare class` 致 worker 崩溃

Open
#456 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
PHP
Stars
2.7k
Forks
251
PR merge metrics
No merged PRs in 30d

Description

## 环境

- webman-framework: **v2.2.2**
- workerman: 5.x(含 `workerman/coroutine` 1.1.5)
- PHP: 8.4.4 (NTS),Linux

## 问题描述

安装 `workerman/coroutine` 后,webman 默认以 PHP Fiber 协程处理请求:

```php
// vendor/workerman/workerman/src/Worker.php:2655-2660
match (Worker::$eventLoopClass) {
Swoole::class, Swow::class, Fiber::class => Coroutine::create($callback),
default => (new \Fiber($callback))->start(),
};
```

但框架内部加载 controller 仍用传统 `require_once`:

- `vendor/workerman/webman-framework/src/Route.php:680-681`
- `vendor/workerman/webman-framework/src/App.php:1223-1224`

`require_once` 的"已加载去重"在 Fiber 协程并发下**不原子**:多个协程同时首次解析到同一 controller,各自穿过 `require_once` 守卫、重复 include 同一文件 → `Cannot redeclare class` 编译错误 → worker 崩溃 → 上游网关 502。

## 复现

1. `composer require workerman/coroutine`
2. 准备一个 controller `app/controller/FooController.php`,路由 `/foo`
3. 高并发首次加载:
```bash
seq 1 100 | xargs -I{} -P 50 curl http://127.0.0.1:8787/foo
```
4. `runtime/logs/workerman.log` 间歇出现:
```
Worker[...] process terminated with ERROR: E_COMPILE_ERROR
"Cannot redeclare class app\controller\FooController
(previously declared in .../FooController.php:N)
in .../FooController.php on line N"
```

**关键特征:同一文件、同一行**被声明两次。低并发不易触发,生产真实流量下偶发 502。

## 根因

- 协程默认开启且无法配置关闭(`default` 分支硬编码 Fiber);
- `require_once` / autoloader 在 PHP 协程下非原子,是语言层限制;但框架作为协程的启用方,应在加载层做协程安全处理。

## 期望

框架在协程模式下对 controller / 类文件加载做协程安全处理,建议任一:
1. `onWorkerStart` 启动期全量预加载所有 controller;
2. 用 `workerman/coroutine` 的 `Locker` 协程锁包裹 `require_once`;
3. 至少在文档明确提示协程模式下的类加载 race 及规避方式。

## 临时绕过(项目侧)

给类文件加文件级 guard,`namespace` 声明后紧跟:

```php
namespace app\controller\admin;
if (class_exists('app\controller\admin\ConfigController', false)) { return; }
```

文件被二次 include 时首句即 `return`,不执行到 `class` 声明,redeclare 在源头阻断。本项目对 29 个 controller 加 guard 后,100 并发压测零 crash。

## 证据

```
2026-07-22 02:32:13 pid:33055 Worker[33055] process terminated with ERROR:
E_COMPILE_ERROR "Cannot redeclare class app\controller\admin\ConfigController
(previously declared in /.../ConfigController.php:15)
in /.../ConfigController.php on line 15"
```

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the Fiber dispatch in vendor/workerman/workerman/src/Worker.php:2655-2660, then inspect the require_once calls in vendor/workerman/webman-framework/src/Route.php:680-681 and App.php:1223-1224. Reproduce the issue with workerman/coroutine, the /foo controller, and the provided concurrent curl command. Done means the first-load race no longer causes Cannot redeclare class or worker crashes under concurrent requests.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.