intermittent issue in wallet_txn_clone.py (Windows ucrt) while starting network thread? #34367

issue maflcko opened this issue on January 21, 2026
  1. maflcko commented at 12:17 PM on January 21, 2026: member

    https://github.com/bitcoin/bitcoin/actions/runs/20090968443/job/57638966158?pr=32800#step:13:203:

    77/276 - wallet_txn_clone.py failed, Duration: 1 s
    
    stdout:
    2025-12-10T08:04:27.500134Z TestFramework (INFO): PRNG seed is: 4018092284830106117
    
    
    
    stderr:
    
    
    Combine the logs and print the last 99999999 lines ...
    
    ============
    Combined log for D:\a\_temp/test_runner_₿_🏃_20251210_075632/wallet_txn_clone_196:
    ============
    
     test  2025-12-10T08:04:27.500134Z TestFramework (INFO): PRNG seed is: 4018092284830106117 
     test  2025-12-10T08:04:27.500433Z TestFramework (DEBUG): Setting up network thread 
    

    Given that nothing else is printed on stdout, and stderr, the test must have failed before setup_chain, because that should log as well.

    This leaves something with the network thread. However, if the thread throws an exception, it should still print to stderr. If it kills the program silently, it seems like an upstream python bug.

  2. maflcko added the label Tests on Jan 21, 2026
  3. maflcko added the label CI failed on Jan 21, 2026
  4. maflcko added the label Windows on Jan 21, 2026
  5. maflcko commented at 12:18 PM on January 21, 2026: member

    So this seems like an upstream Python bug.

    Closing for now, but discussion can continue, if someone finds something.

  6. fanquake closed this on Jan 21, 2026

  7. fanquake commented at 4:37 PM on March 12, 2026: member
  8. fanquake reopened this on Mar 12, 2026

  9. l0rinc commented at 8:54 PM on March 12, 2026: contributor

    Asked an agent to play around with it on Windows, and it flagged https://github.com/bitcoin/bitcoin/blob/f7e0c3d3d370a4e5b4060fefcb9e8d83e2533bbc/test/functional/test_framework/p2p.py#L743-L744 as a potential source of the problem.

    I haven't investigated if it's total garbage (since I haven't worked with that area of the code yet), but the changes the agent made locally were:

    • p2p.py:736: NetworkThread no longer creates the asyncio loop in __init__. It now creates and registers the loop inside run(), adds _loop_ready, and makes close() tolerate startup failures.
    • test_framework.py:258: the startup wait now checks network_event_loop is not None and is_running() instead of assuming the loop object already exists.

    <details><summary>Patch</summary>

    diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
    index 7a7cce6176..2468b4acf6 100755
    --- a/test/functional/test_framework/p2p.py
    +++ b/test/functional/test_framework/p2p.py
    @@ -740,16 +740,28 @@ class NetworkThread(threading.Thread):
    
             NetworkThread.listeners = {}
             NetworkThread.protos = {}
    -        if platform.system() == 'Windows':
    -            asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    -        NetworkThread.network_event_loop = asyncio.new_event_loop()
    +        self._loop_ready = threading.Event()
    
         def run(self):
             """Start the network thread."""
    -        self.network_event_loop.run_forever()
    +        try:
    +            if platform.system() == 'Windows':
    +                asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    +            # Create and register the loop on the worker thread instead of
    +            # constructing it on the main thread and handing it across.
    +            NetworkThread.network_event_loop = asyncio.new_event_loop()
    +            asyncio.set_event_loop(self.network_event_loop)
    +            self._loop_ready.set()
    +            self.network_event_loop.run_forever()
    +        finally:
    +            self._loop_ready.set()
    
         def close(self, *, timeout):
             """Close the connections and network event loop."""
    +        if not self._loop_ready.wait(timeout=timeout) or self.network_event_loop is None:
    +            self.join(timeout)
    +            NetworkThread.network_event_loop = None
    +            return
             self.network_event_loop.call_soon_threadsafe(self.network_event_loop.stop)
             wait_until_helper_internal(lambda: not self.network_event_loop.is_running(), timeout=timeout)
             self.network_event_loop.close()
    diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
    index 1f95756445..f968d6cc56 100755
    --- a/test/functional/test_framework/test_framework.py
    +++ b/test/functional/test_framework/test_framework.py
    @@ -258,7 +258,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
             self.log.debug('Setting up network thread')
             self.network_thread = NetworkThread()
             self.network_thread.start()
    -        self.wait_until(lambda: self.network_thread.network_event_loop.is_running())
    +        self.wait_until(lambda: self.network_thread.network_event_loop is not None and self.network_thread.network_event_loop.is_running())
    
             if self.options.usecli:
    

    </details>

  10. maflcko commented at 8:59 PM on March 12, 2026: member

    Interesting. It would be good to ask the agent to write a patch to make this fully reproducible, so that the patch can be tested and reviewed easier. (Or possibly a stand-alone minimal reproducer for the bug and the fix)

  11. l0rinc commented at 9:51 PM on March 12, 2026: contributor

    After a few iterations it might have found something useful:

    win@WIN-A2EHOAU4JET:/mnt/my_storage/bitcoin$ python3 contrib/devtools/networkthread_repro.py --mode legacy --iterations 1 --assert-loop-owned-by-runner --stop-on-failure
    iteration 1/1: child exited with return code 1
    Traceback (most recent call last):
      File "/mnt/c/my_storage/bitcoin/contrib/devtools/networkthread_repro.py", line 280, in <module>
        sys.exit(main())
      File "/mnt/c/my_storage/bitcoin/contrib/devtools/networkthread_repro.py", line 275, in main
        return run_child(args)
      File "/mnt/c/my_storage/bitcoin/contrib/devtools/networkthread_repro.py", line 139, in run_child
        exercise(
      File "/mnt/c/my_storage/bitcoin/contrib/devtools/networkthread_repro.py", line 128, in exercise
        assert_loop_owned_by_runner(thread)
      File "/mnt/c/my_storage/bitcoin/contrib/devtools/networkthread_repro.py", line 107, in assert_loop_owned_by_runner
        raise RuntimeError(
    RuntimeError: event loop created on thread 139945949421568 but run on thread 139945935119936
    
    win@WIN-A2EHOAU4JET:/mnt/my_storage/bitcoin$ python3 contrib/devtools/networkthread_repro.py --mode fixed --iterations 1 --assert-loop-owned-by-runner --stop-on-failure
    iteration 1/1: ok
    completed 1 iterations in mode=fixed with 1 cycle(s) per child
    

    agent explanation:

    Problem
    
    The intermittent wallet_txn_clone.py failure is not really about that test. The process dies in the shared functional test setup, after Setting up network thread and before setup_chain(). The
    suspicious part was p2p.py:44: the old NetworkThread pattern created the asyncio event loop on the main thread and then ran it on a different worker thread. On Windows, that is the narrowest code
    path consistent with the silent startup failure.
    
    Solution
    
    The fix in p2p.py:64 and test_framework.py:258 changes ownership so the worker thread creates and registers its own event loop. It also makes startup/shutdown more defensive:
    
    - wait until the loop exists before checking is_running()
    - track loop readiness explicitly
    - clean up correctly even if startup fails early
    
    What the reproducer demonstrates
    
    The standalone reproducer in networkthread_repro.py:44 does two things:
    
    - legacy mode reproduces the old pattern: loop created on thread A, run on thread B
    - fixed mode reproduces the new pattern: loop created and run on the same thread
    
    With --assert-loop-owned-by-runner, it makes that difference deterministic:
    
    - legacy fails with RuntimeError: event loop created on thread X but run on thread Y
    - fixed passes with the same command
    
    So the reproducer does not claim to force the original Windows interpreter crash on Linux. What it does prove is the exact behavioral difference the fix is making, and it also provides a child-
    process stress harness that can still be run on Windows to probe the original crash more directly.
    

    agent patch with devtools reproducer:

    <details><summary>Details</summary>

    diff --git a/contrib/devtools/README.md b/contrib/devtools/README.md
    index 50e807814c..29d40aaf14 100644
    --- a/contrib/devtools/README.md
    +++ b/contrib/devtools/README.md
    @@ -110,3 +110,32 @@ Example usage:
    
         cd .../src
         ../contrib/devtools/circular-dependencies.py {*,*/*,*/*/*}.{h,cpp}
    +
    +networkthread_repro.py
    +======================
    +
    +A standalone reproducer for the Windows-only `NetworkThread` startup issue from
    +`test/functional/test_framework/p2p.py`. It isolates the two patterns under
    +review:
    +
    +- `legacy`: create the asyncio loop on the main thread, then run it on a worker
    +  thread
    +- `fixed`: create and register the loop inside the worker thread
    +
    +The script runs each attempt in a child process so interpreter crashes or hangs
    +do not abort the full stress run.
    +
    +Example usage:
    +
    +```bash
    +python3 contrib/devtools/networkthread_repro.py --mode legacy --iterations 500
    +python3 contrib/devtools/networkthread_repro.py --mode fixed --iterations 500
    +```
    +
    +To make the thread-ownership bug deterministic for review, require the event
    +loop to be created on the same thread that runs it:
    +
    +```bash
    +python3 contrib/devtools/networkthread_repro.py --mode legacy --assert-loop-owned-by-runner --stop-on-failure
    +python3 contrib/devtools/networkthread_repro.py --mode fixed --assert-loop-owned-by-runner --stop-on-failure
    +```
    diff --git a/contrib/devtools/networkthread_repro.py b/contrib/devtools/networkthread_repro.py
    new file mode 100644
    index 0000000000..229ca92aad
    --- /dev/null
    +++ b/contrib/devtools/networkthread_repro.py
    @@ -0,0 +1,280 @@
    +#!/usr/bin/env python3
    +# Copyright (c) 2026-present The Bitcoin Core developers
    +# Distributed under the MIT software license, see the accompanying
    +# file COPYING or http://www.opensource.org/licenses/mit-license.php.
    +"""Stress the NetworkThread startup pattern from issue [#34367](/bitcoin-bitcoin/34367/).
    +
    +This script isolates the suspected bug pattern without requiring a build or the
    +functional test framework:
    +
    +- legacy: create the asyncio event loop on the main thread, then start a worker
    +  thread that runs it.
    +- fixed: create and register the event loop inside the worker thread itself.
    +
    +Run each mode repeatedly in subprocesses. If the legacy pattern triggers an
    +interpreter crash or hang on Windows, the parent process can still report which
    +iteration failed and preserve the child output.
    +"""
    +
    +import argparse
    +import asyncio
    +import faulthandler
    +from pathlib import Path
    +import platform
    +import subprocess
    +import sys
    +import threading
    +import time
    +
    +
    +def configure_windows_policy() -> None:
    +    if platform.system() == "Windows":
    +        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    +
    +
    +def wait_until(predicate, *, timeout: float, check_interval: float = 0.01) -> None:
    +    deadline = time.monotonic() + timeout
    +    while time.monotonic() < deadline:
    +        if predicate():
    +            return
    +        time.sleep(check_interval)
    +    raise TimeoutError("predicate did not become true before timeout")
    +
    +
    +class LegacyNetworkThread(threading.Thread):
    +    def __init__(self):
    +        super().__init__(name="LegacyNetworkThread")
    +        configure_windows_policy()
    +        self.loop_creator_thread_id = threading.get_ident()
    +        self.loop_runner_thread_id = None
    +        self.loop = asyncio.new_event_loop()
    +
    +    def run(self) -> None:
    +        self.loop_runner_thread_id = threading.get_ident()
    +        self.loop.run_forever()
    +
    +    def close(self, *, timeout: float) -> None:
    +        self.loop.call_soon_threadsafe(self.loop.stop)
    +        self.join(timeout)
    +        if self.is_alive():
    +            raise RuntimeError("network thread did not stop")
    +        self.loop.close()
    +
    +
    +class FixedNetworkThread(threading.Thread):
    +    def __init__(self):
    +        super().__init__(name="FixedNetworkThread")
    +        self.loop = None
    +        self.loop_ready = threading.Event()
    +        self.loop_creator_thread_id = None
    +        self.loop_runner_thread_id = None
    +
    +    def run(self) -> None:
    +        try:
    +            configure_windows_policy()
    +            self.loop_runner_thread_id = threading.get_ident()
    +            self.loop_creator_thread_id = self.loop_runner_thread_id
    +            self.loop = asyncio.new_event_loop()
    +            asyncio.set_event_loop(self.loop)
    +            self.loop_ready.set()
    +            self.loop.run_forever()
    +        finally:
    +            self.loop_ready.set()
    +
    +    def close(self, *, timeout: float) -> None:
    +        if not self.loop_ready.wait(timeout=timeout) or self.loop is None:
    +            self.join(timeout)
    +            if self.is_alive():
    +                raise RuntimeError("network thread never created its event loop")
    +            return
    +        self.loop.call_soon_threadsafe(self.loop.stop)
    +        self.join(timeout)
    +        if self.is_alive():
    +            raise RuntimeError("network thread did not stop")
    +        self.loop.close()
    +
    +
    +def build_thread(mode: str):
    +    if mode == "legacy":
    +        return LegacyNetworkThread()
    +    if mode == "fixed":
    +        return FixedNetworkThread()
    +    raise ValueError(f"unknown mode: {mode}")
    +
    +
    +def assert_loop_owned_by_runner(thread) -> None:
    +    if thread.loop_creator_thread_id != thread.loop_runner_thread_id:
    +        raise RuntimeError(
    +            "event loop created on thread "
    +            f"{thread.loop_creator_thread_id} but run on thread {thread.loop_runner_thread_id}"
    +        )
    +
    +
    +def exercise(
    +    mode: str,
    +    *,
    +    cycles: int,
    +    startup_timeout: float,
    +    shutdown_timeout: float,
    +    assert_loop_owned_by_runner_mode: bool,
    +) -> None:
    +    for cycle in range(cycles):
    +        thread = build_thread(mode)
    +        thread.start()
    +        try:
    +            wait_until(lambda: thread.loop is not None and thread.loop.is_running(), timeout=startup_timeout)
    +            wait_until(lambda: thread.loop_runner_thread_id is not None, timeout=startup_timeout)
    +            if assert_loop_owned_by_runner_mode:
    +                assert_loop_owned_by_runner(thread)
    +            callback_ran = threading.Event()
    +            thread.loop.call_soon_threadsafe(callback_ran.set)
    +            wait_until(callback_ran.is_set, timeout=startup_timeout)
    +            print(f"child cycle {cycle + 1}/{cycles}: ok", flush=True)
    +        finally:
    +            thread.close(timeout=shutdown_timeout)
    +
    +
    +def run_child(args) -> int:
    +    faulthandler.enable(all_threads=True)
    +    exercise(
    +        args.mode,
    +        cycles=args.cycles_per_child,
    +        startup_timeout=args.startup_timeout,
    +        shutdown_timeout=args.shutdown_timeout,
    +        assert_loop_owned_by_runner_mode=args.assert_loop_owned_by_runner,
    +    )
    +    return 0
    +
    +
    +def run_parent(args) -> int:
    +    script_path = Path(__file__).resolve()
    +    failures = 0
    +    for iteration in range(args.iterations):
    +        cmd = [
    +            sys.executable,
    +            str(script_path),
    +            "--child",
    +            "--mode",
    +            args.mode,
    +            "--cycles-per-child",
    +            str(args.cycles_per_child),
    +            "--startup-timeout",
    +            str(args.startup_timeout),
    +            "--shutdown-timeout",
    +            str(args.shutdown_timeout),
    +        ]
    +        if args.assert_loop_owned_by_runner:
    +            cmd.append("--assert-loop-owned-by-runner")
    +        try:
    +            completed = subprocess.run(
    +                cmd,
    +                capture_output=True,
    +                text=True,
    +                timeout=args.child_timeout,
    +                check=False,
    +            )
    +        except subprocess.TimeoutExpired as exc:
    +            failures += 1
    +            print(f"iteration {iteration + 1}/{args.iterations}: timeout after {args.child_timeout}s", flush=True)
    +            if exc.stdout:
    +                print(exc.stdout, end="")
    +            if exc.stderr:
    +                print(exc.stderr, end="", file=sys.stderr)
    +            if args.stop_on_failure:
    +                return 1
    +            continue
    +
    +        if completed.returncode == 0:
    +            print(f"iteration {iteration + 1}/{args.iterations}: ok", flush=True)
    +            continue
    +
    +        failures += 1
    +        print(
    +            f"iteration {iteration + 1}/{args.iterations}: child exited with return code {completed.returncode}",
    +            flush=True,
    +        )
    +        if completed.stdout:
    +            print(completed.stdout, end="")
    +        if completed.stderr:
    +            print(completed.stderr, end="", file=sys.stderr)
    +        if args.stop_on_failure:
    +            return 1
    +
    +    if failures:
    +        print(f"{failures} child process failures observed in {args.iterations} iterations", flush=True)
    +        return 1
    +
    +    print(
    +        f"completed {args.iterations} iterations in mode={args.mode} with {args.cycles_per_child} cycle(s) per child",
    +        flush=True,
    +    )
    +    return 0
    +
    +
    +def parse_args():
    +    parser = argparse.ArgumentParser(
    +        description="Stress the legacy and fixed NetworkThread startup patterns from issue [#34367](/bitcoin-bitcoin/34367/).",
    +    )
    +    parser.add_argument(
    +        "--mode",
    +        choices=["legacy", "fixed"],
    +        default="legacy",
    +        help="which startup pattern to exercise",
    +    )
    +    parser.add_argument(
    +        "--iterations",
    +        type=int,
    +        default=100,
    +        help="number of child processes to run in parent mode",
    +    )
    +    parser.add_argument(
    +        "--cycles-per-child",
    +        type=int,
    +        default=1,
    +        help="number of thread start/stop cycles to run inside each child process",
    +    )
    +    parser.add_argument(
    +        "--startup-timeout",
    +        type=float,
    +        default=5.0,
    +        help="seconds to wait for the event loop to start",
    +    )
    +    parser.add_argument(
    +        "--shutdown-timeout",
    +        type=float,
    +        default=5.0,
    +        help="seconds to wait for the worker thread to stop",
    +    )
    +    parser.add_argument(
    +        "--child-timeout",
    +        type=float,
    +        default=15.0,
    +        help="seconds to allow a child process before the parent reports a timeout",
    +    )
    +    parser.add_argument(
    +        "--stop-on-failure",
    +        action="store_true",
    +        help="stop the parent loop after the first failed child process",
    +    )
    +    parser.add_argument(
    +        "--assert-loop-owned-by-runner",
    +        action="store_true",
    +        help="fail if the event loop is created on a different thread than the one that runs it",
    +    )
    +    parser.add_argument(
    +        "--child",
    +        action="store_true",
    +        help=argparse.SUPPRESS,
    +    )
    +    return parser.parse_args()
    +
    +
    +def main() -> int:
    +    args = parse_args()
    +    if args.child:
    +        return run_child(args)
    +    return run_parent(args)
    +
    +
    +if __name__ == "__main__":
    +    sys.exit(main())
    diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
    index 7a7cce6176..2468b4acf6 100755
    --- a/test/functional/test_framework/p2p.py
    +++ b/test/functional/test_framework/p2p.py
    @@ -740,16 +740,28 @@ class NetworkThread(threading.Thread):
    
             NetworkThread.listeners = {}
             NetworkThread.protos = {}
    -        if platform.system() == 'Windows':
    -            asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    -        NetworkThread.network_event_loop = asyncio.new_event_loop()
    +        self._loop_ready = threading.Event()
    
         def run(self):
             """Start the network thread."""
    -        self.network_event_loop.run_forever()
    +        try:
    +            if platform.system() == 'Windows':
    +                asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    +            # Create and register the loop on the worker thread instead of
    +            # constructing it on the main thread and handing it across.
    +            NetworkThread.network_event_loop = asyncio.new_event_loop()
    +            asyncio.set_event_loop(self.network_event_loop)
    +            self._loop_ready.set()
    +            self.network_event_loop.run_forever()
    +        finally:
    +            self._loop_ready.set()
    
         def close(self, *, timeout):
             """Close the connections and network event loop."""
    +        if not self._loop_ready.wait(timeout=timeout) or self.network_event_loop is None:
    +            self.join(timeout)
    +            NetworkThread.network_event_loop = None
    +            return
             self.network_event_loop.call_soon_threadsafe(self.network_event_loop.stop)
             wait_until_helper_internal(lambda: not self.network_event_loop.is_running(), timeout=timeout)
             self.network_event_loop.close()
    diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
    index 1f95756445..f968d6cc56 100755
    --- a/test/functional/test_framework/test_framework.py
    +++ b/test/functional/test_framework/test_framework.py
    @@ -258,7 +258,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
             self.log.debug('Setting up network thread')
             self.network_thread = NetworkThread()
             self.network_thread.start()
    -        self.wait_until(lambda: self.network_thread.network_event_loop.is_running())
    +        self.wait_until(lambda: self.network_thread.network_event_loop is not None and self.network_thread.network_event_loop.is_running())
    
             if self.options.usecli:
                 if not self.supports_cli:
    

    </details>

    I didn't spend enough time with it to understand it deeply, hope it's not a complete waste of time, the explanation does sound reasonable.

  12. maflcko referenced this in commit fab0b5d17e on Mar 13, 2026
  13. maflcko commented at 9:32 AM on March 13, 2026: member

    Yeah, I don't understand thread-safety of Windows sockets either. However, it seems a good shot and harmless try. Draft commit:

    commit fab0b5d17ea6f5e2357c41fe647cfbc1f8d4af21 (HEAD -> 2603-test-windows-network-thread-socket-safe, origin/2603-test-windows-network-thread-socket-safe)
    Author: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz>
    Date:   Fri Mar 13 10:03:49 2026 +0100
    
        test: Move event loop creation to network thread
        
        This should fix [#34367](/bitcoin-bitcoin/34367/)
        
        I am not familiar with Windows sockets thread-safety, but creating the event loop on the main thread, but running it in the network thread, could lead to a Windows fast abort (without any stderr):
        
        ```
        77/276 - wallet_txn_clone.py failed, Duration: 1 s
        
        stdout:
        2025-12-10T08:04:27.500134Z TestFramework (INFO): PRNG seed is: 4018092284830106117
        
        stderr:
        
        Combine the logs and print the last 99999999 lines ...
        ============
        Combined log for D:\a\_temp/test_runner_₿_🏃_20251210_075632/wallet_txn_clone_196:
        ============
         test  2025-12-10T08:04:27.500134Z TestFramework (INFO): PRNG seed is: 4018092284830106117
         test  2025-12-10T08:04:27.500433Z TestFramework (DEBUG): Setting up network thread
        ```
        
        Also, I couldn't find any docs that require the loop must be created on the thread that runs them:
        
        * https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.new_event_loop
        * https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_forever
        
        However, the patch seems trivial to review, harmless, and easy to revert, so it may be a good try to fix the intermittent Windows Python crash.
    
    diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
    index 7a7cce6176..68118af20b 100755
    --- a/test/functional/test_framework/p2p.py
    +++ b/test/functional/test_framework/p2p.py
    @@ -740,12 +740,12 @@ class NetworkThread(threading.Thread):
     
             NetworkThread.listeners = {}
             NetworkThread.protos = {}
    -        if platform.system() == 'Windows':
    -            asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    -        NetworkThread.network_event_loop = asyncio.new_event_loop()
     
         def run(self):
             """Start the network thread."""
    +        if platform.system() == 'Windows':
    +            asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    +        NetworkThread.network_event_loop = asyncio.new_event_loop()
             self.network_event_loop.run_forever()
     
         def close(self, *, timeout):
    diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
    index fb0fc0af6b..95bfc964d6 100755
    --- a/test/functional/test_framework/test_framework.py
    +++ b/test/functional/test_framework/test_framework.py
    @@ -260,7 +260,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
             self.log.debug('Setting up network thread')
             self.network_thread = NetworkThread()
             self.network_thread.start()
    -        self.wait_until(lambda: self.network_thread.network_event_loop.is_running())
    +        self.wait_until(lambda: self.network_thread.network_event_loop is not None and self.network_thread.network_event_loop.is_running())
     
             if self.options.usecli:
                 if not self.supports_cli:
    
  14. DrahtBot added this to the milestone 33.0 on Mar 13, 2026
  15. maflcko commented at 11:18 AM on March 13, 2026: member

    I guess there is no rush here, as the issue only happens once every 3 months.

    We can try #34820 and see if anything changes.

    If it happens again, I'll probably submit my above patch. If it disappears, I guess we can close this for 33.x (added a milestone, so it isn't forgotten).

  16. maflcko referenced this in commit fa050da980 on Mar 13, 2026
  17. maflcko removed this from the milestone 33.0 on Mar 13, 2026
  18. sedited closed this on Mar 18, 2026

  19. fanquake referenced this in commit 72d6c88165 on Mar 19, 2026
  20. fanquake reopened this on Mar 26, 2026

  21. fanquake commented at 11:10 AM on March 26, 2026: member
  22. maflcko commented at 11:20 AM on March 26, 2026: member

    It could be related, but it is different, because it does not mute before the network thread creation in Python, but after the msghand thread creation in bitcoind:

     node1 2026-03-26T02:50:47.892243Z [init] [noui.cpp:56] [noui_InitMessage] init message: Verifying blocks… 
     node1 2026-03-26T02:50:47.892263Z [init] [validation.cpp:4628] [VerifyDB] Verifying last 6 blocks at level 3 
     node1 2026-03-26T02:50:47.892293Z [init] [validation.cpp:4637] [VerifyDB] Verification progress: 0% 
     node1 2026-03-26T02:50:47.892706Z [init] [validation.cpp:4645] [VerifyDB] Verification progress: 16% 
     node1 2026-03-26T02:50:47.893030Z [init] [validation.cpp:4645] [VerifyDB] Verification progress: 33% 
     node1 2026-03-26T02:50:47.893338Z [init] [validation.cpp:4645] [VerifyDB] Verification progress: 50% 
     node1 2026-03-26T02:50:47.893698Z [init] [validation.cpp:4645] [VerifyDB] Verification progress: 66% 
     node1 2026-03-26T02:50:47.894033Z [init] [validation.cpp:4645] [VerifyDB] Verification progress: 83% 
     node1 2026-03-26T02:50:47.894362Z [init] [validation.cpp:4645] [VerifyDB] Verification progress: 99% 
     node1 2026-03-26T02:50:47.894384Z [init] [validation.cpp:4739] [VerifyDB] Verification: No coin database inconsistencies in last 6 blocks (6 transactions) 
     node1 2026-03-26T02:50:47.894425Z [init] [init.cpp:1414] [InitAndLoadChainstate] Block index and chainstate loaded 
     node1 2026-03-26T02:50:47.894490Z [init] [init.cpp:1949] [AppInitMain] Setting NODE_NETWORK in non-prune mode 
     node1 2026-03-26T02:50:47.894701Z [init] [init.cpp:2080] [AppInitMain] block tree size = 200 
     node1 2026-03-26T02:50:47.894725Z [init] [init.cpp:2093] [AppInitMain] nBestHeight = 199 
     node1 2026-03-26T02:50:47.894858Z [init] [net.cpp:3332] [BindListenPort] Bound to 127.0.0.1:13389 
     node1 2026-03-26T02:50:47.894903Z [init] [noui.cpp:56] [noui_InitMessage] init message: Starting network threads… 
     node1 2026-03-26T02:50:47.895016Z [init] [net.cpp:3528] [Start] DNS seeding disabled 
     node1 2026-03-26T02:50:47.895107Z [initload] [util/thread.cpp:20] [TraceThread] initload thread start 
     node1 2026-03-26T02:50:47.895180Z [initload] [node/mempool_persist.cpp:49] [LoadMempool] Failed to open mempool file. Continuing anyway. 
     node1 2026-03-26T02:50:47.895201Z [initload] [util/thread.cpp:22] [TraceThread] initload thread exit 
     node1 2026-03-26T02:50:47.895340Z [net] [util/thread.cpp:20] [TraceThread] net thread start 
     node1 2026-03-26T02:50:47.895406Z [addcon] [util/thread.cpp:20] [TraceThread] addcon thread start 
     node1 2026-03-26T02:50:47.895575Z [init] [noui.cpp:56] [noui_InitMessage] init message: Done loading 
     node1 2026-03-26T02:50:47.895720Z [msghand] [util/thread.cpp:20] [TraceThread] msghand thread start 
    
    (mute)
    

    Could make sense to open a new issue. Also it was on msvcrt, not on ucrt.

  23. fanquake commented at 12:33 PM on March 26, 2026: member

    Moved to #34925.

  24. fanquake closed this on Mar 26, 2026


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-24 09:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me