Skip to content

Commit 176767a

Browse files
authored
Merge pull request #18 from rcalicdan/UV-support
Uv support
2 parents 681a37b + 4c7fc7f commit 176767a

19 files changed

+1085
-27
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ vendor/
44
.vscode/
55
cache/
66
.env
7+
/php-uv
78

php-uv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 670a609efc36c9043be37bae4126f06ed30fde21
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Rcalicdan\FiberAsync\EventLoop\Detectors;
4+
5+
/**
6+
* Detects availability of ext-uv and provides fallback mechanisms
7+
*/
8+
final class UvDetector
9+
{
10+
private static ?bool $uvAvailable = null;
11+
12+
public static function isUvAvailable(): bool
13+
{
14+
if (self::$uvAvailable === null) {
15+
self::$uvAvailable = extension_loaded('uv');
16+
}
17+
18+
return self::$uvAvailable;
19+
}
20+
21+
public static function requiresUv(): bool
22+
{
23+
return self::isUvAvailable() &&
24+
!empty($_ENV['FIBER_ASYNC_FORCE_UV']) ||
25+
!empty($_SERVER['FIBER_ASYNC_FORCE_UV']);
26+
}
27+
}

src/EventLoop/EventLoop.php

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Rcalicdan\FiberAsync\EventLoop;
44

55
use Fiber;
6+
use Rcalicdan\FiberAsync\EventLoop\Detectors\UvDetector;
7+
use Rcalicdan\FiberAsync\EventLoop\Factories\EventLoopComponentFactory;
68
use Rcalicdan\FiberAsync\EventLoop\Handlers\ActivityHandler;
79
use Rcalicdan\FiberAsync\EventLoop\Handlers\SleepHandler;
810
use Rcalicdan\FiberAsync\EventLoop\Handlers\StateHandler;
@@ -81,27 +83,21 @@ class EventLoop implements EventLoopInterface
8183
private int $iterationCount = 0;
8284
private float $lastOptimizationCheck = 0;
8385
private const OPTIMIZATION_INTERVAL = 1.0;
84-
private const MAX_ITERATIONS = 1000000; // Reset counter at 1M iterations
86+
private const MAX_ITERATIONS = 1000000;
8587

