WebWorker.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. log(' start INPX import');
  71. const dbCreator = new DbCreator(config);
  72. await dbCreator.run(db, (state) => {
  73. this.setMyState(ssDbCreating, state);
  74. if (state.fileName)
  75. log(` load ${state.fileName}`);
  76. if (state.recsLoaded)
  77. log(` processed ${state.recsLoaded} records`);
  78. if (state.job)
  79. log(` ${state.job}`);
  80. });
  81. log(' finish INPX import');
  82. } finally {
  83. await db.unlock();
  84. log('Searcher DB successfully created');
  85. }
  86. }
  87. async loadOrCreateDb(recreate = false) {
  88. this.setMyState(ssDbLoading);
  89. try {
  90. const config = this.config;
  91. const dbPath = `${config.dataDir}/db`;
  92. //пересоздаем БД из INPX если нужно
  93. if (config.recreateDb || recreate)
  94. await fs.remove(dbPath);
  95. if (!await fs.pathExists(dbPath)) {
  96. await this.createDb(dbPath);
  97. await utils.freeMemory();
  98. }
  99. //загружаем БД
  100. this.setMyState(ssDbLoading);
  101. log('Searcher DB loading');
  102. const db = new JembaDbThread();
  103. await db.lock({
  104. dbPath,
  105. softLock: true,
  106. tableDefaults: {
  107. cacheSize: 5,
  108. },
  109. });
  110. //открываем все таблицы
  111. await db.openAll();
  112. //закроем title для экономии памяти, откроем при необходимости
  113. await db.close({table: 'title'});
  114. this.titleOpen = false;
  115. this.dbSearcher = new DbSearcher(db);
  116. db.wwCache = {};
  117. this.db = db;
  118. log('Searcher DB is ready');
  119. } catch (e) {
  120. log(LM_FATAL, e.message);
  121. ayncExit.exit(1);
  122. } finally {
  123. this.setMyState(ssNormal);
  124. }
  125. }
  126. async dbConfig() {
  127. this.checkMyState();
  128. const db = this.db;
  129. if (!db.wwCache.config) {
  130. const rows = await db.select({table: 'config'});
  131. const config = {};
  132. for (const row of rows) {
  133. config[row.id] = row.value;
  134. }
  135. db.wwCache.config = config;
  136. }
  137. return db.wwCache.config;
  138. }
  139. async search(query) {
  140. this.checkMyState();
  141. const config = await this.dbConfig();
  142. const result = await this.dbSearcher.search(query);
  143. return {
  144. author: result.result,
  145. totalFound: result.totalFound,
  146. inpxHash: (config.inpxHash ? config.inpxHash : ''),
  147. };
  148. }
  149. async logServerStats() {
  150. while (1) {// eslint-disable-line
  151. try {
  152. const memUsage = process.memoryUsage().rss/(1024*1024);//Mb
  153. let loadAvg = os.loadavg();
  154. loadAvg = loadAvg.map(v => v.toFixed(2));
  155. log(`Server info [ memUsage: ${memUsage.toFixed(2)}MB, loadAvg: (${loadAvg.join(', ')}) ]`);
  156. } catch (e) {
  157. log(LM_ERR, e.message);
  158. }
  159. await utils.sleep(5000);
  160. }
  161. }
  162. }
  163. module.exports = WebWorker;