跳到正文
W Winse Blog
dev automation 2 min read

被 n8n 任务 60s 超时反复折磨后的排查记录:从报错、堆栈到源码,理清 TASK_TIMEOUT 的真实用途与位置

一个完美、符合各种要求的环境是不存在的,只能在掌控和便捷之间来回游离。

最开始在 Windows 上部署的测试环境,用着也蛮好。刚开始,事情还没那么复杂,也还没用到Python。就这样通过拖拽 n8n 低代码来实现简单的功能,跑得也挺顺。

但程序员嘛,总会难耐的去各种折腾。环境一旦开始换,就很难再停下来。后面把最新的 n8n 2.x 部署进了 Docker。一开始都是正常的,流程都能正常的跑,任务也都不大。直到开始跑一些耗时的任务,比如下载大视频,运行时间一长,超过60秒就直接报错了

重试多次,错误日志都是:Task execution timed out after 60 seconds

遇到问题,第一个当然是下意识的去问 AI。给的方案里,其实有符合我预期的,但它不推荐这么做🧐。

程序员除了自己写代码,还有一个天然优势:用开源软件遇到问题,可以直接翻源码自己处理🤣。

开发环境配置

按照运行的版本,切换到 n8n@2.2.5,重新把开发环境搭起来。以前写过的相关文章翻出来,当复习资料,也算是自己教自己了。

这一步其实挺花时间的,不是难,而是前前后后的环境很多需要对应。

虚拟环境路径在源码 task-runner-python 的位置下。它用了一些只能在 Linux 下运行的依赖包,于是直接切到 VSCode 的 WSL 模式来进行调试(代码千万得放 WSL内部)。

一整套流程下来,总算能顺畅的查看代码、打断点、跑调试了。

C:\Users\P16>wsl --set-default-version 2

巨细节的定位问题

接下来,一起来,进行一次沉浸式文字版调试,DEBUG 走起。

最直接的报错的点在 PythonTaskRunnerSandbox#runUsingIncomingItems。

错误日志的关键字:Task execution timed out。

直觉上,这个是 TaskRunner 被强制中断的异常,应该对应的是  TaskRunnerExecutionTimeoutError 的异常,这与上面的都是 TS 类。

但问题在于,这个异常和 PythonTaskRunnerSandbox 之间不清楚有多大的鸿沟,前后关系是完全对不上的。

那就只能从两头向中间逼近。只要能在中间某个点对上了,问题就一定藏在那里了。

一、

1、先从任务启动的入口 executionFunctions.startJob 找。

2、接着找 startRunnerTask。这里的 Container 有点像 Spring 的依赖注入。

3、继续往下追,找 TaskRequester.startTask 的实现。

startTask 这里是提交任务的地方,通过消息把任务发给 TaskBroker。

4、在顺着 requester:taskrequest 消息往下找,看它在哪里被处理的。

这里就是 TaskRequester 和 TaskBroker 之间,通过发送消息进行任务管理的。

从官网的运行架构图来看,它们两个都是运行在 n8n 主进程里的。

二、

另一边,我们再拿着 TaskRunnerExecutionTimeoutError 沿着引用往回查找。

现在,两条线最后在 task-broker.service.ts 这里对上了。到这里,前面的链条算是合上了,方向是对的。

精神一振,曙光就在眼前了😎。

超时异常是在 handleTaskTimeout 里抛出来的。这个方法是在超时检测定时器 setTimeout 里被调用。

超时时间来自配置项 taskRunnersConfig.taskTimeout,它上面标注了环境变量 N8N_RUNNERS_TASK_TIMEOUT

整条链路基本串起来了,接下来就很自然了:修改环境变量,运行,验证。

在 n8n 主容器添加环境变量 N8N_RUNNERS_TASK_TIMEOUT 设成3600(1个小时)。重启,再跑一次 docker compose。

第一尝试:失败,结果还是同样报错超时。

从前面代码一路看下来,方向是对的,尽管还是报同样的错,第六感告诉我就在前方不远了。

其实,上面简单一行的报错信息并不是 TaskRunnersConfig#taskTimeout 这个配置导致的。

把时间设成 10s 时,它抛出的报错信息明显更加详细。

从结果来看,至少说明这个配置是生效的。但它不是最终导致任务超时的直接原因。还要继续精进,问题还没有完全的解决掉。

回到起点,再定位

回到最开始的报错信息,再查一次 Task execution timed out,现在来看,它就是 Python TaskRunner 超时抛出来的。

上面 TaskBroker 中异常处理 taskErrorHandler,还可能来自任务运行时的报错。

于是顺着 Python 这条线找,依次查找:

  • TaskTimeoutError(task_timeout_error.py)

  • -> execute_process(task_executor.py)

  • -> TaskRunner(task_runner.py)

  • -> TaskRunnerConfig

从 Python 的 TaskRunnerConfig 也是从环境变量  N8N_RUNNERS_TASK_TIMEOUT 获取数据,并且它的默认值是 60s。

还可以进到 runners 容器验证。查看运行任务的环境变量,可以看到  N8N_RUNNERS_TASK_TIMEOUT 的值也确实为 60s。

距离清楚问题又近了一点。

我之前只改了 n8n 主容器,以为这就够了的。但 external 模式下 Task Runner 实际是单独在 runners 容器运行的。

把主容器和 runners 容器的超时时间环境变量 N8N_RUNNERS_TASK_TIMEOUT 都一起改掉。

再跑一次。

这次就完美的下载到这个大视频了。

至此,超时的问题通过配置环境变量 N8N_RUNNERS_TASK_TIMEOUT 算是完美的解决了。

解惑

1、 TaskRunnersConfig 代码 taskTimeout 里面明明是300,怎么运行就变成60了呢?

这其实和 internal 模式搞混淆了,TaskRunnersConfig.taskTimeout 实际就是300s。

可以通过只设置 runners 的 task timeout 的值,跑一个 sleep 300s 的代码,就能验证了。

2、 PyTaskRunnerProcess 启动任务进程时从 TaskRunnersConfig 拿了 taskTimeout 设置了环境变量。为什么没有作用到 Python 任务呢?

PyTaskRunnerProcess 只用于 internal 模式

在 external 模式下使用 GO 写的 task-runner-launcher 来启动任务进程,它读取的是 runners 系统环境变量。

总结

由于 n8n 任务节点的超时,我们细致地的翻了翻源码,最后把 N8N_RUNNERS_TASK_TIMEOUT 这一个环境变量搞明白了。

虽然听起来确实很小,就一个变量,一个超时时间而已。但真正搞清楚那一刻,心里还是有一丝不同。以后再遇到类型超时问题,不用连滚带爬,而是从从容容。

把 N8N_RUNNERS_TASK_TIMEOUT 设好之后,任务该跑多久就跑多久。

这次就到这。代码还在那,坑也还在那。但至少,这一个已经不会再绊到我了。

在 GitHub 上讨论

欢迎通过 GitHub Issue 留言或反馈。每条讨论都会关联到对应文章的源文件路径。

2026-01-10-被-n8n-任务-60s-超时反复折磨后的排查记录:从报错、堆栈到源码,理清-TASK_TIMEOUT-的真实用途与位置.md

Related posts