StreamSelectLoopTest.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. <?php
  2. namespace React\Tests\EventLoop;
  3. use React\EventLoop\LoopInterface;
  4. use React\EventLoop\StreamSelectLoop;
  5. use React\EventLoop\Timer\Timer;
  6. class StreamSelectLoopTest extends AbstractLoopTest
  7. {
  8. protected function tearDown()
  9. {
  10. parent::tearDown();
  11. if (strncmp($this->getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) {
  12. $this->resetSignalHandlers();
  13. }
  14. }
  15. public function createLoop()
  16. {
  17. return new StreamSelectLoop();
  18. }
  19. public function testStreamSelectTimeoutEmulation()
  20. {
  21. $this->loop->addTimer(
  22. 0.05,
  23. $this->expectCallableOnce()
  24. );
  25. $start = microtime(true);
  26. $this->loop->run();
  27. $end = microtime(true);
  28. $interval = $end - $start;
  29. $this->assertGreaterThan(0.04, $interval);
  30. }
  31. public function signalProvider()
  32. {
  33. return [
  34. ['SIGUSR1'],
  35. ['SIGHUP'],
  36. ['SIGTERM'],
  37. ];
  38. }
  39. private $_signalHandled = false;
  40. /**
  41. * Test signal interrupt when no stream is attached to the loop
  42. * @dataProvider signalProvider
  43. */
  44. public function testSignalInterruptNoStream($signal)
  45. {
  46. if (!extension_loaded('pcntl')) {
  47. $this->markTestSkipped('"pcntl" extension is required to run this test.');
  48. }
  49. // dispatch signal handler once before signal is sent and once after
  50. $this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); });
  51. $this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); });
  52. if (defined('HHVM_VERSION')) {
  53. // hhvm startup is slow so we need to add another handler much later
  54. $this->loop->addTimer(0.5, function() { pcntl_signal_dispatch(); });
  55. }
  56. $this->setUpSignalHandler($signal);
  57. // spawn external process to send signal to current process id
  58. $this->forkSendSignal($signal);
  59. $this->loop->run();
  60. $this->assertTrue($this->_signalHandled);
  61. }
  62. /**
  63. * Test signal interrupt when a stream is attached to the loop
  64. * @dataProvider signalProvider
  65. */
  66. public function testSignalInterruptWithStream($signal)
  67. {
  68. if (!extension_loaded('pcntl')) {
  69. $this->markTestSkipped('"pcntl" extension is required to run this test.');
  70. }
  71. // dispatch signal handler every 10ms
  72. $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); });
  73. // add stream to the loop
  74. list($writeStream, $readStream) = $this->createSocketPair();
  75. $this->loop->addReadStream($readStream, function($stream, $loop) {
  76. /** @var $loop LoopInterface */
  77. $read = fgets($stream);
  78. if ($read === "end loop\n") {
  79. $loop->stop();
  80. }
  81. });
  82. $this->loop->addTimer(0.05, function() use ($writeStream) {
  83. fwrite($writeStream, "end loop\n");
  84. });
  85. $this->setUpSignalHandler($signal);
  86. // spawn external process to send signal to current process id
  87. $this->forkSendSignal($signal);
  88. $this->loop->run();
  89. $this->assertTrue($this->_signalHandled);
  90. }
  91. /**
  92. * add signal handler for signal
  93. */
  94. protected function setUpSignalHandler($signal)
  95. {
  96. $this->_signalHandled = false;
  97. $this->assertTrue(pcntl_signal(constant($signal), function() { $this->_signalHandled = true; }));
  98. }
  99. /**
  100. * reset all signal handlers to default
  101. */
  102. protected function resetSignalHandlers()
  103. {
  104. foreach($this->signalProvider() as $signal) {
  105. pcntl_signal(constant($signal[0]), SIG_DFL);
  106. }
  107. }
  108. /**
  109. * fork child process to send signal to current process id
  110. */
  111. protected function forkSendSignal($signal)
  112. {
  113. $currentPid = posix_getpid();
  114. $childPid = pcntl_fork();
  115. if ($childPid == -1) {
  116. $this->fail("Failed to fork child process!");
  117. } else if ($childPid === 0) {
  118. // this is executed in the child process
  119. usleep(20000);
  120. posix_kill($currentPid, constant($signal));
  121. die();
  122. }
  123. }
  124. /**
  125. * https://github.com/reactphp/event-loop/issues/48
  126. *
  127. * Tests that timer with very small interval uses at least 1 microsecond
  128. * timeout.
  129. */
  130. public function testSmallTimerInterval()
  131. {
  132. /** @var StreamSelectLoop|\PHPUnit_Framework_MockObject_MockObject $loop */
  133. $loop = $this->getMock('React\EventLoop\StreamSelectLoop', ['streamSelect']);
  134. $loop
  135. ->expects($this->at(0))
  136. ->method('streamSelect')
  137. ->with([], [], 1);
  138. $loop
  139. ->expects($this->at(1))
  140. ->method('streamSelect')
  141. ->with([], [], 0);
  142. $callsCount = 0;
  143. $loop->addPeriodicTimer(Timer::MIN_INTERVAL, function() use (&$loop, &$callsCount) {
  144. $callsCount++;
  145. if ($callsCount == 2) {
  146. $loop->stop();
  147. }
  148. });
  149. $loop->run();
  150. }
  151. }