[+] Linux Watcher

[*] Expand watcher API -> Breaking NT
[*] Reexpand loop queue API -> Breaking NT
[*] Linux CPUInfo clean up
[*] Bug fix: mkdir should set execute flag... because directories are special
[*] Refactor: Cleanup base64
[*] Bug fix: UNIX path normalization
[*] Bug fix: missing O_CREAT flag (au auto-creates)
[*] Normalize line endings
This commit is contained in:
Reece Wilson 2022-04-10 16:40:49 +01:00
parent cf219eabaa
commit 3defb1bb14
19 changed files with 1041 additions and 424 deletions

View File

@ -1,146 +1,146 @@
#pragma once #pragma once
namespace Aurora::Async namespace Aurora::Async
{ {
#pragma region EASE_OF_READING #pragma region EASE_OF_READING
struct BasicWorkStdFunc : IWorkItemHandler struct BasicWorkStdFunc : IWorkItemHandler
{ {
AuFunction<void()> callback; AuFunction<void()> callback;
AuFunction<void()> shutdown; // error AuFunction<void()> shutdown; // error
BasicWorkStdFunc(AuFunction<void()> &&callback, AuFunction<void()> &&shutdown) : callback(std::move(callback)), shutdown(std::move(shutdown)) BasicWorkStdFunc(AuFunction<void()> &&callback, AuFunction<void()> &&shutdown) : callback(std::move(callback)), shutdown(std::move(shutdown))
{} {}
BasicWorkStdFunc(AuFunction<void()> &&callback) : callback(std::move(callback)) BasicWorkStdFunc(AuFunction<void()> &&callback) : callback(std::move(callback))
{} {}
BasicWorkStdFunc(const AuFunction<void()> &callback) : callback(callback) BasicWorkStdFunc(const AuFunction<void()> &callback) : callback(callback)
{} {}
BasicWorkStdFunc(const AuFunction<void()> &callback, const AuFunction<void()> &shutdown) : callback(callback), shutdown(shutdown) BasicWorkStdFunc(const AuFunction<void()> &callback, const AuFunction<void()> &shutdown) : callback(callback), shutdown(shutdown)
{} {}
private: private:
#if !defined(_CPPSHARP) #if !defined(_CPPSHARP)
void DispatchFrame(ProcessInfo &info) override void DispatchFrame(ProcessInfo &info) override
{ {
try try
{ {
callback(); callback();
} }
catch (...) catch (...)
{ {
Debug::PrintError(); Debug::PrintError();
} }
} }
void Shutdown() override void Shutdown() override
{ {
try try
{ {
if (shutdown) if (shutdown)
{ {
shutdown(); shutdown();
} }
} }
catch (...) catch (...)
{ {
Debug::PrintError(); Debug::PrintError();
} }
} }
#endif #endif
}; };
/// @hideinitializer /// @hideinitializer
template<typename Frame_t = AuFunction<void()>, typename Cleanup_t = AuFunction<void()>> template<typename Frame_t = AuFunction<void()>, typename Cleanup_t = AuFunction<void()>>
struct WorkItemCallable : IWorkItemHandler struct WorkItemCallable : IWorkItemHandler
{ {
Frame_t frame; Frame_t frame;
Cleanup_t cleanup; Cleanup_t cleanup;
private: private:
void DispatchFrame(ProcessInfo &info) override void DispatchFrame(ProcessInfo &info) override
{ {
if constexpr (AuIsBaseOfTemplate<AuFunction, Frame_t>::value) if constexpr (AuIsBaseOfTemplate<AuFunction, Frame_t>::value)
{ {
if (!frame) if (!frame)
{ {
info.type = IWorkItemHandler::EProcessNext::eFinished; info.type = IWorkItemHandler::EProcessNext::eFinished;
return; return;
} }
} }
frame(); frame();
info.type = IWorkItemHandler::EProcessNext::eFinished; info.type = IWorkItemHandler::EProcessNext::eFinished;
} }
void Shutdown() override void Shutdown() override
{ {
if constexpr (AuIsBaseOfTemplate<AuFunction, Cleanup_t>::value) if constexpr (AuIsBaseOfTemplate<AuFunction, Cleanup_t>::value)
{ {
if (!cleanup) if (!cleanup)
{ {
return; return;
} }
} }
cleanup(); cleanup();
} }
}; };
#define ASYNC_ERROR(exp) { if constexpr (AuIsSame_v<T, bool>) { SysPushErrorGen(exp); return {}; } else { throw AuString(exp); } } #define ASYNC_ERROR(exp) { if constexpr (AuIsSame_v<T, bool>) { SysPushErrorGen(exp); return {}; } else { throw AuString(exp); } }
#define ASYNC_FINISH { if constexpr (AuIsSame_v<T, bool>) { return true; } } #define ASYNC_FINISH { if constexpr (AuIsSame_v<T, bool>) { return true; } }
template<typename T = void, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)> template<typename T = void, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)>
static AuFunction<T(Args&&...)> TranslateAsyncFunctionToDispatcherWithThread(WorkerId_t id, AuFunction<void(Args...)> func) static AuFunction<T(Args&&...)> TranslateAsyncFunctionToDispatcherWithThread(WorkerId_t id, AuFunction<void(Args...)> func)
{ {
if (!func) return {}; if (!func) return {};
return [=](Args&&... in) -> T return [=](Args&&... in) -> T
{ {
auto work = AuMakeShared<BasicWorkStdFunc>([=]() -> void { auto work = AuMakeShared<BasicWorkStdFunc>([=]() -> void {
func(in...); func(in...);
}); });
if (!work) ASYNC_ERROR("can't dispatch async call. out of memory"); if (!work) ASYNC_ERROR("can't dispatch async call. out of memory");
auto workItem = NewWorkItem(id, work); auto workItem = NewWorkItem(id, work);
if (!workItem) ASYNC_ERROR("can't dispatch async call. out of memory"); if (!workItem) ASYNC_ERROR("can't dispatch async call. out of memory");
workItem->Dispatch(); workItem->Dispatch();
ASYNC_FINISH; ASYNC_FINISH;
}; };
} }
/// Async app only /// Async app only
template<typename T = void, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)> template<typename T = void, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)>
static AuFunction<T(Args&&...)> TranslateAsyncFunctionToDispatcher(AuFunction<void(Args...)> func) static AuFunction<T(Args&&...)> TranslateAsyncFunctionToDispatcher(AuFunction<void(Args...)> func)
{ {
return TranslateAsyncFunctionToDispatcherWithThread(GetAsyncApp()->GetCurrentThread(), func); return TranslateAsyncFunctionToDispatcherWithThread(GetAsyncApp()->GetCurrentThread(), func);
} }
/// Async app only /// Async app only
template<typename B = void, typename T, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)> template<typename B = void, typename T, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)>
static AuFunction<T(AuFunction<void(const B&)>, Args...)> TranslateAsyncReturnableFunctionToDispatcherWithThread(WorkerId_t id, AuFunction<B(Args...)> func) static AuFunction<T(AuFunction<void(const B&)>, Args...)> TranslateAsyncReturnableFunctionToDispatcherWithThread(WorkerId_t id, AuFunction<B(Args...)> func)
{ {
return [=](AuFunction<T(const B&)> callback, Args... in) -> T return [=](AuFunction<T(const B&)> callback, Args... in) -> T
{ {
auto work = AuMakeShared<WorkPairImpl<AVoid, B>>(); auto work = AuMakeShared<WorkPairImpl<AVoid, B>>();
if (!work) ASYNC_ERROR("can't dispatch async call; out of memory"); if (!work) ASYNC_ERROR("can't dispatch async call; out of memory");
work.task.onProcess = [=](const AVoid &) -> B work.task.onProcess = [=](const AVoid &) -> B
{ {
if (!func) return B{}; if (!func) return B{};
return func(in...); return func(in...);
}; };
work.callback.onSuccess = [=](const AVoid &, const B &ret) work.callback.onSuccess = [=](const AVoid &, const B &ret)
{ {
callback(ret); callback(ret);
}; };
auto workItem = NewWorkItem(id, work); auto workItem = NewWorkItem(id, work);
if (!workItem) ASYNC_ERROR("can't dispatch async call; out of memory"); if (!workItem) ASYNC_ERROR("can't dispatch async call; out of memory");
workItem->Dispatch(); workItem->Dispatch();
ASYNC_FINISH; ASYNC_FINISH;
}; };
} }
#undef ASYNC_ERROR #undef ASYNC_ERROR
#undef ASYNC_FINISH #undef ASYNC_FINISH
#pragma endregion EASE_OF_READING #pragma endregion EASE_OF_READING
} }

