You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 15自定义服务器Jest测试遇CustomGC打开句柄错误如何解决?

问题描述

使用自定义Next.js 15服务器,代码如下:

import { DotenvPopulateInput } from "@/utilities/dotenv"
import { createServer, Server } from "http"
import next from "next"

let server: null | Server

export const startServer = async (env?: DotenvPopulateInput) => {
  const app = next({})
  await app.prepare()
  Object.assign(process.env, env)
  server = createServer(app.getRequestHandler()).listen(
    parseInt(process.env.PORT || "3000")
  )
}

export const stopServer = async (): Promise<void> => {
  return new Promise((resolve, reject) => {
    if (server) {
      server.close((error) => {
        if (error) {
          reject(error)
        } else {
          resolve()
        }
      })
      server = null
    } else {
      resolve()
    }
  })
}

export const restartServer = async (env?: DotenvPopulateInput) => {
  await stopServer()
  await startServer(env)
}

所有Jest测试均通过,但出现如下打开句柄错误:

Jest has detected the following 1 open handle potentially keeping Jest from exiting:

  ●  CustomGC

      at Runtime._loadModule (node_modules/jest-runtime/build/index.js:1018:29)
      at loadNative (node_modules/next/src/build/swc/index.ts:1149:20)
      at node_modules/next/src/build/swc/index.ts:242:22
      at loadBindings (node_modules/next/src/build/swc/index.ts:196:21)
      at transform (node_modules/next/src/build/swc/index.ts:1284:24)
      at transpileConfig (node_modules/next/src/build/next-config-ts/transpile-config.ts:60:28)
      at async loadConfig (node_modules/next/src/server/config.ts:1156:28)
      at async initialize (node_modules/next/src/server/lib/router-server.ts:91:18)
      at async NextCustomServer.prepare (node_modules/next/src/server/next.ts:351:24)

请问该如何修复或排查此问题?

解决方案

1. 补充Next.js应用实例的资源清理

当前stopServer仅关闭了HTTP服务器,但Next.js的app实例内部还有未释放的资源(比如SWC绑定、配置加载句柄)。需要保存app实例并调用其close()方法(Next.js 13+版本支持):

修改服务器代码,新增保存nextApp实例的逻辑:

import { DotenvPopulateInput } from "@/utilities/dotenv"
import { createServer, Server } from "http"
import next from "next"

let server: null | Server;
let nextApp: null | ReturnType<typeof next> = null;

export const startServer = async (env?: DotenvPopulateInput) => {
  nextApp = next({})
  await nextApp.prepare()
  Object.assign(process.env, env)
  server = createServer(nextApp.getRequestHandler()).listen(
    parseInt(process.env.PORT || "3000")
  )
}

export const stopServer = async (): Promise<void> => {
  return new Promise(async (resolve, reject) => {
    try {
      // 先关闭Next.js内部资源
      if (nextApp) {
        await nextApp.close();
        nextApp = null;
      }
      // 再关闭HTTP服务器
      if (server) {
        server.close((error) => {
          if (error) {
            reject(error)
          } else {
            resolve()
          }
        })
        server = null
      } else {
        resolve()
      }
    } catch (err) {
      reject(err);
    }
  })
}

export const restartServer = async (env?: DotenvPopulateInput) => {
  await stopServer()
  await startServer(env)
}

2. 确保测试中正确执行清理逻辑

在Jest测试文件中,通过afterAll或afterEach钩子确保服务器资源被完全清理,且使用async/await等待清理完成:

// 测试文件示例
import { startServer, stopServer } from "../path/to/server";

beforeAll(async () => {
  await startServer();
});

afterAll(async () => {
  await stopServer();
});

3. 兜底:强制退出进程(仅必要时使用)

如果上述方法仍无法解决,可在测试清理阶段强制退出Node进程,但这是兜底方案,优先修复资源泄漏:

afterAll(async () => {
  await stopServer();
  process.exit(0);
});

4. 深入排查句柄泄漏

  • 运行Jest时添加--detectOpenHandles --verbose参数,获取更详细的句柄泄漏信息;
  • 使用Node.js的async_hooks模块追踪句柄的创建和销毁,定位具体泄漏点;
  • 检查Next.js配置中SWC相关的持久化设置,临时禁用swcMinify等特性验证是否为SWC导致的问题。

内容的提问来源于stack exchange,提问作者sunknudsen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 17:33:24