Skip to content

Commit 1200ac9

Browse files
committed
test defer function
1 parent 7438d56 commit 1200ac9

File tree

9 files changed

+1162
-7
lines changed

9 files changed

+1162
-7
lines changed

index.php

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
<?php
2+
require_once 'vendor/autoload.php';
3+
4+
use Rcalicdan\FiberAsync\EventLoop\EventLoop;
5+
6+
// Start output buffering to control response timing
7+
ob_start();
8+
?>
9+
<!DOCTYPE html>
10+
<html lang="en">
11+
12+
<head>
13+
<meta charset="UTF-8">
14+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
15+
<title>Web Defer Test</title>
16+
<style>
17+
body {
18+
font-family: Arial, sans-serif;
19+
max-width: 800px;
20+
margin: 0 auto;
21+
padding: 20px;
22+
background-color: #f5f5f5;
23+
}
24+
25+
.container {
26+
background: white;
27+
padding: 20px;
28+
border-radius: 8px;
29+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
30+
}
31+
32+
.status {
33+
padding: 10px;
34+
border-radius: 4px;
35+
margin: 10px 0;
36+
}
37+
38+
.success {
39+
background-color: #d4edda;
40+
color: #155724;
41+
}
42+
43+
.info {
44+
background-color: #d1ecf1;
45+
color: #0c5460;
46+
}
47+
48+
.warning {
49+
background-color: #fff3cd;
50+
color: #856404;
51+
}
52+
53+
button {
54+
background-color: #007bff;
55+
color: white;
56+
padding: 10px 20px;
57+
border: none;
58+
border-radius: 4px;
59+
cursor: pointer;
60+
margin: 5px;
61+
}
62+
63+
button:hover {
64+
background-color: #0056b3;
65+
}
66+
67+
.log-box {
68+
background-color: #f8f9fa;
69+
border: 1px solid #dee2e6;
70+
border-radius: 4px;
71+
padding: 15px;
72+
font-family: monospace;
73+
max-height: 400px;
74+
overflow-y: auto;
75+
white-space: pre-wrap;
76+
}
77+
78+
.form-group {
79+
margin-bottom: 15px;
80+
}
81+
82+
label {
83+
display: block;
84+
margin-bottom: 5px;
85+
font-weight: bold;
86+
}
87+
88+
input,
89+
textarea,
90+
select {
91+
width: 100%;
92+
padding: 8px;
93+
border: 1px solid #ddd;
94+
border-radius: 4px;
95+
box-sizing: border-box;
96+
}
97+
</style>
98+
</head>
99+
100+
<body>
101+
<div class="container">
102+
<h1>🌐 Web Defer Functionality Test</h1>
103+
104+
<?php if (empty($_POST)): ?>
105+
<!-- Test Selection Form -->
106+
<div class="status info">
107+
<strong>📋 Select a test to run:</strong><br>
108+
These tests simulate background tasks and demonstrate defer cleanup in web context.
109+
</div>
110+
111+
<form method="POST">
112+
<div class="form-group">
113+
<label for="test_type">Test Type:</label>
114+
<select name="test_type" id="test_type">
115+
<option value="email">📧 Background Email Processing</option>
116+
<option value="file">📁 File Processing with Cleanup</option>
117+
<option value="api">🌍 API Calls with Retry Queue</option>
118+
<option value="session">👤 User Session Logging</option>
119+
</select>
120+
</div>
121+
122+
<div class="form-group">
123+
<label for="duration">Processing Duration (seconds):</label>
124+
<input type="number" name="duration" id="duration" value="5" min="1" max="30">
125+
<small>How long the background task should run</small>
126+
</div>
127+
128+
<div class="form-group">
129+
<label for="early_exit">Simulate Early Exit:</label>
130+
<select name="early_exit" id="early_exit">
131+
<option value="no">No - Let task complete normally</option>
132+
<option value="yes">Yes - Terminate early to test defer cleanup</option>
133+
</select>
134+
</div>
135+
136+
<button type="submit">🚀 Run Test</button>
137+
</form>
138+
139+
<!-- Show existing log files -->
140+
<div style="margin-top: 30px;">
141+
<h3>📂 Previous Test Results:</h3>
142+
<?php
143+
$logFiles = glob("web_test_*.log");
144+
$jsonFiles = glob("web_*_queue*.json");
145+
146+
if (empty($logFiles) && empty($jsonFiles)) {
147+
echo '<div class="status info">No previous test results found.</div>';
148+
} else {
149+
echo '<div class="log-box">';
150+
foreach (array_merge($logFiles, $jsonFiles) as $file) {
151+
$size = filesize($file);
152+
$time = date('Y-m-d H:i:s', filemtime($file));
153+
echo "📄 <a href='view_log.php?file=" . urlencode($file) . "' target='_blank'>{$file}</a> ({$size} bytes, {$time})\n";
154+
}
155+
echo '</div>';
156+
}
157+
?>
158+
</div>
159+
160+
<?php else:
161+
// Process the test
162+
$testType = $_POST['test_type'] ?? 'email';
163+
$duration = (int)($_POST['duration'] ?? 5);
164+
$earlyExit = $_POST['early_exit'] === 'yes';
165+
166+
$testId = uniqid();
167+
$logFile = "web_test_{$testType}_{$testId}.log";
168+
169+
// Set up defer cleanup for web context
170+
process_defer(function () use ($testType, $testId, $logFile) {
171+
$timestamp = date('Y-m-d H:i:s');
172+
$logEntry = "[{$timestamp}] 🧹 WEB DEFER CLEANUP EXECUTED\n";
173+
$logEntry .= "[{$timestamp}] Test Type: {$testType}\n";
174+
$logEntry .= "[{$timestamp}] Test ID: {$testId}\n";
175+
$logEntry .= "[{$timestamp}] Cleanup completed after response sent to user\n";
176+
177+
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
178+
179+
// Also log to error log for verification
180+
error_log("Web defer cleanup executed for test: {$testType}_{$testId}");
181+
});
182+
183+
echo "<div class='status success'>✅ <strong>Test Started:</strong> {$testType} (ID: {$testId})</div>";
184+
echo "<div class='status info'>📊 <strong>Parameters:</strong> Duration: {$duration}s, Early Exit: " . ($earlyExit ? 'Yes' : 'No') . "</div>";
185+
186+
// Start the background task
187+
async(function () use ($testType, $duration, $earlyExit, $testId, $logFile) {
188+
$startTime = time();
189+
$logEntry = "[" . date('Y-m-d H:i:s') . "] 🚀 Background task started: {$testType}\n";
190+
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
191+
192+
for ($i = 1; $i <= $duration; $i++) {
193+
$logEntry = "[" . date('Y-m-d H:i:s') . "] Processing step {$i}/{$duration}\n";
194+
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
195+
196+
switch ($testType) {
197+
case 'email':
198+
$logEntry = "[" . date('Y-m-d H:i:s') . "] 📧 Sending email batch {$i}\n";
199+
break;
200+
case 'file':
201+
$logEntry = "[" . date('Y-m-d H:i:s') . "] 📁 Processing file batch {$i}\n";
202+
break;
203+
case 'api':
204+
$logEntry = "[" . date('Y-m-d H:i:s') . "] 🌍 Making API call {$i}\n";
205+
break;
206+
case 'session':
207+
$logEntry = "[" . date('Y-m-d H:i:s') . "] 👤 Logging user activity {$i}\n";
208+
break;
209+
}
210+
211+
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
212+
await(delay(1));
213+
214+
// Simulate early exit
215+
if ($earlyExit && $i >= 2) {
216+
$logEntry = "[" . date('Y-m-d H:i:s') . "] ⚠️ Simulating early exit at step {$i}\n";
217+
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
218+
219+
// Create unfinished work queue
220+
$unfinishedWork = [];
221+
for ($j = $i + 1; $j <= $duration; $j++) {
222+
$unfinishedWork[] = [
223+
'step' => $j,
224+
'type' => $testType,
225+
'status' => 'pending',
226+
'created_at' => date('Y-m-d H:i:s')
227+
];
228+
}
229+
230+
if (!empty($unfinishedWork)) {
231+
$queueFile = "web_{$testType}_queue_{$testId}.json";
232+
file_put_contents($queueFile, json_encode($unfinishedWork, JSON_PRETTY_PRINT));
233+
$logEntry = "[" . date('Y-m-d H:i:s') . "] 💾 Saved " . count($unfinishedWork) . " unfinished tasks to {$queueFile}\n";
234+
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
235+
}
236+
237+
exit(0); // Force early exit to test defer
238+
}
239+
}
240+
241+
$logEntry = "[" . date('Y-m-d H:i:s') . "] ✅ Background task completed successfully\n";
242+
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
243+
});
244+
?>
245+
246+
<div class="status warning">
247+
⏳ <strong>Background task is running...</strong><br>
248+
The task will continue processing in the background even after this page loads.
249+
Check the log files below to see the results.
250+
</div>
251+
252+
<div class="status info">
253+
📋 <strong>What's happening:</strong><br>
254+
1. This response is sent to your browser immediately<br>
255+
2. Background task continues processing for <?= $duration ?> seconds<br>
256+
<?php if ($earlyExit): ?>
257+
3. Task will exit early around step 2 to test defer cleanup<br>
258+
4. Defer cleanup will execute after early exit<br>
259+
<?php else: ?>
260+
3. Task will complete all <?= $duration ?> steps<br>
261+
4. Defer cleanup will execute after task completion<br>
262+
<?php endif; ?>
263+
5. All activity is logged to: <strong><?= $logFile ?></strong>
264+
</div>
265+
266+
<div style="margin-top: 20px;">
267+
<button onclick="window.location.reload()">🔄 Run Another Test</button>
268+
<button onclick="location.href='view_log.php?file=<?= urlencode($logFile) ?>'">📄 View Log File</button>
269+
<button onclick="location.href='web_defer_test.php'">🏠 Back to Home</button>
270+
</div>
271+
272+
<script>
273+
// Auto-refresh log file link after a few seconds
274+
setTimeout(function() {
275+
console.log('Background task should be running...');
276+
}, 2000);
277+
</script>
278+
<?php endif; ?>
279+
</div>
280+
</body>
281+
282+
</html>
283+
284+
<?php
285+
// Send response to user immediately, but keep PHP running for background tasks
286+
if (!empty($_POST)) {
287+
// Flush output to user
288+
$output = ob_get_contents();
289+
ob_end_clean();
290+
291+
echo $output;
292+
293+
// Send response to user immediately
294+
if (function_exists('fastcgi_finish_request')) {
295+
fastcgi_finish_request();
296+
} else {
297+
// For non-FastCGI environments
298+
if (ob_get_level()) {
299+
ob_end_flush();
300+
}
301+
flush();
302+
}
303+
304+
// Now run the event loop for background processing
305+
306+
EventLoop::getInstance()->run();
307+
}
308+
?>

