Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: impl boot methods #171

Merged
merged 2 commits into from
Sep 6, 2018
Merged

feat: impl boot methods #171

merged 2 commits into from
Sep 6, 2018

Conversation

killagu
Copy link
Contributor

@killagu killagu commented Jul 3, 2018

  1. Add Lifecycle class, control EggCore lifecycle.
  2. After config, extend, app.js, angent.js loaded, loader should call initBoots.
  3. After all files loaded, loader should call triggerDidLoad.

Refs: eggjs/egg#2520

Checklist
  • npm test passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)
Description of change

@killagu killagu requested review from popomore and dead-horse July 3, 2018 12:15
@killagu killagu changed the title feat: impl boot methods [WIP] feat: impl boot methods Jul 3, 2018
@killagu
Copy link
Contributor Author

killagu commented Jul 3, 2018

lifecyle

@codecov
Copy link

codecov bot commented Jul 3, 2018

Codecov Report

Merging #171 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@          Coverage Diff           @@
##           master   #171    +/-   ##
======================================
  Coverage     100%   100%            
======================================
  Files          18     20     +2     
  Lines         958   1068   +110     
======================================
+ Hits          958   1068   +110
Impacted Files Coverage Δ
lib/egg.js 100% <100%> (ø) ⬆️
lib/loader/mixin/custom.js 100% <100%> (ø) ⬆️
lib/loader/mixin/boot.js 100% <100%> (ø)
lib/utils/index.js 100% <100%> (ø) ⬆️
lib/loader/egg_loader.js 100% <100%> (ø) ⬆️
lib/lifecycle.js 100% <100%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ae38fa4...f74f6f0. Read the comment docs.

lib/egg.js Outdated
}
}

ready(flagOrFunction) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个直接用 get-ready 吧

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里没法直接用 get-ready 的,就是上次讲过的 第二个 ready 对象需要延迟init

lib/egg.js Outdated
/**
* @member {BootClass} EggCore#BootClass
*/
this.BootClass = BootClass;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感觉没必要继承这个类

app.bootLog.push('didReady');
}

async beforeClose() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个我们要调整吗,didClose

this.timing.end('Load app.js');
this.app[Symbol.for('EggCore#triggerConfigDidLoad')]();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个应该在 loadconfig 后

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadCustomApp 是在 loadConfig 之后的。

@popomore
Copy link
Member

popomore commented Jul 3, 2018

逻辑可以在整理下,这里 app 上比较乱,建议放到一个类里面去处理所有逻辑,比如 lifecycle

  1. Boot 类要提前加载,所以要把 customApp 拆分两步,require 和执行
  2. 实例化 lifecycle 类,提前 require 所有的 app.js,实例化所有的类。
  3. 在 loadConfig 后 trigger didLoadConfig
  4. 在 load 后 trigger didLoad
  5. 在 didLoad 后 trigger willReady
  6. 以下后现在类似

在 lifecycle 管理生命周期,lifecycle ready 后触发 app ready。app 现有的 api 可以直接调用这个类的 api 来注册

@fengmk2
Copy link
Member

fengmk2 commented Jul 11, 2018

关注,puml 图也提交到 README 中,或者 egg 文档中。

@killagu
Copy link
Contributor Author

killagu commented Jul 11, 2018

ping @popomore 把生命周期的控制抽取到 Lifecycle 这个类里面了。

Copy link
Member

@popomore popomore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

先提这些

lib/lifecycle.js Outdated
*/
initBoots() {
this.boots = this.app[BOOT_FILES]
.map(t => (is.function(t) && !is.class(t) ? t(this.app) : t))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里为啥还要执行下?应该就筛选出类

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadCustom 里面不再执行了,统一只执行了 requireFile , 所以这里执行了一下。兼容旧的方法的写法。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

还是分开吧,这里只处理 boot 类,那里保持原样。

lib/lifecycle.js Outdated
this.triggerConfigDidLoad();
}

beforeStart(scope) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里改成 registerDidLoad 吧,和 trigger 对应。

app 的 beforeStart 还是调用这个。

加一个 legacyReadyCallback 的 getter 调用 loadReady 的 readyCallback,然后 app 的 readyCallback 调用这个。

lib/lifecycle.js Outdated
return this.closePromise;
}

ready(flagOrFunction) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里还是直接用 get-ready 吧

this.loadReady.ready(() => {
this.triggerWillReady()
})

this.bootReady.ready(() => {
this.ready(true)
});

这样把整个流程串起来

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果错误就直接调用 this.ready(err)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app.ready 这个方法中传入的函数实际调用的是 bootReady,而 bootReady 是在 loadReady ready 之后才实例化的。所以 lifecycle 的 ready 方法不能直接由 ready 对象代理。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app.ready 应该是应用完成 ready 后,不是注册到 bootReady

lib/lifecycle.js Outdated
});
}

beforeClose(fn) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个也改成 registerDidClose 吧

lib/lifecycle.js Outdated
this.closeSet.add(fn);
}

close() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里改成 async 吧,然后判断下是否执行了两次

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个判断我加一下。不过是 break change 了,旧版本是可以 close 多次的。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

之前也不行只是返回相同的 promise,看看测试看

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

现在这里重复执行也是返回相同的 promise,判断不是抛出重复 close 异常的意思吗?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

改成 async 会更加简洁,现在还要有个闭包

lib/lifecycle.js Outdated
.filter(t => t.didReady)
.map(t => t.didReady.bind(t));
const readyCbs = [
...this.readyCallbacks,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里只需要处理 boot 类的就好了,直接进入到 lifecycle 的 ready 阶段了,开发者自己坚挺 app.ready 不需要我们处理了

lib/egg.js Outdated
@@ -123,6 +122,7 @@ class EggCore extends KoaApplication {
logger: this.console,
serverScope: options.serverScope,
});
this[BOOT_FILES] = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个有用?

continue;
}
const file = this.requireFile(filePath);
this.app[BOOT_FILES].push(file);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

直接用 lifecycle 的 api?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个阶段应该在 didConfigLoad 之后了,我绝对 loader 最开始可以先加载 boot 文件,然后到这里才执行函数。

lib/egg.js Outdated
app: this,
logger: this.console,
});
this.lifecycle.on('error', this.emit.bind(this, 'error'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error => this.emit('error', error) 的写法吧,可以不用 bind 了

lib/lifecycle.js Outdated
return this.app.timing;
}

get legacyReadyCallback() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

直接用函数呗,bind 比较丑

lib/lifecycle.js Outdated
*/
initBoots() {
this.boots = this.bootFiles.map(t => new t(this.app));
this[REGISTER_BEFORE_CLOSE]();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是不是可以在这里把所有的都给注册一遍

for (const boot of this.boots) {
  if (boot.didLoad) {
    this.didLoadSet.add(boot.didLoad);
  }
  if (boot.willReady) {
    this.willReadySet.add(boot.willReady);
  }
  // ...所有生命周期
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个没什么必要吧,单独把 function 存起来又要用 bind 了。

lib/lifecycle.js Outdated
this.closeSet.add(fn);
}

close() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

改成 async 会更加简洁,现在还要有个闭包

lib/lifecycle.js Outdated
this.loadReady.ready(error => {
debug('didLoad done');
if (error) {
this.triggerDidReady(error);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果 error 这里就不用触发了吧

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

triggerDidReady 同样是通过 this.ready(err => this.triggerDidReady(err)) 触发的

lib/lifecycle.js Outdated
});
}

[INIT_BOOT_READY]() {
Copy link
Member

@popomore popomore Jul 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个和 INIT_LOAD_READY 应该是类似的,这里有些差异,比如 readyTimeout 和 stat

lib/lifecycle.js Outdated
/**
* init boots and trigger config did config
*/
initBoots() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个在哪里调用的

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

由 loader 在 loadCustomApp/loadCustomAgent 结束后调用

@popomore
Copy link
Member

initBoots 的位置还是不对,应该在 customApp 前面,比如在 configDidLoad 修改中间件,app.js 里面应该可以取到最新的。

@killagu
Copy link
Contributor Author

killagu commented Jul 17, 2018

@popomore 改对了,测试用例里面也加入了这种情况。

@killagu killagu force-pushed the impl-boot branch 2 times, most recently from a273973 to 451d41f Compare July 19, 2018 02:37
lib/lifecycle.js Outdated
}

[INIT_BOOT_READY]() {
if (!this.bootReady) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里会调用两次?

lib/lifecycle.js Outdated
this[REGISTER_READY_CALLBACK](didLoad, this.loadReady, 'Did Load');
}
}
this.loadReady.ready(err => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是不是可以移到 INIT_LOAD_READY 去,定义和 trigger 分开。

lib/lifecycle.js Outdated

[INIT_BOOT_READY]() {
if (!this.bootReady) {
this.bootReady = new Ready({ timeout: this.readyTimeout });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

},

loadBootFile(fileName) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我又想了下,是不是增加一个 config.initBoot() 的方法,用来加载 app.js/agent.js,这个方法放到 config.loadPlugin 之后。

lib/lifecycle.js Outdated
await boot.didReady(err);
}
} catch (e) {
this.app.emit('error', e);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个不用 this.emit?

lib/lifecycle.js Outdated
(async () => {
for (const boot of this.boots) {
try {
if (boot.didReady) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if 放到外面去

lib/egg.js Outdated
}).on('ready_timeout', id => {
this.console.warn('[egg:core:ready_timeout] %s seconds later %s was still unable to finish.', eggReadyTimeoutEnv / 1000, id);
});
boot() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 API 是不是可以不需要了,triggerDidLoad 应该在 app.js 里面就触发了?

@fengmk2
Copy link
Member

fengmk2 commented Aug 21, 2018

egg 文档也改起来,加上这个章节 👍

@killagu killagu changed the title [WIP] feat: impl boot methods feat: impl boot methods Aug 23, 2018
@killagu
Copy link
Contributor Author

killagu commented Aug 23, 2018

我又想了下,是不是增加一个 config.initBoot() 的方法,用来加载 app.js/agent.js,这个方法放到 config.loadPlugin 之后。

这个 app.js/agent.js 我想想还是不拿出来了,app.js/agent.js 加载是有特殊性的,appWrokerLoader 只加载 app.js,agentWrokerLoader 只加载 agent.js。现在是通过方法的方式来区分的,而在 EggLoader 自身是区分不出是 appWorkerLoader 还是 agentWorkerLoader 的,不能做到只加载需要的文件。

@killagu
Copy link
Contributor Author

killagu commented Aug 25, 2018

ping @popomore

* @see https://github.com/node-modules/ready
* @since 1.0.0
* @param {boolean|Error|Function} flagOrFunction -
* @return {Promise|null} return promise when argument is undefined
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

啥时会返回 null ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应该推荐使用 await app.ready() 的方式而不是 callback 的方式?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

使用 ready(() => {}) 的时候是返回 null 的。这里是保证接口的兼容性。

lib/egg.js Outdated
this.console.warn('[egg:core:ready_timeout] %s seconds later %s was still unable to finish.', eggReadyTimeoutEnv / 1000, id);
});
async close() {
if (this.closePromise) return this.closePromise;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closePromise 是内部变量?用 Symbol 或 _ ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个可以有。

lib/lifecycle.js Outdated

const utils = require('./utils');

class Lifecycle extends EventEmitter {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

要不要继承 sdk-base 啥的? @popomore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 Lifecycle 和 SDKBase 还不太一样,SDKBase 一般是异步 init 的时候用一下。

this.timing.start('Application Start');
// get app timeout from env or use default timeout 10 second
const eggReadyTimeoutEnv = Number.parseInt(process.env.EGG_READY_TIMEOUT_ENV || 10000);
assert(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 这句应该在上面的 parseInt 前面?
  • Number.isInteger 用 is-type-of ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • parseInt 之后才能使用 Number.isInteger
  • 这个 Number.isInteger 还用 is-type-of 就没什么必要了吧。


this[INIT_READY]();
this
.on('ready_stat', data => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ready_done / ready_suc 会不会好点?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是 ready-callback emit 过来的事件了,不好改了。

lib/lifecycle.js Outdated
init() {
assert(this[INIT] === false, 'lifecycle have been init');
this[INIT] = true;
this.boots = this.bootFiles.map(t => new t(this.app));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 t 是一个 class ? 上面的 addBootFile 是一个 file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是一个 class,在 customLoader 里面有这样一个判断 is.class(bootFile)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addBootFile 这个命名会不会有点误导? addBootHook(Clz) 这样是不是好点?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是有点误导,改了。

lib/lifecycle.js Outdated

registerBeforeClose(fn) {
assert(is.function(fn), 'argument should be function');
assert(this.isClose === false, 'app has been closed');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isClosed ? 不过要看看之前的风格

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

之前就是 isClose 😂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不过还是改一下吧。改一下合理一点

1. add `Boot` Class, developer can inherit from it.
2. add `startBoot`, application like egg should call
`startBoot` after all files loaded.

Refs: eggjs/egg#2520
@fengmk2
Copy link
Member

fengmk2 commented Sep 4, 2018

@killagu @popomore @atian25 是否可以合并了?

@atian25
Copy link
Member

atian25 commented Sep 5, 2018

+1

@popomore popomore merged commit 9d2f2fc into master Sep 6, 2018
@popomore popomore deleted the impl-boot branch September 6, 2018 05:49
@popomore
Copy link
Member

popomore commented Sep 6, 2018

@initial-wu
Copy link
Contributor

lifecycle 内部 beforeClose 钩子保存在一个 Set 里面是出于什么考虑?私以为用数组貌似也可以?

@killagu
Copy link
Contributor Author

killagu commented Sep 29, 2018

@initial-wu 可以防止重复添加。

@initial-wu
Copy link
Contributor

@initial-wu 可以防止重复添加。

明白。btw,考虑调整一下这里的写法吗 #171 (comment)

image

@killagu
Copy link
Contributor Author

killagu commented Sep 29, 2018

没明白你说的意思。 get legacyReadyCallback 这里已经改掉了。

@initial-wu
Copy link
Contributor

没明白你说的意思。 get legacyReadyCallback 这里已经改掉了。

这个评论的锚点应该已经变了,@popomore 可能是想说改成这样?

image

@killagu
Copy link
Contributor Author

killagu commented Sep 29, 2018

之前说的就是你截图的地方,这里不用在封装一遍了,会多一层调用栈。

hyj1991 pushed a commit to hyj1991/egg-core that referenced this pull request Sep 16, 2021
1. Add `Lifecycle` class, control EggCore lifecycle.
2. After config, extend, app.js, angent.js loaded, loader should call `initBoots`.
3. After all files loaded, loader should call `triggerDidLoad`.

Refs: eggjs/egg#2520
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants