1
1
<?php
2
2
3
+ use Rcalicdan \FiberAsync \Promise \Promise ;
3
4
4
- use Rcalicdan \FiberAsync \Api \Timer ;
5
5
6
6
require_once __DIR__ . '/vendor/autoload.php ' ;
7
7
8
- function logWithTimestamp (string $ message ): void {
9
- $ timestamp = number_format (microtime (true ) - $ _SERVER ['REQUEST_TIME_FLOAT ' ], 4 );
10
- echo "[ {$ timestamp }s] {$ message }" . PHP_EOL ;
11
- }
8
+ $ microtime = microtime (true );
9
+ $ results = run (function () {
10
+ $ Promise = Promise::race ([
11
+ 'google ' => http ()->get ('https://www.google.com ' )->then (fn ()=>print "google wins \n" ),
12
+ 'facebook ' => http ()->get ('https://www.facebook.com ' )->then (fn ()=>print "facebook wins \n" ),
13
+ 'linkedIn ' => http ()->get ('https://www.linkedin.com ' )->then (fn ()=>print "linkedIn wins \n" ),
14
+ 'instagram ' => http ()->get ('https://www.instagram.com ' )->then (fn ()=>print "instagram wins \n" ),
15
+ ]);
12
16
13
- $ startTime = microtime (true );
14
- $ _SERVER ['REQUEST_TIME_FLOAT ' ] = $ startTime ;
15
-
16
- logWithTimestamp ("=== Testing Cancellable Streaming Promises === \n" );
17
-
18
- // Test 1: Cancel a streaming request mid-way
19
- logWithTimestamp ("--- Test 1: Cancel streaming request after 1 second --- " );
20
-
21
- $ test1Result = run (function () {
22
- logWithTimestamp ("Starting long-running stream (5 second delay)... " );
23
-
24
- $ chunkCount = 0 ;
25
- $ streamPromise = http_stream ('https://httpbin.org/delay/5 ' , [
26
- 'timeout ' => 10
27
- ], function ($ chunk ) use (&$ chunkCount ) {
28
- $ chunkCount ++;
29
- logWithTimestamp ("Stream chunk # {$ chunkCount } received: " . strlen ($ chunk ) . " bytes " );
30
- });
31
-
32
- // Set up cancellation after 1 second using Timer::delay
33
- logWithTimestamp ("Setting up cancellation timer for 1 second... " );
34
- Timer::delay (1.0 )->then (function () use ($ streamPromise ) {
35
- logWithTimestamp ("⚠️ CANCELLING STREAM NOW! " );
36
- $ streamPromise ->cancel ();
37
- logWithTimestamp ("Cancel signal sent " );
38
- });
39
-
40
- try {
41
- logWithTimestamp ("Waiting for stream result... " );
42
- $ result = await ($ streamPromise );
43
- logWithTimestamp ("❌ ERROR: Stream completed instead of being cancelled! " );
44
- return ['status ' => 'completed ' , 'chunks ' => $ chunkCount ];
45
- } catch (Exception $ e ) {
46
- logWithTimestamp ("✅ SUCCESS: Stream was cancelled - " . $ e ->getMessage ());
47
- return ['status ' => 'cancelled ' , 'chunks ' => $ chunkCount , 'error ' => $ e ->getMessage ()];
48
- }
49
- });
50
-
51
- logWithTimestamp ("Test 1 result: " . $ test1Result ['status ' ]);
52
- logWithTimestamp ("Chunks received before cancellation: " . $ test1Result ['chunks ' ]);
53
- logWithTimestamp ("Is promise cancelled: " . (method_exists ($ streamPromise ?? null , 'isCancelled ' ) ? ($ streamPromise ->isCancelled () ? 'Yes ' : 'No ' ) : 'Unknown ' ));
54
-
55
- echo "\n" ;
56
-
57
- // Test 2: Cancel a download mid-way
58
- logWithTimestamp ("--- Test 2: Cancel file download after 0.5 seconds --- " );
59
-
60
- $ test2Result = run (function () {
61
- $ tempFile = sys_get_temp_dir () . '/cancel_test_ ' . uniqid () . '.tmp ' ;
62
- logWithTimestamp ("Starting download to: " . basename ($ tempFile ));
63
-
64
- // Use a larger file for download to ensure we can cancel it
65
- $ downloadPromise = http_download ('https://httpbin.org/delay/3 ' , $ tempFile );
66
-
67
- // Cancel after 0.5 seconds using Timer::delay
68
- logWithTimestamp ("Setting up cancellation timer for 0.5 seconds... " );
69
- Timer::delay (0.5 )->then (function () use ($ downloadPromise ) {
70
- logWithTimestamp ("⚠️ CANCELLING DOWNLOAD NOW! " );
71
- $ downloadPromise ->cancel ();
72
- logWithTimestamp ("Download cancel signal sent " );
73
- });
74
-
75
- try {
76
- logWithTimestamp ("Waiting for download result... " );
77
- $ result = await ($ downloadPromise );
78
- logWithTimestamp ("❌ ERROR: Download completed instead of being cancelled! " );
79
-
80
- // Clean up file if it exists
81
- if (file_exists ($ result ['file ' ])) {
82
- unlink ($ result ['file ' ]);
83
- }
84
-
85
- return ['status ' => 'completed ' ];
86
- } catch (Exception $ e ) {
87
- logWithTimestamp ("✅ SUCCESS: Download was cancelled - " . $ e ->getMessage ());
88
-
89
- // Verify file was cleaned up
90
- $ fileExists = file_exists ($ tempFile );
91
- logWithTimestamp ("Partial file cleaned up: " . ($ fileExists ? "❌ No " : "✅ Yes " ));
92
-
93
- return [
94
- 'status ' => 'cancelled ' ,
95
- 'file_cleaned ' => !$ fileExists ,
96
- 'error ' => $ e ->getMessage (),
97
- 'is_cancelled ' => $ downloadPromise ->isCancelled ()
98
- ];
99
- }
100
- });
101
-
102
- logWithTimestamp ("Test 2 result: " . $ test2Result ['status ' ]);
103
- if (isset ($ test2Result ['file_cleaned ' ])) {
104
- logWithTimestamp ("File cleanup successful: " . ($ test2Result ['file_cleaned ' ] ? "✅ Yes " : "❌ No " ));
105
- logWithTimestamp ("Promise cancelled status: " . ($ test2Result ['is_cancelled ' ] ? "✅ Yes " : "❌ No " ));
106
- }
107
-
108
- echo "\n" ;
109
-
110
- // Test 3: Test immediate cancellation
111
- logWithTimestamp ("--- Test 3: Immediate cancellation --- " );
112
-
113
- $ test3Result = run (function () {
114
- logWithTimestamp ("Creating stream promise... " );
115
-
116
- $ streamPromise = http_stream ('https://httpbin.org/delay/2 ' , [], function ($ chunk ) {
117
- logWithTimestamp ("❌ This should not be called - chunk received: " . strlen ($ chunk ) . " bytes " );
118
- });
119
-
120
- logWithTimestamp ("Checking initial cancelled status: " . ($ streamPromise ->isCancelled () ? "Yes " : "No " ));
121
- logWithTimestamp ("Cancelling immediately... " );
122
- $ streamPromise ->cancel ();
123
- logWithTimestamp ("Cancelled status after cancel(): " . ($ streamPromise ->isCancelled () ? "Yes " : "No " ));
124
-
125
- try {
126
- $ result = await ($ streamPromise );
127
- logWithTimestamp ("❌ ERROR: Stream completed despite immediate cancellation! " );
128
- return ['status ' => 'completed ' ];
129
- } catch (Exception $ e ) {
130
- logWithTimestamp ("✅ SUCCESS: Immediate cancellation worked - " . $ e ->getMessage ());
131
- return ['status ' => 'cancelled ' , 'error ' => $ e ->getMessage (), 'is_cancelled ' => $ streamPromise ->isCancelled ()];
132
- }
133
- });
134
-
135
- logWithTimestamp ("Test 3 result: " . $ test3Result ['status ' ]);
136
- logWithTimestamp ("Final cancelled status: " . ($ test3Result ['is_cancelled ' ] ? "✅ Yes " : "❌ No " ));
137
-
138
- echo "\n" ;
139
-
140
- // Test 4: Test cancellation with multiple promises
141
- logWithTimestamp ("--- Test 4: Cancel individual promises in concurrent execution --- " );
142
-
143
- $ test4Result = run (function () {
144
- logWithTimestamp ("Starting multiple streams... " );
145
-
146
- $ stream1Chunks = 0 ;
147
- $ stream2Chunks = 0 ;
148
-
149
- $ stream1 = http_stream ('https://httpbin.org/delay/1 ' , [], function ($ chunk ) use (&$ stream1Chunks ) {
150
- $ stream1Chunks ++;
151
- logWithTimestamp ("Stream1 chunk: " . strlen ($ chunk ) . " bytes " );
152
- });
153
-
154
- $ stream2 = http_stream ('https://httpbin.org/delay/4 ' , [], function ($ chunk ) use (&$ stream2Chunks ) {
155
- $ stream2Chunks ++;
156
- logWithTimestamp ("Stream2 (will be cancelled) chunk: " . strlen ($ chunk ) . " bytes " );
157
- });
158
-
159
- // Cancel stream2 after 1.5 seconds
160
- Timer::delay (1.5 )->then (function () use ($ stream2 ) {
161
- logWithTimestamp ("⚠️ CANCELLING STREAM2 after 1.5s! " );
162
- $ stream2 ->cancel ();
163
- logWithTimestamp ("Stream2 cancelled status: " . ($ stream2 ->isCancelled () ? "Yes " : "No " ));
164
- });
165
-
166
- // Wait for both individually to see their results
167
- $ results = [];
168
-
169
- try {
170
- $ results ['stream1 ' ] = await ($ stream1 );
171
- logWithTimestamp ("✅ Stream1 completed successfully " );
172
- } catch (Exception $ e ) {
173
- logWithTimestamp ("Stream1 failed: " . $ e ->getMessage ());
174
- $ results ['stream1 ' ] = null ;
175
- }
176
-
177
- try {
178
- $ results ['stream2 ' ] = await ($ stream2 );
179
- logWithTimestamp ("❌ Stream2 completed (should have been cancelled) " );
180
- } catch (Exception $ e ) {
181
- logWithTimestamp ("✅ Stream2 was cancelled: " . $ e ->getMessage ());
182
- $ results ['stream2 ' ] = null ;
183
- }
184
-
185
- return [
186
- 'stream1_completed ' => $ results ['stream1 ' ] !== null ,
187
- 'stream2_cancelled ' => $ stream2 ->isCancelled (),
188
- 'stream1_chunks ' => $ stream1Chunks ,
189
- 'stream2_chunks ' => $ stream2Chunks ,
190
- ];
17
+ return await ($ Promise );
191
18
});
192
19
193
- logWithTimestamp ("Test 4 results: " );
194
- logWithTimestamp ("- Stream1 completed: " . ($ test4Result ['stream1_completed ' ] ? "✅ Yes " : "❌ No " ));
195
- logWithTimestamp ("- Stream2 cancelled: " . ($ test4Result ['stream2_cancelled ' ] ? "✅ Yes " : "❌ No " ));
196
- logWithTimestamp ("- Stream1 chunks: " . $ test4Result ['stream1_chunks ' ]);
197
- logWithTimestamp ("- Stream2 chunks: " . $ test4Result ['stream2_chunks ' ]);
20
+ $ microtime = microtime (true ) - $ microtime ;
21
+ echo $ microtime . PHP_EOL ;
198
22
199
- echo "\n" ;
200
23
201
- // Test 5: Test cancel handler is called
202
- logWithTimestamp ("--- Test 5: Verify cancel handler is called --- " );
203
-
204
- $ test5Result = run (function () {
205
- $ cancelHandlerCalled = false ;
206
- $ cleanupExecuted = false ;
207
-
208
- logWithTimestamp ("Creating stream with custom cancel handler... " );
209
-
210
- $ streamPromise = http_stream ('https://httpbin.org/delay/3 ' , [], function ($ chunk ) {
211
- logWithTimestamp ("Chunk received: " . strlen ($ chunk ) . " bytes " );
212
- });
213
-
214
- // Add additional cancel handler to test if it's called
215
- $ originalSetCancelHandler = [$ streamPromise , 'setCancelHandler ' ];
216
- if (is_callable ($ originalSetCancelHandler )) {
217
- // Get the original handler first
218
- $ streamPromise ->setCancelHandler (function () use (&$ cancelHandlerCalled , &$ cleanupExecuted ) {
219
- $ cancelHandlerCalled = true ;
220
- logWithTimestamp ("🔧 Custom cancel handler called! " );
221
-
222
- // Simulate cleanup
223
- $ cleanupExecuted = true ;
224
- logWithTimestamp ("🧹 Cleanup executed in cancel handler " );
225
- });
226
- }
227
-
228
- // Cancel after 1 second
229
- Timer::delay (1.0 )->then (function () use ($ streamPromise ) {
230
- logWithTimestamp ("⚠️ Cancelling stream to test cancel handler... " );
231
- $ streamPromise ->cancel ();
232
- });
233
-
234
- try {
235
- await ($ streamPromise );
236
- logWithTimestamp ("❌ Stream completed unexpectedly " );
237
- return ['completed ' => true ];
238
- } catch (Exception $ e ) {
239
- logWithTimestamp ("✅ Stream cancelled: " . $ e ->getMessage ());
240
-
241
- return [
242
- 'cancelled ' => true ,
243
- 'cancel_handler_called ' => $ cancelHandlerCalled ,
244
- 'cleanup_executed ' => $ cleanupExecuted ,
245
- 'is_cancelled ' => $ streamPromise ->isCancelled ()
246
- ];
247
- }
248
- });
24
+
249
25
250
- if (isset ($ test5Result ['cancelled ' ])) {
251
- logWithTimestamp ("Test 5 results: " );
252
- logWithTimestamp ("- Promise cancelled: " . ($ test5Result ['is_cancelled ' ] ? "✅ Yes " : "❌ No " ));
253
- logWithTimestamp ("- Cancel handler called: " . ($ test5Result ['cancel_handler_called ' ] ? "✅ Yes " : "❌ No " ));
254
- logWithTimestamp ("- Cleanup executed: " . ($ test5Result ['cleanup_executed ' ] ? "✅ Yes " : "❌ No " ));
255
- }
256
26
257
- $ totalTime = microtime (true ) - $ startTime ;
258
- logWithTimestamp ("\n=== All cancellation tests completed in " . number_format ($ totalTime , 4 ) . "s === " );
0 commit comments