SRS带宽不足下内存泄漏

  • 最近解决了SRS中的一个bug,特此记录一下。
  • SRS(4.0)服务器上,使用RTMP推流,在网页端用webrtc拉流。设置低带宽场景下存在内存大幅度持续不断上涨现象,应该是有内存泄漏。
  • 观察发现2个现象——1.存上涨幅度与推流端码率成正比。2.关闭nack后内存上涨幅度明显减小。
  • 控制台上打印SRS日志发现错误日志:
    [Warn][24154][x6w4gl27][62] handle udp pkt, count=1/1, err: code=1011 : size=104, data=https://www.it610.com/article/[00 01 00 54 21 12 a4 42] : stun binding request failed : stun binding response send failed : sendto thread [24154][x6w4gl27]: cycle() [src/app/srs_app_listener.cpp:630][errno=62] thread [24154][982648t3]: on_stun() [src/app/srs_app_rtc_conn.cpp:2113][errno=62] thread [24154][982648t3]: on_binding_request() [src/app/srs_app_rtc_conn.cpp:2773][errno=62] thread [24154][982648t3]: sendto() [src/app/srs_app_listener.cpp:347][errno=62]

  • 结合日志中代码调用路径,分析可能是SrsUdpMuxSocket::sendto中srs_error_new中new的对象没有释放,修改函数代码将错误直接返回:
    if (nb_write <= 0) { if (nb_write < 0 && errno == ETIME) { return err; //return srs_error_new(ERROR_SOCKET_TIMEOUT, "sendto timeout %d ms", srsu2msi(timeout)); }return srs_error_new(ERROR_SOCKET_WRITE, "sendto"); }

  • 使用gperf.gmp工具分析代码修改前和修改后的函数内存使用情况:
    // 原始代码分析结果 Using local file objs/srs. Using local file gperf.srs.gmp.0001.heap. Total: 9.8 MB 2.323.2%23.2%2.323.2% SrsResourceManager::SrsResourceManager 2.020.2%43.5%2.020.2% _st_new_stk_segment 1.818.2%61.7%1.818.2% SrsCommonMessage::create_payload 1.717.6%79.4%1.717.6% std::string::_Rep::_S_create 1.414.5%93.8%1.414.5% SrsCplxError::create 0.11.3%95.1%0.11.3% SrsFastStream::SrsFastStream 0.10.7%95.8%0.10.7% av_malloc_array (inline) 0.10.6%96.4%0.10.6% SrsUdpMuxSocket::SrsUdpMuxSocket 0.10.6%97.1%0.10.6% SrsUdpMuxListener::SrsUdpMuxListener// 修改后代码分析结果 Using local file objs/srs. Using local file gperf.srs.gmp.0001.heap. Total: 6.8 MB 2.333.7%33.7%2.333.7% SrsResourceManager::SrsResourceManager 2.029.3%63.0%2.029.3% _st_new_stk_segment 1.927.8%90.8%1.927.8% SrsCommonMessage::create_payload 0.11.8%92.7%0.11.8% SrsFastStream::SrsFastStream 0.11.0%93.7%0.11.1% av_malloc_array (inline) 0.10.9%94.6%0.10.9% SrsUdpMuxSocket::SrsUdpMuxSocket 0.10.9%95.5%0.10.9% SrsUdpMuxListener::SrsUdpMuxListener

  • 发现修改代码后SrsCplxError::create函数的内存使用就没有那么多了,结合代码基本就能定位问题,应该是有地方调用的SrsCplxError::create,但没有在外面释放内存。查找所有调用了SrsUdpMuxSocket::sendto函数的地方发现华点——果然在SrsRtcConnection::do_send_packet函数中有一段代码没有处理这个错误:
    // TODO: FIXME: Handle error. sendonly_skt->sendto(iov->iov_base, iov->iov_len, 0);

  • 【SRS带宽不足下内存泄漏】这里加trace打印发现有频繁调用,而且开启nack后在弱网下调用更频繁,结合前面现象,基本可以确定问题就出在这里。修改如下:
    if ((err = sendonly_skt->sendto(iov->iov_base, iov->iov_len, 0)) != srs_success) { return srs_error_wrap(err, "send to"); }

  • 修改后测试,同样场景下未见内存大幅度增长。
  • 这里不得不吐槽下SRS这种错误处理方式。首先说好处是,打印的错误日志可以还原调用的过程,查找问题很方便。但是带来的问题就是,只要外面有一个地方没有free这个错误指针就会存在内存泄漏。对于像SrsUdpMuxSocket::sendto这样频繁调用的函数内存增长还很明显,比较好查,但是其他地方如果有这样的情况发生,但是内存只会一点点的增长,就很难查了。
  • 很奇怪这个问题应该很明显,而且作者还备注了需要修改,但我后来查看最新的代码这块也还没有修改,不知道是基于什么考虑,望大神指点。

    推荐阅读