86-
/**
87-
* Initialize the event loop with all required managers and handlers.
88-
*
89-
* Private constructor to enforce singleton pattern. Sets up all managers
90-
* and handlers with proper dependency injection.
91-
*/
9288
private function __construct()
9389
{
94-
$this->timerManager = new TimerManager;
90+
$this->timerManager = EventLoopComponentFactory::createTimerManager();
9591
$this->httpRequestManager = new HttpRequestManager;
96-
$this->streamManager = new StreamManager;
92+
$this->streamManager = EventLoopComponentFactory::createStreamManager();
9793
$this->fiberManager = new FiberManager;
9894
$this->tickHandler = new TickHandler;
9995
$this->activityHandler = new ActivityHandler;
10096
$this->stateHandler = new StateHandler;
10197
$this->fileManager = new FileManager;
102-
$this->socketManager = new SocketManager;
98+
$this->socketManager = EventLoopComponentFactory::createSocketManager();
10399

104-
$this->workHandler = new WorkHandler(
100+
$this->workHandler = EventLoopComponentFactory::createWorkHandler(
105101
timerManager: $this->timerManager,
106102
httpRequestManager: $this->httpRequestManager,
107103
streamManager: $this->streamManager,
@@ -111,12 +107,17 @@ private function __construct()
111107
socketManager: $this->socketManager,
112108
);
113109

114-
$this->sleepHandler = new SleepHandler(
110+
$this->sleepHandler = EventLoopComponentFactory::createSleepHandler(
115111
$this->timerManager,
116112
$this->fiberManager
117113
);
118114
}
119115

116+
public function isUsingUv(): bool
117+
{
118+
return UvDetector::isUvAvailable();
119+
}
120+
120121
public function getSocketManager(): SocketManager
121122
{
122123
return $this->socketManager;
@@ -248,6 +249,8 @@ public function defer(callable $callback): void
248249
*/
249250
public function run(): void
250251
{
252+
$isUsingUv = UvDetector::isUvAvailable();
253+
251254
while ($this->stateHandler->isRunning() && $this->workHandler->hasWork()) {
252255
$this->iterationCount++;
253256
$hasImmediateWork = $this->tick();
@@ -256,7 +259,7 @@ public function run(): void
256259
$this->optimizeLoop();
257260
}
258261

259-
if ($this->sleepHandler->shouldSleep($hasImmediateWork)) {
262+
if (!$isUsingUv && $this->sleepHandler->shouldSleep($hasImmediateWork)) {
260263
$sleepTime = $this->sleepHandler->calculateOptimalSleep();
261264
$this->sleepHandler->sleep($sleepTime);
262265
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
namespace Rcalicdan\FiberAsync\EventLoop\Factories;
4+
5+
use Rcalicdan\FiberAsync\EventLoop\Detectors\UvDetector;
6+
use Rcalicdan\FiberAsync\EventLoop\Handlers\SleepHandler;
7+
use Rcalicdan\FiberAsync\EventLoop\Handlers\UvWorkHandler;
8+
use Rcalicdan\FiberAsync\EventLoop\Handlers\WorkHandler;
9+
use Rcalicdan\FiberAsync\EventLoop\IOHandlers\Uv\UvSleepHandler;
10+
use Rcalicdan\FiberAsync\EventLoop\Managers\SocketManager;
11+
use Rcalicdan\FiberAsync\EventLoop\Managers\StreamManager;
12+
use Rcalicdan\FiberAsync\EventLoop\Managers\TimerManager;
13+
use Rcalicdan\FiberAsync\EventLoop\Managers\Uv\UvSocketManager;
14+
use Rcalicdan\FiberAsync\EventLoop\Managers\Uv\UvStreamManager;
15+
use Rcalicdan\FiberAsync\EventLoop\Managers\Uv\UvTimerManager;
16+
17+
/**
18+
* Factory for creating UV-aware or fallback components
19+
*/
20+
final class EventLoopComponentFactory
21+
{
22+
private static $uvLoop = null;
23+
24+
public static function createTimerManager(): TimerManager
25+
{
26+
if (UvDetector::isUvAvailable()) {
27+
return new UvTimerManager(self::getUvLoop());
28+
}
29+
30+
return new TimerManager();
31+
}
32+
33+
public static function createStreamManager(): StreamManager
34+
{
35+
if (UvDetector::isUvAvailable()) {
36+
return new UvStreamManager(self::getUvLoop());
37+
}
38+
39+
return new StreamManager();
40+
}
41+
42+
public static function createSocketManager(): SocketManager
43+
{
44+
if (UvDetector::isUvAvailable()) {
45+
return new UvSocketManager(self::getUvLoop());
46+
}
47+
48+
return new SocketManager();
49+
}
50+
51+
public static function createWorkHandler(
52+
$timerManager,
53+
$httpRequestManager,
54+
$streamManager,
55+
$fiberManager,
56+
$tickHandler,
57+
$fileManager,
58+
$socketManager
59+
): WorkHandler {
60+
if (UvDetector::isUvAvailable()) {
61+
return new UvWorkHandler(
62+
self::getUvLoop(),
63+
$timerManager,
64+
$httpRequestManager,
65+
$streamManager,
66+
$fiberManager,
67+
$tickHandler,
68+
$fileManager,
69+
$socketManager
70+
);
71+
}
72+
73+
return new WorkHandler(
74+
$timerManager,
75+
$httpRequestManager,
76+
$streamManager,
77+
$fiberManager,
78+
$tickHandler,
79+
$fileManager,
80+
$socketManager
81+
);
82+
}
83+
84+
public static function createSleepHandler(
85+
$timerManager,
86+
$fiberManager
87+
): SleepHandler {
88+
if (UvDetector::isUvAvailable()) {
89+
return new UvSleepHandler(
90+
$timerManager,
91+
$fiberManager,
92+
self::getUvLoop()
93+
);
94+
}
95+
96+
return new SleepHandler($timerManager, $fiberManager);
97+
}
98+
99+
private static function getUvLoop()
100+
{
101+
if (self::$uvLoop === null && UvDetector::isUvAvailable()) {
102+
self::$uvLoop = \uv_default_loop();
103+
}
104+
105+
return self::$uvLoop;
106+
}
107+
108+
public static function resetUvLoop(): void
109+
{
110+
self::$uvLoop = null;
111+
}
112+
}

src/EventLoop/Handlers/SleepHandler.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,27 @@
99
* Decides when the event loop should sleep to conserve CPU,
1010
* based on pending timers and active fibers.
1111
*/
12-
final class SleepHandler
12+
class SleepHandler
1313
{
1414
/**
1515
* @var TimerManager Manages scheduled timers and their delays.
1616
*/
17-
private TimerManager $timerManager;
17+
protected TimerManager $timerManager;
1818

1919
/**
2020
* @var FiberManager Manages currently active fibers.
2121
*/
22-
private FiberManager $fiberManager;
22+
protected FiberManager $fiberManager;
2323

2424
/**
2525
* Minimum sleep duration in microseconds to actually perform usleep.
2626
*/
27-
private const MIN_SLEEP_THRESHOLD = 50;
27+
protected const MIN_SLEEP_THRESHOLD = 50;
2828

2929
/**
3030
* Maximum sleep duration in microseconds to avoid long pauses.
3131
*/
32-
private const MAX_SLEEP_DURATION = 500;
32+
protected const MAX_SLEEP_DURATION = 500;
3333

3434
/**
3535
* @param TimerManager $timerManager The timer manager to query next timer delays.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
namespace Rcalicdan\FiberAsync\EventLoop\Handlers;
4+
5+
use Rcalicdan\FiberAsync\EventLoop\Handlers\TickHandler;
6+
use Rcalicdan\FiberAsync\EventLoop\Managers\FiberManager;
7+
use Rcalicdan\FiberAsync\EventLoop\Managers\FileManager;
8+
use Rcalicdan\FiberAsync\EventLoop\Managers\HttpRequestManager;
9+
10+
/**
11+
* UV-aware work handler that integrates with libuv event loop
12+
*/
13+
final class UvWorkHandler extends WorkHandler
14+
{
15+
private $uvLoop;
16+
17+
// UV run mode constants (hardcoded since they're not defined)
18+
private const UV_RUN_DEFAULT = 0; // Run until no more active handles
19+
private const UV_RUN_ONCE = 1; // Run until at least one event is processed
20+
private const UV_RUN_NOWAIT = 2; // Process available events without blocking
21+
22+
public function __construct(
23+
$uvLoop,
24+
$timerManager,
25+
HttpRequestManager $httpRequestManager,
26+
$streamManager,
27+
FiberManager $fiberManager,
28+
TickHandler $tickHandler,
29+
FileManager $fileManager,
30+
$socketManager,
31+
) {
32+
$this->uvLoop = $uvLoop;
33+
34+
parent::__construct(
35+
$timerManager,
36+
$httpRequestManager,
37+
$streamManager,
38+
$fiberManager,
39+
$tickHandler,
40+
$fileManager,
41+
$socketManager
42+
);
43+
}
44+
45+
public function processWork(): bool
46+
{
47+
$workDone = false;
48+
49+
if ($this->tickHandler->processNextTickCallbacks()) {
50+
$workDone = true;
51+
}
52+
53+
// Use uv_run with mode 2 (UV_RUN_NOWAIT) for non-blocking execution
54+
if ($this->runUvLoop()) {
55+
$workDone = true;
56+
}
57+
58+
if ($this->timerManager->processTimers()) {
59+
$workDone = true;
60+
}
61+
62+
if ($this->socketManager->processSockets()) {
63+
$workDone = true;
64+
}
65+
66+
if ($this->streamManager->hasWatchers()) {
67+
$this->streamManager->processStreams();
68+
$workDone = true;
69+
}
70+
71+
if ($this->fiberManager->processFibers()) {
72+
$workDone = true;
73+
}
74+
75+
if ($this->httpRequestManager->processRequests()) {
76+
$workDone = true;
77+
}
78+
79+
if ($this->fileManager->processFileOperations()) {
80+
$workDone = true;
81+
}
82+
83+
if ($this->tickHandler->processDeferredCallbacks()) {
84+
$workDone = true;
85+
}
86+
87+
return $workDone;
88+
}
89+
90+
private function runUvLoop(): bool
91+
{
92+
try {
93+
$result = \uv_run($this->uvLoop, self::UV_RUN_NOWAIT);
94+
return $result > 0;
95+
} catch (\Error | \Exception $e) {
96+
error_log("UV loop error: " . $e->getMessage());
97+
return false;
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)