Sentry-javascript: @ sentry /节点内存在节点10.13.0上泄漏

创建于 2018-11-22  ·  50评论  ·  资料来源: getsentry/sentry-javascript

套餐+版本

  • [x] @sentry/browser
  • [x] @sentry/node

版:

4.3.4

描述

我尝试将哨兵集成到一个next.js项目中。 我使用此模板https://github.com/sheerun/next.js/tree/with-sentry-fix/examples/with-sentry进行了尝试,发现似乎是哨兵内存泄漏。 如果您签出此项目并添加memwatch

if (!dev) {
  memwatch.on("leak", info => {
    console.log("memwatch::leak");
    console.error(info);
  });

  memwatch.on("stats", stats => {
    console.log("memwatch::stats");
    console.error(Util.inspect(stats, true, null));
  });
}

并用请求轰炸服务器,我请求了以下资源:

    "/",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/_app.js",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/_error.js",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/index.js",
    "/_next/static/runtime/main-1eaa6d1d0c8e7d048efd.js",
    "/_next/static/chunks/commons.b34d260fee0c4a698139.js",
    "/_next/static/runtime/webpack-42652fa8b82c329c0559.js"

有了这个,记忆的使用对我无休止地增长。 一旦我从server.js中删除了request和errorHandler,内存泄漏就停止了。 所以它似乎与那两个

In Progress Bug

最有用的评论

@michalkvasnicak我对此进行了调查,这并非直接由Sentry引起。

我们的@sentry/node传输依赖于https-proxy-agent ,依赖于agent-base ,这需要它的patch-core.js文件_and_这就是造成泄漏的原因:耸耸肩:

https://github.com/TooTallNate/node-agent-base/issues/22

您可以通过将其内容复制到测试中并使用堆统计信息运行它来轻松验证这一点:

const url = require("url");
const https = require("https");

https.request = (function(request) {
  return function(_options, cb) {
    let options;
    if (typeof _options === "string") {
      options = url.parse(_options);
    } else {
      options = Object.assign({}, _options);
    }
    if (null == options.port) {
      options.port = 443;
    }
    options.secureEndpoint = true;
    return request.call(https, options, cb);
  };
})(https.request);

https.get = function(options, cb) {
  const req = https.request(options, cb);
  req.end();
  return req;
};

it("works", () => {
  expect(true).toBe(true);
});

我们可能必须将其分叉或编写解决方法。

所有50条评论

@abraxxas您可以帮助我重现此问题吗? 您究竟是如何触发此memleak的?
我没有运气就尝试过您的描述,它只会产生统计事件,永远不会泄漏事件。

@kamilogorek感谢您的回复。 我所做的是以下。 我签出了这个示例https://github.com/sheerun/next.js/tree/with-sentry-fix/examples/with-sentry并将memwatch添加到server.js

if (!dev) {
  memwatch.on("leak", info => {
    console.log("memwatch::leak");
    console.error(info);
  });

  memwatch.on("stats", stats => {
    console.log("memwatch::stats");
    console.error(Util.inspect(stats, true, null));
  });
}

然后,我使用节点10.x(使用8.xi观察到没有内存问题)运行该示例,并使用我们的加特林测试套件要求以下资源:

    "/",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/_app.js",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/_error.js",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/index.js",
    "/_next/static/runtime/main-1eaa6d1d0c8e7d048efd.js",
    "/_next/static/chunks/commons.b34d260fee0c4a698139.js",
    "/_next/static/runtime/webpack-42652fa8b82c329c0559.js"

(请注意,哈希可能会为您更改)

但您应该可以使用这种方法非常简单的https://www.simonholywell.com/post/2015/06/parallel-benchmark-many-urls-with-apachebench/获得相同的结果

经过数千次请求后,我们的内存使用量几乎达到1GB,即使在空闲时也从未减少。 一旦我从server.js中删除了request和errorHandler,内存泄漏就停止了。 因此,它似乎已连接到那些2。也许您请求太少或使用了节点8.x?

@abraxxas已确认。 每个资源的节点10 +〜300req都可以完成工作。 将进一步调查。 谢谢!

