Audience: production evaluators, architects, and developers running GEMVC under OpenSwoole / Swoole workers.
Purpose: answer recurring questions about request isolation, memory, pooling, and app constraints — once, in one place.
Related: http-lifecycle.md · architecture.md · database.md · ecosystem.md · apm.md · api.md · frankenphp.md
Framework version: 5.13+ (unified ApiService / ProtectedApiService on all servers).
| Question | Jump to |
|---|---|
| How is each HTTP request isolated? | Request isolation model |
Do you use Coroutine::getContext()? |
Not coroutine context |
| What happens after the response? | Lifecycle after response |
| DB / Redis pools and stale sockets | Connection pooling |
| What must app authors never do? | Developer rules |
| Apache vs OpenSwoole differences | Runtime differences |
| FAQ for common critiques | FAQ |
Under OpenSwoole, GEMVC keeps the worker process alive and isolates work by building a new request object graph on every HTTP hit. Auth, body, JWT, and APM live on that request — not on worker globals. Responses use showSwoole() (never die() / exit()). Database access on OpenSwoole uses a real pool via gemvc/connection-openswoole (Hyperf). Infrastructure singletons hold connections/config only.
GEMVC does not use Swoole\Coroutine::getContext() as an application request-context API. If your code stores user/token/Request in static properties or custom singletons, you can still leak state across requests on the same worker.
OpenSwoole HTTP request
→ SecurityManager path check (Swoole-only early deny)
→ new SwooleRequest($swooleRequest) → new Request()
→ new SwooleBootstrap($request) → APM root on $request->apm
→ new App\Api\{Service}($request)
→ Controller / Model / Table as invoked (typically new instances)
→ JsonResponse|HtmlResponse::showSwoole($response)
→ $request->apm->flush() (when APM enabled)
→ per-request objects become unreachable → GC
| Object | Per request? | Notes |
|---|---|---|
SwooleRequest + Request |
Yes | Constructor resets auth/token/body fields |
SwooleBootstrap |
Yes | Routes and invokes the API method |
App\Api\* service |
Yes | new $service($this->request) |
Controller (via callController / new) |
Yes when used | Receives that request |
| APM provider | Yes | ApmFactory::create($request) → $request->apm |
JWT after auth() |
On that $request->token |
Not a process-global “current user” |
| Object | Holds request user/token? |
|---|---|
OpenSwooleServer, config, hot-reload |
No |
DatabaseManagerFactory / OpenSwoole connection manager + pool |
No — pool / connections |
RedisManager |
No — Redis client / config |
RateLimiter backends (APCu / Redis) |
Counters keyed by bucket; reads IP/token from the passed Request |
SecurityManager |
Path rules, not session identity |
Isolation guarantee (framework): request-scoped identity and payload live on the per-request Request graph.
Not guaranteed: application static / custom singletons / unbounded in-process caches that hold request data.
GEMVC does not implement request isolation via Swoole\Coroutine::getContext().
That pattern is valid for some Swoole apps; GEMVC’s product model is:
- Adapter at the edge (
SwooleRequest) - Unified
Gemvc\Http\Requestper hit - Same
app/layers as Apache/Nginx - Return a response from bootstrap — never kill the worker
SWOOLE_ENABLE_COROUTINE may default on for the server; that does not mean the framework stores your user in coroutine context. Prefer keeping state on $this->request / locals.
- Write response —
showSwoole()sets status/headers/body on the OpenSwoole response object. Nodie(). - APM flush — if APM is enabled,
OpenSwooleServercalls$request->apm->flush()after send. Relying on PHPregister_shutdown_functionalone is not treated as request-scoped under persistent workers. - GC — request-local objects drop out of scope.
- Worker recycle —
SWOOLE_MAX_REQUEST(default often5000) recycles the worker after N requests. That bounds long-lived growth; it is a safety net, not the isolation mechanism.
There is no framework hook that walks and clears all application statics after each response.
DatabaseManagerFactorydetects OpenSwoole and usesgemvc/connection-openswoole(Hyperf-based pool).- Table /
UniversalQueryExecuterget a connection for work and release it afterward (including error paths). - Pool env (see Swoole
example.env/ connection package README):MIN_DB_CONNECTION_POOL,MAX_DB_CONNECTION_POOL,DB_CONNECTION_MAX_AGE(idle), optionalDB_HEARTBEAT.
Apache/Nginx/CLI use gemvc/connection-pdo (not the same pooling model). Same Table API in app/.
RedisManageris a worker singleton (optional persistent connect via env).- Used for infrastructure (e.g. rate-limit Redis driver), not as a request-context bag.
- Idle eviction / pool health is largely handled inside the OpenSwoole connection package (Hyperf pool).
- GEMVC does not claim a custom “ping before every query” or full MySQL
2006/ “gone away” discard policy in the library core beyond get/release (+ error logging). - Production: monitor pool stats, set sensible idle/
max_request, DB/proxy timeouts; prefer short queries.
Queries still run through PDO on the Table path. With coroutines enabled, a long blocking query occupies the worker for that time. Keep transactions on one Table instance (beginTransaction / forUpdate / commit) as documented in model.md.
- Extend
ApiService/ProtectedApiService(all servers, including OpenSwoole). Deprecated:SwooleApiService/ProtectedSwooleApiService. - Keep auth, body, and identity on
$this->request(or method locals). - Prefer
callController(...)andcreateModel(...)so Request + APM propagate. - Return
JsonResponsefrom API/Controller; letSwooleBootstrap/OpenSwooleServerdeliver it. - Let Table/query helpers release connections; do not hold raw PDO across requests.
- Configure URL sections: OpenSwoole has no automatic
/apihop —SERVICE_IN_URL_SECTION/METHOD_IN_URL_SECTION(see architecture.md).
die()/exit()/dieafterJsonResponse::show()on the OpenSwoole request path (kills or breaks the worker model). UseshowSwoolevia the server — app code should just return the response.- Store
Request, JWT, user id, or tenant id instaticproperties, custom singletons, or worker globals. - Cache Controller / Model / Table instances as process-wide singletons “for performance.”
- Assume APCu rate limits are cluster-wide (they are per worker). Use Redis /
bothfor multi-node quotas. - Invent PSR-7/15 middleware stacks or
Coroutine::getContext()as required GEMVC APIs — not part of the product model.
| Concern | Apache / Nginx (PHP-FPM) | OpenSwoole |
|---|---|---|
| Process model | One request ≈ one process (or FPM worker reset semantics) | Persistent worker, many requests |
| Entry | startup/apache or startup/nginx → Bootstrap |
startup/swoole → OpenSwooleServer → SwooleBootstrap |
| Adapter | StandardHttpRequest (shared) |
SwooleRequest |
| API base | ApiService / ProtectedApiService |
Same (5.13+) |
| Response | JsonResponse::show() then terminate |
showSwoole() then continue worker |
| Early path deny | .htaccess / server config |
SecurityManager in server |
| DB package | connection-pdo |
connection-openswoole (pool) |
| Uploads | Field name file; limited MIME sanitize |
Normalized + name/MIME sanitize |
URL /api hop |
Yes (convention) | No automatic hop — configure sections |
| APM end | Bootstrap path + flush semantics | Explicit flush after showSwoole |
Same app/ for all servers including FrankenPHP classic/worker — see frankenphp.md.
Framework singletons are for infrastructure. Request identity is on the new Request each hit. Leakage happens if your code puts request data on statics — that is an app bug, not missing getContext().
Not in GEMVC’s design. Isolation = new request graph + return response + GC. Coroutine context is optional for advanced app patterns; it is not the framework contract.
Incorrect for the supported OpenSwoole path: gemvc/connection-openswoole provides Hyperf pooling selected by DatabaseManagerFactory. See ecosystem.md and database.md.
showSwoole → optional APM flush → drop request graph → GC. Plus optional worker max_request recycle. No global static scrubber.
Heavy queries can block the worker for their duration. Mitigate with query design, pool sizing, and max_request. This is an operational characteristic of PDO-on-Swoole, not “GEMVC has no pool.”
Historically dual bases; 5.13 unifies on ApiService / ProtectedApiService. Swoole* names remain deprecated thin subclasses for BC. See api.md.
No. Nginx PHP-FPM uses StandardHttpRequest + Bootstrap (same as Apache). Only OpenSwoole uses SwooleRequest.
| Piece | Location |
|---|---|
| Per-request handler | src/core/OpenSwooleServer.php |
| Bootstrap (return, no die) | src/core/SwooleBootstrap.php |
| Adapter | src/http/SwooleRequest.php |
| Unified request | src/http/Request.php |
| Response (Swoole) | src/http/JsonResponse.php → showSwoole() |
| DB manager selection | src/database/DatabaseManagerFactory.php |
| OpenSwoole pool package | vendor/gemvc/connection-openswoole/ |
| Redis singleton | src/core/RedisManager.php |
| Example env | src/startup/swoole/example.env |
- App code: no request data on
static/ custom singletons - No
die/exiton API path ApiService/ProtectedApiService(not inventing a second stack)- Pool env set; monitor pool stats under load
- Rate limit: Redis/
bothif multiple workers/nodes - URL sections correct for Swoole (no accidental
/apias service name) - APM: root flush after response verified in staging
- Load test: concurrent users + worker recycle + DB idle timeout
When in doubt, this page is the canonical answer for OpenSwoole behavior. Layer guides (api, controller, model, database) still define how to write app/ code.