log.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello, world!

src/EventLoop/EventLoop.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Rcalicdan\FiberAsync\EventLoop\Detectors\UvDetector;
77
use Rcalicdan\FiberAsync\EventLoop\Factories\EventLoopComponentFactory;
88
use Rcalicdan\FiberAsync\EventLoop\Handlers\ActivityHandler;
9+
use Rcalicdan\FiberAsync\EventLoop\Handlers\ProcessDeferHandler;
910
use Rcalicdan\FiberAsync\EventLoop\Handlers\SleepHandler;
1011
use Rcalicdan\FiberAsync\EventLoop\Handlers\StateHandler;
1112
use Rcalicdan\FiberAsync\EventLoop\Handlers\TickHandler;
@@ -69,6 +70,11 @@ class EventLoop implements EventLoopInterface
6970
*/
7071
private ActivityHandler $activityHandler;
7172

73+
/**
74+
* @var ProcessDeferHandler Handles deferred callbacks for process shutdown
75+
*/
76+
private ProcessDeferHandler $processDeferHandler;
77+
7278
/**
7379
* @var StateHandler Manages the running state of the event loop
7480
*/
@@ -87,6 +93,7 @@ class EventLoop implements EventLoopInterface
8793

8894
private function __construct()
8995
{
96+
$this->processDeferHandler = new ProcessDeferHandler();
9097
$this->timerManager = EventLoopComponentFactory::createTimerManager();
9198
$this->httpRequestManager = new HttpRequestManager;
9299
$this->streamManager = EventLoopComponentFactory::createStreamManager();
@@ -113,6 +120,36 @@ private function __construct()
113120
);
114121
}
115122

123+
/**
124+
* Schedule a callback to run when the process terminates.
125+
*
126+
* Similar to Go's defer statement or Laravel's defer helper.
127+
* Callbacks are executed in LIFO order (last registered, first executed).
128+
*
129+
* @param callable $callback Function to execute on process termination
130+
* @param object|null $context Optional context object for scoped deferred callbacks
131+
*/
132+
public function processDefer(callable $callback, ?object $context = null): void
133+
{
134+
$this->processDeferHandler->addProcessDeferred($callback, $context);
135+
}
136+
137+
/**
138+
* Get the number of pending process-deferred callbacks.
139+
*/
140+
public function getPendingDeferredCount(): int
141+
{
142+
return $this->processDeferHandler->getPendingCount();
143+
}
144+
145+
/**
146+
* Manually execute all deferred callbacks (mainly for testing).
147+
*/
148+
public function executeDeferredCallbacks(): void
149+
{
150+
$this->processDeferHandler->executeDeferred();
151+
}
152+
116153
/**
117154
* Check if event loop is using UV extension.
118155
*

0 commit comments

Comments
 (0)