@kamilogorek有趣,但是如果您查看自己的堆大小
复制,您会发现没有处理程序,它的大小保持在20mb左右
但随着他们的增加而迅速增加。

我认为没有处理程序的内存泄漏警告只是因为
由于请求,有一些常量内存增加。

使用以下版本的版本之间在内存使用方面仍然存在巨大差异
哨兵和没有。

2018年11月29日,星期四,12:45卡米尔·奥古瑞克< [email protected]写道:

@abraxxas https://github.com/abraxx,因为我成功复制了它,
但是,服务器似乎仍然会自行泄漏请求对象,
即使没有哨兵

https://streamable.com/bad9j

由于我们确实附加了域名和我们自己的范围,因此增长率要大一点
反对请求,但GC会与请求一起使用。


您收到此邮件是因为有人提到您。
直接回复此电子邮件,在GitHub上查看
https://github.com/getsentry/sentry-javascript/issues/1762#issuecomment-442804709
或使线程静音
https://github.com/notifications/unsubscribe-auth/AIbrNlgPjPd5Jra1aahR-Dthf7XvbCexks5uz8jjgaJpZM4YvOA2

@abraxxas忽略我之前的评论(我删除的评论),它完全在我们这一边:)

这似乎是Node核心本身的问题。 请参考此评论https://github.com/getsentry/sentry-javascript/issues/1762#issuecomment -444126990

@kamilogorek您有没有机会研究这个? 这给我们造成了巨大的内存泄漏。 在查看了堆之后,这一行看起来可能是问题的根源:

https://github.com/getsentry/sentry-javascript/blob/c27e1e32d88cc03c8474fcb1e12d5c9a2055a150/packages/node/src/handlers.ts#L233

检查员在eventProcessors列表中显示了数千个条目
image

我没有关于事物如何设计的上下文,但是我们注意到请求的范围不正确,并且提供了错误的元数据(请参阅#1773),因此似乎所有内容都在全局状态下进行管理,并且当请求结束

@abraxxas @tpbowden Node核心本身中的域模块泄漏存在问题。 我们将继续对其进行监视,并尝试提出一个临时解决方案,然后再将其固定在核心中。 相关问题: https :

@kamilogorek您是否对此有任何解决方法或临时解决方案? 节点问题的进度看起来很慢

当前,当内存达到一定阈值时,我们正在使用PM2重新启动Node.js进程。 https://pm2.io/doc/zh/runtime/features/memory-limit/#max -memory-threshold-auto-reload

使用实验室进行单元测试。 泄漏仍然存在。 我知道泄漏可能很难调试。 是否有ETA可以解决?

1个测试完成
测试时间:1832毫秒
检测到以下泄漏:__extends,__ assign,__ rest,__ decorate,__ param,__ metadata,__ awaiter,__ generator,__ exportStar,__ values,__ read,__ spread,__ await,__ asyncGenerator,__ asyncDelegator,__ asyncValues,__ import,

npm ERR! 代码ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected]测试: lab build/test
npm ERR! 退出状态1
npm ERR!
npm ERR! 在[email protected]测试脚本处失败。
npm ERR! npm可能不是问题。 上面可能还有其他日志记录输出。

npm ERR! 可以在以下位置找到此运行的完整日志:
npm ERR! /Users/sunknudsen/.npm/_logs/2019-02-13T14_41_28_595Z-debug.log

@sunknudsen该问题实际上已在https://github.com/nodejs/node/issues/23862https://github.com/nodejs/node/pull/25993。 我们可能需要等待发布。

@garthenweb由于软件包的开发方式,这是否影响@sentry/node ? 我在hapi上开发的项目之一(并依赖于许多其他依赖项)不会产生泄漏(至少它们没有被Lab捕获)。

@sunknudsen或多或少。 据我所知,它是(已弃用的)域软件包和Promise的组合。 参见https://github.com/getsentry/sentry-javascript/blob/master/packages/node/src/handlers.ts#L233

就我而言,我只是从我的(快速)服务器中删除了哨兵中间件进行修复。

这不是节点问题,而是在Sentry中,因为禁用Sentry处理程序可以解决该问题;

image

