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

为何nng_dial仅在gtest环境中出现阻塞?

GTest环境下NNG的nng_dial在第二个测试中阻塞的问题

问题描述

我在C++中使用NNG搭配GTest时遇到了一个疑难问题:独立编写的发布者程序可持续发送消息,订阅者程序也能成功订阅并接收消息,说明NNG的基础配置没有问题。但我编写的两个GTest测试中,第二个测试始终在nng_dial调用处阻塞——即便已尽力清理资源,nng_dial本应即时返回(即使连接失败)。我也尝试了创建dialer并启动的等效代码,问题依旧。请问为什么这个问题仅在GTest环境中出现?

相关代码

发布者程序代码

#include <nng/nng.h>
#include <nng/protocol/pubsub0/pub.h>
#include <iostream>
#include <cstring>
#include <string>
#include <chrono>
#include <thread>

#define URL "ipc:///tmp/tc3-pub"

int main() {
    nng_socket sock;
    int rv;

    // Open the socket
    if ((rv = nng_pub0_open(&sock)) != 0) {
        std::cout << "Failed to open nng socket\n";
        return rv;
    }

    // Listen for subscribers
    if ((rv = nng_listen(sock, URL, NULL, 0)) != 0) {
        std::cout << "Failed to listen on nng socket\n";
        return rv;
    }

    std::cout << "Socket is listening!\n";
    
    for (int i = 0; i < 60; ++i) {
        std::string msg = "Message " + std::to_string(i);
        if ((rv = nng_send(sock, (void *) msg.c_str(), msg.size() + 1, 0)) != 0) {
            std::cout << "Failed to send message\n";
            return rv;
        }

        std::cout << "Sent: " << msg << "\n";

        // Sleep for one second
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    // Cleanup the socket
    nng_close(sock);

    return 0;
}

订阅者程序代码

#include <nng/nng.h>
#include <nng/protocol/pubsub0/sub.h>
#include <nng/protocol/pubsub0/pub.h>
#include <iostream>
#include <cstring>
#include <string>
#include <thread>

#define URL "ipc:///tmp/tc3-pub"

int main() {
    nng_socket sock;
    int rv;

    // Open the socket
    if ((rv = nng_sub0_open(&sock)) != 0) {
        std::cerr << "Failed to open nng socket\n";
        return rv;
    }

    nng_socket_set(sock, NNG_OPT_SUB_SUBSCRIBE, "", 0);

    std::cout << "Opened a socket!\n";
    
    // Dial (connect to) the server
    if ((rv = nng_dial(sock, URL, NULL, 0)) != 0) {
        std::cerr << "Failed to dial on nng socket\n";
        return rv;
    }

    // Receive and print messages continuously until an error occurs
    while (true) {
        char* buf = NULL;
        size_t size = 0;
        rv = nng_recv(sock, &buf, &size, NNG_FLAG_ALLOC);
        if (rv != 0) {
            std::cerr << "Failed to receive message\n";
            break;
        }

        std::cout << "Received message: " << std::string(buf, size) << '\n';
        nng_free(buf, size);
    }

    nng_close(sock);

    return rv;
}

GTest测试代码

#include "gtest/gtest.h"

#include <cstdlib>
#include <filesystem>
#include <condition_variable>
#include <mutex>
#include <thread>

#include "nng/nng.h"
#include "nng/protocol/pubsub0/pub.h"
#include "nng/protocol/pubsub0/sub.h"

TEST(NngTest, Test1)
{
  nng_socket sock;
  int rv;
    
  if ((rv = nng_sub0_open(&sock)) != 0) {
      std::cout << "Failed to open nng socket\n";
  }
  std::cout << "nng_sub0_open done\n";

  nng_close(sock);
  nng_fini();
}

TEST(NngTest, Test2)
{
  nng_socket sock;
  int rv;
    
  if ((rv = nng_sub0_open(&sock)) != 0) {
      std::cout << "Failed to open nng socket\n";
  }
  std::cout << "nng_sub0_open done\n";

  if ((rv = nng_socket_set(sock, NNG_OPT_SUB_SUBSCRIBE, "", 0)) != 0) {
      std::cout << "Failed to set nng socket\n";
  }
  std::cout << "nng_socket_set done\n";

  if ((rv = nng_dial(sock, "ipc:///tmp/tc4-pub", NULL, 0)) != 0) {
      std::cout << "Failed to dial nng socket\n";
  }
  std::cout << "nng_dial done\n";

  nng_close(sock);
  nng_fini();
}


int main(int argc, char** argv)
{
  try {
    ::testing::InitGoogleTest(&argc, argv);

    int retval = RUN_ALL_TESTS();
    std::cout << "Finished running all tests\n";

    return retval;
  } catch (const std::exception& e) {
    std::cerr << "Caught exception: " << e.what() << '\n';
  } catch (...) {
    std::cerr << "Caught unknown exception\n";
  }

  return -1;
}

问题原因与解决方法

核心原因

问题出在每个测试用例中都调用了nng_fini()。NNG的nng_fini()是用来销毁整个库的全局状态的,应该在进程退出前仅调用一次。而GTest的所有测试用例都运行在同一个进程空间内,第一个测试调用nng_fini()后,NNG的内部全局资源已经被销毁,第二个测试再调用NNG的API时,内部初始化状态异常,导致nng_dial的行为不符合预期(本该快速失败却阻塞)。

解决步骤

  1. 移除每个测试用例中的nng_fini()调用;
  2. 仅在整个测试程序结束时(比如main函数最后)调用一次nng_fini();
  3. 确保每个测试用例中的NNG资源(如sock)都通过nng_close()正确释放,避免资源泄漏。

修改后的GTest测试代码示例

#include "gtest/gtest.h"

#include <cstdlib>
#include <filesystem>
#include <condition_variable>
#include <mutex>
#include <thread>

#include "nng/nng.h"
#include "nng/protocol/pubsub0/pub.h"
#include "nng/protocol/pubsub0/sub.h"

TEST(NngTest, Test1)
{
  nng_socket sock;
  int rv;
    
  if ((rv = nng_sub0_open(&sock)) != 0) {
      std::cout << "Failed to open nng socket\n";
  }
  std::cout << "nng_sub0_open done\n";

  nng_close(sock);
  // 移除nng_fini()
}

TEST(NngTest, Test2)
{
  nng_socket sock;
  int rv;
    
  if ((rv = nng_sub0_open(&sock)) != 0) {
      std::cout << "Failed to open nng socket\n";
  }
  std::cout << "nng_sub0_open done\n";

  if ((rv = nng_socket_set(sock, NNG_OPT_SUB_SUBSCRIBE, "", 0)) != 0) {
      std::cout << "Failed to set nng socket\n";
  }
  std::cout << "nng_socket_set done\n";

  if ((rv = nng_dial(sock, "ipc:///tmp/tc4-pub", NULL, 0)) != 0) {
      std::cout << "Failed to dial nng socket\n";
  }
  std::cout << "nng_dial done\n";

  nng_close(sock);
  // 移除nng_fini()
}


int main(int argc, char** argv)
{
  try {
    ::testing::InitGoogleTest(&argc, argv);

    int retval = RUN_ALL_TESTS();
    std::cout << "Finished running all tests\n";

    nng_fini(); // 所有测试结束后统一调用一次
    return retval;
  } catch (const std::exception& e) {
    std::cerr << "Caught exception: " << e.what() << '\n';
  } catch (...) {
    std::cerr << "Caught unknown exception\n";
  }

  nng_fini(); // 异常场景也确保调用清理
  return -1;
}

补充说明

独立运行的发布者/订阅者程序中,nng_fini()在进程结束前被隐式或显式调用一次,所以不会有问题。但GTest的多测试用例共享进程空间,重复调用nng_fini()会破坏NNG的全局状态,导致后续API行为异常,这就是问题仅在GTest环境中出现的根本原因。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 22:17:01