PHPackages                             sconcur/sconcur - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. sconcur/sconcur

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

sconcur/sconcur
===============

PHP concurrency library backed by an extension written in Go

0.9.0(2w ago)159MITPHPPHP ^8.4CI passing

Since Nov 27Pushed 1w agoCompare

[ Source](https://github.com/sprust/sconcur)[ Packagist](https://packagist.org/packages/sconcur/sconcur)[ RSS](/packages/sconcur-sconcur/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (10)Dependencies (34)Versions (41)Used By (0)

English | [Русский](README.ru.md)

SConcur
=======

[](#sconcur)

> ⚠️ Experimental project, not for production. Yet another attempt to make PHP asynchronous, but without a C extension — with one written in Go.

A concurrency library for PHP on top of a custom Go extension. The PHP side (a Fiber) suspends while the Go extension runs the task (MongoDB operations, sleep, and so on) concurrently in goroutines. PHP and Go exchange data over MessagePack.

> 📊 Numbers right away: the ["Is SConcur for you?" verdict table](docs/positioning.md#is-sconcur-for-you), [feature benchmarks](docs/benchmarks.md) and [behaviour under load](docs/load-testing.md).

Contents
--------

[](#contents)

- [Idea](#idea)
- [Example](#example)
- [What it replaces](#what-it-replaces)
- [How it works](#how-it-works)
- [Why Go specifically](#why-go-specifically)
- [Use and limitations](#use-and-limitations)
- [Tested versions](#tested-versions)
- [Documentation](#documentation)
- [Build](#build)
- [echo test](#echo-test)
- [Roadmap](#roadmap)

Idea
----

[](#idea)

Regular PHP is synchronous: `sleep()`, a PDO query, an HTTP call — each one blocks the process, and they run strictly one after another. Two one-second operations take two seconds.

SConcur runs such operations at the same time. You swap the blocking calls for SConcur equivalents (see [What it replaces](#what-it-replaces)), wrap them in coroutines, and the work moves into Go and runs in parallel goroutines. The total time is bound by the slowest operation, not by their sum. PHP stays a thin orchestration layer; all concurrency lives in Go.

Example
-------

[](#example)

Two coroutines run at the same time, each a one-second operation. Sequentially this would be about two seconds; concurrently it is about one.

```
use SConcur\WaitGroup;
use SConcur\Features\Sleeper\Sleeper;

$start = microtime(true);

$waitGroup = WaitGroup::create();

// coroutine 1: a one-second operation (instead of the blocking sleep())
$waitGroup->add(function () {
    Sleeper::sleep(seconds: 1);

    return 1;
});

// coroutine 2: the same operation again
$waitGroup->add(function () {
    Sleeper::sleep(seconds: 1);

    return 2;
});

$waitGroup->waitResults();

$seconds = round(microtime(true) - $start, 2);

echo "done in {$seconds} s" . PHP_EOL;
```

Output: `done in 1 s` — two one-second operations ran in parallel.

What it replaces
----------------

[](#what-it-replaces)

Operations and clients — wrapped in a coroutine (`$waitGroup->add()` + `$waitGroup->wait*()`), they run concurrently instead of blocking the process:

Native PHPSConcurWhat changes`sleep()`, `usleep()``Sleeper::sleep()`, `Sleeper::usleep()`pause for seconds or microseconds`PDO` / `mysqli` (MySQL)`Features\Mysql\Connection`queries, transactions, SELECT streaming; a connection pool in Go`PDO` (PostgreSQL)`Features\Pgsql\Connection`the same SQL feature on the pgx driver`mongodb/mongodb`, `ext-mongodb``Features\Mongodb\Connection\*`CRUD, aggregation, cursors (BSON types stay native `ext-mongodb`)`curl`, `file_get_contents`, Guzzle`Features\HttpClient\HttpClient` (PSR-18)response streaming, download straight to a file on the Go side`fsockopen`, `stream_socket_client``Features\SocketClient\SocketClient`TCP with length-prefix framinga WS client library`Features\WsClient\WsClient`text/binary messagesLong-lived servers:

Native PHPSConcurWhat changesPHP-FPM, RoadRunner, Swoole, Workerman`Features\HttpServer\HttpServer` (PSR-7)HTTP server`stream_socket_server``Features\SocketServer\SocketServer`TCP serverRatchet, Workerman (WS)`Features\WsServer\WsServer`WebSocket serverHow it works
------------

[](#how-it-works)

`WaitGroup` wraps each closure in a `Fiber`. When an async feature is called, the coroutine suspends and the task goes to Go, into its own goroutine. A single process-wide `Scheduler` waits on the extension (`waitAnyBatch`): it blocks for the first ready result of any flow, drains the already-ready ones with it in the same crossing, and resumes the right coroutines by `taskKey`. Results arrive in task-completion order, not in `add()` order.

The number of concurrently live coroutines in a group is unlimited by default. For backpressure (memory, a DB connection pool) set a limit: `WaitGroup::create(maxConcurrency: N)` — excess `add()` calls queue and start as slots free up.

Details — the "PHP Fiber ↔ Go goroutine" diagrams, the layers and the task lifecycle — in [docs/architecture.md](docs/architecture.md).

Why Go specifically
-------------------

[](#why-go-specifically)

The whole concurrency model reduces to one primitive: a channel. Every task runs in its own goroutine, and the results of all goroutines of all flows land in one shared buffered channel. PHP runs no event loop and polls nothing — the single process-wide `Scheduler` blocks reading a message from that channel (`waitAnyBatch`) and wakes on the first ready result, taking the already-ready tail with it in the same crossing. Which task finished first is decided by the channel; PHP only resumes the matching coroutine by `taskKey`.

Everything else follows from that:

- A feature is ordinary synchronous Go code. You write a blocking handler; the runtime runs it on a goroutine and puts the result into the channel. No promises, no callbacks, no event-loop reasoning.
- Mature drivers are reused as-is — mongo-driver, pgx, go-sql-driver, `net/http`, coder/websocket — and run concurrently right away. No waiting for async drivers or extensions.
- The C glue is frozen: `push`, `wait`, `waitAny`/`waitAnyBatch`, `next`, `stopFlow` plus the `version`/`destroy` lifecycle. A new feature is data (a `MethodEnum` value, a MessagePack payload DTO, a Go handler), not a new C symbol, so the export set never grows. A new long-lived server adds at most one control function, like `httpStopAccepting` for the graceful drain.
- One transport and one streaming model for everything: MessagePack DTOs plus the streaming states behind `next()` — MongoDB cursors, HTTP bodies, socket frames, WS messages all travel the same mechanism with backpressure.
- One API for sync and async, no function coloring. The same call works inside a `WaitGroup` (concurrent) and outside it (an ordinary blocking call); concurrency is chosen by the caller, not by the feature author.
- A feature gets the runtime for free: nested coroutines, context cancellation, deadline propagation, graceful shutdown with unwinding.
- Client and server features expose PSR interfaces (PSR-7/17, PSR-18) with injected factories, so they drop into any application without adapters.

Use and limitations
-------------------

[](#use-and-limitations)

- CLI only (the `cli` SAPI) — about the SAPI, not about "no web". The target is long-lived CLI processes: workers, daemons, console commands, and the HTTP, WebSocket and socket servers themselves, which are ordinary PHP scripts that listen on a port on their own (the Swoole / ReactPHP model). It also drops into a long-lived process you already run, including a RoadRunner worker. PHP-FPM and mod\_php are impossible: the extension holds the Go runtime at process level, which contradicts the FPM model.
- No `pcntl_fork` after the extension is loaded. The Go runtime does not survive a `fork` (the child hangs or crashes). Fork before the first call into the extension, or launch separate processes (`exec`).
- NTS (non-thread-safe) only; a ZTS build is not supported.
- Linux only — core-count detection, signals/`posix`, `SO_REUSEPORT`, the master's `flock`.
- `exit()`/`die()` with active tasks is safe but loses their results. The shutdown handler unwinds unfinished coroutines (finally blocks run, transactions roll back, cursors and flows are released), then the process exits normally. Better to run tasks to completion or stop them explicitly (`WaitGroup::stop()`).
- Concurrent mode is optional. Any feature can be called outside a `WaitGroup` as an ordinary synchronous call: outside a Fiber `FeatureExecutor` detects the non-async context and simply waits for the result (`Extension::wait`).

```
// synchronous, without WaitGroup — returns the result immediately
$collection->insertOne(['name' => 'example']);
```

Tested versions
---------------

[](#tested-versions)

The environment the project is built and tested against in CI:

ComponentVersionPHP8.4.15 (NTS, cli)Go (extension build)1.26.1MongoDB (server)8.0.5ext-mongodb (PHP extension)1.21.5mongodb/mongodb (composer package)1.21.3ext-msgpack3.0.1MySQL (server)8.4go-sql-driver/mysql1.8.1PostgreSQL (server)16jackc/pgx/v55.7.2go.mongodb.org/mongo-driver/v22.6.0Documentation
-------------

[](#documentation)

- [Console commands](docs/cli.md) — `sconcur-load`, `sconcur-status`, `sconcur-server`.
- [Architecture](docs/architecture.md) — Fiber ↔ goroutine, the scheduler, the layers, the task lifecycle.
- [Coroutine switching](docs/coroutine-switching.md) — `Scheduler::switch()` and the servers' automatic preemption for CPU-bound code.
- [Coroutine context](docs/coroutine-context.md) — per-coroutine key-value store.
- [MongoDB](docs/mongodb.md) — collection operations, cursors, BSON types.
- [MySQL](docs/mysql.md) — the universal SQL feature: bindings, streaming, transactions, the pool.
- [PostgreSQL](docs/pgsql.md) — the same feature's second driver; PG specifics.
- [HTTP server](docs/http-server.md) — PSR-7 daemon, a request per coroutine.
- [Socket server (TCP)](docs/socket-server.md) — length-prefix framing, push model.
- [WebSocket server](docs/websocket-server.md) — HTTP-Upgrade listener + push model.
- [HTTP client](docs/http-client.md) — async PSR-18 client with response streaming.
- [Socket client (TCP)](docs/socket-client.md) — the socket server's dial-side mirror.
- [WebSocket client](docs/websocket-client.md) — the WS server's dial-side mirror.
- [Worker master](docs/worker-master.md) — a supervisor for a pool of workers (`bin/sconcur-server`).
- [Server statistics](docs/admin-stats.md) — `GET /api/stats`, live panel, SSE, Prometheus.
- [How to add a new feature](docs/adding-a-feature.md) — step by step, with and without streaming.
- [How to add a new server](docs/adding-a-server.md) — the Serve/Respond pattern and the serve loop.
- [Feature benchmarks](docs/benchmarks.md) — per-feature measurements (native/sync/async).
- [Load testing](docs/load-testing.md) — server behaviour under load with all I/O features at once.
- [Positioning](docs/positioning.md) — SConcur vs php-fpm, RoadRunner and Swoole.

Build
-----

[](#build)

```
cd ext && \
  rm -f build/sconcur.so build/sconcur.h && \
  CGO_CFLAGS=$(php-config --includes) \
  go build -buildmode=c-shared -o build/sconcur.so .
```

echo test
---------

[](#echo-test)

```
php -d extension=./ext/build/sconcur.so -r "echo \SConcur\Extension\ping('hello') . PHP_EOL;"
```

Roadmap
-------

[](#roadmap)

- The `Std` feature — SConcur equivalents of standard PHP functions that block the worker or are CPU-bound non-preemptible monoliths (sleep, json, hash, gzip, password hashing, file I/O), executed in Go; absorbs `Sleeper`.
- Auto-recovery of stuck workers — a master watchdog by heartbeat: `SIGKILL` and respawn a worker whose PHP thread has hung.
- Split the core and the features into separate packages.
- Stopping a single coroutine from anywhere, not just the whole flow.
- Optimize the synchronous path — a call outside a coroutine goes to Go directly, bypassing the scheduler and the Fiber machinery.
- Explore a cross-process concurrency mode, so a fan-out can use several processes (and cores) instead of the goroutines of one process.

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance98

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 99.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~4 days

Total

9

Last Release

15d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/eb5944a5bfd39c64f072b6c43fc6a315e4eea4d14c409538d1fd6de61a234847?d=identicon)[spp28rus](/maintainers/spp28rus)

---

Top Contributors

[![sprust](https://avatars.githubusercontent.com/u/57857525?v=4)](https://github.com/sprust "sprust (541 commits)")[![sprust28](https://avatars.githubusercontent.com/u/190169196?v=4)](https://github.com/sprust28 "sprust28 (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sconcur-sconcur/health.svg)

```
[![Health](https://phpackages.com/badges/sconcur-sconcur/health.svg)](https://phpackages.com/packages/sconcur-sconcur)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

86337.5k](/packages/flow-php-flow)[cakephp/cakephp

The CakePHP framework

8.9k20.0M1.9k](/packages/cakephp-cakephp)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

36826.2k2](/packages/telnyx-telnyx-php)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

265.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