@MartijnHols我们目前正在开发一个主要版本,该版本应会大大减少SDK的内存占用。 如果您喜欢冒险,可以尝试一下https://github.com/getsentry/sentry-javascript/pull/1919

@HazAT谢谢,我昨晚将其安装在生产中(在图中的23:10),并使用以下结果重新启用了处理程序。 通常,CPU使用率在23:00-24:00左右会略有上升(如前一天所见),但这似乎更高。 与没有处理程序的情况相比,标准的CPU使用情况也更加刺眼。 不知道这是由于新版本中的更改还是启用了处理程序。 我将在几个小时内再次尝试禁用处理程序。 每小时大约捕获2.5个错误。

image

@MartijnHols感谢您尝试!

请记住两点,对节点中域的内存泄漏修复仅在11.10才出现在节点中。
另外,我们不得不取消发布5.0.0-beta1因为它被错误地标记为latest ,而5.0.0-rc.1现在是最新的next版本。
请尝试5.0.0-rc.1我们对事件的排队方式进行了小的更改,这将大大改善负载/内存。

更新到节点11.12似乎已稳定了内存和CPU使用率。 与禁用处理程序相比,现在在资源使用上似乎没有任何明显的区别,甚至可能更好。 似乎还可以用我需要的所有信息捕获错误(它可能有更多的控制台“面包屑”,很好)。 不知道我还能为5.0.0检查什么。 如果遇到任何问题,我会通知您,但可能不会。

LGTM。 谢谢!

我也很乐意尝试一下。 @HazAT您是否知道11.10的修复程序是否已经反向移植到活动的LTS版本10.x

@adriaanmeuris我读过有人问它是否会10.x ,不确定他们是否会这样做。
参考: https :

我解决了在快速中间件中手动创建Client和Scope的问题

我创建了文件services/sentry来导出函数

import {
  NodeClient as SentryClient, Hub, Integrations, Scope,
} from '@sentry/node';
import config from 'config';

const sentryClient = new SentryClient({
  ...config.sentry,
  frameContextLines: 0,
  integrations: [new Integrations.RewriteFrames()],
});

export default () => {
  const scope = new Scope();
  const client = new Hub(sentryClient, scope);
  return Object.freeze({ client, scope });
};

在中间件中,我将哨兵客户端/范围保存在这样的请求对象上

app.use((req, res, next) => {
  req.sentry = getSentry();
  req.sentry.scope.setTag('requestId', req.requestId);
  req.sentry.scope.setExtra('More info', 'XXXXXX');
  next();
});
....
// and use the express error handler
app.use((err, req, res, next) => {
  const code = err.code || 500;
  res.status(code).json({
    code,
    error: err.message,
  });
  if (code >= 500) {
    req.sentry.client.captureException(err);
  }
  next();
});

这样看来,内存泄漏已修复

image

@couds这个实现真的很好,您必须考虑一件事。

对于每个请求,您都会获得一个新的客户端,并且我们不会捕获任何全局错误/自动痕迹(或其他任何默认集成所做的操作)。

@HazAT谢谢,我知道,但这是一个很小的代价。 为了解决巨大的内存泄漏,但是为了最大程度地减少这种丢失,我做了几件事。

  1. 我尝试处理所有我不依赖于unCoughException / promise事件的异常。
  2. 对于面包屑,只要可以访问req对象,就可以手动添加它们。
  3. 使用@sentry/webpack-plugin将源地图上传到哨兵
  4. 在每个请求上添加标签/附加信息,以帮助识别问题

还有一件事,我忘记粘贴要添加的另一个标签,即事务(模拟默认的Handler)

req.sentry.scope.setTag('transaction', `${req.method}|${req.route ? req.route.path : req.path}`);

我希望这能对某人有所帮助,Sentry它是一个非常出色的工具,我已尽我所能避免将其从堆栈中删除。

@HazAT @kamilogorek我们仍然看到[email protected][email protected]的巨大内存增长-您确定此Node.js补丁已修复它吗?

@tpbowden您能否提供一个
另外,您是否还在使用与Sentry相关的其他任何插件?

