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

Node.js完成Ws断开后等待2分10秒才退出的原因排查

问题:基于polkadot.js WsProvider的RxJS Observable执行后Node.js进程延迟退出

我尝试编写一个Creation Operator,基于polkadot.js的WsProvider创建Observable实例,用于获取polkadot事件。

以下是实现代码:

import {from, fromEvent, of,Observable} from 'rxjs';
import {tap,mergeMap} from "rxjs/operators"

import { WsProvider } from '@polkadot/api';

console.time("main")
const urls = of("wss://192")

const fromWs =  (url:string) => {
  const ws = new WsProvider(url,0);
  ws.connect().then(val => console.timeLog("main",val)).catch(console.error)
  return new Observable((subscriber) => {

    ws.on("disconnected",()=>{
      console.timeLog("main","wss disconnected")
      subscriber.next(url);
      subscriber.complete()
    })
    ws.on('error', () =>{
      console.timeLog("main","wss error")
      subscriber.complete()
    })
    ws.on('connected' ,() => {
      console.timeLog("main", `${url} connected`)
    })
    setTimeout(() => {
      console.timeLog("main", `${url} timeout`)
      ws.disconnect().then(val=> console.timeLog("main",'ws disconnect')).catch(err => console.error("disconnect error"))
      subscriber.complete()
    },2000)
  })
}

urls.pipe(
    tap(console.log),
    mergeMap( (url:string) =>{
      return fromWs(url)
    }),
).subscribe({
  next: val => console.timeLog("main", 'subcrnext'),
  error: err=> console.timeLog("main", "sub error"),
  complete: () => console.timeLog("main", "sub complete")
})

process.on('exit', (code) => {
  console.timeLog("main",'System exit');
});

使用ts-node执行该脚本,执行命令及输出如下:

➜  rxjsexample ts-node src/main.ts
wss://192
main: 11.959ms undefined
main: 2.013s wss://192 timeout
main: 2.015s wss error
main: 2.016s sub complete
main: 2.017s wss disconnected
main: 2.018s ws disconnect
main: 2:10.176 (m:ss.mmm) System exit

从日志可以看到,程序在2.018s时就已经完成网络连接断开操作,但Node.js进程直到2分10秒才正式退出。


原因分析

2分10秒的等待时长是典型的TCP SYN包默认重传超时总时长:主流操作系统默认配置下,TCP第一次握手发送SYN包后未收到响应,会按1s、2s、4s、8s、16s、32s的间隔重传,最后一次重传后等待64s仍无响应才会判定连接失败,总耗时约127秒,和日志中的2分10秒完全吻合,差值为系统调度、运行环境的正常开销。

等待期间Node.js没有执行任何业务层代码,事件循环中没有待处理的JS回调,只是被残留的底层TCP socket句柄阻塞了退出流程,具体根因有两点:

  1. 传入的wss://192不是合法可访问的WebSocket端点,WsProvider初始化后发起的TCP连接一直处于三次握手的SYN_SENT状态,内核按规则持续重传SYN包。
  2. 调用的ws.disconnect()方法只会清理已经建立成功的WebSocket连接的状态和事件监听,不会终止还在三次握手阶段的底层TCP连接请求。这个未完成的TCP socket属于事件循环追踪的活跃异步资源,Node.js默认会等待所有活跃资源全部释放后才退出进程,因此一直空等到TCP连接超时、内核自动回收socket后才终止。

修复方案
  • 优先传入合法可访问的WsProvider端点地址,从源头避免无效连接请求。
  • 不要仅调用disconnect()方法终止连接:需要在连接终止逻辑中额外调用ws.destroy()方法,彻底销毁所有底层资源,包括未完成握手的TCP socket。
  • 补全Observable的标准清理逻辑:在Observable构造函数的回调中返回teardown清理函数,在订阅终止、超时触发时主动移除注册的所有事件监听、清除定时器、彻底销毁WsProvider实例,避免残留资源持有事件循环引用。

修正后的核心实现片段:

const fromWs =  (url:string) => {
  const ws = new WsProvider(url,0);
  ws.connect().then(val => console.timeLog("main",val)).catch(console.error)

  // 提前定义事件回调,方便后续移除
  const onDisconnected = () => {
    console.timeLog("main","wss disconnected")
    subscriber.next(url);
    subscriber.complete()
  }
  const onError = () =>{
    console.timeLog("main","wss error")
    subscriber.complete()
  }
  const onConnected = () => {
    console.timeLog("main", `${url} connected`)
  }

  return new Observable((subscriber) => {
    ws.on("disconnected", onDisconnected)
    ws.on('error', onError)
    ws.on('connected' ,onConnected)

    const timer = setTimeout(() => {
      console.timeLog("main", `${url} timeout`)
      ws.disconnect()
        .finally(() => {
          ws.destroy()
          console.timeLog("main",'ws disconnect')
        })
      subscriber.complete()
    },2000)

    // Teardown逻辑:订阅结束时自动执行清理
    return () => {
      clearTimeout(timer)
      ws.off("disconnected", onDisconnected)
      ws.off("error", onError)
      ws.off("connected", onConnected)
      ws.disconnect().finally(() => ws.destroy())
    }
  })
}

补上资源销毁逻辑后,无论连接处于什么状态,所有底层socket、定时器、事件监听器都会被立即释放,Node.js进程会在业务逻辑执行完成后立刻退出,不会等待TCP超时。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 21:39:04