Refactor WindowsFileSystemWatcher to properly handle file modifications

This commit is contained in:
Wojtek Figat
2024-06-02 00:51:11 +02:00
parent 6dacf9e1f1
commit 2492d0b38f
4 changed files with 116 additions and 109 deletions

View File

@@ -11,10 +11,11 @@
/// </summary> /// </summary>
enum class FileSystemAction enum class FileSystemAction
{ {
Unknown, Unknown = 0,
Create, Create = 1,
Delete, Delete = 2,
Modify, Modify = 4,
Rename = 8,
}; };
/// <summary> /// <summary>

View File

@@ -35,16 +35,18 @@ void DirectoryWatchCallback( ConstFSEventStreamRef StreamRef, void* FileWatcherP
{ {
action = FileSystemAction::Create; action = FileSystemAction::Create;
} }
if (renamed)
if (renamed || modified)
{ {
action = FileSystemAction::Delete; action = FileSystemAction::Rename;
} }
if (rmodified)
if (removed)
{ {
action = FileSystemAction::Modify; action = FileSystemAction::Modify;
} }
if (removed)
{
action = FileSystemAction::Delete;
}
const String resolvedPath = AppleUtils::ToString((CFStringRef)CFArrayGetValueAtIndex(EventPathArray,EventIndex)); const String resolvedPath = AppleUtils::ToString((CFStringRef)CFArrayGetValueAtIndex(EventPathArray,EventIndex));

View File

@@ -11,117 +11,45 @@
#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Array.h"
#include "../Win32/IncludeWindowsHeaders.h" #include "../Win32/IncludeWindowsHeaders.h"
BOOL RefreshWatch(WindowsFileSystemWatcher* watcher);
namespace FileSystemWatchers namespace FileSystemWatchers
{ {
CriticalSection Locker; CriticalSection Locker;
Array<WindowsFileSystemWatcher*, InlinedAllocation<16>> Watchers; Array<WindowsFileSystemWatcher*, InlinedAllocation<16>> Watchers;
Win32Thread* Thread = nullptr; Win32Thread* Thread = nullptr;
Windows::HANDLE IoHandle = INVALID_HANDLE_VALUE;
bool ThreadActive; bool ThreadActive;
int32 Run() int32 Run()
{ {
DWORD numBytes = 0;
LPOVERLAPPED overlapped;
ULONG_PTR compKey = 0;
while (ThreadActive) while (ThreadActive)
{ {
SleepEx(INFINITE, true); if (GetQueuedCompletionStatus(IoHandle, &numBytes, &compKey, &overlapped, INFINITE) && overlapped && numBytes != 0)
{
// Send further to the specific watcher
Locker.Lock();
for (auto watcher : Watchers)
{
if ((OVERLAPPED*)&watcher->Overlapped == overlapped)
{
watcher->NotificationCompletion();
break;
}
}
Locker.Unlock();
}
} }
return 0; return 0;
} }
static void CALLBACK StopProc(ULONG_PTR arg)
{
ThreadActive = false;
}
static void CALLBACK AddDirectoryProc(ULONG_PTR arg)
{
const auto watcher = (FileSystemWatcher*)arg;
RefreshWatch(watcher);
}
}; };
VOID CALLBACK NotificationCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
{
auto watcher = (FileSystemWatcher*)lpOverlapped->hEvent;
if (dwErrorCode == ERROR_OPERATION_ABORTED ||
dwNumberOfBytesTransfered <= 0 ||
!watcher)
{
return;
}
// Swap buffers
watcher->CurrentBuffer = (watcher->CurrentBuffer + 1) % 2;
// Get the new read issued as fast as possible
if (!watcher->StopNow)
{
RefreshWatch(watcher);
}
// Process notifications
auto notify = (FILE_NOTIFY_INFORMATION*)watcher->Buffer[(watcher->CurrentBuffer + 1) % 2];
do
{
// Convert action type
auto action = FileSystemAction::Unknown;
switch (notify->Action)
{
case FILE_ACTION_RENAMED_NEW_NAME:
case FILE_ACTION_ADDED:
action = FileSystemAction::Create;
break;
case FILE_ACTION_RENAMED_OLD_NAME:
case FILE_ACTION_REMOVED:
action = FileSystemAction::Delete;
break;
case FILE_ACTION_MODIFIED:
action = FileSystemAction::Modify;
break;
default:
action = FileSystemAction::Unknown;
break;
}
if (action != FileSystemAction::Unknown)
{
// Build path
String path(notify->FileName, notify->FileNameLength / sizeof(WCHAR));
path = watcher->Directory / path;
// Send event
watcher->OnEvent(path, action);
}
// Move to the next notify
notify = (FILE_NOTIFY_INFORMATION*)((byte*)notify + notify->NextEntryOffset);
} while (notify->NextEntryOffset != 0);
}
// Refreshes the directory monitoring
BOOL RefreshWatch(WindowsFileSystemWatcher* watcher)
{
DWORD dwBytesReturned = 0;
return ReadDirectoryChangesW(
watcher->DirectoryHandle,
watcher->Buffer[watcher->CurrentBuffer],
FileSystemWatcher::BufferSize,
watcher->WithSubDirs ? TRUE : FALSE,
FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_FILE_NAME,
&dwBytesReturned,
(OVERLAPPED*)&watcher->Overlapped,
NotificationCompletion
);
}
WindowsFileSystemWatcher::WindowsFileSystemWatcher(const String& directory, bool withSubDirs) WindowsFileSystemWatcher::WindowsFileSystemWatcher(const String& directory, bool withSubDirs)
: FileSystemWatcherBase(directory, withSubDirs) : FileSystemWatcherBase(directory, withSubDirs)
, StopNow(false)
, CurrentBuffer(0)
{ {
// Setup // Setup
Platform::MemoryClear(&Overlapped, sizeof(Overlapped)); Platform::MemoryClear(&Overlapped, sizeof(Overlapped));
((OVERLAPPED&)Overlapped).hEvent = this;
// Create directory handle for events handling // Create directory handle for events handling
DirectoryHandle = CreateFileW( DirectoryHandle = CreateFileW(
@@ -144,25 +72,26 @@ WindowsFileSystemWatcher::WindowsFileSystemWatcher(const String& directory, bool
FileSystemWatchers::Watchers.Add(this); FileSystemWatchers::Watchers.Add(this);
if (!FileSystemWatchers::Thread) if (!FileSystemWatchers::Thread)
{ {
FileSystemWatchers::IoHandle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);
FileSystemWatchers::ThreadActive = true; FileSystemWatchers::ThreadActive = true;
FileSystemWatchers::Thread = ThreadSpawner::Start(FileSystemWatchers::Run, TEXT("File System Watchers"), ThreadPriority::BelowNormal); FileSystemWatchers::Thread = ThreadSpawner::Start(FileSystemWatchers::Run, TEXT("File System Watchers"), ThreadPriority::BelowNormal);
} }
CreateIoCompletionPort(DirectoryHandle, FileSystemWatchers::IoHandle, 0, 1);
FileSystemWatchers::Locker.Unlock(); FileSystemWatchers::Locker.Unlock();
// Issue the first read // Initialize filesystem events tracking
QueueUserAPC(FileSystemWatchers::AddDirectoryProc, FileSystemWatchers::Thread->GetHandle(), (ULONG_PTR)this); ReadDirectoryChanges();
} }
WindowsFileSystemWatcher::~WindowsFileSystemWatcher() WindowsFileSystemWatcher::~WindowsFileSystemWatcher()
{ {
FileSystemWatchers::Locker.Lock(); FileSystemWatchers::Locker.Lock();
FileSystemWatchers::Watchers.Remove(this); FileSystemWatchers::Watchers.Remove(this);
StopNow = true;
FileSystemWatchers::Locker.Unlock(); FileSystemWatchers::Locker.Unlock();
if (DirectoryHandle != INVALID_HANDLE_VALUE) if (DirectoryHandle != INVALID_HANDLE_VALUE)
{ {
StopNow = true;
#if WINVER >= 0x600 #if WINVER >= 0x600
CancelIoEx(DirectoryHandle, (OVERLAPPED*)&Overlapped); CancelIoEx(DirectoryHandle, (OVERLAPPED*)&Overlapped);
#else #else
@@ -180,12 +109,85 @@ WindowsFileSystemWatcher::~WindowsFileSystemWatcher()
if (FileSystemWatchers::Watchers.IsEmpty() && FileSystemWatchers::Thread) if (FileSystemWatchers::Watchers.IsEmpty() && FileSystemWatchers::Thread)
{ {
FileSystemWatchers::ThreadActive = false; FileSystemWatchers::ThreadActive = false;
QueueUserAPC(FileSystemWatchers::StopProc, FileSystemWatchers::Thread->GetHandle(), 0); FileSystemWatchers::Locker.Unlock();
PostQueuedCompletionStatus(FileSystemWatchers::IoHandle, 0, 0, nullptr);
FileSystemWatchers::Thread->Join(); FileSystemWatchers::Thread->Join();
FileSystemWatchers::Locker.Lock();
Delete(FileSystemWatchers::Thread); Delete(FileSystemWatchers::Thread);
FileSystemWatchers::Thread = nullptr; FileSystemWatchers::Thread = nullptr;
CloseHandle(FileSystemWatchers::IoHandle);
FileSystemWatchers::IoHandle = INVALID_HANDLE_VALUE;
} }
FileSystemWatchers::Locker.Unlock(); FileSystemWatchers::Locker.Unlock();
} }
void WindowsFileSystemWatcher::ReadDirectoryChanges()
{
BOOL result = ReadDirectoryChangesW(
DirectoryHandle,
Buffer,
BufferSize,
WithSubDirs ? TRUE : FALSE,
FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_FILE_NAME,
nullptr,
(OVERLAPPED*)&Overlapped,
nullptr
);
if (!result)
{
LOG_WIN32_LAST_ERROR;
Sleep(1);
}
}
void WindowsFileSystemWatcher::NotificationCompletion()
{
ScopeLock lock(Locker);
// Process notifications
auto notify = (FILE_NOTIFY_INFORMATION*)Buffer;
do
{
// Convert action type
FileSystemAction action;
switch (notify->Action)
{
case FILE_ACTION_RENAMED_NEW_NAME:
case FILE_ACTION_RENAMED_OLD_NAME:
action = FileSystemAction::Rename;
break;
case FILE_ACTION_ADDED:
action = FileSystemAction::Create;
break;
case FILE_ACTION_REMOVED:
action = FileSystemAction::Delete;
break;
case FILE_ACTION_MODIFIED:
action = FileSystemAction::Modify;
break;
default:
action = FileSystemAction::Unknown;
break;
}
if (action != FileSystemAction::Unknown)
{
// Build path
String path(notify->FileName, notify->FileNameLength / sizeof(WCHAR));
path = Directory / path;
// Send event
OnEvent(path, action);
}
// Move to the next notify
notify = (FILE_NOTIFY_INFORMATION*)((byte*)notify + notify->NextEntryOffset);
} while (notify->NextEntryOffset != 0);
// Get the new read issued as fast as possible
if (!StopNow)
{
ReadDirectoryChanges();
}
}
#endif #endif

View File

@@ -13,7 +13,6 @@
class FLAXENGINE_API WindowsFileSystemWatcher : public FileSystemWatcherBase class FLAXENGINE_API WindowsFileSystemWatcher : public FileSystemWatcherBase
{ {
public: public:
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WindowsFileSystemWatcher"/> class. /// Initializes a new instance of the <see cref="WindowsFileSystemWatcher"/> class.
/// </summary> /// </summary>
@@ -27,13 +26,16 @@ public:
~WindowsFileSystemWatcher(); ~WindowsFileSystemWatcher();
public: public:
Windows::OVERLAPPED Overlapped; Windows::OVERLAPPED Overlapped;
Windows::HANDLE DirectoryHandle; Windows::HANDLE DirectoryHandle;
bool StopNow; Win32Thread* Thread = nullptr;
int32 CurrentBuffer; Win32CriticalSection Locker;
bool StopNow = false;
static const int32 BufferSize = 32 * 1024; static const int32 BufferSize = 32 * 1024;
byte Buffer[2][BufferSize]; alignas(Windows::DWORD) byte Buffer[BufferSize];
void ReadDirectoryChanges();
void NotificationCompletion();
}; };
#endif #endif