@HazAT我试图重现,但它是由具有许多中间件(服务器端呈现React)的相当复杂的服务器引起的。 安装了Sentry中间件后,我们正在实现巨大的内存增长(在高负载下,它可以增加约10MB /秒)。 当我们从服务器中删除Sentry.Handlers.requestHandler()中间件时,内存恒定为〜200MB

我们没有使用任何插件,只有@sentry/node 。 您能想到什么我可以尝试帮助重现的内容吗?

@HazAT似乎与在服务器上使用Webpack捆绑Sentry有关-仅在由Webpack构建应用程序时发生。 将@sentry/nodeexternals已解决了我们的内存问题。 知道为什么会这样吗?

使用@ sentry / node 5.1.0在节点11.14.0上仍然可以重现该泄漏

对我们来说,泄漏是由@ sentry / node与i18next-express-middleware之间的相互作用引起的。 我们的快速应用看起来类似于https://github.com/i18next/react-i18next/blob/master/example/razzle-ssr/src/server.js。

如果我们将.use(Sentry.Handlers.requestHandler());放在.use(i18nextMiddleware.handle(i18n))之上,则会发生内存泄漏。 如果我们将哨兵放在下面,那么我们就不会泄漏。

我们有同样的问题。 我尝试了node 10.15.311.14.0 。 这是重现问题的最小存储库,网址为https://github.com/michalkvasnicak/sentry-memory-leak-reproduction。 只需运行yarn testyarn test:watch报告堆使用情况。 它总是增加。

yarn test
image

yarn test:watch

image

image

@michalkvasnicak感谢您抽出

您并未真正测试有关SDK的任何内容

const Sentry = require('@sentry/node');

it('works', () => {
  expect(true).toBe(true);
});

您需要包装,仅此而已。
我不确定,因为我还没有jest泄漏测试的经验,但是仅需要打包就能泄漏什么?
不确定,也许我丢失了一些东西,但是我希望加载新软件包时内存会增加。

关于可能泄漏的内容,我们确实创建了一些全局变量,但是我们需要它们来跟踪状态。

@HazAT是的,这很奇怪,但足以使测试泄漏, jest将失败,并检测到泄漏。 我们的代码库中存在相同的问题,只需注释@sentry/node的导入即可解决泄漏问题。

@michalkvasnicak我对此进行了调查,这并非直接由Sentry引起。

我们的@sentry/node传输依赖于https-proxy-agent ,依赖于agent-base ,这需要它的patch-core.js文件_and_这就是造成泄漏的原因:耸耸肩:

https://github.com/TooTallNate/node-agent-base/issues/22

您可以通过将其内容复制到测试中并使用堆统计信息运行它来轻松验证这一点:

const url = require("url");
const https = require("https");

https.request = (function(request) {
  return function(_options, cb) {
    let options;
    if (typeof _options === "string") {
      options = url.parse(_options);
    } else {
      options = Object.assign({}, _options);
    }
    if (null == options.port) {
      options.port = 443;
    }
    options.secureEndpoint = true;
    return request.call(https, options, cb);
  };
})(https.request);

https.get = function(options, cb) {
  const req = https.request(options, cb);
  req.end();
  return req;
};

it("works", () => {
  expect(true).toBe(true);
});

我们可能必须将其分叉或编写解决方法。

这个有什么解决方法吗?

https://nodejs.org/en/blog/release/v10.16.0/

修复了一些内存泄漏,有人可以测试吗?

我在节点11.10上遇到了同样的问题,但似乎在节点12.3.1上已解决

agent-base问题有一个PR打开: https :

我们可以对此进行分叉并覆盖Yarn分辨率中的依赖项,也可以找到一种方法将其合并。

可能有些偏离主题,但也可能导致一些泄漏,即在Handlers.ts中没有域清理,只有domain.create?
中篇文章SO建议应该有removeListeners和domain.exit。 但是没有找到确切的答案。

更新:现在,我看到处理程序正在使用domain.run,在内部调用domain.exit。 仍然删除侦听器/发射器可能会有所不同,但是说实话,这没什么意义。

我升级到Node 12.4.0,但仍然看到不良行为,似乎与Sentry有关。

这是一些运行在90秒钟内的--autocannon选项完成的节点诊所医生。

