4

I have some code that resembles this minimal reproduction example (the real version generates some code and compiles it):

#include <fstream>
#include <string>
#include <thread>
#include <vector>

void write(unsigned int thread)
{
    std::ofstream stream("test_" + std::to_string(thread) + ".txt");
    stream << "test" << std::endl;
    stream << "thread" << std::endl;
    stream << "bad" << std::endl;
}

void test(unsigned int thread)
{
    write(thread);
#ifdef _WIN32
    const std::string command = "rename test_" + std::to_string(thread) + ".txt test_renamed_" + std::to_string(thread) + ".txt";
#else
    const std::string command = "mv test_" + std::to_string(thread) + ".txt test_renamed_" + std::to_string(thread) + ".txt";
#endif
    system(command.c_str());
}

int main()
{
    std::vector<std::thread> threads;
    for(unsigned int i = 0; i < 5; i++) {
        // Remove renamed file
        std::remove(("test_renamed_" + std::to_string(i) + ".txt").c_str());

        threads.emplace_back(test, i);
    }

    // Join all threads
    for(auto &t : threads) {
        t.join();
    }
    return EXIT_SUCCESS;
}

My understanding is that std::ofstream should behave in a nice RAII manner and close and flush at the end of the write function. On Linux, it appears to do just this. However, on Windows 10 I get sporadic "The process cannot access the file because it is being used by another process" errors. I've dug into it with procmon and it looks like the file isn't getting closed by the parent process (22224) resulting in the SHARING_VIOLATION which presumably causes the error: Procmon trace Although the procmon trace looks like the problem is within my process, I have tried turning off the virus scanner. I have also tried using C-style fopen,fprintf,fclose and also ensuring that the process I'm spawning with system isn't inheriting file handles somehow by clearing HANDLE_FLAG_INHERIT on the underlying file handle...which leaves me somewhat out of ideas! Any thoughts SO?

  • [This](https://stackoverflow.com/a/50887115/6427477)? – C.M. Jun 18 '21 at 18:02
  • @C.M.: No, that's related to "I closed all my handles but something else on the computer locked the file", where in this question, the first-party handle is not being closed. – Ben Voigt Jun 18 '21 at 20:02
  • @BenVoigt Looks the same to me -- in both cases CloseHandle() is called, but underlying kernel object gets destroyed (and related locks released) few miliseconds later... – C.M. Jun 18 '21 at 20:11
  • 1
    @C.M. According to the procmon capture in the question, `CloseHandle` *hasn't* been called by `test.exe`. – Ben Voigt Jun 18 '21 at 21:40
  • @BenVoigt Huh... Compiler-lvl reordering? :) – C.M. Jun 18 '21 at 22:43
  • @C.M.: That definitely is not a reordering allowed under the as-if rule. – Ben Voigt Jun 18 '21 at 23:46
  • Neither explicitly closing the stream nor switching to explicit C-style fopen,fprintf,fclose solves the problem – neworderofjamie Jun 19 '21 at 21:39
  • Try (temporarily) disabling antivirus to see if that changes things. – Raymond Chen Jun 20 '21 at 20:35
  • Have you tried running this on another machine? – Paul Sanders Jun 21 '21 at 23:38
  • Good question! I only have access to two Windows machines but I can confirm that this happens in the same way on both (6 core i5 compiled with Visual Studio 2019 and 4 core, 8 thread i7 compiled with Visual Studio 2017) – neworderofjamie Jun 22 '21 at 11:51
  • Why in your image the file is always "test_4.txt"? If you use always the same name it will fail because Windows OS as others tell you. What's that "QueryDirectory" call? And you say "the real version generates some code and compiles it": are you sure your `write` function isn't merged with your "generate & compile" code, thus doing the file close later? If there is some amount of data to write, the file could be "busy" (from an OS perspective) after close, even more if there are still threads to spawn (I/O waits.) – Manuel Jun 22 '21 at 20:37

