Eliminating Blocking Degradation in Webman/Workerman with a Coroutine Plugin

This article explains the blocking‑degradation problem in Webman/Workerman's event‑loop, analyzes why traditional solutions fall short, and introduces the webman‑coroutine plugin that adapts multiple coroutine drivers, rewrites the web server, and provides practical guidance for safe coroutine integration.

Open Source Tech Hub
Open Source Tech Hub
Open Source Tech Hub
Eliminating Blocking Degradation in Webman/Workerman with a Coroutine Plugin

Introduction

During a busy period I finally had time to read the community discussion about coroutine support and the blocking‑degradation issue in Webman/Workerman. What started as a brief note turned into a full‑blown plugin, webman‑coroutine , after several iterations.

Current Situation

Blocking degradation in Workerman/Webman

Workerman uses a master/worker multi‑process model where each worker runs a single event‑loop handling streams, timers, and other events. The loop is single‑threaded, so if a callback blocks, the entire loop stalls until the callback returns. This creates a queue‑like situation: the event‑loop is the sole consumer, and a blocked callback prevents new events from being processed, eventually exhausting the worker pool.

In practice, long‑running or blocking calls (e.g., PDO, curl, file I/O) can fill workers, reducing throughput. The naive fix is to increase the number of workers, but this only mitigates the symptom.

Workerman’s Swoole driver does not use coroutines

Some assume that switching the driver to Swoole automatically enables coroutines. The actual implementation loads Swoole’s event‑loop but does not wrap callbacks in \Co\run(), so blocking I/O still blocks the loop. The underlying stream_socket_server() call waits for callbacks, and even with Swoole’s system‑call hooks, the main socket’s next event cannot be processed until the current callback finishes.

Consequences

Unintentional blocking occupies all workers, drastically lowering throughput.

PDO

curl

File read/write

Other blocking I/O

Traditional solution: increase the number of workers.

Long‑polling interfaces

HTTP‑SSE

Long‑connection scenarios

Timers with blocking business logic

Queue producer/consumer patterns

Traditional solution: custom processes or external services.

Solution

I developed a coroutine infrastructure plugin compatible with both Webman and Workerman, named webman‑coroutine . The plugin uses the adapter and factory patterns to abstract over common coroutine drivers (Swow, Swoole, php‑fiber/ripple), providing a unified API that works both in coroutine and non‑coroutine environments.

The plugin re‑implements the web server for Webman, enabling full coroutine support without invasive changes. Below is a simplified excerpt of the driver implementation for Workerman 4.x:

<?php
/**
 * This file is part of workerman.
 * Licensed under the MIT License
 */
namespace Workerman\Events;
use Workerman\Worker;
use Swoole\Event;
use Swoole\Timer;
class Swoole implements EventInterface {
    protected $_timer = [];
    protected $_timerOnceMap = [];
    protected $mapId = 0;
    protected $_fd = [];
    public static $signalDispatchInterval = 500;
    protected $_hasSignal = false;
    public function add($fd, $flag, $func, $args = []) {
        switch ($flag) {
            case self::EV_SIGNAL:
                $res = pcntl_signal($fd, $func, false);
                if (! $this->_hasSignal && $res) {
                    Timer::tick(static::$signalDispatchInterval, function () { pcntl_signal_dispatch(); });
                    $this->_hasSignal = true;
                }
                return $res;
            case self::EV_TIMER:
            case self::EV_TIMER_ONCE:
                $timer_id = $this->mapId++;
                $t = (int)($fd * 1000);
                if ($t < 1) $t = 1;
                $timer_id = Timer::tick($t, function () use ($func, $args, $timer_id) {
                    try { call_user_func_array($func, (array)$args); }
                    catch (\Exception $e) { Worker::stopAll(250, $e); }
                    catch (\Error $e) { Worker::stopAll(250, $e); }
                });
                return $timer_id;
            // ... other cases omitted for brevity ...
        }
    }
    // del, clearAllTimer, loop, destroy, getTimerCount implementations omitted
}
?>

