Skip to content

Commit 121b23e

Browse files
committed
wip
1 parent af86430 commit 121b23e

21 files changed

Lines changed: 1010 additions & 22 deletions

app/Config/WorkerMode.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
namespace Config;
4+
5+
/**
6+
* This configuration controls how CodeIgniter behaves when running
7+
* in worker mode (with FrankenPHP).
8+
*/
9+
class WorkerMode
10+
{
11+
/**
12+
* Persistent Services
13+
*
14+
* List of service names that should persist across requests.
15+
* These services will NOT be reset between requests.
16+
*
17+
* Services not in this list will be reset for each request to prevent
18+
* state leakage.
19+
*
20+
* Recommended persistent services:
21+
* - `autoloader`: PSR-4 autoloading configuration
22+
* - `locator`: File locator
23+
* - `exceptions`: Exception handler
24+
* - `logger`: Logger instance
25+
* - `timer`: Performance timer
26+
* - `commands`: CLI commands registry
27+
* - `codeigniter`: Main application instance
28+
* - `superglobals`: Superglobals wrapper
29+
*
30+
* @var list<string>
31+
*/
32+
public array $persistentServices = [
33+
'autoloader',
34+
'locator',
35+
'exceptions',
36+
'logger',
37+
'timer',
38+
'commands',
39+
'codeigniter',
40+
'superglobals',
41+
];
42+
43+
/**
44+
* Database connection strategy
45+
*
46+
* Determines how database connections are handled between requests:
47+
*
48+
* - 'keep-alive' (recommended):
49+
* Keeps connections alive across requests for best performance
50+
* - 'disconnect':
51+
* Closes all connections after each request
52+
*
53+
* @var 'keep-alive'|'disconnect'
54+
*/
55+
public string $databaseStrategy = 'keep-alive';
56+
57+
/**
58+
* Cache handler strategy
59+
*
60+
* Determines how cache handlers are managed between requests:
61+
*
62+
* - 'keep-alive' (recommended):
63+
* Keeps cache handler alive across requests for best performance
64+
* - 'disconnect':
65+
* Closes the handler after each request
66+
*
67+
* @var 'keep-alive'|'disconnect'
68+
*/
69+
public string $cacheStrategy = 'keep-alive';
70+
71+
/**
72+
* Reset Factories
73+
*
74+
* Whether to reset Factories (Models, etc.) between requests.
75+
* Set to false if you want to cache model instances across requests.
76+
*/
77+
public bool $resetFactories = true;
78+
79+
/**
80+
* Garbage Collection
81+
*
82+
* Whether to force garbage collection after each request.
83+
* Helps prevent memory leaks at a small performance cost.
84+
*/
85+
public bool $garbageCollection = true;
86+
}

system/Boot.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,39 @@ public static function bootWeb(Paths $paths): int
7676
return EXIT_SUCCESS;
7777
}
7878

