bitcoin wrapper: Fix Windows exec so wrapper waits for child process #36105

pull ryanofsky wants to merge 4 commits into bitcoin:master from ryanofsky:pr/bitwin changing 6 files +38 −20
  1. ryanofsky commented at 5:34 PM on August 27, 2026: contributor

    Problem: On Windows, bitcoin.exe spawns a child process and immediately exits rather than waiting for it to finish. This breaks capturing output and checking exit codes in scripts and tests.

    Solution: Switch util::ExecVp from _execvp to _spawnvp(_P_WAIT), which blocks until the child exits and forwards its exit code. Also enable tool_bitcoin.py and interface_gui.py tests which were skipped due to previous behavior.

    This fix requires small changes to src/util/exec.cpp and src/bitcoin.cpp and there is also an extra commit fixing errno message strings on Windows when ExecVp fails. Details in commit messages.

  2. util: Fix ExecVp on Windows to wait for child process
    Switch from _execvp to _spawnvp(_P_NOWAIT) + _cwait so bitcoin.exe
    waits for the child process to finish and forwards its exit code.
    Previously _execvp would exit the parent as soon as the child started,
    making it impossible for anything waiting on bitcoin.exe (such as a test
    framework) to track whether the child succeeded or failed. _P_NOWAIT is
    used instead of _P_WAIT so that a child exit code of -1 (0xffffffff) is
    not confused with a spawn failure: _spawnvp(_P_WAIT) returns -1 for both,
    but _spawnvp(_P_NOWAIT) returns the child handle on success, and _cwait
    fills a separate status that is forwarded via _exit.
    
    Co-Authored-By: Bortlesboat <169967362+Bortlesboat@users.noreply.github.com>
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
    6bce70ad03
  3. bitcoin: Fix msvcrt EINVAL regression from _execvp to _spawnvp switch
    Treat EINVAL from _spawnvp as "not found" on msvcrt so the wrapper moves on
    to the next candidate path. msvcrt's _spawnvp returns EINVAL rather than
    ENOENT when the file name has a directory component and the file does not
    exist (see code comment), so after the previous commit switched from _execvp
    to _spawnvp, msvcrt builds threw on the first nonexistent candidate,
    libexec/bitcoind, instead of trying the others. _execvp and ucrt's _spawnvp
    return ENOENT, so ucrt builds (MinGW-ucrt and MSVC) are unaffected and the
    change is guarded with !defined(_UCRT).
    
    Measured by calling the CRT functions directly on GitHub's windows-2022 and
    windows-2025 runners, through msvcrt.dll and ucrtbase.dll via ctypes and
    through MinGW (msvcrt and ucrt) and MSVC builds:
    https://github.com/ryanofsky/bitcoin/actions/runs/34365395636
    https://github.com/ryanofsky/bitcoin/actions/runs/34367754505
    
    Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
    68ae4a0f84
  4. test: Enable tool_bitcoin.py and interface_gui.py tests on Windows
    Now that util::ExecVp on Windows uses _spawnvp(_P_NOWAIT) + _cwait
    instead of _execvp, the bitcoin wrapper process blocks until the child
    exits and Python can capture its stdout/stderr and exit code normally.
    Remove the Windows skips added in #33229 and #35551.
    
    The interface_gui.py test is still skipped in vcpkg Qt builds. vcpkg builds Qt
    with -opengl dynamic, making the minimal platform plugin unusable due to
    internal Qt bugs. This matches existing logic in src/qt/test/CMakeLists.txt
    avoiding the minimal platform plugin with test_bitcoin-qt.
    
    Co-Authored-By: Hodlinator <172445034+hodlinator@users.noreply.github.com>
    ac15f5654a
  5. bitcoin: Use generic_category for errno from exec/spawn CRT functions
    Switch from system_category to generic_category when throwing from
    ExecVp failure, so errno is read as a POSIX value. On Windows,
    system_category interprets codes as Win32 errors, so EINVAL=22 would
    produce "The device does not recognize the command" instead of
    "Invalid argument".
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
    2b8779842a
  6. DrahtBot commented at 5:34 PM on August 27, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/36105.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Concept ACK hebasto, hodlinator

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36106 (bitcoin wrapper: respect CMAKE_INSTALL_BINDIR/LIBEXECDIR by ryanofsky)
    • #32387 (ipc: add windows support by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  7. hebasto commented at 10:17 PM on August 27, 2026: member

    Concept ACK.

  8. hebasto commented at 10:23 PM on August 27, 2026: member

    There is a preliminary understanding among the build crowd that #33593 might land shortly after branching off "32.x", which renders the "bitcoin: Fix msvcrt regressions from _execvp to _spawnvp switch" commit unnecessary.

  9. bitcoin deleted a comment on Aug 31, 2026
  10. in test/functional/test_framework/test_framework.py:1 in 6ca9199eec outdated


    hodlinator commented at 12:06 PM on August 31, 2026:

    In 6ca9199eecd9934563351c6b4b11468b75c6d088 - "test: Enable tool_bitcoin.py and interface_gui.py tests on Windows":

    The commit message refers to _wspawnvp() and _wexecvp(), however we use the non-wide versions in code.



    ryanofsky commented at 1:45 AM on September 9, 2026:

    re: #36105 (review)

    The commit message refers to _wspawnvp() and _wexecvp(), however we use the non-wide versions in code.

    Good catch. These changes are older than #35704 and the commit message was not updated when it was merged.


    ryanofsky commented at 1:46 AM on September 9, 2026:

    re: #36105 (review)

    Thanks. It's important to me to distinguish words and ideas which are my own from those that come from other sources, so I use co-author tags for this. I think the AI policy was written with a different kind of PR in mind than this one. I understand the changes in this PR and take responsibility for them.


    hodlinator commented at 7:25 PM on September 10, 2026:

    thread #36105 (review):

    Thanks for the generous attribution.

    Commit ac15f5654aec8ae96fc814cbab331c580f790250 "test: Enable tool_bitcoin.py and interface_gui.py tests on Windows" happens to be the commit I feel is still I don't fully grasp, but it's more what it's referencing than what it's doing.

    What it's doing in the scope of interface_gui.py - instead of avoiding to run at all on Windows, only skip when the build is a vcpkg build. It references src/qt/test/CMakeLists.txt but that file is not immediately understandable to me. From looking at https://qt.developpez.com/doc/6.4/qguiapplication/#platformName-prop I think this is how I would make it more understandable:

    --- a/src/qt/test/CMakeLists.txt
    +++ b/src/qt/test/CMakeLists.txt
    @@ -40,12 +40,14 @@ add_test(NAME test_bitcoin-qt
       COMMAND test_bitcoin-qt
     )
     if(WIN32 AND VCPKG_TARGET_TRIPLET)
       set(plugin_path "$<SHELL_PATH:$<PATH:GET_PARENT_PATH,$<PATH:GET_PARENT_PATH,$<TARGET_PROPERTY:Qt6::QWindowsIntegrationPlugin,LOCATION_$<CONFIG>>>>>")
       set_property(TEST test_bitcoin-qt APPEND PROPERTY
         ENVIRONMENT_MODIFICATION
           # On Windows, vcpkg configures Qt with `-opengl dynamic`, which makes
    -      # the "minimal" platform plugin unusable due to internal Qt bugs.
    +      # the otherwise appropriate headless "minimal" platform plugin unusable
    +      # due to internal Qt bugs. So override to "windows" plugin in order to
    +      # make it work when built in that configuration (VCPKG_TARGET_TRIPLET).
           QT_QPA_PLATFORM=set:windows
           QT_PLUGIN_PATH=set:${plugin_path}
       )
       unset(plugin_path)
    

    My impression is that src/qt/test/CMakeLists.txt is supposed to make test_bitcoin-qt actually work, even when built with vcpkg.

    So why would you be changing the functional test to skip something which should be working?

    Well, when I build and run .\build\bin\Release\test_bitcoin-qt.exe I get:

    qt.qpa.plugin: Could not find the Qt platform plugin "windows" in ""
    This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
    

    Setting it to "minimal" has the same result.

    I tried printing the plugin path through adding this before the variable is unset:

      message("QT_PLUGIN_PATH=${plugin_path} (plugin_path)")
    

    and it prints:

    QT_PLUGIN_PATH=$<SHELL_PATH:$<PATH:GET_PARENT_PATH,$<PATH:GET_PARENT_PATH,$<TARGET_PROPERTY:Qt6::QWindowsIntegrationPlugin,LOCATION_$<CONFIG>>>>> (plugin_path)
    

    Manually setting:

    set QT_PLUGIN_PATH=C:\Users\hodlinator\bitcoin\build\vcpkg_installed\x64-windows\Qt6\plugins
    

    makes .\build\bin\Release\test_bitcoin-qt.exe run fine, both with minimal and windows plugins!

    So I wonder if the setting of QT_PLUGIN_PATH in CMakeLists.txt is somehow broken? I'm on CMake 4.4.2.

    Having the QT_PLUGIN_PATH set correctly also makes interface_gui.py run fine when modified to not skip!


    hodlinator commented at 7:31 PM on September 10, 2026:

    Yeah, didn't mean to put you in the same category as new-entrant sloppers. But I think it may be good to abide by the same rules or else alter/amend them.


    hebasto commented at 8:37 PM on September 10, 2026:

    FWIW, in the QML repo, we use the following code:

        file(GENERATE
          OUTPUT $<TARGET_FILE_DIR:bitcoin-qt>/qt.conf
          CONTENT "[Paths]\nPrefix = $<PATH:GET_PARENT_PATH,$<PATH:GET_PARENT_PATH,$<TARGET_FILE_DIR:Qt6::QWindowsIntegrationPlugin>>>\n"
        )
    

    This makes all targets runnable form the build tree on Windows.

  11. in src/bitcoin.cpp:214 in 09f3531588 outdated
     209 |          exec_args[0] = exe_path_str.c_str();
     210 |          if (util::ExecVp(exec_args[0], (char*const*)exec_args.data()) == -1) {
     211 | +#if defined(WIN32) && !defined(_UCRT)
     212 | +            // msvcrt's _spawnvp returns EINVAL (not ENOENT) for any missing
     213 | +            // file, even after ".exe" is appended. ucrt returns ENOENT.
     214 | +            if (allow_notfound && (errno == ENOENT || errno == EINVAL)) return false;
    


    hodlinator commented at 12:13 PM on August 31, 2026:

    Checked https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/spawnvp-wspawnvp?view=msvc-170 but this difference in error codes between runtimes is not documented there. Is it admitted elsewhere?


    ryanofsky commented at 2:14 AM on September 9, 2026:

    re: #36105 (review)

    Checked https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/spawnvp-wspawnvp?view=msvc-170 but this difference in error codes between runtimes is not documented there. Is it admitted elsewhere?

    It's not documented anywhere and was just seen in CI. I was meaning to follow up and get more information about this though, so I'll try to do that soon.


    ryanofsky commented at 4:13 PM on September 9, 2026:

    re: #36105 (review)

    this difference in error codes between runtimes is not documented there

    Followed up and this difference does not seem to be documented anywhere, only observed in CI. Did some more testing though and was able to simplify and write a better comment which is in the latest push


    hodlinator commented at 7:27 PM on September 10, 2026:

    cyb3ralbert commented at 5:21 AM on September 13, 2026:

    One more observation on this, from a windows-2022 runner with the cross-built binaries from the #36190 branch (which carries this PR's commits). When the target executable is missing, the error the user sees differs between runtimes:

    MSVCRT: Error: execvp failed to execute '...\bitcoind': Invalid argument
    UCRT:   Error: execvp failed to execute '...\bitcoind': No such file or directory
    

    The intermediate lookups in ExecCommand accept EINVAL as "not found" and move on, but the last attempt passes allow_notfound=false, so the raw errno goes into the std::system_error. Mapping EINVAL to ENOENT in that throw as well would make an MSVCRT build report a missing file the same way UCRT does, instead of "Invalid argument".

  12. hodlinator commented at 12:16 PM on August 31, 2026: contributor

    Concept ACK 30ae05a21bdb30e63400385cf6d9dfd87dccfc70

  13. ryanofsky force-pushed on Sep 9, 2026
  14. ryanofsky commented at 2:18 AM on September 9, 2026: contributor

    <!-- begin push-9 -->

    Updated 30ae05a21bdb30e63400385cf6d9dfd87dccfc70 -> c02747ea3706e0c028b4be61a4dff054d65f3a3e (pr/bitwin.8 -> pr/bitwin.9, compare)<!-- end --> with exit code status fix and test from @Bortlesboat from https://github.com/bitcoin/bitcoin/pull/36190

  15. ryanofsky force-pushed on Sep 9, 2026
  16. ryanofsky commented at 2:38 PM on September 9, 2026: contributor

    <!-- begin push-10 -->

    Updated c02747ea3706e0c028b4be61a4dff054d65f3a3e -> 359a57d653406365a6158457e77ff926d6f58d0d (pr/bitwin.9 -> pr/bitwin.10, compare)<!-- end --> just dropping test commit 00fe470459ff36f22ef4a2e7d3e3e8903dac207b as requested

    <!-- begin push-11 -->

    Updated 359a57d653406365a6158457e77ff926d6f58d0d -> 5cfe9d873c7b3ca826467e33b6b650f43957d4e8 (pr/bitwin.10 -> pr/bitwin.11, compare)<!-- end --> to fix lint (unused os/shutil/subprocess imports in tool_bitcoin.py) https://github.com/bitcoin/bitcoin/actions/runs/34364703981/job/102510308140

    <!-- begin push-12 -->

    Updated 5cfe9d873c7b3ca826467e33b6b650f43957d4e8 -> 2b8779842a3fcb4afaa533a0490b9618ccc42c36 (pr/bitwin.11 -> pr/bitwin.12, compare)<!-- end --> simplifying MSVCRT workaround and documenting it better

  17. ryanofsky force-pushed on Sep 9, 2026
  18. DrahtBot added the label CI failed on Sep 9, 2026
  19. Bortlesboat referenced this in commit 57b837251e on Sep 9, 2026
  20. ryanofsky force-pushed on Sep 9, 2026
  21. DrahtBot removed the label CI failed on Sep 9, 2026
  22. hodlinator commented at 8:27 PM on September 10, 2026: contributor

    Reviewed 2b8779842a3fcb4afaa533a0490b9618ccc42c36

  23. hebasto commented at 5:09 PM on September 16, 2026: member

    There is a preliminary understanding among the build crowd that #33593 might land shortly after branching off "32.x", which renders the "bitcoin: Fix msvcrt regressions from _execvp to _spawnvp switch" commit unnecessary.

    #33593 has been merged now.


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-09-16 23:51 UTC

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