View File

@ -19,17 +19,41 @@ namespace Aurora::IO::FS
AuSPtr<UserWatchData> userData; AuSPtr<UserWatchData> userData;
AuString path; AuString path;
}; };
AUE_DEFINE(EWatchEvent,
(
eSelfModify,
eSelfDelete,
eFileModify,
eFileDelete,
eFileCreate
));
struct WatchRequest
{
WatchedFile watch;
// events are mere optimization hint. additional events may be provided, if available.
AuList<EWatchEvent> events;
};
struct WatchEvent
{
EWatchEvent event;
WatchedFile watch;
AuString file;
};
struct IWatcher struct IWatcher
{ {
virtual bool AddWatch(const WatchedFile &file) = 0; virtual bool AddWatch(const WatchRequest &file) = 0;
virtual bool RemoveByName(const AuString &path) = 0; virtual bool RemoveByName(const AuString &path) = 0;
virtual bool RemoveByPrivateContext(const AuSPtr<UserWatchData> &file) = 0; virtual bool RemoveByPrivateContext(const AuSPtr<UserWatchData> &file) = 0;
virtual AuSPtr<Loop::ILoopSource> AsLoopSource() = 0; virtual AuSPtr<Loop::ILoopSource> AsLoopSource() = 0;
virtual AuList<WatchedFile> QueryUpdates() = 0; virtual AuList<WatchEvent> QueryUpdates() = 0;
}; };
AUKN_SHARED_API(NewWatcher, IWatcher); AUKN_SHARED_API(NewWatcher, IWatcher);

View File

