PoolTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <?php
  2. namespace test;
  3. use PHPUnit\Framework\TestCase;
  4. use ReflectionMethod;
  5. use ReflectionProperty;
  6. use Workerman\Coroutine;
  7. use Workerman\Coroutine\Exception\PoolException;
  8. use Workerman\Coroutine\Pool;
  9. use Psr\Log\LoggerInterface;
  10. use ReflectionClass;
  11. use stdClass;
  12. use Exception;
  13. use Workerman\Events\Event;
  14. use Workerman\Events\Select;
  15. use Workerman\Timer;
  16. use Workerman\Worker;
  17. class PoolTest extends TestCase
  18. {
  19. public function testConstructorWithConfig()
  20. {
  21. $config = [
  22. 'min_connections' => 2,
  23. 'idle_timeout' => 30,
  24. 'heartbeat_interval' => 10,
  25. 'wait_timeout' => 5,
  26. ];
  27. $pool = new Pool(10, $config);
  28. $this->assertEquals(10, $this->getPrivateProperty($pool, 'maxConnections'));
  29. $this->assertEquals(2, $this->getPrivateProperty($pool, 'minConnections'));
  30. $this->assertEquals(30, $this->getPrivateProperty($pool, 'idleTimeout'));
  31. $this->assertEquals(10, $this->getPrivateProperty($pool, 'heartbeatInterval'));
  32. $this->assertEquals(5, $this->getPrivateProperty($pool, 'waitTimeout'));
  33. }
  34. public function testSetConnectionCreator()
  35. {
  36. $pool = new Pool(5);
  37. $connectionCreator = function () {
  38. return new stdClass();
  39. };
  40. $pool->setConnectionCreator($connectionCreator);
  41. $this->assertSame($connectionCreator, $this->getPrivateProperty($pool, 'connectionCreateHandler'));
  42. }
  43. public function testSetConnectionCloser()
  44. {
  45. $pool = new Pool(5);
  46. $connectionCloser = function ($conn) {
  47. // Close connection.
  48. };
  49. $pool->setConnectionCloser($connectionCloser);
  50. $this->assertSame($connectionCloser, $this->getPrivateProperty($pool, 'connectionDestroyHandler'));
  51. }
  52. public function testGetConnection()
  53. {
  54. $pool = new Pool(5);
  55. $connectionMock = $this->createMock(stdClass::class);
  56. // 设置连接创建器
  57. $pool->setConnectionCreator(function () use ($connectionMock) {
  58. return $connectionMock;
  59. });
  60. $connection = $pool->get();
  61. $this->assertSame($connectionMock, $connection);
  62. $this->assertEquals(1, $this->getCurrentConnections($pool));
  63. // 检查 WeakMap 是否更新
  64. $connections = $this->getPrivateProperty($pool, 'connections');
  65. $lastUsedTimes = $this->getPrivateProperty($pool, 'lastUsedTimes');
  66. $lastHeartbeatTimes = $this->getPrivateProperty($pool, 'lastHeartbeatTimes');
  67. $this->assertTrue($connections->offsetExists($connection));
  68. $this->assertTrue($lastUsedTimes->offsetExists($connection));
  69. $this->assertTrue($lastHeartbeatTimes->offsetExists($connection));
  70. }
  71. public function testPutConnection()
  72. {
  73. $pool = new Pool(5);
  74. $connectionMock = $this->createMock(stdClass::class);
  75. $pool->setConnectionCreator(function () use ($connectionMock) {
  76. return $connectionMock;
  77. });
  78. $connection = $pool->get();
  79. $pool->put($connection);
  80. if (Coroutine::isCoroutine()) {
  81. $channel = $this->getPrivateProperty($pool, 'channel');
  82. $this->assertEquals(1, $channel->length());
  83. }
  84. $this->assertEquals(1, $pool->getConnectionCount());
  85. }
  86. public function testPutConnectionDoesNotBelong()
  87. {
  88. $this->expectException(PoolException::class);
  89. $this->expectExceptionMessage('The connection does not belong to the connection pool.');
  90. $pool = new Pool(5);
  91. $connection = new stdClass();
  92. $pool->put($connection);
  93. }
  94. public function testCreateConnection()
  95. {
  96. $pool = new Pool(5);
  97. $connectionMock = $this->createMock(stdClass::class);
  98. $pool->setConnectionCreator(function () use ($connectionMock) {
  99. return $connectionMock;
  100. });
  101. $connection = $pool->createConnection();
  102. $this->assertSame($connectionMock, $connection);
  103. // 确保 currentConnections 增加
  104. $this->assertEquals(1, $this->getCurrentConnections($pool));
  105. // 检查 WeakMap 是否更新
  106. $connections = $this->getPrivateProperty($pool, 'connections');
  107. $lastUsedTimes = $this->getPrivateProperty($pool, 'lastUsedTimes');
  108. $lastHeartbeatTimes = $this->getPrivateProperty($pool, 'lastHeartbeatTimes');
  109. $this->assertTrue($connections->offsetExists($connection));
  110. $this->assertTrue($lastUsedTimes->offsetExists($connection));
  111. $this->assertTrue($lastHeartbeatTimes->offsetExists($connection));
  112. }
  113. public function testCreateMaxConnections()
  114. {
  115. if (in_array(Worker::$eventLoopClass, [Select::class, Event::class])) {
  116. $this->assertTrue(true);
  117. return;
  118. }
  119. $maxConnections = 2;
  120. $pool = new Pool($maxConnections);
  121. $pool->setConnectionCreator(function () {
  122. Timer::sleep(0.01);
  123. return $this->createMock(stdClass::class);
  124. });
  125. $connections = [];
  126. for ($i = 0; $i < 3; $i++) {
  127. Coroutine::create(function () use ($pool, &$connections) {
  128. $connections[] = $pool->get();
  129. });
  130. }
  131. Timer::sleep(0.1);
  132. $this->assertEquals($maxConnections, $this->getCurrentConnections($pool));
  133. $lastUsedTimes = $this->getPrivateProperty($pool, 'lastUsedTimes');
  134. $lastHeartbeatTimes = $this->getPrivateProperty($pool, 'lastHeartbeatTimes');
  135. $this->assertCount($maxConnections, $lastUsedTimes);
  136. $this->assertCount($maxConnections, $lastHeartbeatTimes);
  137. foreach ($connections as $connection) {
  138. $pool->put($connection);
  139. }
  140. }
  141. public function testCreateConnectionThrowsException()
  142. {
  143. $pool = new Pool(5);
  144. $pool->setConnectionCreator(function () {
  145. throw new Exception('Failed to create connection');
  146. });
  147. $this->expectException(Exception::class);
  148. $this->expectExceptionMessage('Failed to create connection');
  149. try {
  150. $pool->createConnection();
  151. } finally {
  152. // 确保 currentConnections 减少
  153. $this->assertEquals(0, $this->getCurrentConnections($pool));
  154. }
  155. }
  156. public function testCloseConnection()
  157. {
  158. $pool = new Pool(5);
  159. $connection = $this->createMock(ConnectionMock::class);
  160. // 模拟连接属于连接池
  161. $connections = $this->getPrivateProperty($pool, 'connections');
  162. $connections[$connection] = time();
  163. $connection->expects($this->once())->method('close');
  164. $pool->setConnectionCloser(function ($conn) {
  165. $conn->close();
  166. });
  167. $pool->closeConnection($connection);
  168. // 确保 currentConnections 减少
  169. $this->assertEquals(0, $this->getCurrentConnections($pool));
  170. // 确保连接从 WeakMap 中移除
  171. $this->assertFalse($connections->offsetExists($connection));
  172. }
  173. public function testCloseConnections()
  174. {
  175. $maxConnections = 5;
  176. $pool = new Pool($maxConnections);
  177. $pool->setConnectionCreator(function () {
  178. $connection = $this->createMock(ConnectionMock::class);
  179. $connection->expects($this->once())->method('close');
  180. return $connection;
  181. });
  182. $pool->setConnectionCloser(function ($conn) {
  183. $conn->close();
  184. });
  185. $connections = [];
  186. for ($i = 0; $i < $maxConnections; $i++) {
  187. $connections[] = $pool->get();
  188. }
  189. $this->assertEquals(Coroutine::isCoroutine() ? $maxConnections : 1, $this->getCurrentConnections($pool));
  190. $pool->closeConnections();
  191. $this->assertEquals(Coroutine::isCoroutine() ? $maxConnections : 0, $this->getCurrentConnections($pool));
  192. if (!Coroutine::isCoroutine()) {
  193. return;
  194. }
  195. foreach ($connections as $connection) {
  196. $pool->put($connection);
  197. }
  198. $this->assertEquals($maxConnections, $this->getCurrentConnections($pool));
  199. $pool->closeConnections();
  200. $this->assertEquals(0, $this->getCurrentConnections($pool));
  201. $connections = [];
  202. for ($i = 0; $i < $maxConnections; $i++) {
  203. $connections[] = $pool->get();
  204. }
  205. $this->assertEquals($maxConnections, $this->getCurrentConnections($pool));
  206. foreach ($connections as $connection) {
  207. $pool->put($connection);
  208. }
  209. $pool->closeConnections();
  210. unset($connections);
  211. $this->assertEquals(0, $this->getCurrentConnections($pool));
  212. }
  213. public function testCloseConnectionWithExceptionInDestroyHandler()
  214. {
  215. $pool = new Pool(5);
  216. $connection = $this->createMock(stdClass::class);
  217. // 模拟连接属于连接池
  218. $connections = $this->getPrivateProperty($pool, 'connections');
  219. $connections[$connection] = time();
  220. $exception = new Exception('Error closing connection');
  221. $pool->setConnectionCloser(function ($conn) use ($exception) {
  222. throw $exception;
  223. });
  224. // 设置日志记录器
  225. $loggerMock = $this->createMock(LoggerInterface::class);
  226. $loggerMock->expects($this->once())
  227. ->method('info')
  228. ->with($this->stringContains('Error closing connection'));
  229. $this->setPrivateProperty($pool, 'logger', $loggerMock);
  230. $pool->closeConnection($connection);
  231. // 确保 currentConnections 减少
  232. $this->assertEquals(0, $this->getCurrentConnections($pool));
  233. // 确保连接从 WeakMap 中移除
  234. $this->assertFalse($connections->offsetExists($connection));
  235. }
  236. public function testHeartbeatChecker()
  237. {
  238. $pool = $this->getMockBuilder(Pool::class)
  239. ->setConstructorArgs([5])
  240. ->onlyMethods(['closeConnection'])
  241. ->getMock();
  242. $connection = $this->createMock(stdClass::class);
  243. // 设置连接心跳检测器
  244. $pool->setHeartbeatChecker(function ($conn) {
  245. // 模拟心跳检测
  246. });
  247. // 模拟连接在通道中
  248. $channel = $this->getPrivateProperty($pool, 'channel');
  249. $channel->push($connection);
  250. // 设置连接的上次使用时间和心跳时间
  251. $connections = $this->getPrivateProperty($pool, 'connections');
  252. $connections[$connection] = time();
  253. $lastUsedTimes = $this->getPrivateProperty($pool, 'lastUsedTimes');
  254. $lastUsedTimes[$connection] = time();
  255. $lastHeartbeatTimes = $this->getPrivateProperty($pool, 'lastHeartbeatTimes');
  256. $lastHeartbeatTimes[$connection] = time() - 100; // 超过心跳间隔
  257. // 调用受保护的 checkConnections 方法
  258. $reflectedMethod = new ReflectionMethod($pool, 'checkConnections');
  259. $reflectedMethod->invoke($pool);
  260. // 检查心跳时间是否更新
  261. $lastHeartbeatTimes = $this->getPrivateProperty($pool, 'lastHeartbeatTimes');
  262. $this->assertGreaterThan(time() - 2, $lastHeartbeatTimes[$connection]);
  263. }
  264. public function testConnectionDestroyedWithoutReturn()
  265. {
  266. $pool = new Pool(5);
  267. // 设置连接创建器
  268. $pool->setConnectionCreator(function () {
  269. return new stdClass;
  270. });
  271. // 获取初始的 currentConnections
  272. $initialConnections = $this->getCurrentConnections($pool);
  273. // 从连接池获取一个连接
  274. $connection = $pool->get();
  275. // 检查 currentConnections 是否增加
  276. $this->assertEquals(Coroutine::isCoroutine() ? $initialConnections + 1 : 1, $this->getCurrentConnections($pool));
  277. // 不归还连接,并销毁连接对象
  278. unset($connection);
  279. // 检查 currentConnections 是否减少
  280. $this->assertEquals(Coroutine::isCoroutine() ? $initialConnections : 1, $this->getCurrentConnections($pool));
  281. }
  282. private function getPrivateProperty($object, string $property)
  283. {
  284. $prop = new ReflectionProperty($object, $property);
  285. return $prop->getValue($object);
  286. }
  287. private function setPrivateProperty($object, string $property, $value)
  288. {
  289. $prop = new ReflectionProperty($object, $property);
  290. $prop->setValue($object, $value);
  291. }
  292. private function getCurrentConnections($object): int
  293. {
  294. return $object->getConnectionCount();
  295. }
  296. }
  297. // 定义 ConnectionMock 类用于测试
  298. class ConnectionMock
  299. {
  300. public function close()
  301. {
  302. // 模拟关闭连接
  303. }
  304. }