[Service][Reland] Apply node worker for standalone model 35/245635/7
authorDongHyun Song <dh81.song@samsung.com>
Tue, 13 Oct 2020 10:03:27 +0000 (19:03 +0900)
committerDongHyun Song <dh81.song@samsung.com>
Wed, 21 Oct 2020 05:51:11 +0000 (14:51 +0900)
This patch includes below,

 1) unify source code with global model.
 2) isolate v8 variable scope from main thread.
   - standalone service application cannot get main global variable.
     of service_manaer / service_runner
 3) refactor handling message with node worker.
 4) refactor termination sequence of node worker more gracefully.
   - process.exit() is invoked by worker side
   - parent handles on('exit') for clean-up

Additionally, there is no noticeable memory increase with node worker.

References:
  https://review.tizen.org/gerrit/244938/
  https://review.tizen.org/gerrit/245591/

Change-Id: Ie9c17b373a69585ca755e39c4ab56243406821a2
Signed-off-by: DongHyun Song <dh81.song@samsung.com>
wrt_app/common/service_manager.ts
wrt_app/common/service_runner.ts
wrt_app/service/builtins/wasm_builder.ts
wrt_app/service/main.ts

index 3038364e97cf490220f377a427c027c08dccdfca..b48f2d427db462c40a86a8e35de266d9ad027817 100644 (file)
@@ -5,25 +5,34 @@ interface WorkerMap {
   [id: string]: any;
 }
 let workers: WorkerMap = {};
-let runner: any;
 
-global.serviceType = wrt.getServiceModel();
+Object.defineProperty(global, 'serviceType', {
+  value: wrt.getServiceModel(),
+  writable: false
+});
 
