Skip to content

[Windows] Launching a child by Boost.Process v2 fails due to a pipe name clash with unrelated (plug-in) DLL running its own child process around the same time #1619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
RK-BFX opened this issue Apr 10, 2025 · 1 comment · May be fixed by boostorg/asio#450 or #1620

Comments

@RK-BFX
Copy link

RK-BFX commented Apr 10, 2025

General

Imagine an application that is a plug-in host that loads unrelated DLLs (e.g. from different vendors) as its plug-ins. If two or more such plug-ins use Boost.Process under the hood (e.g. built statically, with no boost_process*.dll) to launch child processes and e.g. read their output, and if it happens so that they do it around the same time (e.g. upon project opened), then chances are high that some of them will fail with error 231: All pipe instances are busy because each plug-in will generate (inside ASIO) and attempt using the same pipe name.

Reproduce:

  1. Download the following source files to the same empty folder:

    exe.cpp

    #include <thread>
    
    #include <Windows.h>
    
    int main() {
       using ProcType = void(*)();
       HMODULE h1 = ::LoadLibraryA("DLL1.dll");
       HMODULE h2 = ::LoadLibraryA("DLL2.dll");
       auto p1 = reinterpret_cast< ProcType >( ::GetProcAddress( h1, "usePipe" ) );
       auto p2 = reinterpret_cast< ProcType >( ::GetProcAddress( h2, "usePipe" ) );
       std::thread t1( p1 ), t2( p2 );
       t1.join();
       t2.join();
       return 0;
    }

    dll.cpp

    It also includes #ifed-out code for v1 as that suffers from the same issue (#476).

    #include <future>
    #include <iostream>
    
    #define BOOST_PROCESS_VERSION 2
    #include <boost/asio.hpp>
    #include <boost/process.hpp>
    
    #if BOOST_PROCESS_VERSION == 2  // link to required libraries
    #define BOOST_LIB_NAME boost_process  // hack to auto-link Boost.Process v2
    #define _DLL  // for BOOST_LIB_PREFIX to be empty
    #define BOOST_DYN_LINK  // ditto
    #include <boost/config/auto_link.hpp>
    
    // For EnumWindows in boost::process::v2::detail::basic_process_handle_win<>::request_exit(...),
    // for SendMessageW, GetWindowThreadProcessId in boost::process::v2::detail::enum_window(...).
    #pragma comment(lib, "User32.lib")
    // For RtlNtStatusToDosError, NtResumeProcess in boost::process::v2::detail::basic_process_handle_win<>::resume(...),
    // for NtSuspendProcess in boost::process::v2::detail::basic_process_handle_win<>::suspend(...).
    #pragma comment(lib, "NTDLL.lib")
    #endif
    // For SHGetFileInfoW in boost::process::v2::environment::detail::is_exec_type(...),
    #pragma comment(lib, "Shell32.lib")  // for SHGetFileInfoW in boost::winapi::sh_get_file_info(...).
    
    extern "C" __declspec(dllexport) void __cdecl usePipe() {
       try {
          namespace ba = boost::asio;
          ba::io_context ioc;
          std::string output;
    #if BOOST_PROCESS_VERSION == 1
          namespace bp = boost::process;
          std::future< std::string > out;
          bp::child cp( bp::search_path("cmd.exe"), bp::std_in.close(), bp::std_out > out, ioc );
          ioc.run();  // blocks until the pipe closes
          output = out.get();
    #elif BOOST_PROCESS_VERSION == 2
          namespace bp = boost::process::v2;
          ba::readable_pipe out{ ioc };
          bp::process cp( ioc, bp::environment::find_executable("cmd.exe"), {/* no args */},
                          bp::process_stdio{ /*.in=*/ nullptr /* closed */, out, /*.err=*/ {/* default */} } );
          boost::system::error_code ec;
          ba::read( out, ba::dynamic_buffer( output ), ec );
          // Getting ec == 109 (ERROR_BROKEN_PIPE) rather than ba::error::eof for some reason
    #endif
          cp.wait();  // necessary to get the exit code, at least in v1
          std::cout << "CMD finished. Code: " << cp.exit_code() << ". Output:\n" << output << std::endl;
       }
       catch( std::system_error const & e ) {
          std::cerr << "CMD failed to start. Code: " << e.code() << ". Message:\n" << e.what() << std::endl;
       }
       catch( std::exception const & e ) {
          std::cerr << "CMD failed to start. Message:\n" << e.what() << std::endl;
       }
    }

    rebuild.cmd

    @echo off
    
    del *.obj *.dll *.exe *.map *.exp *.lib *.pdb *.idb *.ilk
    
    :: Used `vcpkg install --triplet x64-windows-static` to fetch 'boost-process' and its dependencies
    
    set "BOOST_ROOT=.\vcpkg_installed\x64-windows-static"
    
    cl.exe /nologo "/I%BOOST_ROOT%\include" ^
           /DBOOST_PROCESS_USE_STD_FS ^
           /DBOOST_ASIO_DISABLE_VISIBILITY ^
           /D_WIN32_WINNT=0x0A00 /DWIN32_LEAN_AND_MEAN ^
           /std:c++17 /EHsc /ZI ^
           /FeDLL dll.cpp ^
           /LD /link /DEBUG:FULL "/LIBPATH:%BOOST_ROOT%\lib"
    
    copy DLL.dll DLL2.dll
    move DLL.dll DLL1.dll
    
    cl.exe /nologo /ZI /FeEXE exe.cpp /link /DEBUG:FULL

    For convenience, they are attached as an archive, along with vcpkg configuration files I used to fetch Boost libs: Boost.Process-v2_Windows-pipe-name-clash_2025-04-09.zip.

  2. Adjust the BOOST_ROOT variable value in rebuild.cmd to point to the Boost directory that has its libs built statically (to model the real-life situation and avoid dependency on boost_process*.dll).

  3. Run rebuild.cmd — it should produce, among others, EXE.exe , DLL1.dll and DLL2.dll. DLL1.dll and DLL2.dll are identical and model two distinct plug-ins.

  4. Run EXE.exe.

