From 31b87d3d695e35c1daf1c88d2b5d6ddebec3e62b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 3 Aug 2016 09:01:56 +0200 Subject: Implement faster emulation of Windows NUL via temporary file --- butl/fdstream | 18 ++++++++++++++++++ butl/fdstream.cxx | 34 ++++++++++++++++++++++++++++++++-- butl/process.cxx | 6 +++++- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/butl/fdstream b/butl/fdstream index 0caac5c..71de6a0 100644 --- a/butl/fdstream +++ b/butl/fdstream @@ -414,8 +414,26 @@ namespace butl // Note that it's the caller's responsibility to close the returned file // descriptor. // + // On Windows the null device is NUL and writing anything substantial to it + // (like redirecting a process' output) is extremely slow, as in, an order + // of magnitude slower than writing to disk. If you are using the descriptor + // yourself this can be mitigated by setting the binary mode (already done + // by fdopen()) and using a buffer of around 64K. However, sometimes you + // have no control of how the descriptor will be used. For instance, it can + // be used to redirect a child's stdout and the way the child sets up its + // stdout is out of your control (on Windows). For such cases, there is an + // emulation via a temporary file. Mostly it functions as a proper null + // device with the file automatically removed once the descriptor is + // closed. One difference, however, would be if you were to both write to + // and read from the descriptor. + // +#ifndef _WIN32 LIBBUTL_EXPORT int fdnull () noexcept; +#else + LIBBUTL_EXPORT int + fdnull (bool temp = false) noexcept; +#endif } #include diff --git a/butl/fdstream.cxx b/butl/fdstream.cxx index f761e7b..cff9b59 100644 --- a/butl/fdstream.cxx +++ b/butl/fdstream.cxx @@ -21,6 +21,7 @@ #include // errno, E* #include // ios_base::openmode, ios_base::failure +#include // bad_alloc #include // numeric_limits #include #include // uncaught_exception() @@ -542,9 +543,38 @@ namespace butl } int - fdnull () noexcept + fdnull (bool temp) noexcept { - return _sopen ("nul", _O_RDWR, _SH_DENYNO); + // No need to translate /r/n before sending it to void. + // + if (!temp) + return _sopen ("nul", _O_RDWR | _O_BINARY, _SH_DENYNO); + + try + { + // We could probably implement a Windows-specific version of getting + // the temporary file that avoid any allocations and exceptions. + // + path p (path::temp_path ("null")); // Can throw. + return _sopen (p.string ().c_str (), + (_O_CREAT | + _O_RDWR | + _O_BINARY | // Don't translate. + _O_TEMPORARY | // Remove on close. + _O_SHORT_LIVED), // Don't flush to disk. + _SH_DENYNO, + _S_IREAD | _S_IWRITE); + } + catch (const bad_alloc&) + { + errno = ENOMEM; + return -1; + } + catch (const system_error& e) + { + errno = e.code ().value (); + return -1; + } } fdstream_mode diff --git a/butl/process.cxx b/butl/process.cxx index b78321c..b207ee5 100644 --- a/butl/process.cxx +++ b/butl/process.cxx @@ -451,7 +451,11 @@ namespace butl auto create_null = [&get_osfhandle, &fail](auto_handle& n) { - auto_fd fd (fdnull ()); + // Note that we are using a faster, temporary file-based emulation of + // NUL since we have no way of making sure the child buffers things + // properly (and by default they seem no to). + // + auto_fd fd (fdnull (true)); if (fd.get () == -1) fail (); -- cgit v1.1