reconnect-test-mysql-based-drivers.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /**
  2. * Test case for figuring out robust way to recognize if connection is dead
  3. * for mysql based drivers.
  4. */
  5. const Bluebird = require('bluebird');
  6. const toxiproxy = require('toxiproxy-node-client');
  7. const toxicli = new toxiproxy.Toxiproxy('http://localhost:8474');
  8. const rp = require('request-promise-native');
  9. async function stdMysqlQuery(con, sql) {
  10. return new Promise((resolve, reject) => {
  11. try {
  12. con.query({
  13. sql,
  14. timeout: 4000
  15. }, function (error, results, fields) {
  16. if (error) {
  17. reject(error);
  18. } else {
  19. resolve(results);
  20. }
  21. });
  22. } catch (err) {
  23. reject(err); // double sure...
  24. }
  25. });
  26. }
  27. // ALL THE DRIVERS HAS DIFFERENT BAG OF TRICKS TO RECOVER AND
  28. // RECOGNIZE WHEN CONNECTION HAS BEEN CLOSED
  29. // ------------- setup mysql db driver connection
  30. const mysql = require('mysql');
  31. let mysqlCon = {state: 'disconnected'};
  32. async function mysqlQuery(sql) {
  33. // best way to check if connection is still alive
  34. if (mysqlCon.state === 'disconnected') {
  35. console.log('reconnecting mysql');
  36. mysqlCon = mysql.createConnection({
  37. host: 'localhost',
  38. user: 'root',
  39. password: 'mysqlrootpassword',
  40. port: 23306,
  41. connectTimeout: 500
  42. });
  43. // not always triggered, if this happens during query
  44. mysqlCon.on('error', err => {
  45. console.log('- STATS Mysql connection died:', err);
  46. });
  47. }
  48. return stdMysqlQuery(mysqlCon, sql);
  49. }
  50. // ------------- setup mysql2 db driver connection
  51. const mysql2 = require('mysql2');
  52. let mysql2Con = {_fatalError: 'initmefirst'};
  53. async function mysql2Query(sql) {
  54. if (mysql2Con._fatalError) {
  55. console.log('reconnecting mysql2');
  56. mysql2Con = mysql2.createConnection({
  57. host: 'localhost',
  58. user: 'root',
  59. password: 'mysqlrootpassword',
  60. port: 23306,
  61. connectTimeout: 500
  62. });
  63. mysql2Con.on('error', err => {
  64. console.log('- STATS Mysql2 connection died:', err);
  65. });
  66. }
  67. console.log('================ MYSQL2 Running query....');
  68. const res = await stdMysqlQuery(mysql2Con, sql);
  69. console.log('=========== done');
  70. return res;
  71. }
  72. const counters = {};
  73. function setMysqlQueryCounters(name) {
  74. const counts = counters[name] = {queries: 0, results: 0, errors: 0};
  75. }
  76. setMysqlQueryCounters('mysql');
  77. setMysqlQueryCounters('mysql2');
  78. const _ = require('lodash');
  79. // start printing out counters
  80. let lastCounters = _.cloneDeep(counters);
  81. setInterval(() => {
  82. const reqsPerSec = {};
  83. for (let key of Object.keys(counters)) {
  84. reqsPerSec[key] = {
  85. queries: (counters[key].queries - lastCounters[key].queries),
  86. results: (counters[key].results - lastCounters[key].results),
  87. errors: (counters[key].errors - lastCounters[key].errors),
  88. }
  89. }
  90. console.log('------------------------ STATS PER SECOND ------------------------');
  91. console.dir(reqsPerSec, {colors: true});
  92. console.log('------------------------------- EOS ------------------------------');
  93. lastCounters = _.cloneDeep(counters);
  94. // if hang
  95. ///if (reqsPerSec.mysql2.queries === 0) process.exit(0);
  96. }, 1000);
  97. async function main() {
  98. async function recreateProxy(serviceName, listenPort, proxyToPort) {
  99. try {
  100. await rp.delete({
  101. url: `${toxicli.host}/proxies/${serviceName}`
  102. });
  103. } catch(err) {}
  104. const proxy = await toxicli.createProxy({
  105. name: serviceName,
  106. listen: `0.0.0.0:${listenPort}`,
  107. upstream: `${serviceName}:${proxyToPort}`
  108. });
  109. // add some latency
  110. await proxy.addToxic(new toxiproxy.Toxic(proxy, {
  111. type: 'latency',
  112. attributes: {latency: 1, jitter: 1}
  113. }));
  114. // cause connections to be closed every some transferred bytes
  115. await proxy.addToxic(new toxiproxy.Toxic(proxy, {
  116. type: 'limit_data',
  117. attributes: {bytes: 1000}
  118. }));
  119. }
  120. // create TCP proxies for simulating bad connections etc.
  121. async function recreateProxies() {
  122. console.log('----- Recreating proxies -> cutting connections completely');
  123. await recreateProxy('postgresql', 25432, 5432);
  124. await recreateProxy('mysql', 23306, 3306);
  125. await recreateProxy('oracledbxe', 21521, 1521);
  126. }
  127. setInterval(() => recreateProxies(), 2000);
  128. async function loopQueries(prefix, query) {
  129. const counts = counters[prefix];
  130. while(true) {
  131. try {
  132. counts.queries += 1;
  133. // without this delay we endup to busy failure loop
  134. await Bluebird.delay(0);
  135. await query();
  136. counts.results += 1;
  137. } catch (err) {
  138. counts.errors += 1;
  139. console.log(prefix, err);
  140. }
  141. }
  142. }
  143. await recreateProxies();
  144. loopQueries('mysql', () => mysqlQuery('select 1'));
  145. loopQueries('mysql2', () => mysql2Query('select 1'));
  146. // wait forever
  147. while(true) {
  148. await Bluebird.delay(1000);
  149. }
  150. }
  151. main().then(() => console.log('DONE')).catch(err => console.log(err));