Expected:

Assuming the current directory is F:\Experiments\Boost, we should get some output like this:

CMD finished. Code: 0. Output:
Microsoft Windows [Version 10.0.19045.5737]
(c) Microsoft Corporation. All rights reserved.

F:\Experiments\Boost>

doubled and intertwined due to concurrent execution of two threads. E.g.:

CMD finished. Code: CMD finished. Code: 0. Output:
Microsoft Windows [Version 10.0.19045.5737]
(c) Microsoft Corporation. All rights reserved.

F:\Experiments\Boost>0
. Output:
Microsoft Windows [Version 10.0.19045.5737]
(c) Microsoft Corporation. All rights reserved.

F:\Experiments\Boost>

Actual:

Due to both plug-ins trying to use the same pipe name, one of the child processes will fail to launch, so we get e.g.:

CMD failed to start. Message:
create_pipe: All pipe instances are busy [system:231]
CMD finished. Code: 0. Output:
Microsoft Windows [Version 10.0.19045.5737]
(c) Microsoft Corporation. All rights reserved.

F:\Experiments\Boost>
@RK-BFX
Copy link
Author

RK-BFX commented Apr 10, 2025

The report is based on Boost 1.87.0 and the bug is present in the latest sources.
I have a local fix (simple and low-risk) and will submit a PR soon.

RK-BFX added a commit to boris-fx/boost-asio that referenced this issue Apr 10, 2025
Include numerical representation of local static variable's address into the pipe name to discriminate Boost.Asio instances in independent DLLs.

Fixes chriskohlhoff/asio#1619

Also make the code slightly more future-proof and add useful comments and external documentation links.
RK-BFX added a commit to boris-fx/chriskohlhoff-asio that referenced this issue Apr 11, 2025
Include numerical representation of local static variable's address into the pipe name to discriminate Boost.Asio instances in independent DLLs.

Fixes chriskohlhoff#1619, mirrors [44fdc62](boostorg/asio@44fdc62).

Also make the code slightly more future-proof and add useful comments and external documentation links.
RK-BFX added a commit to boris-fx/boost-asio that referenced this issue Apr 14, 2025
Include numerical representation of local static variable's address into the pipe name to discriminate Boost.Asio instances in independent DLLs.

Fixes chriskohlhoff/asio#1619

Also make the code slightly more future-proof and add useful comments and external documentation links.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment