Assertion Failed: pSock->m_callback.m_fnCallback (socketthread.cpp:2609) after re-initializing the library with GameNetworkingSockets_Kill + GameNetworkingSockets_Init

Đang mở
#458 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 2 ngày

@siddiqueirshad đang làm issue này rồi.

Từ ngày 27/10/2026.

  • #460 của @siddiqueirshad — đang mở

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
45/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
cpp
Lĩnh vực
networking

Hướng nghiên cứu

Start with src/steamnetworkingsockets/clientlib/steamnetworkingsockets_socketthread.cpp, especially PollRawUDPSockets() and the deferred cleanup path named in the report. Reproduce with the supplied three-file CMake project on Windows using the no-wait Init/Kill loop, then compare with the 10 ms wait case. Done means repeated Init/Kill cycles no longer trigger the assertion while the reproducer still closes listen sockets successfully.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Summary

In a process that has already called GameNetworkingSockets_Init and GameNetworkingSockets_Kill once, a second Init followed by creating and immediately closing a listen socket sometimes makes the service thread report:

src/steamnetworkingsockets/clientlib/steamnetworkingsockets_socketthread.cpp(2609): Assertion Failed: pSock->m_callback.m_fnCallback
  • Not observed after the first Init: 0 of 500 processes that Init once.
  • Observed after a re-Init: 18 of 200 processes that Init twice.
  • Not observed when the code waits 10 ms after Init before creating the socket.

No crash or functional failure was observed; the report comes from the DBGFLAG_ASSERT sanity check, which tier0/dbg.h compiles into release builds by default.

Versions
  • GameNetworkingSockets v1.6.1 (tag commit d534c19aa760df3fb75fd20db13ba1932b8a5463). On 2026-10-24 this is also the head of master, and steamnetworkingsockets_socketthread.cpp at master is byte-identical to the v1.6.1 copy we built.
  • The library was built with vcpkg (commit 65a2c2391af39b773f83f91e0ab6c8ced8f9c727), port gamenetworkingsockets 1.6.1 with default-features: false, triplet x64-windows (OpenSSL 3.6.5, protobuf 7.36.2).
  • Windows 11 Pro x64 (build 26300), Visual Studio 2026 (MSVC 19.51.36260), CMake 4.4.4, Release configuration.
Reproducer (public API only)

Three files in an empty directory.

main.cpp:

// Repeats GameNetworkingSockets_Init -> CreateListenSocketIP -> CloseListenSocket -> GameNetworkingSockets_Kill
// and counts "Assertion Failed" messages reported through the debug output callback.
// Usage: gns_listen_close_repro [iterations=400] [wait_ms_after_init=0] [port=47900]
// Prints one JSON line: {"iterations":..,"wait_ms":..,"listen_failures":..,"assertions":..,"first_assert":".."}
#include <steam/isteamnetworkingutils.h>
#include <steam/steamnetworkingsockets.h>

#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <string>
#include <thread>

static std::atomic<int> g_asserts{0};
static std::mutex g_mutex;
static std::string g_first_assert;

static void on_debug(ESteamNetworkingSocketsDebugOutputType, const char* msg) {
    if (!std::strstr(msg, "Assertion Failed")) return;
    if (g_asserts++ == 0) {
        std::lock_guard<std::mutex> lock(g_mutex);
        const char* a = std::strstr(msg, "Assertion Failed");
        g_first_assert = a;
        while (!g_first_assert.empty() && (g_first_assert.back() == '\n' || g_first_assert.back() == '\r')) g_first_assert.pop_back();
    }
}

