StreamSelectLoopTest.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <?php
  2. namespace React\Tests\EventLoop;
  3. use React\EventLoop\LoopInterface;
  4. use React\EventLoop\StreamSelectLoop;
  5. class StreamSelectLoopTest extends AbstractLoopTest
  6. {
  7. /**
  8. * @after
  9. */
  10. protected function tearDownSignalHandlers()
  11. {
  12. parent::tearDown();
  13. if (strncmp($this->getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) {
  14. $this->resetSignalHandlers();
  15. }
  16. }
  17. public function createLoop()
  18. {
  19. return new StreamSelectLoop();
  20. }
  21. public function testStreamSelectTimeoutEmulation()
  22. {
  23. $this->loop->addTimer(
  24. 0.05,
  25. $this->expectCallableOnce()
  26. );
  27. $start = microtime(true);
  28. $this->loop->run();
  29. $end = microtime(true);
  30. $interval = $end - $start;
  31. $this->assertGreaterThan(0.04, $interval);
  32. }
  33. public function testStreamSelectReportsWarningForStreamWithFilter()
  34. {
  35. if (defined('HHVM_VERSION')) {
  36. $this->markTestSkipped('Not supported on legacy HHVM');
  37. }
  38. $stream = tmpfile();
  39. stream_filter_append($stream, 'string.rot13');
  40. $this->loop->addReadStream($stream, $this->expectCallableNever());
  41. $loop = $this->loop;
  42. $this->loop->futureTick(function () use ($loop, $stream) {
  43. $loop->futureTick(function () use ($loop, $stream) {
  44. $loop->removeReadStream($stream);
  45. });
  46. });
  47. $error = null;
  48. $previous = set_error_handler(function ($_, $errstr) use (&$error) {
  49. $error = $errstr;
  50. });
  51. try {
  52. $this->loop->run();
  53. } catch (\ValueError $e) {
  54. // ignore ValueError for PHP 8+ due to empty stream array
  55. }
  56. restore_error_handler();
  57. $this->assertNotNull($error);
  58. $now = set_error_handler(function () { });
  59. restore_error_handler();
  60. $this->assertEquals($previous, $now);
  61. }
  62. public function testStreamSelectThrowsWhenCustomErrorHandlerThrowsForStreamWithFilter()
  63. {
  64. if (defined('HHVM_VERSION')) {
  65. $this->markTestSkipped('Not supported on legacy HHVM');
  66. }
  67. $stream = tmpfile();
  68. stream_filter_append($stream, 'string.rot13');
  69. $this->loop->addReadStream($stream, $this->expectCallableNever());
  70. $loop = $this->loop;
  71. $this->loop->futureTick(function () use ($loop, $stream) {
  72. $loop->futureTick(function () use ($loop, $stream) {
  73. $loop->removeReadStream($stream);
  74. });
  75. });
  76. $previous = set_error_handler(function ($_, $errstr) {
  77. throw new \RuntimeException($errstr);
  78. });
  79. $e = null;
  80. try {
  81. $this->loop->run();
  82. restore_error_handler();
  83. $this->fail();
  84. } catch (\RuntimeException $e) {
  85. restore_error_handler();
  86. } catch (\ValueError $e) {
  87. restore_error_handler(); // PHP 8+
  88. $e = $e->getPrevious();
  89. }
  90. $this->assertInstanceOf('RuntimeException', $e);
  91. $now = set_error_handler(function () { });
  92. restore_error_handler();
  93. $this->assertEquals($previous, $now);
  94. }
  95. public function signalProvider()
  96. {
  97. return array(
  98. array('SIGUSR1'),
  99. array('SIGHUP'),
  100. array('SIGTERM'),
  101. );
  102. }
  103. /**
  104. * Test signal interrupt when no stream is attached to the loop
  105. * @dataProvider signalProvider
  106. * @requires extension pcntl
  107. * @requires function pcntl_signal()
  108. * @requires function pcntl_signal_dispatch()
  109. */
  110. public function testSignalInterruptNoStream($signal)
  111. {
  112. // dispatch signal handler every 10ms for 0.1s
  113. $check = $this->loop->addPeriodicTimer(0.01, function() {
  114. pcntl_signal_dispatch();
  115. });
  116. $loop = $this->loop;
  117. $loop->addTimer(0.1, function () use ($check, $loop) {
  118. $loop->cancelTimer($check);
  119. });
  120. $handled = false;
  121. $this->assertTrue(pcntl_signal(constant($signal), function () use (&$handled) {
  122. $handled = true;
  123. }));
  124. // spawn external process to send signal to current process id
  125. $this->forkSendSignal($signal);
  126. $this->loop->run();
  127. $this->assertTrue($handled);
  128. }
  129. /**
  130. * Test signal interrupt when a stream is attached to the loop
  131. * @dataProvider signalProvider
  132. * @requires extension pcntl
  133. * @requires function pcntl_signal()
  134. * @requires function pcntl_signal_dispatch()
  135. */
  136. public function testSignalInterruptWithStream($signal)
  137. {
  138. // dispatch signal handler every 10ms
  139. $this->loop->addPeriodicTimer(0.01, function() {
  140. pcntl_signal_dispatch();
  141. });
  142. // add stream to the loop
  143. $loop = $this->loop;
  144. list($writeStream, $readStream) = $this->createSocketPair();
  145. $loop->addReadStream($readStream, function ($stream) use ($loop) {
  146. /** @var $loop LoopInterface */
  147. $read = fgets($stream);
  148. if ($read === "end loop\n") {
  149. $loop->stop();
  150. }
  151. });
  152. $this->loop->addTimer(0.1, function() use ($writeStream) {
  153. fwrite($writeStream, "end loop\n");
  154. });
  155. $handled = false;
  156. $this->assertTrue(pcntl_signal(constant($signal), function () use (&$handled) {
  157. $handled = true;
  158. }));
  159. // spawn external process to send signal to current process id
  160. $this->forkSendSignal($signal);
  161. $this->loop->run();
  162. $this->assertTrue($handled);
  163. }
  164. /**
  165. * reset all signal handlers to default
  166. */
  167. protected function resetSignalHandlers()
  168. {
  169. foreach($this->signalProvider() as $signal) {
  170. pcntl_signal(constant($signal[0]), SIG_DFL);
  171. }
  172. }
  173. /**
  174. * fork child process to send signal to current process id
  175. */
  176. protected function forkSendSignal($signal)
  177. {
  178. $currentPid = posix_getpid();
  179. $childPid = pcntl_fork();
  180. if ($childPid == -1) {
  181. $this->fail("Failed to fork child process!");
  182. } else if ($childPid === 0) {
  183. // this is executed in the child process
  184. usleep(20000);
  185. posix_kill($currentPid, constant($signal));
  186. die();
  187. }
  188. }
  189. }