[*] Slight cleanup of the NT watcher

This commit is contained in:
Reece Wilson 2022-04-03 05:46:52 +01:00
parent 3650599064
commit 62e3490d9f

View File

@ -23,40 +23,18 @@ namespace Aurora::IO::FS
struct NTEvent : Loop::LSEvent
{
NTEvent(AuSPtr<NTWatchhandle> parent) : LSEvent(false, true /*[1]*/, true), parent_(parent)
{
}
~NTEvent()
{
}
NTEvent(AuSPtr<NTWatchhandle> parent);
bool IsSignaled() override;
Loop::ELoopSource GetType() override;
//[1] Functions such as GetOverlappedResult and the synchronization wait functions reset auto-reset events to the nonsignaled state. Therefore, you should use a manual reset event; if you use an auto-reset event, your application can stop responding if you wait for the operation to complete and then call GetOverlappedResult with the bWait parameter set to TRUE.
// - https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-overlapped
void OnPresleep() override;
bool OnTrigger(AuUInt handle) override;
private:
AuWPtr<NTWatchhandle> parent_;
};
bool NTEvent::IsSignaled()
{
return LSEvent::IsSignaled();
}
Loop::ELoopSource NTEvent::GetType()
{
return Loop::ELoopSource::eSourceFileWatcher;
}
struct NTWatchObject
{
OVERLAPPED ntOverlapped {};
@ -89,36 +67,8 @@ namespace Aurora::IO::FS
AuSPtr<UserWatchData> userData;
AuSPtr<NTWatchObject> watcher;
FILETIME lastFileTime {};
bool CheckRun()
{
FILETIME curFileTime {};
auto handle = CreateFileW(AuLocale::ConvertFromUTF8(strNormalizedPath).c_str(),
GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | (AuIOFS::DirExists(strNormalizedPath) ? FILE_FLAG_BACKUP_SEMANTICS : 0),
NULL);
if (handle == INVALID_HANDLE_VALUE)
{
return false;
}
if (!GetFileTime(handle, NULL, NULL, &curFileTime))
{
CloseHandle(handle);
return true;
}
bool ret = !((this->lastFileTime.dwLowDateTime == curFileTime.dwLowDateTime) && (this->lastFileTime.dwHighDateTime == curFileTime.dwHighDateTime));
this->lastFileTime = curFileTime;
CloseHandle(handle);
return ret;
}
bool CheckRun();
};
struct NTWatcher : IWatcher
@ -138,23 +88,41 @@ namespace Aurora::IO::FS
private:
// we don't expect to yield to another thread to hit our vector
// our apis are generally expected to be called from a single thread
// so, it's not like we expect to sync against a worker or anything,
// this is pre auroxtl object object, so, fuck it, this is just a dword or so
// these apis are generally expected to be called from a single thread
AuThreadPrimitives::SpinLock spinlock_;
AuBST<AuString, AuWPtr<NTWatchObject>> daObjects_;
AuBST<AuString, AuWPtr<NTWatchObject>> cachedWatchers_;
public:
AuList<WatchedFile> triggered_;
AuSPtr<NTEvent> ntEvent_;
AuList<WatchedFile> triggered_;
AuSPtr<NTEvent> ntEvent_;
AuList<NTCachedPath> paths_;
private:
AuSPtr<NTWatchhandle> watchHandler_;
};
// NT Event class
NTEvent::NTEvent(AuSPtr<NTWatchhandle> parent) : LSEvent(false, true /*[1]*/, true), parent_(parent)
{
// [1] Functions such as GetOverlappedResult and the synchronization wait functions reset auto-reset events to the nonsignaled state. Therefore, you should use a manual reset event; if you use an auto-reset event, your application can stop responding if you wait for the operation to complete and then call GetOverlappedResult with the bWait parameter set to TRUE.
// - https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-overlapped
}
bool NTEvent::IsSignaled()
{
return LSEvent::IsSignaled();
}
Loop::ELoopSource NTEvent::GetType()
{
return Loop::ELoopSource::eSourceFileWatcher;
}
// Event type is latching, not a type of binary semaphore
// Resignal if work is still available
void NTEvent::OnPresleep()
{
if (auto watcher = parent_.lock())
@ -166,6 +134,10 @@ namespace Aurora::IO::FS
}
}
// Filter the latching events signal state once
// based on the availability of work
// ...
/// also, this is kind of like a tick
bool NTEvent::OnTrigger(AuUInt handle)
{
bool ret {};
@ -182,25 +154,45 @@ namespace Aurora::IO::FS
return ret;
}
bool NTWatcher::GoBrr()
{
AU_LOCK_GUARD(this->spinlock_);
// TODO: purge if lock fails
for (auto &[a, watcher] : this->daObjects_)
// NT Cached Path / File Watch Item class
bool NTCachedPath::CheckRun()
{
FILETIME curFileTime {};
HANDLE hFile;
hFile = CreateFileW(AuLocale::ConvertFromUTF8(this->strNormalizedPath).c_str(),
GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | (AuIOFS::DirExists(this->strNormalizedPath) ? FILE_FLAG_BACKUP_SEMANTICS : 0),
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
if (auto test = watcher.lock())
{
if (!test->CheckBroken())
{
this->ntEvent_->Set();
}
}
return false;
}
return this->triggered_.size();
if (!GetFileTime(hFile, NULL, NULL, &curFileTime))
{
CloseHandle(hFile);
return true;
}
bool ret = !((this->lastFileTime.dwLowDateTime == curFileTime.dwLowDateTime) &&
(this->lastFileTime.dwHighDateTime == curFileTime.dwHighDateTime));
this->lastFileTime = curFileTime;
CloseHandle(hFile);
return ret;
}
// Directory Watcher Object
bool NTWatchObject::Init(const AuString &usrStr)
{
AuCtorCode_t code;
@ -218,14 +210,12 @@ namespace Aurora::IO::FS
this->strBaseDir.pop_back();
}
bool isDir = true;
this->hFileHandle = CreateFileW(AuLocale::ConvertFromUTF8(this->strBaseDir.c_str()).c_str(),
GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED | (isDir ? FILE_FLAG_BACKUP_SEMANTICS : 0),
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (this->hFileHandle == INVALID_HANDLE_VALUE)
{
@ -238,13 +228,12 @@ namespace Aurora::IO::FS
return ScheduleOnce();
}
// Request OP lock
bool NTWatchObject::ScheduleOnce()
{
bool firstTime = !this->ntOverlapped.hEvent;
this->ntOverlapped.hEvent = (HANDLE)this->parent->ntEvent_->GetHandle();
// REQUEST_OPLOCK_INPUT_FLAG_ACK
REQUEST_OPLOCK_INPUT_BUFFER input
{
REQUEST_OPLOCK_CURRENT_VERSION,
@ -256,7 +245,6 @@ namespace Aurora::IO::FS
DWORD bytesReturned;
if (DeviceIoControl(this->hFileHandle, FSCTL_REQUEST_OPLOCK, &input, sizeof(input), &whoAsked_, sizeof(whoAsked_), &bytesReturned, &this->ntOverlapped))
{
// doesn't count...
this->CheckBroken();
}
else
@ -277,11 +265,11 @@ namespace Aurora::IO::FS
if (this->bBroken || GetOverlappedResult(this->hFileHandle, &this->ntOverlapped, &bytesTransferred, false))
{
bool bSuccess {true};
this->bBroken = true;
bool bAnyTriggered {};
for (auto &filesWatched : this->parent->paths_)
{
if (!AuStartsWith(filesWatched.strNormalizedPath, this->strBaseDir))
@ -311,16 +299,14 @@ namespace Aurora::IO::FS
bAnyTriggered = true;
}
bool success {true};
// if (bAnyTriggered)
if (this->whoAsked_.Flags & REQUEST_OPLOCK_OUTPUT_FLAG_ACK_REQUIRED)
{
success = ScheduleOnce();
this->whoAsked_.Flags = 0;
bSuccess = ScheduleOnce();
}
this->bBroken = false;
return success;
return bSuccess;
}
return true;
@ -332,13 +318,16 @@ namespace Aurora::IO::FS
AuWin32CloseHandle(this->hFileHandle);
}
// NT Watcher - primary interface implementation
bool NTWatcher::AddWatch(const WatchedFile &file)
{
AuCtorCode_t code;
AuSPtr<NTWatchObject> watcher;
NTCachedPath cached;
AU_LOCK_GUARD(this->spinlock_);
AuSPtr<NTWatchObject> watcher;
AuString translated;
if (!AuIOFS::NormalizePath(translated, file.path))
@ -347,7 +336,6 @@ namespace Aurora::IO::FS
}
// Create the NT path in the midst of path normalization
NTCachedPath cached;
cached.strNormalizedPath = AuTryConstruct<AuString>(code, translated);
if (!code)
{
@ -363,7 +351,8 @@ namespace Aurora::IO::FS
cached.userData = file.userData;
// update the last edited timestamp
// Update the last edited timestamp as a frame of reference for fast compare of
// directory entries upon lock breakage
cached.CheckRun();
// Continue normalizing the parent path
@ -373,9 +362,9 @@ namespace Aurora::IO::FS
AuIOFS::GetDirectoryFromPath(translated, translated);
}
//
// Attempt to locate a watcher for the directoy
for (const auto &[base, wptr] : this->daObjects_)
for (const auto &[base, wptr] : this->cachedWatchers_)
{
if (AuStartsWith(translated, base))
{
@ -384,6 +373,8 @@ namespace Aurora::IO::FS
}
}
// Create one, if missing
if (!watcher)
{
auto item = AuMakeShared<NTWatchObject>();
@ -406,7 +397,7 @@ namespace Aurora::IO::FS
watcher = item;
if (!AuTryInsert(this->daObjects_, AuMove(translated), AuMove(item)))
if (!AuTryInsert(this->cachedWatchers_, AuMove(translated), AuMove(item)))
{
return false;
}
@ -414,21 +405,46 @@ namespace Aurora::IO::FS
cached.watcher = watcher;
// Append path / user private pair alongside the watcher for book keeping
if (!AuTryInsert(this->paths_, AuMove(cached)))
{
return false;
}
// Done
return true;
}
// Post-event trigger processor and filterer
bool NTWatcher::GoBrr()
{
AU_LOCK_GUARD(this->spinlock_);
for (auto &[dir, weakWatcher] : this->cachedWatchers_)
{
if (auto watcher = weakWatcher.lock())
{
if (!watcher->CheckBroken())
{
this->ntEvent_->Set();
}
}
}
return this->triggered_.size();
}
bool NTWatcher::RemoveByName(const AuString &path)
{
AU_LOCK_GUARD(this->spinlock_);
AuString strNormalized;
AuIOFS::NormalizePath(strNormalized, path);
if (!AuIOFS::NormalizePath(strNormalized, path))
{
return false;
}
return AuRemoveIf(this->paths_, [&](const NTCachedPath &object) -> bool
{
@ -448,12 +464,7 @@ namespace Aurora::IO::FS
return AuRemoveIf(this->paths_, [&](const NTCachedPath &object) -> bool
{
if (file == object.userData)
{
return true;
}
return false;
return file == object.userData;
});
}