Skip to content

Commit

Permalink
Fix folder_change_reader_state destructor hang. (#386)
Browse files Browse the repository at this point in the history
* Fix folder_change_reader_state destructor hang. If there is no pending operation, CancelIoEx returns an error and GetOverlappedResult hangs.

* fix indentation.

* Added test to demonstrate fixing the hang issue. Delete the monitored directory during the notification.

* rewrote tests. add a small delay to better control the bug timing.

* code review changes

---------

Co-authored-by: Razvan Pricope <rpricope@bitdefender.com>
  • Loading branch information
razvan-pricope and rpricope-bd authored Dec 8, 2023
1 parent 5cbb589 commit 0fdb072
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 4 deletions.
11 changes: 8 additions & 3 deletions include/wil/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -624,10 +624,15 @@ namespace wil

if (m_folderHandle)
{
CancelIoEx(m_folderHandle.get(), &m_overlapped);
BOOL cancelRes = CancelIoEx(m_folderHandle.get(), &m_overlapped);

DWORD bytesTransferredIgnored = 0;
GetOverlappedResult(m_folderHandle.get(), &m_overlapped, &bytesTransferredIgnored, TRUE);
// no pending operation to cancel. Maybe StartIo returned
// an error?
if (!(cancelRes == FALSE && ::GetLastError() == ERROR_NOT_FOUND))
{
DWORD bytesTransferredIgnored = 0;
GetOverlappedResult(m_folderHandle.get(), &m_overlapped, &bytesTransferredIgnored, TRUE);
}
}

// Wait for callbacks to complete.
Expand Down
58 changes: 57 additions & 1 deletion tests/FileSystemTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <wil/common.h>
#ifdef WIL_ENABLE_EXCEPTIONS
#include <string>
#include <thread>
#endif

// TODO: str_raw_ptr is not two-phase name lookup clean (https://github.com/Microsoft/wil/issues/8)
Expand All @@ -16,6 +17,7 @@ namespace wil
#endif
}

#include <wil/resource.h>
#include <wil/filesystem.h>

#ifdef WIL_ENABLE_EXCEPTIONS
Expand Down Expand Up @@ -772,7 +774,61 @@ TEST_CASE("FileSystemTests::CreateFileW helpers", "[filesystem]")
}
}

#endif
TEST_CASE("FileSystemTest::FolderChangeReader destructor does not hang", "[filesystem]")
{
wil::unique_cotaskmem_string testRootTmp;
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(L"%TEMP%\\wil_test_filesystem", testRootTmp));
std::wstring testRootDir = testRootTmp.get();
std::wstring testFile = testRootDir + L"\\test.dat";
bool deleteDir = false;
wil::unique_event opCompletedEv(wil::EventOptions::None);
wil::unique_event readerDestructNotify(wil::EventOptions::ManualReset);
HANDLE readerDestructNotifyRaw = readerDestructNotify.get();

REQUIRE_FALSE(DirectoryExists(testRootDir.c_str()));
REQUIRE_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(testRootDir.c_str()));
REQUIRE(DirectoryExists(testRootDir.c_str()));

/**
* Move the operation to a new thread.
* The destructor of unique_folder_change_reader might hang. If this happens,
* we want to report an test error instead of hanging forever.
* Initialize the reader in current thread to make sure there is no race condition with test
* creating files
*/
auto reader = wil::make_folder_change_reader_nothrow(testRootDir.c_str(), false, wil::FolderChangeEvents::All,
[&](wil::FolderChangeEvent, PCWSTR)
{
if (deleteDir)
RemoveDirectoryW(testRootDir.c_str());

opCompletedEv.SetEvent();
});
auto readerThread = std::thread([rdn = std::move(readerDestructNotify), r = std::move(reader)]() mutable {
rdn.wait(INFINITE);
});

wil::unique_hfile testFileOut(::CreateFileW(testFile.c_str(), GENERIC_ALL,
FILE_SHARE_READ, nullptr, CREATE_ALWAYS, 0, nullptr));
REQUIRE(testFileOut);
testFileOut.reset();
opCompletedEv.wait(INFINITE);

deleteDir = true;
REQUIRE(DeleteFileW(testFile.c_str()));
opCompletedEv.wait(INFINITE);
std::this_thread::sleep_for(std::chrono::seconds(1)); //enough time for the StartIO call to fail

SetEvent(readerDestructNotifyRaw);
DWORD waitResult = WaitForSingleObject(readerThread.native_handle(), 30 * 1000);
if (waitResult != WAIT_OBJECT_0)
readerThread.detach();
else
readerThread.join();

REQUIRE(waitResult == WAIT_OBJECT_0);
}

#endif

#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)

0 comments on commit 0fdb072

Please sign in to comment.