[*] Slight cleanup of the NT watcher
This commit is contained in:
parent
3650599064
commit
62e3490d9f
@ -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 {};
|
||||
@ -90,35 +68,7 @@ namespace Aurora::IO::FS
|
||||
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<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()
|
||||
{
|
||||
if (auto test = watcher.lock())
|
||||
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 (!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;
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user