WebWorker.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. const os = require('os');
  2. const fs = require('fs-extra');
  3. const WorkerState = require('./WorkerState');
  4. const { JembaDbThread } = require('jembadb');
  5. const DbCreator = require('./DbCreator');
  6. const DbSearcher = require('./DbSearcher');
  7. const ayncExit = new (require('./AsyncExit'))();
  8. const log = new (require('./AppLogger'))().log;//singleton
  9. const utils = require('./utils');
  10. //server states
  11. const ssNormal = 'normal';
  12. const ssDbLoading = 'db_loading';
  13. const ssDbCreating = 'db_creating';
  14. const stateToText = {
  15. [ssNormal]: '',
  16. [ssDbLoading]: 'Загрузка поисковой базы',
  17. [ssDbCreating]: 'Создание поисковой базы',
  18. };
  19. //singleton
  20. let instance = null;
  21. class WebWorker {
  22. constructor(config) {
  23. if (!instance) {
  24. this.config = config;
  25. this.workerState = new WorkerState();
  26. this.wState = this.workerState.getControl('server_state');
  27. this.myState = '';
  28. this.db = null;
  29. this.dbSearcher = null;
  30. ayncExit.add(this.closeDb.bind(this));
  31. this.loadOrCreateDb();//no await
  32. this.logServerStats();//no await
  33. instance = this;
  34. }
  35. return instance;
  36. }
  37. checkMyState() {
  38. if (this.myState != ssNormal)
  39. throw new Error('server_busy');
  40. }
  41. setMyState(newState, workerState = {}) {
  42. this.myState = newState;
  43. this.wState.set(Object.assign({}, workerState, {
  44. state: newState,
  45. serverMessage: stateToText[newState]
  46. }));
  47. }
  48. async closeDb() {
  49. if (this.db) {
  50. await this.db.unlock();
  51. this.db = null;
  52. }
  53. }
  54. async createDb(dbPath) {
  55. this.setMyState(ssDbCreating);
  56. log('Searcher DB create start');
  57. const config = this.config;
  58. if (await fs.pathExists(dbPath))
  59. throw new Error(`createDb.pathExists: ${dbPath}`);
  60. const db = new JembaDbThread();//создаем не в потоке, чтобы лучше работал GC
  61. await db.lock({
  62. dbPath,
  63. create: true,
  64. softLock: true,
  65. tableDefaults: {
  66. cacheSize: 5,
  67. },
  68. });
  69. try {
  70. const dbCreator = new DbCreator(config);
  71. await dbCreator.run(db, (state) => {
  72. this.setMyState(ssDbCreating, state);
  73. if (state.fileName)
  74. log(` load ${state.fileName}`);
  75. if (state.recsLoaded)
  76. log(` processed ${state.recsLoaded} records`);
  77. if (state.job)
  78. log(` ${state.job}`);
  79. });
  80. log('Searcher DB successfully created');
  81. } finally {
  82. await db.unlock();
  83. }
  84. }
  85. async loadOrCreateDb(recreate = false) {
  86. this.setMyState(ssDbLoading);
  87. try {
  88. const config = this.config;
  89. const dbPath = `${config.dataDir}/db`;
  90. //пересоздаем БД из INPX если нужно
  91. if (config.recreateDb || recreate)
  92. await fs.remove(dbPath);
  93. if (!await fs.pathExists(dbPath)) {
  94. await this.createDb(dbPath);
  95. await utils.freeMemory();
  96. }
  97. //загружаем БД
  98. this.setMyState(ssDbLoading);
  99. log('Searcher DB loading');
  100. const db = new JembaDbThread();
  101. await db.lock({
  102. dbPath,
  103. softLock: true,
  104. tableDefaults: {
  105. cacheSize: 5,
  106. },
  107. });
  108. //открываем все таблицы
  109. await db.openAll();
  110. this.dbSearcher = new DbSearcher(config, db);
  111. db.wwCache = {};
  112. this.db = db;
  113. log('Searcher DB is ready');
  114. } catch (e) {
  115. log(LM_FATAL, e.message);
  116. ayncExit.exit(1);
  117. } finally {
  118. this.setMyState(ssNormal);
  119. }
  120. }
  121. async recreateDb() {
  122. this.setMyState(ssDbCreating);
  123. if (this.dbSearcher) {
  124. await this.dbSearcher.close();
  125. this.dbSearcher = null;
  126. }
  127. await this.closeDb();
  128. await this.loadOrCreateDb(true);
  129. }
  130. async dbConfig() {
  131. this.checkMyState();
  132. const db = this.db;
  133. if (!db.wwCache.config) {
  134. const rows = await db.select({table: 'config'});
  135. const config = {};
  136. for (const row of rows) {
  137. config[row.id] = row.value;
  138. }
  139. db.wwCache.config = config;
  140. }
  141. return db.wwCache.config;
  142. }
  143. async search(query) {
  144. this.checkMyState();
  145. const config = await this.dbConfig();
  146. const result = await this.dbSearcher.search(query);
  147. return {
  148. author: result.result,
  149. totalFound: result.totalFound,
  150. inpxHash: (config.inpxHash ? config.inpxHash : ''),
  151. };
  152. }
  153. async logServerStats() {
  154. while (1) {// eslint-disable-line
  155. try {
  156. const memUsage = process.memoryUsage().rss/(1024*1024);//Mb
  157. let loadAvg = os.loadavg();
  158. loadAvg = loadAvg.map(v => v.toFixed(2));
  159. log(`Server info [ memUsage: ${memUsage.toFixed(2)}MB, loadAvg: (${loadAvg.join(', ')}) ]`);
  160. } catch (e) {
  161. log(LM_ERR, e.message);
  162. }
  163. await utils.sleep(5000);
  164. }
  165. }
  166. }
  167. module.exports = WebWorker;