@ -50,6 +50,11 @@ namespace Aurora::Loop
/** /**
* @brief Updates the OS watchdog list cache concept after Source[Remove/Add[WithTimeout]] * @brief Updates the OS watchdog list cache concept after Source[Remove/Add[WithTimeout]]
* Commits may occur while another thread is waiting on the loop queue.
* In those circumstances, they are internally preempted and rescheduled.
* WaitAnd has undefined behaviour across MT commit. Linux *might* work.
* NT wont until an APC-as-a-preempt-signal hack is implemented.
* Rare edge case for all i care - it'll remain a blocking edge case for.now.
* @return * @return
* @note thread safe * @note thread safe
*/ */
@ -103,35 +108,48 @@ namespace Aurora::Loop
/** /**
* @brief Nonblocking wait-any for all objects in the loop queue * @brief Nonblocking check for alert objects in the loop queue
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
* @return * @return
* @note thread safe / nonblocking | can be called alongside any other function marked as such * @note thread safe / nonblocking | can be called alongside any other function marked as such
*/ */
virtual bool IsSignaled() = 0; virtual bool IsSignaledPeek() = 0;
/**
* @brief Nonblocking wait-any for all objects in the loop queue
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
* @warning (technically unsafe, use alloc-unsafe extended version to acknowledge unlocked sources)
* @return
* @note thread safe / nonblocking | can be called alongside any other function marked as such
*/
virtual AuUInt32 PumpNonblocking() = 0;
virtual AuList<AuSPtr<ILoopSource>> PumpNonblockingEx() = 0;
/** /**
* @brief Waits on all the submitted loop sources until they are all complete or until the timeout has finished. * @brief Waits on all the submitted loop sources until they are all complete or until the timeout has finished.
* Note: the completion of another Wait[All/Any[Ex]] call may result in a * Note: the completion of another Wait[All/Any[Ex]] call may result in a
* @param timeout timeout in MS, zero = indefinite
* @return * @return
* @warning (may yield to ILoopSourceSubscriber delegate on the current context) * @warning (may yield to ILoopSourceSubscriber delegate on the current context)
* @warning (thread safety is limited blocking callers of the object) * @warning (thread safety might be limited to blocking callers of the object)
*/ */
virtual bool WaitAll (AuUInt32 timeout = 0) = 0; virtual bool WaitAll (AuUInt32 timeout = 0) = 0;
/** /**
* @brief Waits on all the loop sources until at least one is signaled. * @brief Waits on all the loop sources until at least one is signaled.
* Additional work may be scheduled on other threads. * @param timeout timeout in MS, zero = indefinite.
* @param timeout * Use IsSignaledPump for nonblocking.
* @return * @return
* @warning (may yield to ILoopSourceSubscriber delegate on the current context) * @warning (may yield to ILoopSourceSubscriber delegate on the current context)
* @warning (technically unsafe, use alloc-unsafe extended version to acknowledge unlocked sources)
* @note thread safe | can be called alongside any other function marked as such * @note thread safe | can be called alongside any other function marked as such
*/ */
virtual AuUInt32 WaitAny (AuUInt32 timeout = 0) = 0; virtual AuUInt32 WaitAny (AuUInt32 timeout = 0) = 0;
/** /**
* @brief * @brief Waits on all the loop sources until at least one is signaled.
* @param timeout * @param timeout timeout in MS, zero = indefinite.
* Use IsSignaledPump for nonblocking.
* @return * @return
* @warning (may yield to ILoopSourceSubscriber delegate on the current context) * @warning (may yield to ILoopSourceSubscriber delegate on the current context)
* @note thread safe | can be called alongside any other function marked as such * @note thread safe | can be called alongside any other function marked as such

View File

@ -238,7 +238,11 @@ namespace Aurora::Async
{ {
if (state->rateLimiter.CheckExchangePass()) if (state->rateLimiter.CheckExchangePass())
{ {
bShouldTrySleepForKernel = asyncLoop->IsSignaled(); #if defined(AURORA_PLATFORM_WIN32)
bShouldTrySleepForKernel = asyncLoop->PumpNonblocking();
#else
bShouldTrySleepForKernel = asyncLoop->IsSignaledPeek();
#endif
} }
else else
{ {
@ -256,18 +260,28 @@ namespace Aurora::Async
{ {
AuThreading::ContextYield(); AuThreading::ContextYield();
block = false; block = false;
bShouldTrySleepForKernel = asyncLoop->IsSignaled(); #if defined(AURORA_PLATFORM_WIN32)
bShouldTrySleepForKernel = asyncLoop->PumpNonblocking();
#else
bShouldTrySleepForKernel = asyncLoop->IsSignaledPeek();
#endif
} }
else if (runMode == ERunMode::eEfficient) else if (runMode == ERunMode::eEfficient)
{ {
bShouldTrySleepForKernel = block; bShouldTrySleepForKernel = block;
if (!block) if (!block)
{ {
bShouldTrySleepForKernel = asyncLoop->IsSignaled(); bShouldTrySleepForKernel = asyncLoop->IsSignaledPeek();
} }
} }
if (bShouldTrySleepForKernel && asyncLoop->WaitAny(0)) if (bShouldTrySleepForKernel
// epoll and such like can be checked without read success. kevent works on availablity, not scheduling read like iosubmit, too.
// allow windows to atomically pump instead of wasting time buffering the primitives state
#if defined(AURORA_PLATFORM_WIN32)
&& asyncLoop->WaitAny(0)
#endif
)
{ {
PollInternal(block); PollInternal(block);
success = true; success = true;

View File

@ -76,9 +76,9 @@ namespace Aurora::HWInfo
bool bIsHyperThreaded {}; bool bIsHyperThreaded {};
for (auto &[threadId, coreStr] : cpuThreads) for (auto &[threadId, coreStr] : cpuThreads)
{ {
auto cpuId = CpuBitId(threadId); auto cpuId = CpuBitId(threadId);
auto coreID = ReadUInt(kBasePath + coreStr + "/topology/core_id"); auto coreID = ReadUInt(kBasePath + coreStr + "/topology/core_id");
auto cpuList = ReadString(kBasePath + coreStr +"/topology/core_cpus_list"); auto cpuList = ReadString(kBasePath + coreStr +"/topology/core_cpus_list");
auto isHVCore = AuStringContains(cpuList, ","); auto isHVCore = AuStringContains(cpuList, ",");
@ -111,25 +111,25 @@ namespace Aurora::HWInfo
children.Add(CpuBitId(word)); children.Add(CpuBitId(word));
} }
gCpuInfo.uCores++;
gCpuInfo.coreTopology.push_back(children); gCpuInfo.coreTopology.push_back(children);
gCpuInfo.threadTopology.push_back(children.lower); gCpuInfo.threadTopology.push_back(children.lower);
} }
gCpuInfo.uThreads++;
gCpuInfo.maskAllCores.Add(cpuId); gCpuInfo.maskAllCores.Add(cpuId);
} }
} }
void SetCpuTopologyLinux() void SetCpuTopologyLinux()
{ {
gCpuInfo.uSocket = 1;
gCpuInfo.uCores = 1;
gCpuInfo.uThreads = get_nprocs();
gCpuInfo.bMaskMTHalf = true;
SetCaches(); SetCaches();
SetCpuA(); SetCpuA();
gCpuInfo.uCores = gCpuInfo.coreTopology.size(); gCpuInfo.uSocket = AuMax(gCpuInfo.uSocket, AuUInt8(1));
gCpuInfo.uCores = AuMax(gCpuInfo.uCores, AuUInt8(1));
gCpuInfo.uThreads = gCpuInfo.uThreads ?
gCpuInfo.uThreads :
get_nprocs();
} }
} }

View File

@ -25,7 +25,7 @@ namespace Aurora::IO::FS
{ {
bool _MkDir(const AuString &path) bool _MkDir(const AuString &path)
{ {
return mkdir(path.c_str(), 0760) == 0; return mkdir(path.c_str(), 0775) == 0;
} }
AUKN_SYM bool FilesInDirectory(const AuString &string, AuList<AuString> &files) AUKN_SYM bool FilesInDirectory(const AuString &string, AuList<AuString> &files)
@ -56,7 +56,7 @@ namespace Aurora::IO::FS
return false; return false;
} }
return false; return true;
} }
AUKN_SYM bool ReadFile(const AuString &path, AuByteBuffer &buffer) AUKN_SYM bool ReadFile(const AuString &path, AuByteBuffer &buffer)

View File

@ -87,6 +87,10 @@ namespace Aurora::IO::FS
{ {
result += kDoublePathSplitter; result += kDoublePathSplitter;
} }
else if (buffer.size() && buffer[0] == kPathSplitter)
{
result += kPathSplitter;
}
/** /**
Technically, UTF-8 strings may contain the "NUL" byte. Technically, UTF-8 strings may contain the "NUL" byte.

View File

@ -90,7 +90,11 @@ namespace Aurora::IO::FS
((!isFile) && (end))) ((!isFile) && (end)))
{ {
auto subpath = end ? cpath : AuString(cpath.begin(), cpath.begin() + i); auto subpath = end ? cpath : AuString(cpath.begin(), cpath.begin() + i);
if (subpath.empty())
{
continue;
}
if (DirExists(subpath)) if (DirExists(subpath))
{ {
continue; continue;

View File

@ -303,11 +303,12 @@ namespace Aurora::IO::FS
} }
auto fileHandle = open(pathex.c_str(), auto fileHandle = open(pathex.c_str(),
openMode == EFileOpenMode::eRead ? O_RDONLY : O_RDWR); openMode == EFileOpenMode::eRead ? O_RDONLY : (O_RDWR | O_CREAT),
0644);
if (fileHandle < 0) if (fileHandle < 0)
{ {
SysPushErrorIO("Couldn't open file: {}", path); SysPushErrorIO("Couldn't open file: {} ({}) {}", path, pathex, errno);
return nullptr; return nullptr;
} }

384
Source/IO/FS/Watcher.Linux.cpp Executable file
View File

@ -0,0 +1,384 @@
/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: Watcher.Linux.cpp
Date: 2022-4-10
Author: Reece
***/
#include <Source/RuntimeInternal.hpp>
#include "FS.hpp"
#include "Watcher.Linux.hpp"
#include <sys/inotify.h>
#include <Source/Loop/LSHandle.hpp>
namespace Aurora::IO::FS
{
struct LinuxWatcherHandle : public Loop::LSHandle
{
LinuxWatcherHandle(int handle) : Loop::LSHandle(AuUInt(handle))
{}
virtual Loop::ELoopSource GetType() override;
};
Loop::ELoopSource LinuxWatcherHandle::GetType()
{
return Loop::ELoopSource::eSourceFileWatcher;
}
LinuxWatcher::~LinuxWatcher()
{
Deinit();
}
bool LinuxWatcher::AddWatch(const WatchRequest &request)
{
AuCtorCode_t code;
bool bIsDirectory {};
AuUInt32 mask {};
UnixCachedPath cached;
auto &file = request.watch;
AU_LOCK_GUARD(this->spinlock_);
if (this->inotifyHandle_ == -1)
{
SysPushErrorUninitialized();
return false;
}
AuString translated;
if (!AuIOFS::NormalizePath(translated, file.path))
{
SysPushErrorIO();
translated = file.path;
}
// Create the NT path in the midst of path normalization
cached.strNormalizedPath = AuTryConstruct<AuString>(code, translated);
if (!code)
{
SysPushErrorMem();
return false;
}
cached.strTheCakeIsALie = AuTryConstruct<AuString>(code, file.path);
if (!code)
{
SysPushErrorMem();
return false;
}
cached.userData = file.userData;
if (AuIOFS::DirExists(translated))
{
bIsDirectory = true;
}
mask |= IN_ATTRIB;
if (bIsDirectory)
{
mask |= IN_MOVE_SELF;
if (AuExists(request.events, EWatchEvent::eFileCreate))
{
mask |= IN_CREATE | IN_MOVED_TO /*may as well. easter egg.*/;
}
if (AuExists(request.events, EWatchEvent::eFileDelete))
{
mask |= IN_DELETE | IN_MOVED_FROM;
}
}
if (AuExists(request.events, EWatchEvent::eFileModify) || !bIsDirectory)
{
mask |= IN_MODIFY | IN_CLOSE_WRITE;
}
//if (AuExists(request.events, EWatchEvent::eSelfDelete))
{
mask |= IN_DELETE_SELF | IN_MOVE_SELF;
}
mask = IN_ALL_EVENTS;
int ret = inotify_add_watch(this->inotifyHandle_, cached.strNormalizedPath.c_str(), mask);
if (ret == -1)
{
// TODO: push error
return false;
}
cached.watcherWd = ret;
if (!AuTryInsert(this->paths_, cached))
{
SysPushErrorMem();
auto ree = inotify_rm_watch(this->inotifyHandle_, ret);
if (ree == -1)
{
SysPushErrorGeneric();
}
return false;
}
return true;
}
bool LinuxWatcher::RemoveByName(const AuString &path)
{
AU_LOCK_GUARD(this->spinlock_);
AuString strNormalized;
if (!AuIOFS::NormalizePath(strNormalized, path))
{
return false;
}
return AuRemoveIf(this->paths_, [&](const UnixCachedPath &object) -> bool
{
if ((strNormalized == object.strNormalizedPath) ||
(strNormalized.empty() && object.strNormalizedPath == path))
{
auto ree = inotify_rm_watch(this->inotifyHandle_, object.watcherWd);
if (ree == -1)
{
SysPushErrorGeneric();
}
return true;
}
return false;
});
}
bool LinuxWatcher::RemoveByPrivateContext(const AuSPtr<UserWatchData> &file)
{
AU_LOCK_GUARD(this->spinlock_);
return AuRemoveIf(this->paths_, [&](const UnixCachedPath &object) -> bool
{
if (file != object.userData)
{
return false;
}
auto ree = inotify_rm_watch(this->inotifyHandle_, object.watcherWd);
if (ree == -1)
{
SysPushErrorGeneric();
}
return true;
});
}
AuSPtr<Loop::ILoopSource> LinuxWatcher::AsLoopSource()
{
AU_LOCK_GUARD(this->spinlock_);
return this->loopSource_;
}
AuList<WatchEvent> LinuxWatcher::QueryUpdates()
{
AU_LOCK_GUARD(this->spinlock_);
bool bSuccess {};
AuList<WatchEvent> ret;
if (this->inotifyHandle_ == -1)
{
SysPushErrorUninitialized();
return ret;
}
while (true)
{
char buffer[4096];
int length = read(this->inotifyHandle_, &buffer, sizeof(buffer));
int index {};
if (length <= -1)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
return ret;
}
else
{
SysPushErrorGeneric();
return ret;
}
}
while (index < length)
{
WatchEvent event;
auto & header = *(inotify_event *)(buffer + index);
index += sizeof(struct inotify_event) + header.len;
#if 0
printf("watcher event: %i %i %i\n", index, header.wd, header.mask);
#endif
if (!AuTryResize(event.file, header.len))
{
SysPushErrorMem("Out of memory -> can't consume body. Will misalign stream... Deinitializing...");
Deinit();
return ret;
}
AuMemcpy(event.file.data(), header.name, header.len);
bool bFound {}, bCrinkled{};
for (auto & path : this->paths_)
{
if (path.watcherWd != header.wd)
{
continue;
}
event.watch = WatchedFile {path.userData, path.strTheCakeIsALie};
bCrinkled = path.strNormalizedPath == event.file;
bFound = true;
break;
}
if (!bFound)
{
SysPushErrorGeneric("Couldn't find inotify wd for event");
continue;
}
if (event.file.empty())
{
event.file = event.watch.path; // TODO: alloc
}
event.event = EWatchEvent::eEnumInvalid;
if (header.mask & (IN_CREATE | IN_MOVED_TO))
{
if (event.event != EWatchEvent::eEnumInvalid)
{
AuTryInsert(ret, event);
}
event.event = EWatchEvent::eFileCreate;
}
if (header.mask & (IN_DELETE | IN_MOVED_FROM))
{
if (event.event != EWatchEvent::eEnumInvalid)
{
AuTryInsert(ret, event);
}
event.event = EWatchEvent::eFileDelete;
}
if (header.mask & (IN_DELETE_SELF | IN_MOVE_SELF))
{
if (event.event != EWatchEvent::eEnumInvalid)
{
AuTryInsert(ret, event);
}
event.event = EWatchEvent::eSelfDelete;
}
if (header.mask & (IN_MODIFY))
{
if (event.event != EWatchEvent::eEnumInvalid)
{
AuTryInsert(ret, event);
}
event.event = EWatchEvent::eFileModify;
}
if (event.file.empty() || (bCrinkled) || (header.mask & IN_MOVE_SELF))
{
if (event.event != EWatchEvent::eEnumInvalid)
{
AuTryInsert(ret, event);
}
if (header.mask & IN_ISDIR)
{
event.event = EWatchEvent::eSelfModify;
}
else
{
event.event = EWatchEvent::eFileModify;
}
}
if (event.event != EWatchEvent::eEnumInvalid)
{
AuTryInsert(ret, event);
}
}
}
return ret;
}
bool LinuxWatcher::Init()
{
this->inotifyHandle_ = ::inotify_init1(IN_NONBLOCK);
if (this->inotifyHandle_ == -1)
{
return false;
}
this->loopSource_ = AuMakeShared<LinuxWatcherHandle>(this->inotifyHandle_);
return bool(this->loopSource_);
}
void LinuxWatcher::Deinit()
{
auto paths = AuExchange(this->paths_, {});
for (auto handle : paths)
{
if (handle.watcherWd != -1)
{
auto ree = inotify_rm_watch(this->inotifyHandle_, handle.watcherWd);
if (ree == -1)
{
SysPushErrorGeneric();
}
}
}
if ((this->inotifyHandle_ != 0) &&
(this->inotifyHandle_ != -1))
{
::close(AuExchange(this->inotifyHandle_, -1));
}
}
AUKN_SYM IWatcher *NewWatcherNew()
{
auto watcher = _new LinuxWatcher();
if (!watcher)
{
return {};
}
if (!watcher->Init())
{
delete watcher;
return {};
}
return watcher;
}
AUKN_SYM void NewWatcherRelease(IWatcher *watcher)
{
AuSafeDelete<LinuxWatcher *>(watcher);
}
}

45
Source/IO/FS/Watcher.Linux.hpp Executable file
View File

@ -0,0 +1,45 @@
/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: Watcher.Linux.hpp
Date: 2022-4-10
Author: Reece
***/
#pragma once
namespace Aurora::IO::FS
{
struct LinuxWatcherHandle;
struct UnixCachedPath
{
AuString strNormalizedPath;
AuString strTheCakeIsALie;
AuSPtr<UserWatchData> userData;
int watcherWd {-1};
};
struct LinuxWatcher : IWatcher
{
~LinuxWatcher();
virtual bool AddWatch(const WatchRequest &file) override;
virtual bool RemoveByName(const AuString &path) override;
virtual bool RemoveByPrivateContext(const AuSPtr<UserWatchData> &file) override;
virtual AuSPtr<Loop::ILoopSource> AsLoopSource() override;
virtual AuList<WatchEvent> QueryUpdates() override;
bool Init();
void Deinit();
private:
AuSPtr<LinuxWatcherHandle> loopSource_;
AuThreadPrimitives::SpinLock spinlock_;
AuList<UnixCachedPath> paths_;
int inotifyHandle_ {-1};
};
}

View File

@ -4,6 +4,8 @@
File: Watcher.NT.cpp File: Watcher.NT.cpp
Date: 2022-4-1 Date: 2022-4-1
Author: Reece Author: Reece
Note: FindFirstChangeNotification locks the parent directory, every other library is garbage
Aurora shall implement this sewage instead
***/ ***/
#include <Source/RuntimeInternal.hpp> #include <Source/RuntimeInternal.hpp>
#include "FS.hpp" #include "FS.hpp"
@ -66,21 +68,30 @@ namespace Aurora::IO::FS
AuString strTheCakeIsALie; AuString strTheCakeIsALie;
AuSPtr<UserWatchData> userData; AuSPtr<UserWatchData> userData;
AuSPtr<NTWatchObject> watcher; AuSPtr<NTWatchObject> watcher;
bool bIsDirectory {};
AuList<AuTuple<AuString, bool, AuUInt64>> lastTick;
FILETIME lastFileTime {}; FILETIME lastFileTime {};
bool CheckRun(); AuList<AuTuple<AuString, bool, AuUInt64>> GetCurrentState();
bool CheckDirDelta();
void Warm();
bool CheckRun(NTWatcher *parent);
bool AddEvent(NTWatcher *parent, EWatchEvent type, const AuString &path);
}; };
struct NTWatcher : IWatcher struct NTWatcher : IWatcher
{ {
virtual bool AddWatch(const WatchedFile &file) override; virtual bool WatchRequest(const WatchRequest &file) override;
virtual bool RemoveByName(const AuString &path) override; virtual bool RemoveByName(const AuString &path) override;
virtual bool RemoveByPrivateContext(const AuSPtr<UserWatchData> &file) override; virtual bool RemoveByPrivateContext(const AuSPtr<UserWatchData> &file) override;
virtual AuSPtr<Loop::ILoopSource> AsLoopSource() override; virtual AuSPtr<Loop::ILoopSource> AsLoopSource() override;
virtual AuList<WatchedFile> QueryUpdates() override; virtual AuList<WatchEvent> QueryUpdates() override;
bool Init(); bool Init();
bool GoBrr(); bool GoBrr();
@ -94,7 +105,7 @@ namespace Aurora::IO::FS
public: public:
AuList<WatchedFile> triggered_; AuList<WatchEvent> triggered_;
AuSPtr<NTEvent> ntEvent_; AuSPtr<NTEvent> ntEvent_;
AuList<NTCachedPath> paths_; AuList<NTCachedPath> paths_;
@ -157,7 +168,69 @@ namespace Aurora::IO::FS
// NT Cached Path / File Watch Item class // NT Cached Path / File Watch Item class
bool NTCachedPath::CheckRun() void NTCachedPath::Warm()
{
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 | (this->bIsDirectory ? FILE_FLAG_BACKUP_SEMANTICS : 0),
NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
if (!GetFileTime(hFile, NULL, NULL, &this->lastFileTime))
{
return true;
}
CloseHandle(hFile);
}
this->lastTick = GetCurrentState();
}
AuList<AuTuple<AuString, bool, AuUInt64>> NTCachedPath::GetCurrentState()
{
return {};
}
bool NTCachedPath::CheckDirDelta()
{
auto next = GetCurrentState();
auto currentState = next;
auto oldState = AuExchange(this->lastTick, AuMove(next));
return false;
}
bool NTCachedPath::AddEvent(NTWatcher *parent, EWatchEvent type, const AuString &path)
{
AuCtorCode_t code;
auto watchedFile = AuTryConstruct<WatchedFile>(code, this->userData, this->strTheCakeIsALie);
if (!code)
{
return false;
}
WatchEvent event;
event.event = type;
event.watch = AuMove(watchedFile);
event.file = AuTryConstruct<AuString>(code, path);
if (!code)
{
return false;
}
if (!AuTryInsert(parent->triggered_, AuMove(watchedFile)))
{
return false;
}
return true;
}
bool NTCachedPath::CheckRun(NTWatcher *parent)
{ {
FILETIME curFileTime {}; FILETIME curFileTime {};
HANDLE hFile; HANDLE hFile;
@ -167,14 +240,24 @@ namespace Aurora::IO::FS
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, NULL,
OPEN_EXISTING, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | (AuIOFS::DirExists(this->strNormalizedPath) ? FILE_FLAG_BACKUP_SEMANTICS : 0), FILE_ATTRIBUTE_NORMAL | (this->bIsDirectory ? FILE_FLAG_BACKUP_SEMANTICS : 0),
NULL); NULL);
if (hFile == INVALID_HANDLE_VALUE) if (hFile == INVALID_HANDLE_VALUE)
{ {
return false; if (GetLastError() == ERROR_FILE_NOT_FOUND)
{
// NOTE: we might want to add a check to prevent double dispatch
return AddEvent(parent, EWatchEvent::eSelfDelete, this->strTheCakeIsALie);
}
else
{
return false;
}
} }
bool bDirChanged = CheckDirDelta();
if (!GetFileTime(hFile, NULL, NULL, &curFileTime)) if (!GetFileTime(hFile, NULL, NULL, &curFileTime))
{ {
CloseHandle(hFile); CloseHandle(hFile);
@ -188,7 +271,13 @@ namespace Aurora::IO::FS
CloseHandle(hFile); CloseHandle(hFile);
return ret; if (!ret)
{
return bDirChanged;
}
// Send a self modified update once the timestamp changes
return AddEvent(parent,this->bIsDirectory ? EWatchEvent::eSelfModify : EWatchEvent::eFileModify, this->strTheCakeIsALie) || bDirChanged;
} }
// Directory Watcher Object // Directory Watcher Object
@ -196,7 +285,7 @@ namespace Aurora::IO::FS
bool NTWatchObject::Init(const AuString &usrStr) bool NTWatchObject::Init(const AuString &usrStr)
{ {
AuCtorCode_t code; AuCtorCode_t code;
this->strBaseDir = AuTryConstruct<AuString>(code, usrStr); this->strBaseDir = AuTryConstruct<AuString>(code, usrStr);
if (!code) if (!code)
{ {
@ -277,26 +366,12 @@ namespace Aurora::IO::FS
continue; continue;
} }
if (!filesWatched.CheckRun()) if (!filesWatched.CheckRun(this->parent))
{ {
continue; continue;
} }
bAnyTriggered = true; bAnyTriggered = true;
AuCtorCode_t code;
auto watchedFile = AuTryConstruct<WatchedFile>(code, filesWatched.userData, filesWatched.strTheCakeIsALie);
if (!code)
{
return false;
}
if (!AuTryInsert(this->parent->triggered_, AuMove(watchedFile)))
{
return false;
}
bAnyTriggered = true;
} }
if (this->whoAsked_.Flags & REQUEST_OPLOCK_OUTPUT_FLAG_ACK_REQUIRED) if (this->whoAsked_.Flags & REQUEST_OPLOCK_OUTPUT_FLAG_ACK_REQUIRED)
@ -320,14 +395,15 @@ namespace Aurora::IO::FS
// NT Watcher - primary interface implementation // NT Watcher - primary interface implementation
bool NTWatcher::AddWatch(const WatchedFile &file) bool NTWatcher::AddWatch(const WatchRequest &request)
{ {
AuCtorCode_t code; AuCtorCode_t code;
AuSPtr<NTWatchObject> watcher; AuSPtr<NTWatchObject> watcher;
NTCachedPath cached; NTCachedPath cached;
AU_LOCK_GUARD(this->spinlock_); auto &file = request.watch;
AU_LOCK_GUARD(this->spinlock_);
AuString translated; AuString translated;
if (!AuIOFS::NormalizePath(translated, file.path)) if (!AuIOFS::NormalizePath(translated, file.path))
@ -350,17 +426,21 @@ namespace Aurora::IO::FS
cached.userData = file.userData; cached.userData = file.userData;
// 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 // Continue normalizing the parent path
if (AuIOFS::FileExists(translated)) if (AuIOFS::FileExists(translated))
{ {
AuIOFS::GetDirectoryFromPath(translated, translated); AuIOFS::GetDirectoryFromPath(translated, translated);
} }
else
{
// dammit, had to move checkrun
cached.bIsDirectory = true;
}
// Update the last edited timestamp as a frame of reference for fast compare of
// directory entries upon lock breakage
cached.Warm();
// Attempt to locate a watcher for the directoy // Attempt to locate a watcher for the directoy
@ -387,7 +467,6 @@ namespace Aurora::IO::FS
item->parent = this; item->parent = this;
if (!item->Init(translated)) if (!item->Init(translated))
{ {
SysPushErrorGen(); SysPushErrorGen();
@ -497,9 +576,10 @@ namespace Aurora::IO::FS
return AuStaticCast<Loop::ILSEvent>(this->ntEvent_); return AuStaticCast<Loop::ILSEvent>(this->ntEvent_);
} }
AuList<WatchedFile> NTWatcher::NTWatcher::QueryUpdates() AuList<WatchEvent> NTWatcher::NTWatcher::QueryUpdates()
{ {
AU_LOCK_GUARD(this->spinlock_); AU_LOCK_GUARD(this->spinlock_);
AsLoopSource().IsSignaled(); // nonblocking, wont latch
return AuExchange(this->triggered_, {}); return AuExchange(this->triggered_, {});
} }

View File

@ -1,119 +1,119 @@
/*** /***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: LSEvent.Linux.cpp File: LSEvent.Linux.cpp
Date: 2022-4-4 Date: 2022-4-4
Author: Reece Author: Reece
***/ ***/
#include <Source/RuntimeInternal.hpp> #include <Source/RuntimeInternal.hpp>
#include "LSEvent.hpp" #include "LSEvent.hpp"
#include "LSFromFdNonblocking.hpp" #include "LSFromFdNonblocking.hpp"
namespace Aurora::Loop namespace Aurora::Loop
{ {
LSEvent::LSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers) : atomicRelease_(atomicRelease) LSEvent::LSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers) : atomicRelease_(atomicRelease)
{ {
Init(triggered); Init(triggered);
} }
LSEvent::~LSEvent() LSEvent::~LSEvent()
{ {
if ((this->handle != 0) && if ((this->handle != 0) &&
(this->handle != -1)) (this->handle != -1))
{ {
close(this->handle); close(this->handle);
} }
} }
bool LSEvent::OnTrigger(AuUInt handle) bool LSEvent::OnTrigger(AuUInt handle)
{ {
AuUInt64 oldSemaphoreValue {}; AuUInt64 oldSemaphoreValue {};
if (!this->atomicRelease_) if (!this->atomicRelease_)
{ {
return true; return true;
} }
return read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue)) == 8; return read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue)) == 8;
} }
void LSEvent::Init(bool init) void LSEvent::Init(bool init)
{ {
handle = eventfd(init ? 1 : 0, EFD_NONBLOCK); handle = eventfd(init ? 1 : 0, EFD_NONBLOCK);
} }
bool LSEvent::Set() bool LSEvent::Set()
{ {
AuUInt64 plsNoOverflow {1}; AuUInt64 plsNoOverflow {1};
if (write(this->handle, &plsNoOverflow, sizeof(plsNoOverflow)) != 8) if (write(this->handle, &plsNoOverflow, sizeof(plsNoOverflow)) != 8)
{ {
// todo push error // todo push error
return false; return false;
} }
return true; return true;
} }
bool LSEvent::Reset() bool LSEvent::Reset()
{ {
AuUInt64 oldSemaphoreValue {0}; AuUInt64 oldSemaphoreValue {0};
// RETURN VALUE IS USELESS - Failure is to be expected // RETURN VALUE IS USELESS - Failure is to be expected
read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue)); read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue));
return true; return true;
} }
bool LSEvent::IsSignaled() bool LSEvent::IsSignaled()
{ {
return IsSignaledFromNonblockingImpl(this, this, &LSEvent::IsSignaledNonblocking); return IsSignaledFromNonblockingImpl(this, this, &LSEvent::IsSignaledNonblocking);
} }
bool LSEvent::IsSignaledNonblocking() bool LSEvent::IsSignaledNonblocking()
{ {
if (!this->atomicRelease_) if (!this->atomicRelease_)
{ {
fd_set set; fd_set set;
struct timeval tv {}; struct timeval tv {};
FD_ZERO(&set); FD_ZERO(&set);
FD_SET(this->handle, &set); FD_SET(this->handle, &set);
auto active = select(this->handle + 1, &set, NULL, NULL, &tv); auto active = select(this->handle + 1, &set, NULL, NULL, &tv);
if (active == -1) if (active == -1)
{ {
// todo push error // todo push error
return false; return false;
} }
return active == 1; return active == 1;
} }
else else
{ {
AuUInt64 oldSemaphoreValue {}; AuUInt64 oldSemaphoreValue {};
return read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue)) == 8; return read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue)) == 8;
} }
} }
ELoopSource LSEvent::GetType() ELoopSource LSEvent::GetType()
{ {
return ELoopSource::eSourceEvent; return ELoopSource::eSourceEvent;
} }
AUKN_SYM AuSPtr<ILSEvent> NewLSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers) AUKN_SYM AuSPtr<ILSEvent> NewLSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers)
{ {
auto event = AuMakeShared<LSEvent>(triggered, atomicRelease, permitMultipleTriggers); auto event = AuMakeShared<LSEvent>(triggered, atomicRelease, permitMultipleTriggers);
if (!event) if (!event)
{ {
return {}; return {};
} }
if (!event->HasValidHandle()) if (!event->HasValidHandle())
{ {
return {}; return {};
} }
return event; return event;
} }
} }

