[eggjs/egg]多线程中的ctx以及app疑问

2024-08-05 236 views
7

需求是这样的,使用了egg-sequelize,为了分表外加新建项目后自动生成新建,除了固定的表之后,拟写了一个动态创建表的逻辑。

对egg摸的不是很透彻,所以只能按照自己思路搞了。

大概流程是在新建一个产品的时候会以产品名动态生成5个表,然后sync,然后每个model手动挂载在app.model上(每次启动时在didReady 进行所有动态表同步导入)。

获取model的时候ctx.model和app.model都能获取到动态的表,但是因为开多线程模式只有一个线程能同步所以我使用了this.app.messenger进行发送和监听。

What happens?

这里会出现一个BUG:

app.js didReady里创建:

const ctx = this.app.createAnonymousContext();

ctx.model是Sequelize {} 空对象。

所以导致在其他线程work里的service里无法使用ctx.model,改用app了。

有可能和我只给app.model赋值了有关?然后非多线程模式我却能在ctx获取到model。

this.app.model正常,但是有个奇怪的问题:

我需要生成的表有5个,所以写了一个循环生成。

在messenger.on里执行的生成表逻辑循环里如果直接操作app.model[modelName] = model 手动挂载的话,其中一个线程work走完后,到第二个去挂载的话会自动崩溃重启,没有报错,原因不明。

如果在用一个额外变量储存好所有的model后,待循环完了之后在赋值给app.model就不会崩溃重启,也不是很明白原因。

最小可复现仓库

暂无。

复现步骤,错误日志以及相关配置
  1. 开启多线程。
  2. 操作线程动态的创建model.define对象逻辑,挂载,然后发送给其他线程。
  3. 其他线程同样执行创建model.define对象逻辑,挂载。

另外有个疑问,多线程通信是否可以排除发送者? 我在文档中似乎没有这个配置。

静待答复,拜谢各位大佬。

相关环境信息
  • 操作系统:win10
  • Node 版本:node version v10.12.0
  • Egg 版本:egg version 2.22.2

回答

6

没太看懂,崩溃的话 common-error 日志应该有的,看下日志文档,正式环境日志不是在源码目录下的。

3

没太看懂,崩溃的话 common-error 日志应该有的,看下日志文档,正式环境日志不是在源码目录下的。

common-error 真没有日志,直接就重启所有线程了。

我直接上代码解释吧:

const { app, logger, helper } = this;
    try {
      if (!project.projectId) {
        logger.info(`----- importDynamicTable error !-----`.red);
        return { err: 1, msg: "importDynamicTable error" };
      }

      logger.info(
        `----- importDynamicTable ${project.projectId} !-----`.magenta,
      );

      const importBase = "../domain/dynamic_table/";

      app.model[project.projectId] = {};
      const obj = {};

      for (let index = 0; index < this.app.DynamicPaths.length; index++) {
        const fileName = this.app.DynamicPaths[index];
        const fileShort = fileName.split(".")[0];
        const modelName = `${project.projectId}_${fileShort}`; // 生成项目动态表名
        const modelNameCase = _.upperFirst(_.camelCase(fileShort)); // 转换 (不带项目名)
        const importPath = path.join(__dirname, importBase, fileName);
        const mModel = require(importPath)(modelName, this.app);
        obj[modelNameCase] = mModel;
        if (sync) {
          await mModel.sync({ alter: true });
        }
      }

      app.model[project.projectId] = obj;
    } catch (error) {
      this.logger.error(
        `----- Dynamic sync ${project.projectId} error !-----`.red,
        error,
      );
      return { err: 0, msg: "request is error" };
    }

简单来说,需求是有5个表需要动态生成导入,所以我用如上逻辑代码去动态导入mModel 挂载到app,model上,单线程执行是没有任何问题。多线程其他work上的用户无法获取到新建的model对象。 所以需要在第一个work用户执行如上代码后通知其他work也执行

第一个问题: 我开始的时候service里所有的逻辑操作的model是使用ctx.model,默认启动的时候都是好的, 但是因为要运行后用户新建表通知其他work同步导入model, 所以在app.js里监听消息:

 async didReady () {
    // 应用已经启动完毕
    this.app.messenger.on("refreshProjectCache", async project => {
      // process data
      const ctx = this.app.createAnonymousContext();
      // console.log("didReady1", ctx.model);

      await ctx.service.mysql.importDynamicTable(project); //上图代码逻辑
    });
  }

发现ctx.model是Sequelize {} 空对象。不知为何,导致service里ctx.model报错,无法获取到里面的其他表model对象了,所以只能改成使用app.model.

第二个问题比较玄学:

在importDynamicTable 代码的循环挂载里我是用obj 暂存的model,最后在赋值,因为我发现我直接赋值的话,其中一个work执行完后第二个work执行就会直接大概率崩溃重启所有线程。并且没有错误日志(线上环境的日志)

第二个问题 我需要一定先 执行 app.model[project.projectId] = {}; 提前new obj,不执行必崩,但是不知为啥第一个work能执行成功。后面的不行。

4

this.app.messenger.on("refreshProjectCache", async project => {}

... 谁跟你说一个 on 的 event callback 是支持 async 函数的,这里明显就有一个未捕获的异常点了

5

this.app.messenger.on("refreshProjectCache", async project => {}

... 谁跟你说一个 on 的 event callback 是支持 async 函数的,这里明显就有一个未捕获的异常点了

我看没有报错 0 0,需要执行异步只能封装成一个异步写在on里吗....

请问第一个问题 为啥const ctx = this.app.createAnonymousContext(); 创建出来的 ctx.model 是一个空的Sequelize 对象?

6

你那段代码可能会导致一个 UnhandledPromiseRejectionWarning,因为跳出了 Koa 的 Promise 链了。

不过这种应该也会在 common-error.log 的,你注意观察下启动日志写错误日志输出到哪里了,或者看下 run/application_config.json 里面的 logger 的实际地址

3

你那段代码可能会导致一个 UnhandledPromiseRejectionWarning,因为跳出了 Koa 的 Promise 链了。

不过这种应该也会在 common-error.log 的,你注意观察下启动日志写错误日志输出到哪里了,或者看下 run/application_config.json 里面的 logger 的实际地址

仔细看了一下 日志地址没有错,崩溃原因大致应该是app.model 在其他work赋值的时候发生错误,新建对象解决了,具体原因没有细查。

现在唯一的问题是 const ctx = this.app.createAnonymousContext(); 创建出来的 ctx.model 是一个空的Sequelize 对象。

这个也临时改成 全部使用 app.model 来解决了

8

可以提交个最小可复现仓库看看

9

可以提交个最小可复现仓库看看

我用最小示例调试发现没有这个问题,自己的项目可能复杂度太高,但是不清楚在什么地方导致ctx.model 为空了。还是多谢天猪老哥 的帮忙~

3

还有一个疑问,为什么官方最小示例 在windows下start没有cmd窗口,而一模一样配置的我的项目却有?

2

egg-scripts 后面有个更新处理了这个,你要更新下依赖

$ # reinstall deps and never lock it.
$ rm -rf node_modules yarn.lock package-lock.json
$ npm i --no-package-lock