From cd57944cb82f3c2fc5ef4cfab8cfcdb3ecac60a4 Mon Sep 17 00:00:00 2001 From: MiaoWoo Date: Fri, 20 Nov 2020 10:28:30 +0800 Subject: [PATCH] feat: support full nodejs event loop Signed-off-by: MiaoWoo --- packages/api/src/console.ts | 8 +- packages/nashorn/src/index.ts | 1 + packages/ployfill/src/node-shim.ts | 139 ++++++++++++++++++++--------- 3 files changed, 103 insertions(+), 45 deletions(-) diff --git a/packages/api/src/console.ts b/packages/api/src/console.ts index 4ac75db7..e071c5ed 100644 --- a/packages/api/src/console.ts +++ b/packages/api/src/console.ts @@ -158,9 +158,11 @@ export class MiaoScriptConsole implements Console { var { fileName, lineNumber } = this.readSourceMap(trace.fileName, trace.lineNumber) if (fileName.startsWith(root)) { fileName = fileName.split(root)[1] } } else { - for (let prefix in ignoreLogPrefix) { - if (className.startsWith(ignoreLogPrefix[prefix])) { - return + if (!global.debug) { + for (let prefix in ignoreLogPrefix) { + if (className.startsWith(ignoreLogPrefix[prefix])) { + return + } } } } diff --git a/packages/nashorn/src/index.ts b/packages/nashorn/src/index.ts index 5c9ccfa7..e62ac3f7 100644 --- a/packages/nashorn/src/index.ts +++ b/packages/nashorn/src/index.ts @@ -67,6 +67,7 @@ declare global { interface Core { getClass(name: String): any getProxyClass(): any + getJavaScriptTaskClass(): any getInstance(): any read(path: string): string save(path: string, content: string): void diff --git a/packages/ployfill/src/node-shim.ts b/packages/ployfill/src/node-shim.ts index 2d81d1fe..5ccbfc25 100644 --- a/packages/ployfill/src/node-shim.ts +++ b/packages/ployfill/src/node-shim.ts @@ -5,13 +5,15 @@ const ThreadGroup = Java.type("java.lang.ThreadGroup") const AtomicInteger = Java.type("java.util.concurrent.atomic.AtomicInteger") const ThreadPoolExecutor = Java.type('java.util.concurrent.ThreadPoolExecutor') const LinkedBlockingQueue = Java.type("java.util.concurrent.LinkedBlockingQueue") -const TimeUnit = Java.type("java.util.concurrent.TimeUnit") +const TimeUnit = Java.type('java.util.concurrent.TimeUnit') +const DelayQueue = Java.type('java.util.concurrent.DelayQueue') +const JavaScriptTask = Java.type(base.getJavaScriptTaskClass().name) const threadCount = new AtomicInteger(0) const threadGroup = new ThreadGroup("@ccms/ployfill-micro-task") const microTaskPool = new ThreadPoolExecutor( - 10, 100, 60, Packages.java.util.concurrent.TimeUnit.SECONDS, - new LinkedBlockingQueue(500), + 100, 200, 60, Packages.java.util.concurrent.TimeUnit.SECONDS, + new LinkedBlockingQueue(300), (run: any) => new Thread(threadGroup, run, "@ccms/micro-task-" + threadCount.incrementAndGet()), new ThreadPoolExecutor.CallerRunsPolicy() ) @@ -38,52 +40,105 @@ class Process extends EventEmitter { nextTick(func: Function) { microTaskPool.execute(func) } - queueMicrotask(func: Function) { - microTaskPool.execute(func) - } exit(code: number) { process.emit('exit', code) microTaskPool.shutdown() - console.log('await microTaskPool termination...') + console.log('process exit await microTaskPool termination...') microTaskPool.awaitTermination(5000, TimeUnit.MILLISECONDS) } } -const timeoutCount = new AtomicInteger(0) -const timeoutTasks = [] -function setTimeout(func: Function, time: number, ...args: any[]) { - let taskId = timeoutCount.incrementAndGet() - timeoutTasks[taskId] = func - process.nextTick(() => { - Thread.sleep(time) - if (timeoutTasks[taskId]) { func(...args) } - }) - return taskId -} -function clearTimeout(taskId: number) { - delete timeoutTasks[taskId] -} -const intervalCount = new AtomicInteger(0) -const intervalTasks = [] -function setInterval(func: Function, time: number, ...args: any[]) { - let taskId = intervalCount.incrementAndGet() - intervalTasks[taskId] = func - process.nextTick(() => { - Thread.sleep(time) - while (intervalTasks[taskId]) { - func(...args) - Thread.sleep(time) + +class EventLoop { + private eventLoopMainThread = undefined + private eventLoopTaskQueue = new DelayQueue() + + constructor() { + this.eventLoopMainThread = new Thread(() => { + let task = undefined + try { + while ((task = this.eventLoopTaskQueue.take()) != undefined) { + try { + task.getTask()() + } catch (error) { + try { + process.emit('error', error) + } catch (error) { + console.error(error) + console.ex(error) + } + } + } + } catch (error) { + console.log(`EventLoop Thread isInterrupted exit! remainTask: ${this.eventLoopTaskQueue.size()}`) + this.eventLoopTaskQueue.clear() + this.eventLoopTaskQueue = undefined + this.timeoutCount = undefined + this.timeoutTasks = undefined + this.intervalCount = undefined + this.intervalTasks = undefined + this.eventLoopMainThread = undefined + } + }, "@ccms/node-shim/event-loop") + this.eventLoopMainThread.setDaemon(true) + process.on('exit', () => this.eventLoopMainThread.interrupt()) + } + + startEventLoop() { + this.eventLoopMainThread.start() + } + + private putDelayTask(callback: Function, ms: number) { + this.eventLoopTaskQueue.put(new JavaScriptTask(callback, ms)) + } + + private timeoutCount = new AtomicInteger(0) + private timeoutTasks = [] + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) { + let taskId = this.timeoutCount.incrementAndGet() + this.timeoutTasks[taskId] = callback + console.trace(`create setTimeout task ${taskId} => ${callback}`) + this.putDelayTask(() => { + if (this.timeoutTasks[taskId]) { + callback(...args) + } else { + console.trace(`ignore setTimeout ${ms}ms task ${taskId} because it's cancelled!`) + } + }, ms) + return taskId + } + clearTimeout(taskId: number) { + delete this.timeoutTasks[taskId] + } + + private intervalCount = new AtomicInteger(0) + private intervalTasks = [] + setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]) { + let taskId = this.intervalCount.incrementAndGet() + this.intervalTasks[taskId] = callback + console.trace(`create setInterval ${ms}ms task ${taskId} => ${callback}`) + let intervalTask = () => { + if (this.intervalTasks[taskId]) { + callback(...args) + this.putDelayTask(intervalTask, ms) + } else { + console.trace(`ignore setInterval task ${taskId} because it's cancelled!`) + } } - }) - return taskId -} -function clearInterval(taskId: number) { - delete intervalTasks[taskId] + this.putDelayTask(intervalTask, ms) + return taskId + } + clearInterval(taskId: number) { + delete this.intervalTasks[taskId] + } } global.setGlobal('process', new Process(), {}) +const eventLoop = new EventLoop() +global.setGlobal('eventLoop', eventLoop, {}) +eventLoop.startEventLoop() global.setGlobal('queueMicrotask', (func: any) => microTaskPool.execute(func), {}) -global.setGlobal('setTimeout', setTimeout, {}) -global.setGlobal('clearTimeout', clearTimeout, {}) -global.setGlobal('setImmediate', (func: Function, ...args: any[]) => setTimeout(func, 0, ...args), {}) -global.setGlobal('clearImmediate ', clearTimeout, {}) -global.setGlobal('setInterval', setInterval, {}) -global.setGlobal('clearInterval', clearInterval, {}) +global.setGlobal('setTimeout', eventLoop.setTimeout.bind(eventLoop), {}) +global.setGlobal('clearTimeout', eventLoop.clearTimeout.bind(eventLoop), {}) +global.setGlobal('setImmediate', (callback: (...args: any[]) => void, ...args: any[]) => eventLoop.setTimeout(callback, 0, ...args), {}) +global.setGlobal('clearImmediate ', eventLoop.clearTimeout.bind(eventLoop), {}) +global.setGlobal('setInterval', eventLoop.setInterval.bind(eventLoop), {}) +global.setGlobal('clearInterval', eventLoop.clearInterval.bind(eventLoop), {})