The plugin registers the coroutine‑enabled web server by extending App and overriding lifecycle methods such as onWorkerStart, onConnect, onMessage, and onClose. It tracks per‑connection coroutine counts, uses a WaitGroup to limit concurrent coroutines, and safely recycles resources.

<?php
class CoroutineWebServer extends App {
    protected static array $_connectionCoroutineCount = [];
    public static function getConnectionCoroutineCount(?string $id = null) {
        return $id === null ? static::$_connectionCoroutineCount : (static::$_connectionCoroutineCount[$id] ?? 0);
    }
    public static function unsetConnectionCoroutineCount(string $id, bool $force = false) {
        if (! $force && self::getConnectionCoroutineCount($id) > 0) return;
        unset(static::$_connectionCoroutineCount[$id]);
    }
    public function onMessage($connection, $request, ...$params) {
        $connectionId = spl_object_hash($connection);
        $waitGroup = new WaitGroup();
        $waitGroup->add();
        new Coroutine(function () use (&$res, $waitGroup, $params, $connectionId) {
            $res = parent::onMessage(...$params);
            self::$_connectionCoroutineCount[$connectionId]--;
            self::unsetConnectionCoroutineCount($connectionId);
            $waitGroup->done();
        });
        self::$_connectionCoroutineCount[$connectionId] = (self::$_connectionCoroutineCount[$connectionId] ?? 0) + 1;
        $waitGroup->wait();
        return $res;
    }
    // other lifecycle methods omitted for brevity
}
?>

During testing with Workerman 5.x I discovered several bugs in the original Swoole driver and submitted a PR (e.g., "fix: all coroutines must be canceled before Event::exit #1059"). The plugin also works in pure Workerman environments.

Practical Experience

1. Coroutines are not a silver bullet

Coroutines do not shorten inherently time‑consuming logic; they merely allow other tasks to run during blocking periods, effectively trading space for time.

2. Memory model in PHP

Arrays and objects reside on the heap, while scalars live on the stack. Coroutine switches save registers and stack frames but not heap data, leading to race conditions when multiple coroutines share the same heap objects.

$a = new \stdClass();
$a->id = 1;
new Coroutine(function () use ($a) { $a->id = 2; });
new Coroutine(function () use ($a) { $a->id = 3; });
// Final value of $a->id is nondeterministic
echo $a->id;

Similar race conditions exist for stack variables passed by reference.

$a = 1;
new Coroutine(function () use (&$a) { $a = 2; });
new Coroutine(function () use (&$a) { $a = 3; });
// Final value of $a is nondeterministic
echo $a;

Heap data can be cloned to avoid sharing, but resources cannot be cloned. A common pattern is to store coroutine‑local context in a static array keyed by coroutine ID.

3. Database connection pooling

PDO uses blocking I/O; when multiple coroutines share a single PDO connection, their queries are serialized, and results may be delivered to the wrong coroutine, causing data corruption. Solutions include per‑coroutine connections, a connection pool, or using coroutine‑aware database libraries such as hyperf/database or hyperf/db.

4. Other components that need pooling

Any object or array that is shared across coroutines (e.g., caches, HTTP clients) suffers the same race‑condition problem and should be either isolated per coroutine or managed via a pool.

5. Ongoing work

The current plugin mainly solves blocking degradation for long‑polling, non‑blocking timers, queue processing, and worker/server coroutineification. Future work includes non‑intrusive database connection pooling for Webman, coroutine‑friendly components for Workerman, and a unified API for various coroutine drivers (Swow, Swoole, Ripple, Revolt).

Contributions via issues and pull requests are welcome. If any part of this article contains inaccuracies, please point them out.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Swooleevent-loop
Open Source Tech Hub
Written by

Open Source Tech Hub

Sharing cutting-edge internet technologies and practical AI resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.