int main(int argc, char** argv) {
    const int iterations = argc > 1 ? std::atoi(argv[1]) : 400;
    const int wait_ms = argc > 2 ? std::atoi(argv[2]) : 0;
    const int port = argc > 3 ? std::atoi(argv[3]) : 47900;
    int listen_failures = 0;
    for (int i = 0; i < iterations; ++i) {
        SteamDatagramErrMsg err{};
        if (!GameNetworkingSockets_Init(nullptr, err)) {
            std::fprintf(stderr, "GameNetworkingSockets_Init failed: %s\n", err);
            return 2;
        }
        SteamNetworkingUtils()->SetDebugOutputFunction(k_ESteamNetworkingSocketsDebugOutputType_Warning, on_debug);
        if (wait_ms > 0) std::this_thread::sleep_for(std::chrono::milliseconds(wait_ms));
        SteamNetworkingIPAddr addr;
        addr.Clear();
        addr.SetIPv4(0x7f000001, static_cast<uint16>(port));  // 127.0.0.1
        const HSteamListenSocket s = SteamNetworkingSockets()->CreateListenSocketIP(addr, 0, nullptr);
        if (s == k_HSteamListenSocket_Invalid) ++listen_failures;
        else SteamNetworkingSockets()->CloseListenSocket(s);
        GameNetworkingSockets_Kill();
    }
    std::lock_guard<std::mutex> lock(g_mutex);
    std::printf("{\"iterations\": %d, \"wait_ms\": %d, \"listen_failures\": %d, \"assertions\": %d, \"first_assert\": \"%s\"}\n",
                iterations, wait_ms, listen_failures, g_asserts.load(), g_first_assert.c_str());
    return 0;
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.21)
project(gns_listen_close_repro CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(GameNetworkingSockets CONFIG REQUIRED)
add_executable(gns_listen_close_repro main.cpp)
target_link_libraries(gns_listen_close_repro PRIVATE GameNetworkingSockets::shared)

vcpkg.json:

{
  "name": "gns-listen-close-repro",
  "version-string": "1.0.0",
  "builtin-baseline": "65a2c2391af39b773f83f91e0ab6c8ced8f9c727",
  "dependencies": [
    { "name": "gamenetworkingsockets", "default-features": false }
  ]
}
Build and run on Windows

From a "Developer PowerShell for VS" (or any shell with CMake and a VS 2022/2026 toolchain), with vcpkg cloned to $env:USERPROFILE\vcpkg and bootstrapped:

cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE="$env:USERPROFILE\vcpkg\scripts\buildsystems\vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows
cmake --build build --config Release
.\build\Release\gns_listen_close_repro.exe 400 0     # 400 Init/Kill cycles, no wait after Init
.\build\Release\gns_listen_close_repro.exe 400 10    # the same with a 10 ms wait after Init
.\build\Release\gns_listen_close_repro.exe 1 0       # first Init only (run many times, one process each)
.\build\Release\gns_listen_close_repro.exe 2 0       # one re-Init (run many times, one process each)

The first configure builds OpenSSL, protobuf and abseil through vcpkg, which can take a while; later builds reuse the vcpkg binary cache. The program only binds 127.0.0.1:47900 (third argument changes the port).

Expected: "assertions": 0 in every run.
Actual: non-zero counts in the runs without a wait (see Results).

Results
Scenario Processes Assertions Processes with ≥1 assertion
1 iteration per process (first Init only), no wait 300 0 0
2 iterations per process (one re-Init), no wait 200 18 18
400 iterations, no wait 3 116 (39, 38, 39) 3
400 iterations, 10 ms wait after Init 3 0 (0, 0, 0) 0

Recorded runs. The rows above come from one batch whose per-process JSON lines were written to a file as they ran. An earlier batch, also recorded to a file, gave the same picture:

  • 400 iterations with no wait: 44, 35 and 34 assertions;
  • 10 ms wait: 0, 0, 0;
  • 50 ms wait: 0, 0, 0.

Both batches used an earlier build of this program. It runs the same loop; only the name of one output field (settle_ms instead of wait_ms) and the formatting of the assert text differ.

Console-only observations. These were not written to a file:

  • 400 iterations with no wait: 11, 18 and 21 assertions, plus 40 with the exact main.cpp above in a fresh build;
  • 10 ms wait with the exact main.cpp: 0;
  • first Init only: 0 assertions in another 500 processes;
  • one re-Init: 25 of another 200 processes asserted.
What we looked at in the source (a hypothesis, not a confirmed cause)
  • The check that fires. PollRawUDPSockets() begins with a DBGFLAG_ASSERT loop, Assert( pSock->m_callback.m_fnCallback ), over every entry in s_vecRawSockets.
  • What makes the callback null. CRawUDPSocketImpl::Close() → InternalAddToCleanupQueue() sets m_callback.m_fnCallback = nullptr. The comment there calls this the marker for a socket "pending destruction". When the service thread is running, the actual deletion is deferred to ProcessPendingDestroyClosedRawUDPSockets(), which runs from ProcessDeferredOperations() after polling.
  • Hypothesis. A socket that is legitimately pending destruction can be seen by that sanity loop before the deferred cleanup runs. The fact that it only happens after a re-Init suggests the window depends on state or timing carried over from the previous library lifetime: for example how the newly started service thread interleaves with the first close, or a static that is not reset by Kill.
  • Similar, but not the same assert. #230 (closed) reported static state that Kill did not reset (s_bRecreatePollList, POSIX).

We have not confirmed any of this with a debugger, a stack trace, or instrumentation of the library, so please treat it as a hypothesis. If the analysis is right, skipping sockets with a null callback in that sanity loop (they are pending destruction by design), or processing pending destructions before the check, would avoid the false positive. Happy to test a patch.

Impact

No crash or misbehaviour observed; our application tests pass. We currently avoid it by initializing the library once per process (no Kill/re-Init cycles) and waiting 50 ms after Init. That workaround is empirical.

Duplicate search

Searched open and closed issues and PRs on 2026-10-24 for: m_fnCallback, m_callback.m_fnCallback, PollRawUDPSockets, ProcessPendingDestroyClosedRawUDPSockets, socketthread.cpp Assertion, CloseListenSocket assert, pending destruction socket assert, Assertion Failed Kill Init, 2609, Assertion Failed. No issue reports this assert. #230 is related (re-initialization state), but it is a different assert and platform.

Ngôn ngữ chính
C++
Star
10k
Fork
767
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của ValveSoftware/GameNetworkingSockets

Tất cả issue của ValveSoftware/GameNetworkingSockets

Issue tương tự

Thêm issue về C++

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.