3 Answers3

3

At least on VS 2017, I can confirm the file is closed from your snippet. (In destructor of ofstream, the code calls fclose on the handle).

I think however, that the issue is not in the C++ code, but the behavior of the OS.

With Windows, the act of removing a file which the OS thinks is open, will be blocked. In Unix the behavior of unlinking a file from a directory, is to allow existing handles to continue acting the orphaned files. So in unix the operation could never be a sharing violation, as the act of unlinking a file is a different operation. linux semantics can be opted into for recent windows 10 builds.

procmon on Windows has a given altitude. That means that any operation which is perfformed by virus scanners may be hidden to procmon, and it would give a false answer.

A process can also duplicate a handle on the open file, and that would also cause this issue, but not show the handle being closed.

mksteve
  • 12,614
  • 3
  • 28
  • 50
  • 1
    Totally agree that this is an OS-level issue rather than a C++ one! I think I've ruled out the virus scanner by turning it off and the REAL code doesn't actually rename the files so I don't think the issue is deleting-specific. What could cause a process to duplicate a handle on the open file? – neworderofjamie Jun 21 '21 at 09:04
  • 1
    Something in the OS may trigger "temporary" handle duplication. Further, closing duplicate may be "soon enough" 99% of time. I wonder if sysinternals "handle" command could be used to show the unexpected handle state of your test.exe process. – WilliamClements Jun 22 '21 at 21:01
3

We can rewrite the file writing using Win32 API:

void writeRaw(unsigned int thread)
{
    const auto str = "test_" + std::to_string(thread) + ".txt";
    auto hFile = CreateFileA(str.c_str(), GENERIC_WRITE, 
        FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, 0, nullptr);
    DWORD ret{};
    WriteFile(hFile, str.data(), str.size(), &ret, nullptr);
    CloseHandle(hFile);
}

Running the test still gives a file share violation due to the way windows works. When the last handle is closed, filesystem driver performs IRP_MJ_CLEANUP IOCTL to finish processing anything related to the file. Antivirus software, for instance, would attempt to scan the file (and incidentally holds the lock on it =) ). Additionally documentation MSDN IRP_MJ_CLEANUP states that:

It is important to note that when all handles to a file object have been closed, this does not necessarily mean that the file object is no longer being used. System components, such as the Cache Manager and the Memory Manager, might hold outstanding references to the file object. These components can still read to or write from a file, even after an IRP_MJ_CLEANUP request is received.

Conclusion: It is expected that to receive a file share violation in windows if a process tries to do something with the file shortly after closing the handle as the underlying system components are still processing the file close request.

vpa1977
  • 326
  • 1
  • 5
  • Short of doing something like busy-waiting until you can open the file for reading is there a solution though? Writing to a file and then opening it doesn't seem that unusual a requirement! – neworderofjamie Jun 23 '21 at 21:07
  • I don't write an answer of my own because I support this answer. I think it explains the situation, citing authoritative doc. Only the goalposts were moved to ask for solution. I wonder if there is a way using something like fsync to fix it. – WilliamClements Jun 24 '21 at 20:35
  • I wonder why OP couldn't see `CloseHandle()` in procmon capture? Does the same happen with your code? – C.M. Jul 01 '21 at 18:26
  • I am getting `CloseFile()` both with `fstream` and `WriteFile` code. I can not guess what might be causing `CloseFile()` absence. – vpa1977 Jul 05 '21 at 09:44
1

The most probable cause of the problem is that when you delete a file in windows ,it isn't immediatly deleted (it's just flagged for deletion). It can/will take some milliseconds (up to seconds if you're very unlucky) for it to be actually deleted.

Source : Niall Douglas in “Better mutual exclusion on the filesystem using Boost.AFIO" about 10m:10s https://www.youtube.com/watch?v=9l28ax3Zq0w

engf-010
  • 3,980
  • 1
  • 14
  • 25