View File

@ -1,31 +1,31 @@
/*** /***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: LSEvent.Linux.hpp File: LSEvent.Linux.hpp
Date: 2022-4-4 Date: 2022-4-4
Author: Reece Author: Reece
***/ ***/
#pragma once #pragma once
#include "LSHandle.hpp" #include "LSHandle.hpp"
namespace Aurora::Loop namespace Aurora::Loop
{ {
struct LSEvent : public ILSEvent, public LSHandle struct LSEvent : public ILSEvent, public LSHandle
{ {
LSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers); LSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers);
~LSEvent(); ~LSEvent();
bool Set() override; bool Set() override;
bool Reset() override; bool Reset() override;
virtual bool OnTrigger(AuUInt handle) override; virtual bool OnTrigger(AuUInt handle) override;
bool IsSignaled() override; bool IsSignaled() override;
virtual ELoopSource GetType() override; virtual ELoopSource GetType() override;
private: private:
void Init(bool init); void Init(bool init);
bool IsSignaledNonblocking(); bool IsSignaledNonblocking();
bool atomicRelease_; bool atomicRelease_;
}; };
} }

View File

@ -353,7 +353,7 @@ namespace Aurora::Loop
return true; return true;
} }
bool LoopQueue::IsSignaled() bool LoopQueue::IsSignaledPeek()
{ {
fd_set readSet; fd_set readSet;
struct timeval tv {}; struct timeval tv {};
@ -402,7 +402,6 @@ namespace Aurora::Loop
// [==========] 1 test from 1 test suite ran. (11100 ms total) // [==========] 1 test from 1 test suite ran. (11100 ms total)
// ...and a turbojet // ...and a turbojet
//bool bTryAgain {}; //bool bTryAgain {};
//DoTick(timeout, {}, &bTryAgain); //DoTick(timeout, {}, &bTryAgain);
// ...and + ~10ms latency // ...and + ~10ms latency
@ -450,6 +449,35 @@ namespace Aurora::Loop
return cTicked; return cTicked;
} }
AuUInt32 LoopQueue::PumpNonblocking()
{
AuUInt32 cTicked {};
bool bTryAgain {};
do
{
bTryAgain = false;
AuUInt32 ticked = DoTick(0, {}, &bTryAgain, true);
cTicked += ticked;
} while (bTryAgain);
return cTicked;
}
AuList<AuSPtr<ILoopSource>> LoopQueue::PumpNonblockingEx()
{
AuList<AuSPtr<ILoopSource>> ret;
bool bTryAgain {};
do
{
bTryAgain = false;
AuUInt32 ticked = DoTick(0, &ret, &bTryAgain, true);
} while (bTryAgain);
return ret;
}
AuList<AuSPtr<ILoopSource>> LoopQueue::WaitAnyEx(AuUInt32 timeoutIn) AuList<AuSPtr<ILoopSource>> LoopQueue::WaitAnyEx(AuUInt32 timeoutIn)
{ {
AuList<AuSPtr<ILoopSource>> ret; AuList<AuSPtr<ILoopSource>> ret;
@ -461,12 +489,10 @@ namespace Aurora::Loop
} }
bool bTryAgain {}; bool bTryAgain {};
AuUInt32 cTicked {};
do do
{ {
bTryAgain = false; bTryAgain = false;
AuUInt32 ticked = DoTick(timeout, &ret, &bTryAgain); AuUInt32 ticked = DoTick(timeout, &ret, &bTryAgain);
cTicked += ticked;
} while (bTryAgain); } while (bTryAgain);
return ret; return ret;
@ -582,7 +608,7 @@ namespace Aurora::Loop
} }
} }
AuUInt32 LoopQueue::DoTick(AuUInt64 time, AuList<AuSPtr<ILoopSource>> *optOut, bool *tryAgain) AuUInt32 LoopQueue::DoTick(AuUInt64 time, AuList<AuSPtr<ILoopSource>> *optOut, bool *tryAgain, bool nonblock)
{ {
AuUInt32 bTicked {}; AuUInt32 bTicked {};
AuUInt64 now {}; AuUInt64 now {};
@ -590,7 +616,6 @@ namespace Aurora::Loop
AU_LOCK_GUARD(this->sourceMutex_->AsReadable()); AU_LOCK_GUARD(this->sourceMutex_->AsReadable());
for (const auto & source : this->sources_) for (const auto & source : this->sources_)
{ {
if (source->sourceExtended) if (source->sourceExtended)
@ -599,33 +624,39 @@ namespace Aurora::Loop
} }
} }
// epoll_pwait2 is fucking broken and the dipshits who wrote the test used relative values // syscall epoll_pwait2 is fucking broken and the dipshits who wrote the test used relative values
// //
// Nothing I tried worked. // Nothing I tried worked.
// //
// Am I stupid? Probably, but... // Am I stupid? Probably, but...
// (1) no one as far as i can tell has ever written anything using this api, per a github search // (1) no one as far as i can tell has ever written anything using this syscall, per a github search
// (2) i found one reference that the that are the linux kernel developers used MONO time for this // (2) i found one reference that the that are the linux kernel developers used MONO time for this
// one timespec API unlike everything else, using an abs value rel to that clock didn't change // one timespec API unlike everything else; using an abs value rel to that clock didn't change
// anything. // anything.
// (3) i found a test that would indicate its relative despite the fact UNIX/Linux sync APIs // (3) i found a test that would indicate its relative despite the fact UNIX/Linux sync APIs
// tend to use abs time // tend to use abs time
// //
// What does my experience working on xenus tell me? // What does my experience working on xenus tell me?
// Because the GOOOOGLERs in the form of linux kernel developers were faced with an issue that // Because the gOOOOglers in the form of linux kernel developers were faced with an issue that
// couldn't be solved by involve copy/pasting memory map code, making a mess of public headers, // couldn't be solved by involve copy/pasting memory map code, making a mess of public headers,
// or taking credit for third party driver code as their own kernel code, indeed are to blame // or taking credit for third party driver code as their own kernel code; they indeed are to
// for making my life miserable once again. // blame for making my life miserable once again. Something about this aint adding up.
AuInt64 deltaMS = 0;
if (time)
{
auto deltaMS = time ? AuMin(AuInt64(4), (AuInt64)time - (AuInt64)AuTime::CurrentClockMS()) : 0; deltaMS = AuMin(AuInt64(4), (AuInt64)time - (AuInt64)AuTime::CurrentClockMS());
if (deltaMS < 0) deltaMS = 0; if (deltaMS < 0)
{
deltaMS = 0;
}
}
else
{
deltaMS = nonblock ? 0 : -1;
}
int iEvents = epoll_wait(this->epollFd_, events, AuArraySize(events), deltaMS); int iEvents = epoll_wait(this->epollFd_, events, AuArraySize(events), deltaMS);
if (iEvents == -1) if (iEvents == -1)
{ {
goto out; goto out;

View File

@ -85,9 +85,6 @@ namespace Aurora::Loop
} }
}; };
bool IsValid(); bool IsValid();
bool RemoveSourceNB(const AuSPtr<ILoopSource> &source); bool RemoveSourceNB(const AuSPtr<ILoopSource> &source);
bool WaitAnyNBSpurious(AuUInt32 timeout, AuUInt32 &chuggerIndex, AuList<AuSPtr<ILoopSource>> *trigger, bool poll); bool WaitAnyNBSpurious(AuUInt32 timeout, AuUInt32 &chuggerIndex, AuList<AuSPtr<ILoopSource>> *trigger, bool poll);