-function isStandalone() {
-  return global.serviceType === 'STANDALONE';
-}
-
-function isGlobalService() {
-  return global.serviceType === 'DAEMON';
+function isServiceApplication() {
+  return global['serviceType'] !== 'UI';
 }
 
 function createWorker(id: string, startService: string, filename: string) {
-  return workers[id] ?? (workers[id] = new Worker(startService, {
+  if (workers[id])
+    return;
+
+  workers[id] = new Worker(startService, {
     workerData: {
       id,
       filename
     }
-  }));
+  });
+  workers[id].on('exit', (code: number) => {
+    delete workers[id];
+    let runningServices = Object.keys(workers).length;
+    console.log(`exit code(${code}), remain services(${runningServices})`);
+    if (runningServices === 0 && isServiceApplication()) {
+      setTimeout(() => process.exit(), 500);
+    }
+  });
 }
 
 function terminateWorker(id: string, delay: number) {
@@ -31,44 +40,19 @@ function terminateWorker(id: string, delay: number) {
     console.log(`This worker is already terminated. ${id}`);
     return;
   }
-  workers[id].postMessage('stopService');
-  let terminate = () => {
-    workers[id].terminate();
-    delete workers[id];
-    let runningServices = Object.keys(workers).length;
-    console.log('Running services : ' + runningServices);
-    if (runningServices === 0 && isGlobalService()) {
-      process.exit();
-    }
-  }
-  setTimeout(() => terminate(), delay);
+  console.log(`${id} will shutdown after ${delay}ms`);
+  workers[id].postMessage({ type: 'stop', delay });
 }
 
 export function startService(id: string, filename: string) {
   console.log(`startService - ${id}`);
-  if (isStandalone()) {
-    runner = require('../common/service_runner');
-    runner.start(id, filename);
-  } else {
-    if (isMainThread) {
-      let startService = `${__dirname}/service_runner.js`;
-      createWorker(id, startService, filename);
-    }
-  }
+  let startService = `${__dirname}/service_runner.js`;
+  createWorker(id, startService, filename);
 }
 
 export function stopService(id: string) {
   console.log(`stopService - ${id}`);
-  if (isStandalone()) {
-    if (!runner) {
-      console.log('runner instance is null in standalone mode');
-      return;
-    }
-    runner.stop(id);
-    setTimeout(() => process.exit(), 500);
-  } else {
-    terminateWorker(id, 500);
-  }
+  terminateWorker(id, 500);
 }
 
 export function handleBuiltinService(serviceId: string, serviceName: string) {
@@ -77,15 +61,10 @@ export function handleBuiltinService(serviceId: string, serviceName: string) {
   }
   let need_stop = (serviceName.substr(0, 5) === 'stop_');
   if (need_stop) {
-    terminateWorker(serviceId, 0);
+    workers[serviceId].terminate();
   } else {
-    if (isMainThread) {
-      console.log(`Builtin service is ${serviceName}`);
-      let startService = `${__dirname}/../service/builtins/${serviceName}.js`;
-      let worker = createWorker(serviceId, startService, '');
-      worker.on('stop', () => {
-        terminateWorker(serviceId, 0);
-      });
-    }
+    console.log(`Builtin service is ${serviceName}`);
+    let startService = `${__dirname}/../service/builtins/${serviceName}.js`;
+    createWorker(serviceId, startService, '');
   }
 }
\ No newline at end of file
index 43681b55b932e2b3d551bd23dd952509223b460c..c3c8306d52607992e45df04ea93ecc84ba2eb990 100644 (file)
@@ -4,14 +4,17 @@ import { DeviceAPIRouter } from '../service/device_api_router';
 import { isMainThread, parentPort, workerData } from 'worker_threads';
 import { wrt } from '../browser/wrt';
 
-global.serviceType = wrt.getServiceModel();
+Object.defineProperty(global, 'serviceType', {
+  value: wrt.getServiceModel(),
+  writable: false
+});
 
 function isServiceApplication() {
-  return global.serviceType !== 'UI';
+  return global['serviceType'] !== 'UI';
 }
 
 function isGlobalService() {
-  return global.serviceType === 'DAEMON';
+  return global['serviceType'] === 'DAEMON';
 }
 
 function registerExtensionResolver(id: string) {
@@ -31,17 +34,21 @@ function registerExtensionResolver(id: string) {
   }
 }
 
+function requestStopService(id: string) {
+  setTimeout(() => wrt.stopService(id), 500);
+}
+
 let app: any = null;
 export function start(id: string, filename: string) {
   XWalkExtension.initialize();
   XWalkExtension.setRuntimeMessageHandler((type, data) => {
     if (type === 'tizen://exit') {
       console.log(`${id} will be closed by ${type}`);
-      setTimeout(() => wrt.stopService(id), 500);
+      requestStopService(id);
     }
   });
 
-  console.log(`serviceType : ${global.serviceType}`)
+  console.log(`serviceType : ${global['serviceType']}`)
   new DeviceAPIRouter(id, isGlobalService());
 
   if (isServiceApplication()) {
@@ -66,7 +73,7 @@ export function start(id: string, filename: string) {
     }
   } catch (e) {
     console.log(`exception on start: ${e}`);
-    setTimeout(() => wrt.stopService(id), 500);
+    requestStopService(id);
   }
 }
 
@@ -89,11 +96,14 @@ function run() {
 
   if (!parentPort)
     return;
-  parentPort.on('message', (msg) => {
-    console.log(`message received : ${msg}`);
-    if (msg === 'stopService') {
+  parentPort.on('message', (message) => {
+    console.log(`Received message type : ${message.type}`);
+    if (message.type === 'stop') {
       stop(id);
-      XWalkExtension.cleanup();
+      setTimeout(() => {
+        XWalkExtension.cleanup();
+        process.exit()
+      }, message.delay);
     }
   });
 }
index 7ec30a3dfffc0503e9b8159ddbacdf00df5d6654..54177d0b1a9bda2cc82bea778f7ac9714d764c97 100644 (file)
@@ -3,12 +3,14 @@ import { isMainThread, parentPort, workerData } from 'worker_threads';
 import { wrt } from '../../browser/wrt';
 import * as fs from 'fs';
 
-async function compileWasmForCaching(file_path: string) {
-  console.log(`Requesting WASM compilation for building a cache, file_path:(${file_path})`);
+function compileWasmForCaching(files: string[]) {
   try {
-    let source = fs.readFileSync(file_path);
-    let file = new Uint8Array(source);
-    await WebAssembly.compile(file);
+    files.forEach(async file_path => {
+      console.log(`Requesting WASM compilation for building a cache, file_path:(${file_path})`);
+      let source = fs.readFileSync(file_path);
+      let file = new Uint8Array(source);
+      await WebAssembly.compile(file);
+    });
   } catch (e) {
     console.error(`An error occurred while compiling a wasm module. error:(${e})`);
   }
@@ -21,13 +23,9 @@ export function run(app_id: string) {
   tv.setDiskCache(app_id);
   let files = tv.getWasmFiles(app_id);
   console.log(files);
-  files.forEach((file_path: string) => {
-    tv.delayShutdown();
-    compileWasmForCaching(file_path);
-  });
-  if (parentPort) {
-    parentPort.postMessage('stop');
-  }
+  tv.delayShutdown();
+  compileWasmForCaching(files);
+  process.exit();
 }
 
 if (!isMainThread) {
index b0c371a0598172910312bd51357082275cc2733d..6e6c222bb81d08b1d1f8be892efbae87757aff1e 100755 (executable)
@@ -21,15 +21,13 @@ import { wrt } from '../browser/wrt';
 import * as ServiceManager from '../common/service_manager';
 
 wrt.on('start-service', (event: any, internal_id: string) => {
-  console.log('start service app : ' + internal_id);
+  console.log(`start service app : ${internal_id}`);
   ServiceManager.startService(internal_id, '');
 });
 
 wrt.on('stop-service', (event: any, internal_id: string) => {
+  console.log(`stop service app : ${internal_id}`);
   ServiceManager.stopService(internal_id);
-  if (wrt.getServiceModel() === 'STANDALONE') {
-    setTimeout(() => {process.exit()}, 10);
-  }
 });
 
 wrt.on('builtin-service', (event: any, internal_id: string, service_name: string) => {