8
8
use React \EventLoop \Timer \Timer ;
9
9
use React \EventLoop \Timer \TimerInterface ;
10
10
use SplObjectStorage ;
11
- use stdClass ;
12
11
13
12
/**
14
13
* An ext-event based event-loop.
@@ -17,10 +16,14 @@ class ExtEventLoop implements LoopInterface
17
16
{
18
17
private $ eventBase ;
19
18
private $ nextTickQueue ;
19
+ private $ timerCallback ;
20
20
private $ timerEvents ;
21
- private $ streamEvents ;
21
+ private $ streamCallback ;
22
+ private $ streamEvents = [];
23
+ private $ streamFlags = [];
24
+ private $ readListeners = [];
25
+ private $ writeListeners = [];
22
26
private $ running ;
23
- private $ keepAlive ;
24
27
25
28
/**
26
29
* @param EventBase|null $eventBase The libevent event base object.
@@ -34,13 +37,9 @@ public function __construct(EventBase $eventBase = null)
34
37
$ this ->eventBase = $ eventBase ;
35
38
$ this ->nextTickQueue = new NextTickQueue ($ this );
36
39
$ this ->timerEvents = new SplObjectStorage ;
37
- $ this ->streamEvents = [];
38
40
39
- // Closures for cancelled timers and removed stream listeners are
40
- // kept in the keepAlive array until the flushEvents() is complete
41
- // to prevent the PHP fatal error caused by ext-event:
42
- // "Cannot destroy active lambda function"
43
- $ this ->keepAlive = [];
41
+ $ this ->createTimerCallback ();
42
+ $ this ->createStreamCallback ();
44
43
}
45
44
46
45
/**
@@ -51,7 +50,12 @@ public function __construct(EventBase $eventBase = null)
51
50
*/
52
51
public function addReadStream ($ stream , $ listener )
53
52
{
54
- $ this ->addStreamEvent ($ stream , Event::READ , $ listener );
53
+ $ key = (int ) $ stream ;
54
+
55
+ if (!isset ($ this ->readListeners [$ key ])) {
56
+ $ this ->readListeners [$ key ] = $ listener ;
57
+ $ this ->subscribeStreamEvent ($ stream , Event::READ );
58
+ }
55
59
}
56
60
57
61
/**
@@ -62,7 +66,12 @@ public function addReadStream($stream, $listener)
62
66
*/
63
67
public function addWriteStream ($ stream , $ listener )
64
68
{
65
- $ this ->addStreamEvent ($ stream , Event::WRITE , $ listener );
69
+ $ key = (int ) $ stream ;
70
+
71
+ if (!isset ($ this ->writeListeners [$ key ])) {
72
+ $ this ->writeListeners [$ key ] = $ listener ;
73
+ $ this ->subscribeStreamEvent ($ stream , Event::WRITE , $ listener );
74
+ }
66
75
}
67
76
68
77
/**
@@ -72,7 +81,12 @@ public function addWriteStream($stream, $listener)
72
81
*/
73
82
public function removeReadStream ($ stream )
74
83
{
75
- $ this ->removeStreamEvent ($ stream , Event::READ );
84
+ $ key = (int ) $ stream ;
85
+
86
+ if (isset ($ this ->readListeners [$ key ])) {
87
+ unset($ this ->readListeners [$ key ]);
88
+ $ this ->unsubscribeStreamEvent ($ stream , Event::READ );
89
+ }
76
90
}
77
91
78
92
/**
@@ -82,7 +96,12 @@ public function removeReadStream($stream)
82
96
*/
83
97
public function removeWriteStream ($ stream )
84
98
{
85
- $ this ->removeStreamEvent ($ stream , Event::WRITE );
99
+ $ key = (int ) $ stream ;
100
+
101
+ if (isset ($ this ->writeListeners [$ key ])) {
102
+ unset($ this ->writeListeners [$ key ]);
103
+ $ this ->unsubscribeStreamEvent ($ stream , Event::WRITE );
104
+ }
86
105
}
87
106
88
107
/**
@@ -94,17 +113,16 @@ public function removeStream($stream)
94
113
{
95
114
$ key = (int ) $ stream ;
96
115
97
- if (!isset ($ this ->streamEvents [$ key ])) {
98
- return ;
99
- }
100
-
101
- $ entry = $ this ->streamEvents [$ key ];
102
-
103
- $ entry ->event ->free ();
104
-
105
- unset($ this ->streamEvents [$ key ]);
116
+ if (isset ($ this ->streamEvents [$ key ])) {
117
+ $ this ->streamEvents [$ key ]->free ();
106
118
107
- $ this ->keepAlive [] = $ entry ->callback ;
119
+ unset(
120
+ $ this ->streamFlags [$ key ],
121
+ $ this ->streamEvents [$ key ],
122
+ $ this ->readListeners [$ key ],
123
+ $ this ->writeListeners [$ key ]
124
+ );
125
+ }
108
126
}
109
127
110
128
/**
@@ -155,13 +173,8 @@ public function addPeriodicTimer($interval, $callback)
155
173
public function cancelTimer (TimerInterface $ timer )
156
174
{
157
175
if ($ this ->isTimerActive ($ timer )) {
158
- $ entry = $ this ->timerEvents [$ timer ];
159
-
176
+ $ this ->timerEvents [$ timer ]->free ();
160
177
$ this ->timerEvents ->detach ($ timer );
161
-
162
- $ entry ->event ->free ();
163
-
164
- $ this ->keepAlive [] = $ entry ->callback ;
165
178
}
166
179
}
167
180
@@ -200,8 +213,6 @@ public function tick()
200
213
$ this ->nextTickQueue ->tick ();
201
214
202
215
$ this ->eventBase ->loop (EventBase::LOOP_ONCE | EventBase::LOOP_NONBLOCK );
203
-
204
- $ this ->keepAlive = [];
205
216
}
206
217
207
218
/**
@@ -213,20 +224,13 @@ public function run()
213
224
214
225
while ($ this ->running ) {
215
226
216
- if (
217
- !$ this ->streamEvents
218
- && !$ this ->timerEvents ->count ()
219
- && $ this ->nextTickQueue ->isEmpty ()
220
- ) {
227
+ $ this ->nextTickQueue ->tick ();
228
+
229
+ if (!$ this ->streamEvents && !$ this ->timerEvents ->count ()) {
221
230
break ;
222
231
}
223
232
224
- $ this ->nextTickQueue ->tick ();
225
-
226
233
$ this ->eventBase ->loop (EventBase::LOOP_ONCE );
227
-
228
- $ this ->keepAlive = [];
229
-
230
234
}
231
235
}
232
236
@@ -251,74 +255,48 @@ protected function scheduleTimer(TimerInterface $timer)
251
255
$ flags |= Event::PERSIST ;
252
256
}
253
257
254
- $ entry = new stdClass ;
255
- $ entry ->callback = function () use ($ timer ) {
256
- call_user_func ($ timer ->getCallback (), $ timer );
257
-
258
- // Clean-up one shot timers ...
259
- if ($ this ->isTimerActive ($ timer ) && !$ timer ->isPeriodic ()) {
260
- $ this ->cancelTimer ($ timer );
261
- }
262
- };
263
-
264
- $ entry ->event = new Event (
258
+ $ this ->timerEvents [$ timer ] = $ event = new Event (
265
259
$ this ->eventBase ,
266
260
-1 ,
267
261
$ flags ,
268
- $ entry ->callback
262
+ $ this ->timerCallback ,
263
+ $ timer
269
264
);
270
265
271
- $ this ->timerEvents ->attach ($ timer , $ entry );
272
-
273
- $ entry ->event ->add ($ timer ->getInterval ());
266
+ $ event ->add ($ timer ->getInterval ());
274
267
}
275
268
276
269
/**
277
270
* Create a new ext-event Event object, or update the existing one.
278
271
*
279
- * @param stream $stream
280
- * @param integer $flag Event::READ or Event::WRITE
281
- * @param callable $listener
272
+ * @param stream $stream
273
+ * @param integer $flag Event::READ or Event::WRITE
282
274
*/
283
- protected function addStreamEvent ($ stream , $ flag, $ listener )
275
+ protected function subscribeStreamEvent ($ stream , $ flag )
284
276
{
285
277
$ key = (int ) $ stream ;
286
278
287
279
if (isset ($ this ->streamEvents [$ key ])) {
288
- $ entry = $ this ->streamEvents [$ key ];
289
- } else {
290
- $ entry = new stdClass ;
291
- $ entry ->event = null ;
292
- $ entry ->flags = 0 ;
293
- $ entry ->listeners = [
294
- Event::READ => null ,
295
- Event::WRITE => null ,
296
- ];
297
-
298
- $ entry ->callback = function ($ stream , $ flags , $ loop ) use ($ entry ) {
299
- foreach ([Event::READ , Event::WRITE ] as $ flag ) {
300
- if (
301
- $ flag === ($ flags & $ flag ) &&
302
- is_callable ($ entry ->listeners [$ flag ])
303
- ) {
304
- call_user_func (
305
- $ entry ->listeners [$ flag ],
306
- $ stream ,
307
- $ this
308
- );
309
- }
310
- }
311
- };
312
-
313
- $ this ->streamEvents [$ key ] = $ entry ;
314
- }
280
+ $ event = $ this ->streamEvents [$ key ];
315
281
316
- $ entry ->listeners [$ flag ] = $ listener ;
317
- $ entry ->flags |= $ flag ;
282
+ $ event ->del ();
318
283
319
- $ this ->configureStreamEvent ($ entry , $ stream );
284
+ $ event ->set (
285
+ $ this ->eventBase ,
286
+ $ stream ,
287
+ Event::PERSIST | ($ this ->streamFlags [$ key ] |= $ flag ),
288
+ $ this ->streamCallback
289
+ );
290
+ } else {
291
+ $ this ->streamEvents [$ key ] = $ event = new Event (
292
+ $ this ->eventBase ,
293
+ $ stream ,
294
+ Event::PERSIST | ($ this ->streamFlags [$ key ] = $ flag ),
295
+ $ this ->streamCallback
296
+ );
297
+ }
320
298
321
- $ entry -> event ->add ();
299
+ $ event ->add ();
322
300
}
323
301
324
302
/**
@@ -328,42 +306,80 @@ protected function addStreamEvent($stream, $flag, $listener)
328
306
* @param stream $stream
329
307
* @param integer $flag Event::READ or Event::WRITE
330
308
*/
331
- protected function removeStreamEvent ($ stream , $ flag )
309
+ protected function unsubscribeStreamEvent ($ stream , $ flag )
332
310
{
333
311
$ key = (int ) $ stream ;
334
312
335
- if (!isset ($ this ->streamEvents [$ key ])) {
313
+ $ flags = $ this ->streamFlags [$ key ] &= ~$ flag ;
314
+
315
+ if (0 === $ flags ) {
316
+ $ this ->removeStream ($ stream );
317
+
336
318
return ;
337
319
}
338
320
339
- $ entry = $ this ->streamEvents [$ key ];
340
- $ entry ->flags &= ~$ flag ;
341
- $ entry ->listeners [$ flag ] = null ;
321
+ $ event = $ this ->streamEvents [$ key ];
342
322
343
- if (0 === $ entry ->flags ) {
344
- $ this ->removeStream ($ stream );
345
- } else {
346
- $ this ->configureStreamEvent ($ entry , $ stream );
347
- }
323
+ $ event ->del ();
324
+
325
+ $ event ->set (
326
+ $ this ->eventBase ,
327
+ $ stream ,
328
+ Event::PERSIST | $ flags ,
329
+ $ this ->streamCallback
330
+ );
331
+
332
+ $ event ->add ();
348
333
}
349
334
350
335
/**
351
- * Create or update an ext-event Event object for the stream.
336
+ * Create a callback used as the target of timer events.
337
+ *
338
+ * A reference is kept to the callback for the lifetime of the loop
339
+ * to prevent "Cannot destroy active lambda function" fatal error from
340
+ * the event extension.
352
341
*/
353
- protected function configureStreamEvent ( $ entry , $ stream )
342
+ protected function createTimerCallback ( )
354
343
{
355
- $ flags = $ entry -> flags | Event:: PERSIST ;
344
+ $ this -> timerCallback = function ( $ streamIgnored , $ flagsIgnored , $ timer ) {
356
345
357
- if ($ entry ->event ) {
358
- $ entry ->event ->del ();
359
- $ entry ->event ->set (
360
- $ this ->eventBase , $ stream , $ flags , $ entry ->callback
361
- );
362
- $ entry ->event ->add ();
363
- } else {
364
- $ entry ->event = new Event (
365
- $ this ->eventBase , $ stream , $ flags , $ entry ->callback
366
- );
367
- }
346
+ call_user_func ($ timer ->getCallback (), $ timer );
347
+
348
+ // Clean-up one shot timers ...
349
+ if (!$ timer ->isPeriodic () && $ this ->isTimerActive ($ timer )) {
350
+ $ this ->cancelTimer ($ timer );
351
+ }
352
+
353
+ };
354
+ }
355
+
356
+ /**
357
+ * Create a callback used as the target of stream events.
358
+ *
359
+ * A reference is kept to the callback for the lifetime of the loop
360
+ * to prevent "Cannot destroy active lambda function" fatal error from
361
+ * the event extension.
362
+ */
363
+ protected function createStreamCallback ()
364
+ {
365
+ $ this ->streamCallback = function ($ stream , $ flags ) {
366
+
367
+ $ key = (int ) $ stream ;
368
+
369
+ if (
370
+ Event::READ === (Event::READ & $ flags )
371
+ && isset ($ this ->readListeners [$ key ])
372
+ ) {
373
+ call_user_func ($ this ->readListeners [$ key ], $ stream , $ this );
374
+ }
375
+
376
+ if (
377
+ Event::WRITE === (Event::WRITE & $ flags )
378
+ && isset ($ this ->writeListeners [$ key ])
379
+ ) {
380
+ call_user_func ($ this ->writeListeners [$ key ], $ stream , $ this );
381
+ }
382
+
383
+ };
368
384
}
369
385
}
0 commit comments