79+
/**
80+
* Bootstrap for FrankenPHP worker mode.
81+
*
82+
* This method performs one-time initialization for worker mode,
83+
* loading everything except the CodeIgniter instance, which should
84+
* be created fresh for each request.
85+
*
86+
* @used-by `public/frankenphp-worker.php`
87+
*/
88+
public static function bootWorker(Paths $paths): CodeIgniter
89+
{
90+
static::definePathConstants($paths);
91+
if (! defined('APP_NAMESPACE')) {
92+
static::loadConstants();
93+
}
94+
static::checkMissingExtensions();
95+
96+
static::loadDotEnv($paths);
97+
static::defineEnvironment();
98+
static::loadEnvironmentBootstrap($paths);
99+
100+
static::loadCommonFunctions();
101+
static::loadAutoloader();
102+
static::setExceptionHandler();
103+
static::initializeKint();
104+
105+
// Note: We skip config caching in worker mode
106+
// as it may cause issues with state between requests
107+
static::autoloadHelpers();
108+
109+
return Boot::initializeCodeIgniter();
110+
}
111+
79112
/**
80113
* Used by command line scripts other than
81114
* * `spark`

system/Cache/CacheFactory.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ public static function getHandler(Cache $config, ?string $handler = null, ?strin
8686
$adapter = self::getHandler($config, $backup, 'dummy');
8787
}
8888

89+
// Wrap with reconnect decorator for automatic reconnection on failure
90+
$adapter = new ReconnectCacheDecorator($adapter);
91+
8992
return $adapter;
9093
}
9194
}

system/Cache/Handlers/MemcachedHandler.php

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,6 @@ public function __construct(Cache $config)
5757
$this->config = array_merge($this->config, $config->memcached);
5858
}
5959

60-
/**
61-
* Closes the connection to Memcache(d) if present.
62-
*/
63-
public function __destruct()
64-
{
65-
if ($this->memcached instanceof Memcached) {
66-
$this->memcached->quit();
67-
} elseif ($this->memcached instanceof Memcache) {
68-
$this->memcached->close();
69-
}
70-
}
71-
7260
public function initialize(): void
7361
{
7462
try {
@@ -230,4 +218,35 @@ public function isSupported(): bool
230218
{
231219
return extension_loaded('memcached') || extension_loaded('memcache');
232220
}
221+
222+
/**
223+
* Reconnect to Memcached server
224+
*
225+
* Used by ReconnectCacheDecorator to restore connection after failure.
226+
*/
227+
public function reconnect(): bool
228+
{
229+
// Close existing connection to avoid resource leak
230+
if (isset($this->memcached)) {
231+
try {
232+
if ($this->memcached instanceof Memcached) {
233+
$this->memcached->quit();
234+
} elseif ($this->memcached instanceof Memcache) {
235+
$this->memcached->close();
236+
}
237+
} catch (Exception $e) {
238+
// Connection already dead, that's fine
239+
}
240+
}
241+
242+
try {
243+
$this->initialize();
244+
245+
return true;
246+
} catch (CriticalError $e) {
247+
log_message('error', 'Cache: Memcached reconnection failed: ' . $e->getMessage());
248+
249+
return false;
250+
}
251+
}
233252
}

system/Cache/Handlers/PredisHandler.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,31 @@ public function isSupported(): bool
204204
{
205205
return class_exists(Client::class);
206206
}
207+
208+
/**
209+
* Reconnect to Redis server via Predis
210+
*
211+
* Used by ReconnectCacheDecorator to restore connection after failure.
212+
*/
213+
public function reconnect(): bool
214+
{
215+
// Disconnect existing connection to avoid resource leak
216+
if (isset($this->redis)) {
217+
try {
218+
$this->redis->disconnect();
219+
} catch (Exception $e) {
220+
// Connection already dead, that's fine
221+
}
222+
}
223+
224+
try {
225+
$this->initialize();
226+
227+
return true;
228+
} catch (CriticalError $e) {
229+
log_message('error', 'Cache: Predis reconnection failed: ' . $e->getMessage());
230+
231+
return false;
232+
}
233+
}
207234
}

system/Cache/Handlers/RedisHandler.php

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,6 @@ public function __construct(Cache $config)
6464
$this->config = array_merge($this->config, $config->redis);
6565
}
6666

67-
/**
68-
* Closes the connection to Redis if present.
69-
*/
70-
public function __destruct()
71-
{
72-
if (isset($this->redis)) {
73-
$this->redis->close();
74-
}
75-
}
76-
7767
public function initialize(): void
7868
{
7969
$config = $this->config;
@@ -229,4 +219,31 @@ public function isSupported(): bool
229219
{
230220
return extension_loaded('redis');
231221
}
222+
223+
/**
224+
* Reconnect to Redis server
225+
*
226+
* Used by ReconnectCacheDecorator to restore connection after failure.
227+
*/
228+
public function reconnect(): bool
229+
{
230+
// Close existing connection to avoid resource leak
231+
if (isset($this->redis)) {
232+
try {
233+
$this->redis->close();
234+
} catch (RedisException $e) {
235+
// Connection already dead, that's fine
236+
}
237+
}
238+
239+
try {
240+
$this->initialize();
241+
242+
return true;
243+
} catch (CriticalError $e) {
244+
log_message('error', 'Cache: Redis reconnection failed: ' . $e->getMessage());
245+
246+
return false;
247+
}
248+
}
232249
}

0 commit comments

Comments
 (0)