当请求处理程序到位时,它似乎真的泄漏了。 如果您在不使用处理程序的情况下查看GC槽的底部,它们的水平大致相同(65-70mb),在此情况下,使用处理程序的运行似乎在每个GC周期都上升了约5mb。

@ tstirrat15此修复程序尚未发布,因此这可能就是您仍然遇到此问题的原因。 如果可以的话,您可以尝试使用最新的大师吗?

@ tstirrat15 5.4.2包含的修复程序已发布,请尝试一下:)

这是使用v5.4.2的另一次运行。 似乎还是有点漏气...

GC始终可以正常运行,并将内存恢复到基线。 内存使用量增加是由从事件和事件队列中收集的面包屑引起的,但是它将停止在100个面包屑并且不会进一步增加。 如果能看到大约15-30分钟的转储,并查看峰值内存是否在某个时刻停止,那将是很好的。

嗯...听起来不错。 我将把这个PR传递到生产环境,看看行为是否改变。 谢谢!

看起来这与在服务器上使用Webpack绑定Sentry有关-仅在由Webpack构建应用程序时发生。 在外部添加@ sentry / node可以解决我们的内存问题。 知道为什么会这样吗?

@tpbowden您对此是正确的,我有同样的问题。 我正在运行SDK v5.15.0和节点v12.3.1,它们都应该包含此处提到的所有必需的修复程序。

我将服务器捆绑中的所有依赖项与webpack捆绑在一起。 这样,我可以在不使用node_modules的情况下运送docker映像,但是这种方法会把哨兵SDK弄乱,并且以这种方式捆绑时会泄漏内存。

这可能是某些优化过程造成的错误。 我的猜测很可能是更简短。 一些优化可能会使域模块的使用混乱,并且传递给scope.addEventProcessor的回调的关闭不再被垃圾收集,因此每个请求都泄漏了大量内存。

我还使用了razzle.js,它在webpack / terser版本中有些落后,也许已经修复了。

这似乎不再是哨兵方面的错误。 我将继续对此进行调查,并在适当的地方打开一个问题,并保持该线程更新。

这似乎不再是哨兵方面的错误。 我将继续对此进行调查,并在适当的地方打开一个问题,并保持该线程更新。

保持联系,谢谢!

@kamilogorek您能否指出我在Scope实例中添加到_eventProcessors数组中的事件处理器回调在代码中的何处? 我找不到似乎所有请求都向该数组添加了事件处理器回调,并且从未删除它们。 如果我知道应该如何删除它们,则可以帮助我更好地理解该错误。

Screen Shot 2020-03-23 at 15 49 03

或者,也许应该是整个范围是唯一的,并为每个请求收集垃圾? 似乎每个请求都获得相同的作用域实例

哈! 我想我找到了些东西。

我们使用dynamicRequire:
https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/hub/src/hub.ts#L465-L468

但是当我进入dynamicRequire代码时:
https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/utils/src/misc.ts#L28-L31

requiremod 🤯中未定义

因此它进入getHubFromActiveDomain函数的catch块,而使用getHubFromCarrier()

由于在我的设置中_everyting_是由webpack捆绑的,因此可能对mod对象做出了一些假设,这些对象被webpack破坏了。 您对如何解决这个问题有想法吗? 🤔

回顾

我们使用dynamicRequire:
Screen Shot 2020-03-24 at 12 05 04

mod.require是未定义的:
Screen Shot 2020-03-24 at 12 20 01

mod对象的外观如下:
Screen Shot 2020-03-24 at 12 20 38

我们最终使用getHubFromCarrier:
Screen Shot 2020-03-24 at 12 21 22

我直接在node_modules文件夹中手动修补了集线器模块。 我使用dynamicRequire删除了该行,并在文件顶部添加了import domain from 'domain'; ,...现在可以正常使用了! 不再泄漏! 🎉

也许以前曾经需要dynamicRequire hack,但是新版本的webpack不再需要它了吗? 🤔

我也尝试替换:

const domain = dynamicRequire(module, 'domain');

与:

const domain = require('domain');

而且效果也不错。 我不知道您更喜欢这两种解决方案中的哪一种。

您要我通过此修复程序打开PR吗?

此页面是否有帮助?
0 / 5 - 0 等级