View File

@ -21,12 +21,14 @@ namespace Aurora::Loop
for (const auto i : handles) for (const auto i : handles)
{ {
if (i == -1) continue;
FD_SET(i, &readSet); FD_SET(i, &readSet);
maxHandle = AuMax(maxHandle, i + 1); maxHandle = AuMax(maxHandle, i + 1);
} }
for (const auto i : handlesWrite) for (const auto i : handlesWrite)
{ {
if (i == -1) continue;
FD_SET(i, &writeSet); FD_SET(i, &writeSet);
maxHandle = AuMax(maxHandle, i + 1); maxHandle = AuMax(maxHandle, i + 1);
} }
@ -67,13 +69,24 @@ namespace Aurora::Loop
{ {
fd_set readSet, writeSet; fd_set readSet, writeSet;
struct timeval tv {}; struct timeval tv {};
int maxFd {};
FD_ZERO(&readSet); FD_ZERO(&readSet);
FD_ZERO(&writeSet); FD_ZERO(&writeSet);
FD_SET(read, &readSet);
FD_SET(write, &writeSet); if (read != -1)
{
maxFd = read + 1;
FD_SET(read, &readSet);
}
auto active = select(AuMax(read, write) + 1, read != -1 ? &readSet : NULL, write != -1 ? &writeSet : NULL, NULL, &tv); if (write != -1)
{
FD_SET(write, &writeSet);
maxFd = AuMax(maxFd, int(write) + 1);
}
auto active = select(maxFd, read != -1 ? &readSet : NULL, write != -1 ? &writeSet : NULL, NULL, &tv);
if (active == -1) if (active == -1)
{ {
// todo push error // todo push error

View File

@ -18,14 +18,14 @@ namespace Aurora::Parse
if (!AuTryResize(decoded, length)) if (!AuTryResize(decoded, length))
{ {
SysPushErrorMem(); SysPushErrorMem();
return {}; return false;
} }
auto status = base32_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length, BASE32_RFC4648); auto status = base32_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length, BASE32_RFC4648);
if (!AuTryResize(decoded, length)) if (!AuTryResize(decoded, length))
{ {
SysPushErrorMem(); SysPushErrorMem();
return {}; return false;
} }
return status == CRYPT_OK; return status == CRYPT_OK;
@ -38,14 +38,14 @@ namespace Aurora::Parse
if (!AuTryResize(encoded, length)) if (!AuTryResize(encoded, length))
{ {
SysPushErrorMem(); SysPushErrorMem();
return {}; return false;
} }
auto status = base32_encode(reinterpret_cast<const unsigned char *>(buffer), length, &encoded[0], &outLength, BASE32_RFC4648); auto status = base32_encode(reinterpret_cast<const unsigned char *>(buffer), length, &encoded[0], &outLength, BASE32_RFC4648);
if (!AuTryResize(encoded, length)) if (!AuTryResize(encoded, length))
{ {
SysPushErrorMem(); SysPushErrorMem();
return {}; return false;
} }
return status == CRYPT_OK; return status == CRYPT_OK;

View File

@ -14,56 +14,58 @@ namespace Aurora::Parse
AUKN_SYM bool Base64Decode(const AuString &in, AuByteBuffer &decoded, bool url) AUKN_SYM bool Base64Decode(const AuString &in, AuByteBuffer &decoded, bool url)
{ {
unsigned long length = in.size(); unsigned long length = in.size();
try
if (!AuTryResize(decoded, length))
{ {
decoded.resize(in.size()); SysPushErrorMem();
int status;
if (url)
{
status = base64url_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length);
}
else
{
status = base64_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length);
}
decoded.resize(length);
return status == CRYPT_OK;
}
catch (...)
{
AuLogWarn("Decoding error: {}", in);
Debug::PrintError();
return false; return false;
} }
int status;
if (url)
{
status = base64url_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length);
}
else
{
status = base64_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length);
}
if (!AuTryResize(decoded, length))
{
SysPushErrorMem();
return false;
}
return status == CRYPT_OK;
} }
AUKN_SYM bool Base64Encode(const Memory::MemoryViewRead &input, AuString &encoded, bool url) AUKN_SYM bool Base64Encode(const Memory::MemoryViewRead &input, AuString &encoded, bool url)
{ {
unsigned long outLength = input.length + (input.length / 3.0) + 16; unsigned long outLength = input.length + (input.length / 3.0) + 16;
try
{
encoded.resize(outLength);
int status; if (!AuTryResize(encoded, outLength))
if (url)
{
status = base64url_encode(reinterpret_cast<const unsigned char*>(input.ptr), input.length, &encoded[0], &outLength);
}
else
{
status = base64_encode(reinterpret_cast<const unsigned char*>(input.ptr), input.length, &encoded[0], &outLength);
}
encoded.resize(outLength);
return status == CRYPT_OK;
}
catch (...)
{ {
AuLogWarn("Encoding error"); SysPushErrorMem();
Debug::PrintError();
return false; return false;
} }
int status;
if (url)
{
status = base64url_encode(reinterpret_cast<const unsigned char*>(input.ptr), input.length, &encoded[0], &outLength);
}
else
{
status = base64_encode(reinterpret_cast<const unsigned char*>(input.ptr), input.length, &encoded[0], &outLength);
}
if (!AuTryResize(encoded, outLength))
{
SysPushErrorMem();
return false;
}
return status == CRYPT_OK;
} }
} }