pool.test.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import type { AbstractConnection } from '@sequelize/core';
  2. import { ConnectionAcquireTimeoutError, Sequelize } from '@sequelize/core';
  3. import { expect } from 'chai';
  4. import delay from 'delay';
  5. import type { SinonSandbox } from 'sinon';
  6. import sinon from 'sinon';
  7. import {
  8. sequelize as baseSequelize,
  9. createSingleTransactionalTestSequelizeInstance,
  10. getTestDialect,
  11. setResetMode,
  12. } from './support';
  13. const dialectName = getTestDialect();
  14. function assertSameConnection(
  15. newConnection: AbstractConnection,
  16. oldConnection: AbstractConnection,
  17. ) {
  18. switch (dialectName) {
  19. case 'postgres':
  20. // @ts-expect-error -- untyped
  21. expect(oldConnection.processID).to.equal(newConnection.processID).and.to.be.ok;
  22. break;
  23. case 'mariadb':
  24. case 'mysql':
  25. // @ts-expect-error -- untyped
  26. expect(oldConnection.threadId).to.equal(newConnection.threadId).and.to.be.ok;
  27. break;
  28. case 'db2':
  29. // @ts-expect-error -- untyped
  30. expect(newConnection.connected).to.equal(oldConnection.connected).and.to.be.ok;
  31. break;
  32. case 'sqlite3':
  33. case 'mssql':
  34. case 'ibmi':
  35. // @ts-expect-error -- untyped
  36. expect(newConnection.dummyId).to.equal(oldConnection.dummyId).and.to.be.ok;
  37. break;
  38. default:
  39. throw new Error('Unsupported dialect');
  40. }
  41. }
  42. function assertNewConnection(newConnection: AbstractConnection, oldConnection: AbstractConnection) {
  43. switch (dialectName) {
  44. case 'postgres':
  45. // @ts-expect-error -- untyped
  46. expect(oldConnection.processID).to.not.be.equal(newConnection.processID);
  47. break;
  48. case 'mariadb':
  49. case 'mysql':
  50. // @ts-expect-error -- untyped
  51. expect(oldConnection.threadId).to.not.be.equal(newConnection.threadId);
  52. break;
  53. case 'db2':
  54. // @ts-expect-error -- untyped
  55. expect(newConnection.connected).to.be.ok;
  56. // @ts-expect-error -- untyped
  57. expect(oldConnection.connected).to.not.be.ok;
  58. break;
  59. case 'mssql':
  60. case 'ibmi':
  61. case 'sqlite3':
  62. // @ts-expect-error -- untyped
  63. expect(newConnection.dummyId).to.not.be.ok;
  64. // @ts-expect-error -- untyped
  65. expect(oldConnection.dummyId).to.be.ok;
  66. break;
  67. default:
  68. throw new Error('Unsupported dialect');
  69. }
  70. }
  71. function attachMSSQLUniqueId(connection: AbstractConnection) {
  72. if (['mssql', 'ibmi', 'sqlite3'].includes(dialectName)) {
  73. // @ts-expect-error -- not typed, test only
  74. connection.dummyId = Math.random();
  75. }
  76. return connection;
  77. }
  78. describe('Pool', () => {
  79. if (process.env.DIALECT === 'postgres-native') {
  80. return;
  81. }
  82. setResetMode('none');
  83. let sandbox: SinonSandbox;
  84. beforeEach(() => {
  85. sandbox = sinon.createSandbox();
  86. });
  87. afterEach(() => {
  88. sandbox.restore();
  89. });
  90. describe('network / connection errors', () => {
  91. it('should obtain new connection when old connection is abruptly closed', async () => {
  92. async function simulateUnexpectedError(connection: AbstractConnection) {
  93. // should never be returned again
  94. if (['mssql', 'ibmi', 'sqlite3'].includes(dialectName)) {
  95. connection = attachMSSQLUniqueId(connection);
  96. }
  97. if (dialectName === 'db2' || dialectName === 'mariadb' || dialectName === 'sqlite3') {
  98. await sequelize.pool.destroy(connection);
  99. } else {
  100. const error: NodeJS.ErrnoException = new Error('Test ECONNRESET Error');
  101. error.code = 'ECONNRESET';
  102. // @ts-expect-error -- emit not declared yet
  103. connection.emit('error', error);
  104. }
  105. }
  106. // This function makes
  107. const sequelize = await createSingleTransactionalTestSequelizeInstance(baseSequelize, {
  108. pool: { max: 1, idle: 5000 },
  109. });
  110. const cm = sequelize.dialect.connectionManager;
  111. const firstConnection = await sequelize.pool.acquire();
  112. await simulateUnexpectedError(firstConnection);
  113. expect(sequelize.pool.using).to.eq(
  114. 0,
  115. 'first connection should have errored and not be in use anymore',
  116. );
  117. expect(sequelize.pool.size).to.eq(
  118. 0,
  119. 'first connection should have errored and not be in pool anymore',
  120. );
  121. const secondConnection = await sequelize.pool.acquire();
  122. assertNewConnection(secondConnection, firstConnection);
  123. expect(sequelize.pool.size).to.equal(
  124. 1,
  125. 'pool size should be 1 after new connection is acquired',
  126. );
  127. expect(cm.validate(firstConnection)).to.be.not.ok;
  128. sequelize.pool.release(secondConnection);
  129. });
  130. it('should obtain new connection when released connection dies inside pool', async () => {
  131. const sequelize = await createSingleTransactionalTestSequelizeInstance(baseSequelize, {
  132. pool: { max: 1, idle: 5000 },
  133. });
  134. const cm = sequelize.dialect.connectionManager;
  135. const oldConnection = await sequelize.pool.acquire();
  136. sequelize.pool.release(oldConnection);
  137. attachMSSQLUniqueId(oldConnection);
  138. await sequelize.dialect.connectionManager.disconnect(oldConnection);
  139. const newConnection = await sequelize.pool.acquire();
  140. assertNewConnection(newConnection, oldConnection);
  141. expect(sequelize.pool.size).to.equal(1);
  142. expect(cm.validate(oldConnection)).to.be.not.ok;
  143. sequelize.pool.release(newConnection);
  144. });
  145. });
  146. describe('idle', () => {
  147. it('should maintain connection within idle range', async () => {
  148. const sequelize = await createSingleTransactionalTestSequelizeInstance(baseSequelize, {
  149. pool: { max: 1, idle: 100 },
  150. });
  151. const cm = sequelize.dialect.connectionManager;
  152. const firstConnection = await sequelize.pool.acquire();
  153. attachMSSQLUniqueId(firstConnection);
  154. // returning connection to the pool
  155. sequelize.pool.release(firstConnection);
  156. // Wait a little and then get next available connection
  157. await delay(90);
  158. const secondConnection = await sequelize.pool.acquire();
  159. assertSameConnection(secondConnection, firstConnection);
  160. expect(cm.validate(firstConnection)).to.be.ok;
  161. sequelize.pool.release(secondConnection);
  162. });
  163. it('should get new connection beyond idle range', async () => {
  164. const sequelize = await createSingleTransactionalTestSequelizeInstance(baseSequelize, {
  165. pool: { max: 1, idle: 100, evict: 10 },
  166. });
  167. const pool = sequelize.pool;
  168. const cm = sequelize.dialect.connectionManager;
  169. const firstConnection = await pool.acquire();
  170. attachMSSQLUniqueId(firstConnection);
  171. // returning connection to pool
  172. pool.release(firstConnection);
  173. // Wait a little and then get the next available connection
  174. await delay(150);
  175. const secondConnection = await pool.acquire();
  176. assertNewConnection(secondConnection, firstConnection);
  177. expect(cm.validate(firstConnection)).not.to.be.ok;
  178. pool.release(secondConnection);
  179. });
  180. });
  181. describe('acquire', () => {
  182. it('rejects with ConnectionAcquireTimeoutError when unable to acquire connection', async () => {
  183. const testInstance = new Sequelize({
  184. dialect: dialectName,
  185. databaseVersion: baseSequelize.dialect.minimumDatabaseVersion,
  186. pool: {
  187. acquire: 10,
  188. },
  189. });
  190. sandbox
  191. .stub(testInstance.dialect.connectionManager, 'connect')
  192. .returns(new Promise(() => {}));
  193. await expect(testInstance.authenticate()).to.be.rejectedWith(ConnectionAcquireTimeoutError);
  194. await testInstance.close();
  195. });
  196. });
  197. });