Files
GacUI/Import/VlppOS.Linux.cpp
T
2026-07-26 04:16:55 -07:00

6283 lines
149 KiB
C++

/***********************************************************************
THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY
DEVELOPER: Zihan Chen(vczh)
***********************************************************************/
#include "VlppOS.Linux.h"
/***********************************************************************
.\FILESYSTEM.LINUX.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#ifndef VCZH_GCC
static_assert(false, "Do not build this file for Windows applications.");
#endif
namespace vl
{
namespace stream
{
extern Ptr<IFileStreamImpl> CreateOSFileStreamImpl(const WString& fileName, FileStream::AccessRight accessRight);
}
namespace filesystem
{
using namespace collections;
using namespace stream;
/***********************************************************************
LinuxFileSystemImpl
***********************************************************************/
class LinuxFileSystemImpl : public feature_injection::FeatureImpl<IFileSystemImpl>
{
public:
// FilePath operations implementation
wchar_t GetPathDelimiter() const override
{
return L'/';
}
const wchar_t* GetCompatibleDelimiters() const override
{
return L"";
}
WString ConcatPath(const WString& fullPath, const WString& relativePath) const override
{
auto delimiter = WString::FromChar(GetPathDelimiter());
if (IsRoot(fullPath))
{
return delimiter + relativePath;
}
return fullPath + delimiter + relativePath;
}
void Initialize(WString& fullPath) const override
{
{
Array<wchar_t> buffer(fullPath.Length() + 1);
wcscpy(&buffer[0], fullPath.Buffer());
FilePath::NormalizeDelimiters(buffer);
fullPath = &buffer[0];
}
if (fullPath.Length() == 0)
fullPath = WString::FromChar(GetPathDelimiter());
if (fullPath[0] != GetPathDelimiter())
{
char buffer[PATH_MAX] = { 0 };
getcwd(buffer, PATH_MAX);
fullPath = atow(AString(buffer)) + WString::FromChar(GetPathDelimiter()) + fullPath;
}
{
collections::List<WString> components;
FilePath::GetPathComponents(fullPath, components);
for (int i = 0; i < components.Count(); i++)
{
if (components[i] == L".")
{
components.RemoveAt(i);
i--;
}
else if (components[i] == L"..")
{
if (i > 0)
{
components.RemoveAt(i);
components.RemoveAt(i - 1);
i -= 2;
}
else
{
throw ArgumentException(L"Illegal path.");
}
}
}
fullPath = FilePath::ComponentsToPath(components);
}
FilePath::TrimLastDelimiter(fullPath);
}
bool IsFile(const WString& fullPath) const override
{
struct stat info;
AString path = wtoa(fullPath);
int result = stat(path.Buffer(), &info);
if(result != 0) return false;
else return S_ISREG(info.st_mode);
}
bool IsFolder(const WString& fullPath) const override
{
struct stat info;
AString path = wtoa(fullPath);
int result = stat(path.Buffer(), &info);
if(result != 0) return false;
else return S_ISDIR(info.st_mode);
}
bool IsRoot(const WString& fullPath) const override
{
return fullPath == WString::FromChar(GetPathDelimiter());
}
WString GetRelativePathFor(const WString& fromPath, const WString& toPath) const override
{
if (fromPath.Length() == 0 || toPath.Length() == 0 || fromPath[0] != toPath[0])
{
return toPath;
}
collections::List<WString> srcComponents, tgtComponents, resultComponents;
FilePath::GetPathComponents(IsFolder(fromPath) ? fromPath : FilePath(fromPath).GetFolder().GetFullPath(), srcComponents);
FilePath::GetPathComponents(toPath, tgtComponents);
int minLength = srcComponents.Count() <= tgtComponents.Count() ? srcComponents.Count() : tgtComponents.Count();
int lastCommonComponent = 0;
for (int i = 0; i < minLength; i++)
{
if (srcComponents[i] == tgtComponents[i])
{
lastCommonComponent = i;
}
else
break;
}
for (int i = lastCommonComponent + 1; i < srcComponents.Count(); i++)
{
resultComponents.Add(L"..");
}
for (int i = lastCommonComponent + 1; i < tgtComponents.Count(); i++)
{
resultComponents.Add(tgtComponents[i]);
}
return FilePath::ComponentsToPath(resultComponents);
}
// File operations implementation
bool FileDelete(const FilePath& filePath) const override
{
AString path = wtoa(filePath.GetFullPath());
return unlink(path.Buffer()) == 0;
}
bool FileRename(const FilePath& filePath, const WString& newName) const override
{
AString oldFileName = wtoa(filePath.GetFullPath());
AString newFileName = wtoa((filePath.GetFolder() / newName).GetFullPath());
return rename(oldFileName.Buffer(), newFileName.Buffer()) == 0;
}
// Folder operations implementation
bool GetFolders(const FilePath& folderPath, collections::List<Folder>& folders) const override
{
DIR *dir;
AString searchPath = wtoa(folderPath.GetFullPath());
if ((dir = opendir(searchPath.Buffer())) == NULL)
{
return false;
}
struct dirent* entry;
while ((entry = readdir(dir)) != NULL)
{
WString childName = atow(AString(entry->d_name));
FilePath childFullPath = folderPath / childName;
if (childName != L"." && childName != L".." && childFullPath.IsFolder())
{
folders.Add(Folder(childFullPath));
}
}
if (closedir(dir) != 0)
{
return false;
}
return true;
}
bool GetFiles(const FilePath& folderPath, collections::List<File>& files) const override
{
DIR* dir;
AString searchPath = wtoa(folderPath.GetFullPath());
if ((dir = opendir(searchPath.Buffer())) == NULL)
{
return false;
}
struct dirent* entry;
while ((entry = readdir(dir)) != NULL)
{
FilePath childFullPath = folderPath / (atow(AString(entry->d_name)));
if (childFullPath.IsFile())
{
files.Add(File(childFullPath));
}
}
if (closedir(dir) != 0)
{
return false;
}
return true;
}
bool CreateFolder(const FilePath& folderPath) const override
{
AString path = wtoa(folderPath.GetFullPath());
return mkdir(path.Buffer(), 0777) == 0;
}
bool DeleteFolder(const FilePath& folderPath) const override
{
AString path = wtoa(folderPath.GetFullPath());
return rmdir(path.Buffer()) == 0;
}
bool FolderRename(const FilePath& folderPath, const WString& newName) const override
{
AString oldFileName = wtoa(folderPath.GetFullPath());
AString newFileName = wtoa((folderPath.GetFolder() / newName).GetFullPath());
return rename(oldFileName.Buffer(), newFileName.Buffer()) == 0;
}
Ptr<stream::IFileStreamImpl> GetFileStreamImpl(const WString& fileName, stream::FileStream::AccessRight accessRight) const override
{
return stream::CreateOSFileStreamImpl(fileName, accessRight);
}
};
/***********************************************************************
Global FileSystem Implementation
***********************************************************************/
IFileSystemImpl* GetOSFileSystemImpl()
{
static LinuxFileSystemImpl osFileSystemImpl;
return &osFileSystemImpl;
}
}
}
/***********************************************************************
.\LOCALE.LINUX.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_GCC
static_assert(false, "Do not build this file for Windows applications.");
#endif
namespace vl
{
ILocaleImpl* GetOSLocaleImpl()
{
static EnUsLocaleImpl linuxLocaleImpl;
return &linuxLocaleImpl;
}
}
/***********************************************************************
.\THREADING.LINUX.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#include <pthread.h>
#include <fcntl.h>
#include <semaphore.h>
#include <errno.h>
#include <time.h>
#if defined VCZH_APPLE
#include <CoreFoundation/CoreFoundation.h>
#endif
#ifndef VCZH_GCC
static_assert(false, "Do not build this file for Windows applications.");
#endif
namespace vl
{
using namespace threading_internal;
using namespace collections;
/***********************************************************************
Thread
***********************************************************************/
namespace threading_internal
{
struct ThreadData
{
pthread_t id;
EventObject ev;
bool deleteAfterStopped = false;
};
class ProceduredThread : public Thread
{
private:
Thread::ThreadProcedure procedure;
void* argument;
protected:
void Run()
{
procedure(this, argument);
}
public:
ProceduredThread(Thread::ThreadProcedure _procedure, void* _argument, bool _deleteAfterStopped)
:procedure(_procedure)
,argument(_argument)
{
internalData->deleteAfterStopped = _deleteAfterStopped;
}
};
class LambdaThread : public Thread
{
private:
Func<void()> procedure;
protected:
void Run()
{
procedure();
}
public:
LambdaThread(const Func<void()>& _procedure, bool _deleteAfterStopped)
:procedure(_procedure)
{
internalData->deleteAfterStopped = _deleteAfterStopped;
}
};
}
void InternalThreadProc(Thread* thread)
{
auto deleteAfterStopped = thread->internalData->deleteAfterStopped;
ThreadLocalStorage::FixStorages();
try
{
thread->Run();
ThreadLocalStorage::ClearStorages();
thread->threadState=Thread::Stopped;
thread->internalData->ev.Signal();
}
catch (...)
{
ThreadLocalStorage::ClearStorages();
throw;
}
if(deleteAfterStopped)
{
delete thread;
}
}
void* InternalThreadProcWrapper(void* lpParameter)
{
InternalThreadProc((Thread*)lpParameter);
return 0;
}
Thread::Thread()
{
internalData=new ThreadData;
internalData->ev.CreateManualUnsignal(false);
threadState=Thread::NotStarted;
}
Thread::~Thread()
{
if (internalData)
{
Stop();
if (threadState!=Thread::NotStarted)
{
pthread_detach(internalData->id);
}
delete internalData;
}
}
Thread* Thread::CreateAndStart(ThreadProcedure procedure, void* argument, bool deleteAfterStopped)
{
if(procedure)
{
Thread* thread=new ProceduredThread(procedure, argument, deleteAfterStopped);
if(thread->Start())
{
return thread;
}
else
{
delete thread;
}
}
return 0;
}
Thread* Thread::CreateAndStart(const Func<void()>& procedure, bool deleteAfterStopped)
{
Thread* thread=new LambdaThread(procedure, deleteAfterStopped);
if(thread->Start())
{
return thread;
}
else
{
delete thread;
}
return 0;
}
void Thread::Sleep(vint ms)
{
if (ms >= 1000)
{
sleep(ms / 1000);
}
if (ms % 1000)
{
usleep((ms % 1000) * 1000);
}
}
vint Thread::GetCPUCount()
{
return (vint)sysconf(_SC_NPROCESSORS_ONLN);
}
vint Thread::GetCurrentThreadId()
{
return (vint)::pthread_self();
}
bool Thread::Start()
{
if(threadState==Thread::NotStarted)
{
threadState=Thread::Running;
if(pthread_create(&internalData->id, nullptr, &InternalThreadProcWrapper, this)==0)
{
return true;
}
threadState=Thread::NotStarted;
}
return false;
}
bool Thread::Wait()
{
return internalData->ev.Wait();
}
bool Thread::Stop()
{
if (threadState==Thread::Running)
{
if(pthread_cancel(internalData->id)==0)
{
threadState=Thread::Stopped;
internalData->ev.Signal();
return true;
}
}
return false;
}
Thread::ThreadState Thread::GetState()
{
return threadState;
}
/***********************************************************************
Mutex
***********************************************************************/
namespace threading_internal
{
struct MutexData
{
Semaphore sem;
};
};
Mutex::Mutex()
{
internalData = new MutexData;
}
Mutex::~Mutex()
{
delete internalData;
}
bool Mutex::Create(bool owned, const WString& name)
{
return internalData->sem.Create(owned ? 0 : 1, 1, name);
}
bool Mutex::Open(bool inheritable, const WString& name)
{
return internalData->sem.Open(inheritable, name);
}
bool Mutex::Release()
{
return internalData->sem.Release();
}
bool Mutex::Wait()
{
return internalData->sem.Wait();
}
/***********************************************************************
Semaphore
***********************************************************************/
namespace threading_internal
{
struct SemaphoreData
{
sem_t semUnnamed;
sem_t* semNamed = nullptr;
};
#if defined VCZH_APPLE
void FailOnUnnamedSemaphore()
{
CHECK_FAIL(L"vl::Semaphore::~Semaphore()#Unnamed semaphores are not supported on macOS.");
}
#endif
}
Semaphore::Semaphore()
:internalData(0)
{
}
Semaphore::~Semaphore()
{
if (internalData)
{
if (internalData->semNamed)
{
sem_close(internalData->semNamed);
}
else
{
#if defined VCZH_APPLE
threading_internal::FailOnUnnamedSemaphore();
#else
sem_destroy(&internalData->semUnnamed);
#endif
}
delete internalData;
}
}
bool Semaphore::Create(vint initialCount, vint maxCount, const WString& name)
{
if (internalData) return false;
if (initialCount > maxCount) return false;
internalData = new SemaphoreData;
#if defined VCZH_APPLE
AString auuid;
if(name.Length() == 0)
{
CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);
CFStringRef cfstr = CFUUIDCreateString(kCFAllocatorDefault, cfuuid);
auuid = CFStringGetCStringPtr(cfstr, kCFStringEncodingASCII);
CFRelease(cfstr);
CFRelease(cfuuid);
}
auuid = auuid.Insert(0, "/");
// OSX SEM_NAME_LENGTH = 31
if(auuid.Length() >= 30)
auuid = auuid.Sub(0, 30);
if ((internalData->semNamed = sem_open(auuid.Buffer(), O_CREAT, O_RDWR, initialCount)) == SEM_FAILED)
{
delete internalData;
internalData = 0;
return false;
}
#else
if (name == L"")
{
if(sem_init(&internalData->semUnnamed, 0, (int)initialCount) == -1)
{
delete internalData;
internalData = 0;
return false;
}
}
else
{
AString astr = wtoa(name);
if ((internalData->semNamed = sem_open(astr.Buffer(), O_CREAT, 0777, initialCount)) == SEM_FAILED)
{
delete internalData;
internalData = 0;
return false;
}
}
#endif
Release(initialCount);
return true;
}
bool Semaphore::Open(bool inheritable, const WString& name)
{
if (internalData) return false;
if (inheritable) return false;
internalData = new SemaphoreData;
if (!(internalData->semNamed = sem_open(wtoa(name).Buffer(), 0)))
{
delete internalData;
internalData = 0;
return false;
}
return true;
}
bool Semaphore::Release()
{
return Release(1);
}
vint Semaphore::Release(vint count)
{
for (vint i = 0; i < count; i++)
{
if (internalData->semNamed)
{
sem_post(internalData->semNamed);
}
else
{
sem_post(&internalData->semUnnamed);
}
}
return true;
}
bool Semaphore::Wait()
{
if (internalData->semNamed)
{
return sem_wait(internalData->semNamed) == 0;
}
else
{
return sem_wait(&internalData->semUnnamed) == 0;
}
}
/***********************************************************************
EventObject
***********************************************************************/
namespace threading_internal
{
struct EventData
{
bool autoReset;
volatile bool signaled;
CriticalSection mutex;
ConditionVariable cond;
atomic_vint counter = 0;
vint releasedWaiters = 0;
vuint64_t signalVersion = 0;
};
}
EventObject::EventObject()
{
internalData = nullptr;
}
EventObject::~EventObject()
{
if (internalData)
{
delete internalData;
}
}
bool EventObject::CreateAutoUnsignal(bool signaled, const WString& name)
{
if (name!=L"") return false;
if (internalData) return false;
internalData = new EventData;
internalData->autoReset = true;
internalData->signaled = signaled;
return true;
}
bool EventObject::CreateManualUnsignal(bool signaled, const WString& name)
{
if (name!=L"") return false;
if (internalData) return false;
internalData = new EventData;
internalData->autoReset = false;
internalData->signaled = signaled;
return true;
}
bool EventObject::Signal()
{
if (!internalData) return false;
internalData->mutex.Enter();
if (internalData->autoReset)
{
if (internalData->counter > internalData->releasedWaiters)
{
internalData->releasedWaiters++;
internalData->cond.WakeOnePending();
}
else
{
internalData->signaled = true;
}
}
else
{
internalData->signaled = true;
internalData->signalVersion++;
if (internalData->counter)
{
internalData->cond.WakeAllPendings();
}
}
internalData->mutex.Leave();
return true;
}
bool EventObject::Unsignal()
{
if (!internalData) return false;
internalData->mutex.Enter();
internalData->signaled = false;
internalData->mutex.Leave();
return true;
}
bool EventObject::Wait()
{
if (!internalData) return false;
bool result = true;
internalData->mutex.Enter();
if (internalData->signaled)
{
if (internalData->autoReset)
{
internalData->signaled = false;
}
}
else
{
auto signalVersion = internalData->signalVersion;
INCRC(&internalData->counter);
while (internalData->autoReset
? internalData->releasedWaiters == 0
: !internalData->signaled && internalData->signalVersion == signalVersion)
{
if (!internalData->cond.SleepWith(internalData->mutex))
{
result = false;
break;
}
}
if (result && internalData->autoReset)
{
internalData->releasedWaiters--;
}
DECRC(&internalData->counter);
}
internalData->mutex.Leave();
return result;
}
bool EventObject::WaitForTime(vint ms)
{
if (!internalData) return false;
bool result = true;
internalData->mutex.Enter();
if (internalData->signaled)
{
if (internalData->autoReset)
{
internalData->signaled = false;
}
}
else
{
auto signalVersion = internalData->signalVersion;
timespec now;
#if defined VCZH_APPLE
constexpr auto waitClock = CLOCK_REALTIME;
#else
constexpr auto waitClock = CLOCK_MONOTONIC;
#endif
if (ms <= 0 || clock_gettime(waitClock, &now) != 0)
{
internalData->mutex.Leave();
return false;
}
auto deadline = (vuint64_t)now.tv_sec * 1000000000 + now.tv_nsec + (vuint64_t)ms * 1000000;
auto remaining = ms;
INCRC(&internalData->counter);
while (internalData->autoReset
? internalData->releasedWaiters == 0
: !internalData->signaled && internalData->signalVersion == signalVersion)
{
if (!internalData->cond.SleepWithForTime(internalData->mutex, remaining))
{
result = false;
break;
}
if (clock_gettime(waitClock, &now) != 0)
{
result = false;
break;
}
auto current = (vuint64_t)now.tv_sec * 1000000000 + now.tv_nsec;
if (current >= deadline)
{
result = false;
break;
}
remaining = (vint)((deadline - current + 999999) / 1000000);
}
if (internalData->autoReset && internalData->releasedWaiters > 0)
{
internalData->releasedWaiters--;
result = true;
}
else if (!internalData->autoReset && (internalData->signaled || internalData->signalVersion != signalVersion))
{
result = true;
}
DECRC(&internalData->counter);
}
internalData->mutex.Leave();
return result;
}
/***********************************************************************
ThreadPoolLite
***********************************************************************/
namespace threading_internal
{
struct ThreadPoolTask
{
Func<void()> task;
Ptr<ThreadPoolTask> next;
};
struct ThreadPoolData
{
Semaphore semaphore;
EventObject taskFinishEvent;
Ptr<ThreadPoolTask> taskBegin;
Ptr<ThreadPoolTask>* taskEnd = nullptr;
volatile bool stopping = false;
List<Thread*> taskThreads;
};
SpinLock threadPoolLock;
ThreadPoolData* threadPoolData = nullptr;
void ThreadPoolProc(Thread* thread, void* argument)
{
while (true)
{
Ptr<ThreadPoolTask> task;
threadPoolData->semaphore.Wait();
SPIN_LOCK(threadPoolLock)
{
if (threadPoolData->taskBegin)
{
task = threadPoolData->taskBegin;
threadPoolData->taskBegin = task->next;
}
if (!threadPoolData->taskBegin)
{
threadPoolData->taskEnd = &threadPoolData->taskBegin;
threadPoolData->taskFinishEvent.Signal();
}
}
if (task)
{
ThreadLocalStorage::FixStorages();
try
{
task->task();
ThreadLocalStorage::ClearStorages();
}
catch (...)
{
ThreadLocalStorage::ClearStorages();
}
}
else if (threadPoolData->stopping)
{
return;
}
}
}
bool ThreadPoolQueue(const Func<void()>& proc)
{
SPIN_LOCK(threadPoolLock)
{
if (!threadPoolData)
{
threadPoolData = new ThreadPoolData;
threadPoolData->semaphore.Create(0, 65536);
threadPoolData->taskFinishEvent.CreateManualUnsignal(false);
threadPoolData->taskEnd = &threadPoolData->taskBegin;
for (vint i = 0; i < Thread::GetCPUCount() * 4; i++)
{
threadPoolData->taskThreads.Add(Thread::CreateAndStart(&ThreadPoolProc, nullptr, false));
}
}
if (threadPoolData)
{
if (threadPoolData->stopping)
{
return false;
}
auto task = Ptr(new ThreadPoolTask);
task->task = proc;
*threadPoolData->taskEnd = task;
threadPoolData->taskEnd = &task->next;
threadPoolData->semaphore.Release();
threadPoolData->taskFinishEvent.Unsignal();
}
}
return true;
}
bool ThreadPoolStop(bool discardPendingTasks)
{
SPIN_LOCK(threadPoolLock)
{
if (!threadPoolData) return false;
if (threadPoolData->stopping) return false;
threadPoolData->stopping = true;
if (discardPendingTasks)
{
threadPoolData->taskEnd = &threadPoolData->taskBegin;
threadPoolData->taskBegin = nullptr;
}
threadPoolData->semaphore.Release(threadPoolData->taskThreads.Count());
}
threadPoolData->taskFinishEvent.Wait();
// TODO: (enumerable) foreach
for (vint i = 0; i < threadPoolData->taskThreads.Count(); i++)
{
auto thread = threadPoolData->taskThreads[i];
thread->Wait();
delete thread;
}
threadPoolData->taskThreads.Clear();
SPIN_LOCK(threadPoolLock)
{
delete threadPoolData;
threadPoolData = nullptr;
}
return true;
}
}
ThreadPoolLite::ThreadPoolLite()
{
}
ThreadPoolLite::~ThreadPoolLite()
{
}
bool ThreadPoolLite::Queue(void(*proc)(void*), void* argument)
{
return ThreadPoolQueue([proc, argument](){proc(argument);});
}
bool ThreadPoolLite::Queue(const Func<void()>& proc)
{
return ThreadPoolQueue(proc);
}
bool ThreadPoolLite::Stop(bool discardPendingTasks)
{
return ThreadPoolStop(discardPendingTasks);
}
/***********************************************************************
CriticalSection
***********************************************************************/
namespace threading_internal
{
struct CriticalSectionData
{
pthread_mutex_t mutex;
};
}
CriticalSection::CriticalSection()
{
internalData = new CriticalSectionData;
pthread_mutex_init(&internalData->mutex, nullptr);
}
CriticalSection::~CriticalSection()
{
pthread_mutex_destroy(&internalData->mutex);
delete internalData;
}
bool CriticalSection::TryEnter()
{
return pthread_mutex_trylock(&internalData->mutex) == 0;
}
void CriticalSection::Enter()
{
pthread_mutex_lock(&internalData->mutex);
}
void CriticalSection::Leave()
{
pthread_mutex_unlock(&internalData->mutex);
}
CriticalSection::Scope::Scope(CriticalSection& _criticalSection)
:criticalSection(&_criticalSection)
{
criticalSection->Enter();
}
CriticalSection::Scope::~Scope()
{
criticalSection->Leave();
}
/***********************************************************************
ReaderWriterLock
***********************************************************************/
namespace threading_internal
{
struct ReaderWriterLockData
{
pthread_rwlock_t rwlock;
};
}
ReaderWriterLock::ReaderWriterLock()
{
internalData = new ReaderWriterLockData;
pthread_rwlock_init(&internalData->rwlock, nullptr);
}
ReaderWriterLock::~ReaderWriterLock()
{
pthread_rwlock_destroy(&internalData->rwlock);
delete internalData;
}
bool ReaderWriterLock::TryEnterReader()
{
return pthread_rwlock_tryrdlock(&internalData->rwlock) == 0;
}
void ReaderWriterLock::EnterReader()
{
pthread_rwlock_rdlock(&internalData->rwlock);
}
void ReaderWriterLock::LeaveReader()
{
pthread_rwlock_unlock(&internalData->rwlock);
}
bool ReaderWriterLock::TryEnterWriter()
{
return pthread_rwlock_trywrlock(&internalData->rwlock) == 0;
}
void ReaderWriterLock::EnterWriter()
{
pthread_rwlock_wrlock(&internalData->rwlock);
}
void ReaderWriterLock::LeaveWriter()
{
pthread_rwlock_unlock(&internalData->rwlock);
}
ReaderWriterLock::ReaderScope::ReaderScope(ReaderWriterLock& _lock)
:lock(&_lock)
{
lock->EnterReader();
}
ReaderWriterLock::ReaderScope::~ReaderScope()
{
lock->LeaveReader();
}
ReaderWriterLock::WriterScope::WriterScope(ReaderWriterLock& _lock)
:lock(&_lock)
{
lock->EnterWriter();
}
ReaderWriterLock::WriterScope::~WriterScope()
{
lock->LeaveReader();
}
/***********************************************************************
ConditionVariable
***********************************************************************/
namespace threading_internal
{
struct ConditionVariableData
{
pthread_cond_t cond;
};
}
ConditionVariable::ConditionVariable()
{
#define ERROR_MESSAGE_PREFIX L"vl::ConditionVariable::ConditionVariable()#"
internalData = new ConditionVariableData;
#if defined VCZH_APPLE
auto initResult = pthread_cond_init(&internalData->cond, nullptr);
#else
pthread_condattr_t attributes;
auto attributeResult = pthread_condattr_init(&attributes);
if (attributeResult != 0)
{
delete internalData;
internalData = nullptr;
CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Failed to initialize condition-variable attributes.");
}
auto clockResult = pthread_condattr_setclock(&attributes, CLOCK_MONOTONIC);
auto initResult = clockResult == 0 ? pthread_cond_init(&internalData->cond, &attributes) : clockResult;
pthread_condattr_destroy(&attributes);
#endif
if (initResult != 0)
{
delete internalData;
internalData = nullptr;
CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Failed to initialize the condition variable.");
}
#undef ERROR_MESSAGE_PREFIX
}
ConditionVariable::~ConditionVariable()
{
pthread_cond_destroy(&internalData->cond);
delete internalData;
}
bool ConditionVariable::SleepWith(CriticalSection& cs)
{
return pthread_cond_wait(&internalData->cond, &cs.internalData->mutex) == 0;
}
bool ConditionVariable::SleepWithForTime(CriticalSection& cs, vint ms)
{
if (ms < 0) return false;
timespec timeout;
#if defined VCZH_APPLE
constexpr auto waitClock = CLOCK_REALTIME;
#else
constexpr auto waitClock = CLOCK_MONOTONIC;
#endif
if (clock_gettime(waitClock, &timeout) != 0)
{
return false;
}
timeout.tv_sec += ms / 1000;
timeout.tv_nsec += (ms % 1000) * 1000000;
if (timeout.tv_nsec >= 1000000000)
{
timeout.tv_sec++;
timeout.tv_nsec -= 1000000000;
}
else if (timeout.tv_nsec < 0)
{
timeout.tv_sec--;
timeout.tv_nsec += 1000000000;
}
return pthread_cond_timedwait(&internalData->cond, &cs.internalData->mutex, &timeout) == 0;
}
void ConditionVariable::WakeOnePending()
{
pthread_cond_signal(&internalData->cond);
}
void ConditionVariable::WakeAllPendings()
{
pthread_cond_broadcast(&internalData->cond);
}
/***********************************************************************
ThreadLocalStorage
***********************************************************************/
#define KEY ((pthread_key_t&)key)
ThreadLocalStorage::ThreadLocalStorage(Destructor _destructor)
:destructor(_destructor)
{
static_assert(sizeof(key) >= sizeof(pthread_key_t), "ThreadLocalStorage's key storage is not large enouth.");
PushStorage(this);
auto error = pthread_key_create(&KEY, destructor);
CHECK_ERROR(error != EAGAIN && error != ENOMEM, L"vl::ThreadLocalStorage::ThreadLocalStorage()#Failed to create a thread local storage index.");
}
ThreadLocalStorage::~ThreadLocalStorage()
{
pthread_key_delete(KEY);
}
void* ThreadLocalStorage::Get()
{
CHECK_ERROR(!disposed, L"vl::ThreadLocalStorage::Get()#Cannot access a disposed ThreadLocalStorage.");
return pthread_getspecific(KEY);
}
void ThreadLocalStorage::Set(void* data)
{
CHECK_ERROR(!disposed, L"vl::ThreadLocalStorage::Set()#Cannot access a disposed ThreadLocalStorage.");
pthread_setspecific(KEY, data);
}
#undef KEY
}
/***********************************************************************
.\ENCODING\CHARFORMAT\CHARFORMAT.LINUX.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#include <string.h>
#ifndef VCZH_GCC
static_assert(false, "Do not build this file for Windows applications.");
#endif
namespace vl
{
namespace stream
{
using namespace vl::encoding;
bool IsMbcsLeadByte(char c)
{
return (vint8_t)c < 0;
}
void MbcsToWChar(wchar_t* wideBuffer, vint wideChars, vint wideReaded, char* mbcsBuffer, vint mbcsChars)
{
AString a = AString::CopyFrom(mbcsBuffer, mbcsChars);
WString w = atow(a);
memcpy(wideBuffer, w.Buffer(), wideReaded * sizeof(wchar_t));
}
/***********************************************************************
MbcsEncoder
***********************************************************************/
vint MbcsEncoder::WriteString(wchar_t* _buffer, vint chars)
{
WString w = WString::CopyFrom(_buffer, chars);
AString a = wtoa(w);
vint length = a.Length();
vint result = stream->Write((void*)a.Buffer(), length);
if (result != length)
{
Close();
return 0;
}
return chars;
}
/***********************************************************************
Helper Functions
***********************************************************************/
extern bool CanBeMbcs(unsigned char* buffer, vint size);
extern bool CanBeUtf8(unsigned char* buffer, vint size);
extern bool CanBeUtf16(unsigned char* buffer, vint size, bool& hitSurrogatePairs);
extern bool CanBeUtf16BE(unsigned char* buffer, vint size, bool& hitSurrogatePairs);
/***********************************************************************
TestEncoding
***********************************************************************/
extern void TestEncodingInternal(
unsigned char* buffer,
vint size,
BomEncoder::Encoding& encoding,
bool containsBom,
bool utf16HitSurrogatePairs,
bool utf16BEHitSurrogatePairs,
bool roughMbcs,
bool roughUtf8,
bool roughUtf16,
bool roughUtf16BE
)
{
if (roughUtf16 && roughUtf16BE && !roughUtf8)
{
if (utf16BEHitSurrogatePairs && !utf16HitSurrogatePairs)
{
encoding = BomEncoder::Utf16BE;
}
else
{
encoding = BomEncoder::Utf16;
}
}
else
{
encoding = BomEncoder::Utf8;
}
}
}
}
/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET.LINUX.CPP
***********************************************************************/
#if defined VCZH_GCC && !defined VCZH_APPLE
#include <liburing.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <sys/socket.h>
namespace vl::inter_process::async_tcp_socket::linux_socket
{
using namespace collections;
class RingOperation;
class RingRuntime;
class ConnectionState;
class ServerState;
class AsyncSocketConnection;
static thread_local RingRuntime* currentRingRuntime = nullptr;
struct ConnectionCallbackFrame
{
ConnectionState* connection = nullptr;
ConnectionCallbackFrame* previous = nullptr;
};
struct ServerCallbackFrame
{
ServerState* server = nullptr;
ServerCallbackFrame* previous = nullptr;
};
static thread_local ConnectionCallbackFrame* currentConnectionCallbackFrame = nullptr;
static thread_local ServerCallbackFrame* currentServerCallbackFrame = nullptr;
WString LinuxSocketErrorMessage(const wchar_t* operation, vint error)
{
return WString::Unmanaged(operation)
+ L" failed with Linux error "
+ itow(error)
+ L" ("
+ atow(AString::Unmanaged(strerror((int)error)))
+ L").";
}
void CloseFileDescriptor(vint fileDescriptor)
{
if (fileDescriptor >= 0)
{
// Do not retry close after EINTR: the descriptor number can already be reused.
close((int)fileDescriptor);
}
}
/***********************************************************************
OwnedFileDescriptor
***********************************************************************/
class OwnedFileDescriptor
{
private:
vint fileDescriptor = -1;
public:
OwnedFileDescriptor(vint _fileDescriptor)
: fileDescriptor(_fileDescriptor)
{
}
OwnedFileDescriptor(const OwnedFileDescriptor&) = delete;
OwnedFileDescriptor& operator=(const OwnedFileDescriptor&) = delete;
~OwnedFileDescriptor()
{
CloseFileDescriptor(fileDescriptor);
}
vint Get()
{
return fileDescriptor;
}
vint Detach()
{
auto result = fileDescriptor;
fileDescriptor = -1;
return result;
}
};
/***********************************************************************
OperationDrain
***********************************************************************/
class OperationDrain : public Object
{
private:
CriticalSection lockDrain;
vint pendingOperations = 0;
EventObject eventDrained;
public:
OperationDrain()
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::OperationDrain::OperationDrain()#"
CHECK_ERROR(eventDrained.CreateManualUnsignal(true), ERROR_MESSAGE_PREFIX L"Failed to create an operation drain event.");
#undef ERROR_MESSAGE_PREFIX
}
void Begin()
{
CS_LOCK(lockDrain)
{
if (pendingOperations++ == 0)
{
eventDrained.Unsignal();
}
}
}
void End()
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::OperationDrain::End()#"
CS_LOCK(lockDrain)
{
CHECK_ERROR(pendingOperations > 0, ERROR_MESSAGE_PREFIX L"Operation drain bookkeeping became unbalanced.");
if (--pendingOperations == 0)
{
eventDrained.Signal();
}
}
#undef ERROR_MESSAGE_PREFIX
}
void Wait()
{
eventDrained.Wait();
}
};
/***********************************************************************
RingOperationOwner
***********************************************************************/
class RingOperationOwner : public Object
{
friend class RingRuntime;
protected:
Ptr<OperationDrain> operationDrain;
RingOperationOwner()
: operationDrain(Ptr(new OperationDrain))
{
}
virtual void EndTargetOperation() = 0;
virtual void HandleOperationFailure(Ptr<RingOperationOwner> retainedOwner) = 0;
};
/***********************************************************************
RingOperation
***********************************************************************/
class RingOperation
{
friend class RingRuntime;
private:
Ptr<RingOperationOwner> operationOwner;
bool targetOperation = false;
public:
vuint64_t id = 0;
RingOperation(vuint64_t _id, Ptr<RingOperationOwner> _operationOwner = nullptr, bool _targetOperation = false)
: id(_id)
, operationOwner(_operationOwner)
, targetOperation(_targetOperation)
{
}
virtual ~RingOperation() = default;
virtual void Prepare(io_uring_sqe* sqe) noexcept = 0;
virtual void Handle(vint result) = 0;
};
/***********************************************************************
RuntimeWakeOperation
***********************************************************************/
class RuntimeWakeOperation : public RingOperation
{
public:
RuntimeWakeOperation(vuint64_t id)
: RingOperation(id)
{
}
void Prepare(io_uring_sqe* sqe) noexcept override
{
io_uring_prep_nop(sqe);
}
void Handle(vint) override
{
}
};
/***********************************************************************
RingRuntime
***********************************************************************/
class RingRuntime : public Object
{
private:
enum class FlushResult
{
Done,
ConsumeCompletion,
};
io_uring ring = {};
CriticalSection lockRuntime;
ConditionVariable cvRuntimeProgress;
Dictionary<vuint64_t, RingOperation*> operations;
vuint64_t nextOperationId = 1;
bool ringInitialized = false;
bool workerStarted = false;
bool stopRequested = false;
EventObject eventWorkerStopped;
vuint64_t ReserveOperationIdLocked()
{
auto result = nextOperationId++;
if (result == 0)
{
result = nextOperationId++;
}
return result;
}
FlushResult FlushPendingSubmissionsLocked()
{
while (io_uring_sq_ready(&ring))
{
vint submitResult = 0;
do
{
submitResult = io_uring_submit(&ring);
} while (submitResult == -EINTR);
if (submitResult == -EAGAIN)
{
std::abort();
}
if (submitResult == -EBUSY)
{
return FlushResult::ConsumeCompletion;
}
if (submitResult <= 0)
{
std::abort();
}
}
return FlushResult::Done;
}
void FlushPendingSubmissionsCore(bool completionWorker)
{
while (true)
{
FlushResult result = FlushResult::Done;
bool waited = true;
lockRuntime.Enter();
if (!ringInitialized)
{
lockRuntime.Leave();
return;
}
result = FlushPendingSubmissionsLocked();
if (result == FlushResult::ConsumeCompletion && !completionWorker)
{
waited = cvRuntimeProgress.SleepWith(lockRuntime);
}
lockRuntime.Leave();
if (!waited)
{
std::abort();
}
if (result == FlushResult::Done)
{
return;
}
if (completionWorker)
{
return;
}
}
}
bool SubmitLocked(RingOperation* operation, bool allowStopping)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::RingRuntime::SubmitLocked(RingOperation*, bool)#"
if (!ringInitialized || (!allowStopping && stopRequested) || operation->id == 0 || operations.Keys().Contains(operation->id))
{
delete operation;
return false;
}
try
{
operations.Add(operation->id, operation);
}
catch (...)
{
delete operation;
return false;
}
auto sqe = io_uring_get_sqe(&ring);
if (!sqe)
{
operations.Remove(operation->id);
delete operation;
return false;
}
operation->Prepare(sqe);
io_uring_sqe_set_data64(sqe, operation->id);
vint submitResult = 0;
do
{
submitResult = io_uring_submit(&ring);
} while (submitResult == -EINTR);
if (submitResult == -EAGAIN || (submitResult <= 0 && submitResult != -EBUSY))
{
std::abort();
}
#undef ERROR_MESSAGE_PREFIX
return true;
}
void WorkerLoop()
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::RingRuntime::WorkerLoop()#"
currentRingRuntime = this;
while (true)
{
io_uring_cqe* cqe = nullptr;
vint waitResult = 0;
do
{
waitResult = io_uring_wait_cqe(&ring, &cqe);
} while (waitResult == -EINTR);
if (waitResult != 0 || !cqe)
{
std::abort();
}
auto operationId = io_uring_cqe_get_data64(cqe);
RingOperation* operation = nullptr;
CS_LOCK(lockRuntime)
{
if (operations.Keys().Contains(operationId))
{
operation = operations[operationId];
}
}
if (!operation)
{
std::abort();
}
bool operationFailed = false;
try
{
operation->Handle((vint)cqe->res);
}
catch (...)
{
operationFailed = true;
}
io_uring_cqe_seen(&ring, cqe);
CS_LOCK(lockRuntime)
{
operations.Remove(operationId);
cvRuntimeProgress.WakeAllPendings();
}
auto operationOwner = operation->operationOwner;
auto operationDrain = operationOwner ? operationOwner->operationDrain : nullptr;
auto targetOperation = operation->targetOperation;
delete operation;
if (operationOwner && targetOperation)
{
operationOwner->EndTargetOperation();
}
if (operationOwner && operationFailed)
{
try
{
operationOwner->HandleOperationFailure(operationOwner);
}
catch (...)
{
}
}
operationOwner = nullptr;
if (operationDrain)
{
operationDrain->End();
}
FlushPendingSubmissionsCore(true);
bool shouldStop = false;
CS_LOCK(lockRuntime)
{
shouldStop = stopRequested && operations.Count() == 0;
}
if (shouldStop)
{
break;
}
}
CS_LOCK(lockRuntime)
{
io_uring_queue_exit(&ring);
ringInitialized = false;
cvRuntimeProgress.WakeAllPendings();
}
currentRingRuntime = nullptr;
eventWorkerStopped.Signal();
#undef ERROR_MESSAGE_PREFIX
}
public:
RingRuntime()
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::RingRuntime::RingRuntime()#"
CHECK_ERROR(eventWorkerStopped.CreateManualUnsignal(false), ERROR_MESSAGE_PREFIX L"Failed to create the io_uring worker drain event.");
io_uring_params parameters = {};
auto initResult = io_uring_queue_init_params(1024, &ring, &parameters);
CHECK_ERROR(initResult == 0, ERROR_MESSAGE_PREFIX L"Failed to initialize io_uring.");
ringInitialized = true;
auto probe = io_uring_get_probe_ring(&ring);
if (!probe)
{
io_uring_queue_exit(&ring);
ringInitialized = false;
CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Failed to probe io_uring operations.");
}
auto supported =
io_uring_opcode_supported(probe, IORING_OP_NOP) &&
io_uring_opcode_supported(probe, IORING_OP_ACCEPT) &&
io_uring_opcode_supported(probe, IORING_OP_CONNECT) &&
io_uring_opcode_supported(probe, IORING_OP_RECV) &&
io_uring_opcode_supported(probe, IORING_OP_SEND) &&
io_uring_opcode_supported(probe, IORING_OP_ASYNC_CANCEL) &&
io_uring_opcode_supported(probe, IORING_OP_TIMEOUT);
io_uring_free_probe(probe);
if (!supported)
{
io_uring_queue_exit(&ring);
ringInitialized = false;
CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Requires io_uring accept, connect, receive, send, async-cancel, timeout, and nop operations.");
}
#undef ERROR_MESSAGE_PREFIX
}
~RingRuntime()
{
Stop();
}
static Ptr<RingRuntime> Create(bool startWorker)
{
auto result = Ptr(new RingRuntime);
if (startWorker)
{
result->Start(result);
}
return result;
}
void Start(Ptr<RingRuntime> retainedRuntime)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::RingRuntime::Start(Ptr<RingRuntime>)#"
CS_LOCK(lockRuntime)
{
CHECK_ERROR(!workerStarted && !stopRequested && ringInitialized, ERROR_MESSAGE_PREFIX L"The io_uring runtime can only be started once before stopping.");
auto worker = Thread::CreateAndStart(Func<void()>([retainedRuntime]()
{
retainedRuntime->WorkerLoop();
}), true);
CHECK_ERROR(worker != nullptr, ERROR_MESSAGE_PREFIX L"Failed to start the io_uring completion worker.");
workerStarted = true;
}
#undef ERROR_MESSAGE_PREFIX
}
vuint64_t ReserveOperationId()
{
vuint64_t result = 0;
CS_LOCK(lockRuntime)
{
result = ReserveOperationIdLocked();
}
return result;
}
bool Submit(RingOperation* operation)
{
bool result = false;
CS_LOCK(lockRuntime)
{
result = SubmitLocked(operation, false);
}
return result;
}
void FlushPendingSubmissions()
{
if (currentRingRuntime != this)
{
FlushPendingSubmissionsCore(false);
}
}
void Stop();
};
void RingRuntime::Stop()
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::RingRuntime::Stop()#"
bool waitForWorker = false;
bool signalWithoutWorker = false;
bool wakeFailed = false;
bool wakeSubmitted = false;
CS_LOCK(lockRuntime)
{
if (!stopRequested)
{
if (workerStarted)
{
if (currentRingRuntime == this)
{
stopRequested = true;
}
else
{
auto operation = new RuntimeWakeOperation(ReserveOperationIdLocked());
if (SubmitLocked(operation, true))
{
stopRequested = true;
wakeSubmitted = true;
}
else
{
wakeFailed = true;
}
}
}
else
{
stopRequested = true;
if (ringInitialized)
{
io_uring_queue_exit(&ring);
ringInitialized = false;
cvRuntimeProgress.WakeAllPendings();
}
signalWithoutWorker = true;
}
}
waitForWorker = stopRequested && workerStarted && currentRingRuntime != this;
}
CHECK_ERROR(!wakeFailed, ERROR_MESSAGE_PREFIX L"Failed to wake the stopping io_uring worker.");
if (wakeSubmitted)
{
FlushPendingSubmissions();
}
if (signalWithoutWorker)
{
eventWorkerStopped.Signal();
}
if (waitForWorker)
{
eventWorkerStopped.Wait();
}
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
ConnectionState
***********************************************************************/
class ConnectionState : public RingOperationOwner
{
friend class AsyncSocketConnection;
private:
class ReceiveOperation;
class WriteOperation;
class ConnectOperation;
class RetryOperation;
class CancelOperation;
Ptr<RingRuntime> runtime;
IAsyncSocketConnection* owner = nullptr;
// covers all fields below, callback counts, and target operation counts
CriticalSection lockState;
ConditionVariable cvCallbacks;
vint fileDescriptor = -1;
IAsyncSocketCallback* callback = nullptr;
bool connected = false;
bool stopping = false;
bool reading = false;
bool writePending = false;
bool terminalPending = false;
bool disconnectedNotified = false;
vint activeCallbacks = 0;
vuint64_t readOperationId = 0;
vuint64_t writeOperationId = 0;
vuint64_t connectOperationId = 0;
vuint64_t retryOperationId = 0;
bool readCancelRequested = false;
bool writeCancelRequested = false;
bool connectCancelRequested = false;
bool retryCancelRequested = false;
vint targetOperations = 0;
bool clientMode = false;
vint clientPort = 0;
ClientStatus clientStatus = ClientStatus::Ready;
vint clientAttempts = 0;
EventObject eventWaitForServer;
void BeginTargetLocked()
{
targetOperations++;
operationDrain->Begin();
}
void BeginCancelLocked()
{
operationDrain->Begin();
}
void RollbackTargetLocked()
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::RollbackTargetLocked()#"
CHECK_ERROR(targetOperations > 0, ERROR_MESSAGE_PREFIX L"Target-operation bookkeeping became unbalanced.");
targetOperations--;
operationDrain->End();
#undef ERROR_MESSAGE_PREFIX
}
void RollbackCancelLocked()
{
operationDrain->End();
}
void EndTargetOperation() override
{
CS_LOCK(lockState)
{
if (targetOperations <= 0)
{
std::abort();
}
targetOperations--;
if (stopping && targetOperations == 0 && fileDescriptor >= 0)
{
CloseFileDescriptor(fileDescriptor);
fileDescriptor = -1;
}
}
}
void HandleOperationFailure(Ptr<RingOperationOwner> retainedOwner) override
{
auto retainedState = retainedOwner.Cast<ConnectionState>();
if (!retainedState)
{
std::abort();
}
try
{
BeginTerminal(retainedState, EIO, true);
}
catch (...)
{
Stop(retainedState);
}
}
vint CurrentCallbackDepth()
{
vint result = 0;
for (auto frame = currentConnectionCallbackFrame; frame; frame = frame->previous)
{
if (frame->connection == this)
{
result++;
}
}
return result;
}
IAsyncSocketCallback* BeginCallback(bool terminal)
{
IAsyncSocketCallback* result = nullptr;
CS_LOCK(lockState)
{
if (callback && (terminal || (!stopping && !terminalPending)))
{
result = callback;
activeCallbacks++;
}
}
return result;
}
void EndCallback()
{
CS_LOCK(lockState)
{
activeCallbacks--;
cvCallbacks.WakeAllPendings();
}
}
template<typename TCallback>
bool InvokeCallback(bool terminal, TCallback&& invoke)
{
auto installed = BeginCallback(terminal);
if (!installed)
{
return false;
}
ConnectionCallbackFrame frame{ this, currentConnectionCallbackFrame };
currentConnectionCallbackFrame = &frame;
try
{
invoke(installed);
}
catch (...)
{
}
currentConnectionCallbackFrame = frame.previous;
EndCallback();
return true;
}
void PostRead(Ptr<ConnectionState> retainedState);
void BeginTerminal(Ptr<ConnectionState> retainedState, vint error, bool reportError);
void StartConnectAttempt(Ptr<ConnectionState> retainedState);
void DeliverConnectFailure(Ptr<ConnectionState> retainedState, vint error, bool fatal);
void ScheduleRetry(Ptr<ConnectionState> retainedState);
public:
ConnectionState(Ptr<RingRuntime> _runtime, bool _clientMode, vint _clientPort)
: runtime(_runtime)
, clientMode(_clientMode)
, clientPort(_clientPort)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::ConnectionState(Ptr<RingRuntime>, bool, vint)#"
CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), ERROR_MESSAGE_PREFIX L"Failed to create the client wait event.");
#undef ERROR_MESSAGE_PREFIX
}
ConnectionState(Ptr<RingRuntime> _runtime, vint _fileDescriptor)
: runtime(_runtime)
, fileDescriptor(_fileDescriptor)
, connected(true)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::ConnectionState(Ptr<RingRuntime>, vint)#"
CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), ERROR_MESSAGE_PREFIX L"Failed to create the client wait event.");
#undef ERROR_MESSAGE_PREFIX
}
void InstallCallback(Ptr<ConnectionState> retainedState, IAsyncSocketCallback* value);
void BeginReading(Ptr<ConnectionState> retainedState);
void Write(Ptr<ConnectionState> retainedState, Ptr<AsyncSocketBuffer> buffer);
void Stop(Ptr<ConnectionState> retainedState);
void WaitForServer(Ptr<ConnectionState> retainedState);
ClientStatus GetStatus();
};
/***********************************************************************
ConnectionState::CancelOperation
***********************************************************************/
class ConnectionState::CancelOperation : public RingOperation
{
public:
Ptr<ConnectionState> connection;
vuint64_t targetId = 0;
CancelOperation(vuint64_t id, Ptr<ConnectionState> _connection, vuint64_t _targetId)
: RingOperation(id, _connection, false)
, connection(_connection)
, targetId(_targetId)
{
}
void Prepare(io_uring_sqe* sqe) noexcept override
{
io_uring_prep_cancel64(sqe, targetId, 0);
}
void Handle(vint) override
{
CS_LOCK(connection->lockState)
{
}
}
};
/***********************************************************************
ConnectionState::ReceiveOperation
***********************************************************************/
class ConnectionState::ReceiveOperation : public RingOperation
{
public:
Ptr<ConnectionState> connection;
vint fileDescriptor = -1;
Array<vuint8_t> buffer;
ReceiveOperation(vuint64_t id, Ptr<ConnectionState> _connection, vint _fileDescriptor)
: RingOperation(id, _connection, true)
, connection(_connection)
, fileDescriptor(_fileDescriptor)
{
buffer.Resize(65536);
}
void Prepare(io_uring_sqe* sqe) noexcept override
{
io_uring_prep_recv(sqe, (int)fileDescriptor, &buffer[0], (size_t)buffer.Count(), 0);
}
void Handle(vint result) override
{
bool active = false;
CS_LOCK(connection->lockState)
{
if (connection->readOperationId == id)
{
connection->readOperationId = 0;
connection->readCancelRequested = false;
active = !connection->stopping && connection->connected && connection->reading;
}
}
if (!active)
{
return;
}
if (result > 0 && result <= buffer.Count())
{
auto invoked = connection->InvokeCallback(false, [&](IAsyncSocketCallback* installed)
{
installed->OnRead(&buffer[0], result);
});
if (invoked)
{
connection->PostRead(connection);
}
}
else if (result == 0)
{
connection->BeginTerminal(connection, 0, false);
}
else
{
auto error = result < 0 ? -result : EIO;
connection->BeginTerminal(connection, error, true);
}
}
};
/***********************************************************************
ConnectionState::WriteOperation
***********************************************************************/
class ConnectionState::WriteOperation : public RingOperation
{
public:
Ptr<ConnectionState> connection;
vint fileDescriptor = -1;
Ptr<AsyncSocketBuffer> buffer;
vint offset = 0;
bool empty = false;
WriteOperation(vuint64_t id, Ptr<ConnectionState> _connection, vint _fileDescriptor, Ptr<AsyncSocketBuffer> _buffer, vint _offset)
: RingOperation(id, _connection, true)
, connection(_connection)
, fileDescriptor(_fileDescriptor)
, buffer(_buffer)
, offset(_offset)
, empty(_buffer->data.Count() == 0)
{
}
void Prepare(io_uring_sqe* sqe) noexcept override
{
if (empty)
{
io_uring_prep_nop(sqe);
}
else
{
io_uring_prep_send(
sqe,
(int)fileDescriptor,
&buffer->data[offset],
(size_t)(buffer->data.Count() - offset),
MSG_NOSIGNAL
);
}
}
void Handle(vint result) override
{
if (empty)
{
bool deliver = false;
CS_LOCK(connection->lockState)
{
if (connection->writeOperationId == id)
{
connection->writeOperationId = 0;
connection->writeCancelRequested = false;
if (!connection->stopping && connection->connected && result == 0)
{
connection->writePending = false;
deliver = true;
}
}
}
if (deliver)
{
connection->InvokeCallback(false, [&](IAsyncSocketCallback* installed)
{
installed->OnWriteCompleted(buffer);
});
}
else if (result < 0 && result != -ECANCELED)
{
connection->BeginTerminal(connection, -result, true);
}
return;
}
if (result > 0 && result <= buffer->data.Count() - offset)
{
auto nextOffset = offset + result;
if (nextOffset == buffer->data.Count())
{
bool deliver = false;
CS_LOCK(connection->lockState)
{
if (connection->writeOperationId == id)
{
connection->writeOperationId = 0;
connection->writeCancelRequested = false;
if (!connection->stopping && connection->connected)
{
connection->writePending = false;
deliver = true;
}
}
}
if (deliver)
{
connection->InvokeCallback(false, [&](IAsyncSocketCallback* installed)
{
installed->OnWriteCompleted(buffer);
});
}
}
else
{
auto nextId = connection->runtime->ReserveOperationId();
auto next = new WriteOperation(nextId, connection, fileDescriptor, buffer, nextOffset);
bool attempted = false;
bool submissionFailed = false;
CS_LOCK(connection->lockState)
{
if (connection->writeOperationId == id && !connection->stopping && connection->connected)
{
attempted = true;
connection->writeOperationId = nextId;
connection->writeCancelRequested = false;
connection->BeginTargetLocked();
if (!connection->runtime->Submit(next))
{
connection->writeOperationId = 0;
connection->RollbackTargetLocked();
submissionFailed = true;
}
}
}
if (attempted && !submissionFailed)
{
connection->runtime->FlushPendingSubmissions();
}
if (!attempted)
{
delete next;
}
else if (submissionFailed)
{
connection->BeginTerminal(connection, EIO, true);
}
}
}
else
{
bool active = false;
CS_LOCK(connection->lockState)
{
if (connection->writeOperationId == id)
{
connection->writeOperationId = 0;
connection->writeCancelRequested = false;
active = !connection->stopping && connection->connected;
}
}
if (active)
{
auto error = result < 0 ? -result : EPIPE;
connection->BeginTerminal(connection, error, true);
}
}
}
};
/***********************************************************************
ConnectionState::ConnectOperation
***********************************************************************/
class ConnectionState::ConnectOperation : public RingOperation
{
public:
Ptr<ConnectionState> connection;
OwnedFileDescriptor ownedSocket;
sockaddr_in address = {};
ConnectOperation(vuint64_t id, Ptr<ConnectionState> _connection, vint _fileDescriptor, vint port)
: RingOperation(id, _connection, true)
, connection(_connection)
, ownedSocket(_fileDescriptor)
{
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
address.sin_port = htons((vuint16_t)port);
}
void Prepare(io_uring_sqe* sqe) noexcept override
{
io_uring_prep_connect(sqe, (int)ownedSocket.Get(), (sockaddr*)&address, sizeof(address));
}
void Handle(vint result) override
{
bool accepted = false;
bool failed = false;
bool fatal = false;
CS_LOCK(connection->lockState)
{
if (connection->connectOperationId == id)
{
connection->connectOperationId = 0;
connection->connectCancelRequested = false;
if (!connection->stopping && connection->clientStatus == ClientStatus::WaitingForServer)
{
if (result == 0)
{
connection->connected = true;
connection->clientStatus = ClientStatus::Connected;
ownedSocket.Detach();
accepted = true;
}
else
{
connection->fileDescriptor = -1;
failed = true;
fatal = connection->clientAttempts >= AsyncSocketClientRetryCount;
}
}
else
{
if (connection->fileDescriptor == ownedSocket.Get())
{
connection->fileDescriptor = -1;
}
}
}
}
if (accepted)
{
connection->InvokeCallback(false, [](IAsyncSocketCallback* installed)
{
installed->OnConnected();
});
connection->eventWaitForServer.Signal();
}
else if (failed)
{
connection->DeliverConnectFailure(connection, result < 0 ? -result : EIO, fatal);
}
}
};
/***********************************************************************
ConnectionState::RetryOperation
***********************************************************************/
class ConnectionState::RetryOperation : public RingOperation
{
public:
Ptr<ConnectionState> connection;
__kernel_timespec timeout = {};
RetryOperation(vuint64_t id, Ptr<ConnectionState> _connection)
: RingOperation(id, _connection, true)
, connection(_connection)
{
timeout.tv_sec = AsyncSocketClientRetryDelay / 1000;
timeout.tv_nsec = (AsyncSocketClientRetryDelay % 1000) * 1000000;
}
void Prepare(io_uring_sqe* sqe) noexcept override
{
io_uring_prep_timeout(sqe, &timeout, 0, 0);
}
void Handle(vint result) override
{
bool active = false;
CS_LOCK(connection->lockState)
{
if (connection->retryOperationId == id)
{
connection->retryOperationId = 0;
connection->retryCancelRequested = false;
active = !connection->stopping && connection->clientStatus == ClientStatus::WaitingForServer;
}
}
if (!active)
{
return;
}
if (result == -ETIME)
{
connection->StartConnectAttempt(connection);
}
else if (result != -ECANCELED)
{
connection->BeginTerminal(connection, result < 0 ? -result : EIO, true);
}
}
};
/***********************************************************************
ConnectionState
***********************************************************************/
void ConnectionState::InstallCallback(Ptr<ConnectionState>, IAsyncSocketCallback* value)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::InstallCallback(Ptr<ConnectionState>, IAsyncSocketCallback*)#"
auto callbackDepth = CurrentCallbackDepth();
if (!value)
{
CS_LOCK(lockState)
{
callback = nullptr;
while (activeCallbacks > callbackDepth)
{
cvCallbacks.SleepWith(lockState);
}
}
return;
}
bool canInstall = false;
CS_LOCK(lockState)
{
canInstall = callback == nullptr && !stopping && owner != nullptr;
if (canInstall)
{
callback = value;
activeCallbacks++;
}
}
CHECK_ERROR(canInstall, ERROR_MESSAGE_PREFIX L"Cannot replace a callback or install one on a stopped connection.");
ConnectionCallbackFrame frame{ this, currentConnectionCallbackFrame };
currentConnectionCallbackFrame = &frame;
try
{
value->OnInstalled(owner);
}
catch (...)
{
}
currentConnectionCallbackFrame = frame.previous;
EndCallback();
#undef ERROR_MESSAGE_PREFIX
}
void ConnectionState::PostRead(Ptr<ConnectionState> retainedState)
{
auto operationId = runtime->ReserveOperationId();
ReceiveOperation* operation = nullptr;
bool submitted = false;
bool submissionFailed = false;
CS_LOCK(lockState)
{
if (connected && !stopping && !terminalPending && reading && readOperationId == 0)
{
operation = new ReceiveOperation(operationId, retainedState, fileDescriptor);
readOperationId = operationId;
readCancelRequested = false;
BeginTargetLocked();
submitted = runtime->Submit(operation);
if (!submitted)
{
readOperationId = 0;
RollbackTargetLocked();
submissionFailed = true;
}
}
}
if (submitted)
{
runtime->FlushPendingSubmissions();
}
if (submissionFailed)
{
BeginTerminal(retainedState, EIO, true);
}
}
void ConnectionState::BeginReading(Ptr<ConnectionState> retainedState)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::BeginReading(Ptr<ConnectionState>)#"
CS_LOCK(lockState)
{
CHECK_ERROR(connected && !stopping && !terminalPending, ERROR_MESSAGE_PREFIX L"Requires a connected connection.");
CHECK_ERROR(callback != nullptr, ERROR_MESSAGE_PREFIX L"Requires an installed callback.");
CHECK_ERROR(!reading, ERROR_MESSAGE_PREFIX L"Can only be called once.");
reading = true;
}
PostRead(retainedState);
#undef ERROR_MESSAGE_PREFIX
}
void ConnectionState::Write(Ptr<ConnectionState> retainedState, Ptr<AsyncSocketBuffer> buffer)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::Write(Ptr<ConnectionState>, Ptr<AsyncSocketBuffer>)#"
CHECK_ERROR(buffer, ERROR_MESSAGE_PREFIX L"Requires a buffer.");
auto operationId = runtime->ReserveOperationId();
bool submitted = false;
CS_LOCK(lockState)
{
CHECK_ERROR(connected && !stopping && !terminalPending && !writePending, ERROR_MESSAGE_PREFIX L"Requires a connected connection with no outstanding write.");
auto operation = new WriteOperation(operationId, retainedState, fileDescriptor, buffer, 0);
writePending = true;
writeOperationId = operationId;
writeCancelRequested = false;
BeginTargetLocked();
submitted = runtime->Submit(operation);
if (!submitted)
{
writePending = false;
writeOperationId = 0;
RollbackTargetLocked();
}
}
if (submitted)
{
runtime->FlushPendingSubmissions();
}
CHECK_ERROR(submitted, ERROR_MESSAGE_PREFIX L"Failed to submit the write operation.");
#undef ERROR_MESSAGE_PREFIX
}
void ConnectionState::BeginTerminal(Ptr<ConnectionState> retainedState, vint error, bool reportError)
{
bool claimed = false;
CS_LOCK(lockState)
{
if (!stopping && !terminalPending)
{
terminalPending = true;
claimed = true;
}
}
if (!claimed)
{
return;
}
if (reportError)
{
InvokeCallback(true, [&](IAsyncSocketCallback* installed)
{
installed->OnError(LinuxSocketErrorMessage(L"Asynchronous socket operation", error), true);
});
}
Stop(retainedState);
}
void ConnectionState::StartConnectAttempt(Ptr<ConnectionState> retainedState)
{
bool active = false;
bool fatal = false;
CS_LOCK(lockState)
{
if (clientMode && !stopping && clientStatus == ClientStatus::WaitingForServer && connectOperationId == 0 && retryOperationId == 0)
{
clientAttempts++;
fatal = clientAttempts >= AsyncSocketClientRetryCount;
active = true;
}
}
if (!active)
{
return;
}
OwnedFileDescriptor createdSocket(socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP));
if (createdSocket.Get() < 0)
{
DeliverConnectFailure(retainedState, errno, fatal);
return;
}
vint reuseAddress = 1;
if (setsockopt(createdSocket.Get(), SOL_SOCKET, SO_REUSEADDR, &reuseAddress, sizeof(reuseAddress)) != 0)
{
DeliverConnectFailure(retainedState, errno, fatal);
return;
}
auto operationId = runtime->ReserveOperationId();
auto createdSocketValue = createdSocket.Get();
auto operation = new ConnectOperation(operationId, retainedState, createdSocketValue, clientPort);
createdSocket.Detach();
bool attempted = false;
bool submissionFailed = false;
CS_LOCK(lockState)
{
if (!stopping && clientStatus == ClientStatus::WaitingForServer && connectOperationId == 0 && retryOperationId == 0)
{
attempted = true;
fileDescriptor = createdSocketValue;
connectOperationId = operationId;
connectCancelRequested = false;
BeginTargetLocked();
if (!runtime->Submit(operation))
{
fileDescriptor = -1;
connectOperationId = 0;
RollbackTargetLocked();
submissionFailed = true;
}
}
}
if (attempted && !submissionFailed)
{
runtime->FlushPendingSubmissions();
}
if (!attempted)
{
delete operation;
}
else if (submissionFailed)
{
DeliverConnectFailure(retainedState, EIO, fatal);
}
}
void ConnectionState::DeliverConnectFailure(Ptr<ConnectionState> retainedState, vint error, bool fatal)
{
bool deliver = false;
CS_LOCK(lockState)
{
deliver = !stopping && clientStatus == ClientStatus::WaitingForServer;
}
if (!deliver)
{
return;
}
InvokeCallback(false, [&](IAsyncSocketCallback* installed)
{
installed->OnError(LinuxSocketErrorMessage(L"io_uring connect", error), fatal);
});
if (fatal)
{
Stop(retainedState);
}
else
{
ScheduleRetry(retainedState);
}
}
void ConnectionState::ScheduleRetry(Ptr<ConnectionState> retainedState)
{
auto operationId = runtime->ReserveOperationId();
auto operation = new RetryOperation(operationId, retainedState);
bool attempted = false;
bool submissionFailed = false;
CS_LOCK(lockState)
{
if (!stopping && clientStatus == ClientStatus::WaitingForServer && retryOperationId == 0 && connectOperationId == 0)
{
attempted = true;
retryOperationId = operationId;
retryCancelRequested = false;
BeginTargetLocked();
if (!runtime->Submit(operation))
{
retryOperationId = 0;
RollbackTargetLocked();
submissionFailed = true;
}
}
}
if (attempted && !submissionFailed)
{
runtime->FlushPendingSubmissions();
}
if (!attempted)
{
delete operation;
}
else if (submissionFailed)
{
BeginTerminal(retainedState, EIO, true);
}
}
void ConnectionState::Stop(Ptr<ConnectionState> retainedState)
{
bool firstStop = false;
bool cancellationSubmitted = false;
CS_LOCK(lockState)
{
if (!stopping)
{
stopping = true;
firstStop = true;
connected = false;
reading = false;
writePending = false;
terminalPending = false;
if (clientMode)
{
clientStatus = ClientStatus::Disconnected;
}
}
auto collect = [&](vuint64_t operationId, bool& cancelRequested)
{
if (operationId != 0 && !cancelRequested)
{
auto operation = new CancelOperation(runtime->ReserveOperationId(), retainedState, operationId);
cancelRequested = true;
BeginCancelLocked();
if (!runtime->Submit(operation))
{
cancelRequested = false;
RollbackCancelLocked();
}
else
{
cancellationSubmitted = true;
}
}
};
collect(readOperationId, readCancelRequested);
collect(writeOperationId, writeCancelRequested);
collect(connectOperationId, connectCancelRequested);
collect(retryOperationId, retryCancelRequested);
if (firstStop && fileDescriptor >= 0)
{
shutdown((int)fileDescriptor, SHUT_RDWR);
if (targetOperations == 0)
{
CloseFileDescriptor(fileDescriptor);
fileDescriptor = -1;
}
}
}
if (cancellationSubmitted)
{
runtime->FlushPendingSubmissions();
}
if (currentRingRuntime != runtime.Obj())
{
operationDrain->Wait();
}
auto callbackDepth = CurrentCallbackDepth();
IAsyncSocketCallback* installed = nullptr;
lockState.Enter();
while (activeCallbacks > callbackDepth)
{
cvCallbacks.SleepWith(lockState);
}
if (!disconnectedNotified)
{
disconnectedNotified = true;
if (callback)
{
installed = callback;
activeCallbacks++;
}
}
lockState.Leave();
if (installed)
{
ConnectionCallbackFrame frame{ this, currentConnectionCallbackFrame };
currentConnectionCallbackFrame = &frame;
try
{
installed->OnDisconnected();
}
catch (...)
{
}
currentConnectionCallbackFrame = frame.previous;
EndCallback();
}
if (callbackDepth == 0)
{
CS_LOCK(lockState)
{
while (activeCallbacks > 0)
{
cvCallbacks.SleepWith(lockState);
}
}
}
if (clientMode)
{
eventWaitForServer.Signal();
}
}
void ConnectionState::WaitForServer(Ptr<ConnectionState> retainedState)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::WaitForServer(Ptr<ConnectionState>)#"
bool begin = false;
CS_LOCK(lockState)
{
if (clientMode && clientStatus == ClientStatus::Ready && !stopping)
{
clientStatus = ClientStatus::WaitingForServer;
begin = true;
}
}
CHECK_ERROR(begin, ERROR_MESSAGE_PREFIX L"Can only be called once while the client is ready.");
StartConnectAttempt(retainedState);
eventWaitForServer.Wait();
#undef ERROR_MESSAGE_PREFIX
}
ClientStatus ConnectionState::GetStatus()
{
ClientStatus result;
CS_LOCK(lockState)
{
result = clientStatus;
}
return result;
}
/***********************************************************************
AsyncSocketConnection
***********************************************************************/
class AsyncSocketConnection : public Object, public virtual IAsyncSocketConnection
{
private:
Ptr<ConnectionState> state;
public:
AsyncSocketConnection(Ptr<ConnectionState> _state)
: state(_state)
{
state->owner = this;
}
~AsyncSocketConnection()
{
state->Stop(state);
state->owner = nullptr;
}
void InstallCallback(IAsyncSocketCallback* callback) override
{
state->InstallCallback(state, callback);
}
void BeginReadingLoopUnsafe() override
{
state->BeginReading(state);
}
void WriteAsync(Ptr<AsyncSocketBuffer> buffer) override
{
state->Write(state, buffer);
}
void Stop() override
{
state->Stop(state);
}
};
/***********************************************************************
ServerState
***********************************************************************/
class ServerState : public RingOperationOwner
{
private:
class AcceptOperation;
class CancelOperation;
Ptr<RingRuntime> runtime;
vint port = 0;
// covers all fields below, callback counts, and target operation counts
CriticalSection lockState;
ConditionVariable cvCallbacks;
IAsyncSocketServerCallback* callback = nullptr;
vint listener = -1;
bool startCalled = false;
bool starting = false;
bool started = false;
bool stopping = false;
bool stopped = false;
bool unexpectedStopNotified = false;
EventObject eventStartFinished;
vuint64_t acceptOperationId = 0;
bool acceptCancelRequested = false;
vint targetOperations = 0;
vint activeCallbacks = 0;
List<Ptr<AsyncSocketConnection>> connections;
void FinishStart()
{
CS_LOCK(lockState)
{
starting = false;
eventStartFinished.Signal();
}
}
class StartScope
{
private:
ServerState* server = nullptr;
public:
StartScope(ServerState* _server)
: server(_server)
{
}
~StartScope()
{
server->FinishStart();
}
};
void BeginTargetLocked()
{
targetOperations++;
operationDrain->Begin();
}
void BeginCancelLocked()
{
operationDrain->Begin();
}
void RollbackTargetLocked()
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ServerState::RollbackTargetLocked()#"
CHECK_ERROR(targetOperations > 0, ERROR_MESSAGE_PREFIX L"Target-operation bookkeeping became unbalanced.");
targetOperations--;
operationDrain->End();
#undef ERROR_MESSAGE_PREFIX
}
void RollbackCancelLocked()
{
operationDrain->End();
}
void EndTargetOperation() override
{
CS_LOCK(lockState)
{
if (targetOperations <= 0)
{
std::abort();
}
targetOperations--;
if (stopping && targetOperations == 0 && listener >= 0)
{
CloseFileDescriptor(listener);
listener = -1;
}
}
}
void HandleOperationFailure(Ptr<RingOperationOwner> retainedOwner) override
{
auto retainedState = retainedOwner.Cast<ServerState>();
if (!retainedState)
{
std::abort();
}
HandleUnexpectedStop(retainedState);
}
void HandleUnexpectedStop(Ptr<ServerState> retainedState)
{
IAsyncSocketServerCallback* installedCallback = nullptr;
CS_LOCK(lockState)
{
if (started && !stopping && !unexpectedStopNotified)
{
unexpectedStopNotified = true;
installedCallback = callback;
if (installedCallback)
{
activeCallbacks++;
}
}
}
if (installedCallback)
{
ServerCallbackFrame frame{ this, currentServerCallbackFrame };
currentServerCallbackFrame = &frame;
try
{
installedCallback->OnServerStopped();
}
catch (...)
{
}
currentServerCallbackFrame = frame.previous;
EndCallback();
}
Stop(retainedState);
}
vint CurrentCallbackDepth()
{
vint result = 0;
for (auto frame = currentServerCallbackFrame; frame; frame = frame->previous)
{
if (frame->server == this)
{
result++;
}
}
return result;
}
void EndCallback()
{
CS_LOCK(lockState)
{
activeCallbacks--;
cvCallbacks.WakeAllPendings();
}
}
bool PostAccept(Ptr<ServerState> retainedState);
public:
ServerState(Ptr<RingRuntime> _runtime, vint _port)
: runtime(_runtime)
, port(_port)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ServerState::ServerState(Ptr<RingRuntime>, vint)#"
CHECK_ERROR(eventStartFinished.CreateManualUnsignal(true), ERROR_MESSAGE_PREFIX L"Failed to create the server startup drain event.");
#undef ERROR_MESSAGE_PREFIX
}
void Start(Ptr<ServerState> retainedState, IAsyncSocketServerCallback* value);
void Stop(Ptr<ServerState> retainedState);
bool IsStopped();
};
/***********************************************************************
ServerState::CancelOperation
***********************************************************************/
class ServerState::CancelOperation : public RingOperation
{
public:
Ptr<ServerState> server;
vuint64_t targetId = 0;
CancelOperation(vuint64_t id, Ptr<ServerState> _server, vuint64_t _targetId)
: RingOperation(id, _server, false)
, server(_server)
, targetId(_targetId)
{
}
void Prepare(io_uring_sqe* sqe) noexcept override
{
io_uring_prep_cancel64(sqe, targetId, 0);
}
void Handle(vint) override
{
CS_LOCK(server->lockState)
{
}
}
};
/***********************************************************************
ServerState::AcceptOperation
***********************************************************************/
class ServerState::AcceptOperation : public RingOperation
{
public:
Ptr<ServerState> server;
vint listener = -1;
sockaddr_storage address = {};
socklen_t addressSize = sizeof(address);
AcceptOperation(vuint64_t id, Ptr<ServerState> _server, vint _listener)
: RingOperation(id, _server, true)
, server(_server)
, listener(_listener)
{
}
void Prepare(io_uring_sqe* sqe) noexcept override
{
io_uring_prep_accept(sqe, (int)listener, (sockaddr*)&address, &addressSize, SOCK_CLOEXEC);
}
void Handle(vint result) override
{
bool running = false;
CS_LOCK(server->lockState)
{
if (server->acceptOperationId == id)
{
server->acceptOperationId = 0;
server->acceptCancelRequested = false;
running = server->started && !server->stopping;
}
}
if (result < 0)
{
bool recoverable = false;
switch (-result)
{
case EINTR:
case EAGAIN:
case ECONNABORTED:
case EPROTO:
case ENETDOWN:
case ENOPROTOOPT:
case EHOSTDOWN:
case ENONET:
case EHOSTUNREACH:
case EOPNOTSUPP:
case ENETUNREACH:
recoverable = true;
}
if (running && recoverable)
{
if (!server->PostAccept(server))
{
server->HandleUnexpectedStop(server);
}
}
else if (running && result != -ECANCELED)
{
server->HandleUnexpectedStop(server);
}
return;
}
OwnedFileDescriptor acceptedSocket(result);
if (!running)
{
return;
}
// Keep exactly one accept pending, and rearm before invoking user code.
if (!server->PostAccept(server))
{
server->HandleUnexpectedStop(server);
return;
}
auto connectionState = Ptr(new ConnectionState(server->runtime, acceptedSocket.Get()));
auto connection = Ptr(new AsyncSocketConnection(connectionState));
acceptedSocket.Detach();
IAsyncSocketServerCallback* installedCallback = nullptr;
CS_LOCK(server->lockState)
{
if (server->started && !server->stopping && server->callback)
{
server->connections.Add(connection);
installedCallback = server->callback;
server->activeCallbacks++;
}
}
if (!installedCallback)
{
connection->Stop();
return;
}
WaitForClientResult acceptResult = WaitForClientResult::Reject;
ServerCallbackFrame frame{ server.Obj(), currentServerCallbackFrame };
currentServerCallbackFrame = &frame;
try
{
acceptResult = installedCallback->OnClientConnected(connection.Obj());
}
catch (...)
{
}
currentServerCallbackFrame = frame.previous;
bool accepted = false;
CS_LOCK(server->lockState)
{
accepted = acceptResult == WaitForClientResult::Accept && server->started && !server->stopping;
if (!accepted)
{
server->connections.Remove(connection.Obj());
}
}
server->EndCallback();
if (!accepted)
{
connection->Stop();
}
}
};
/***********************************************************************
ServerState
***********************************************************************/
bool ServerState::PostAccept(Ptr<ServerState> retainedState)
{
auto operationId = runtime->ReserveOperationId();
AcceptOperation* operation = nullptr;
bool submitted = false;
bool submissionFailed = false;
CS_LOCK(lockState)
{
if (started && !stopping && listener >= 0 && acceptOperationId == 0)
{
operation = new AcceptOperation(operationId, retainedState, listener);
acceptOperationId = operationId;
acceptCancelRequested = false;
BeginTargetLocked();
submitted = runtime->Submit(operation);
if (!submitted)
{
acceptOperationId = 0;
RollbackTargetLocked();
submissionFailed = true;
}
}
}
if (submitted)
{
runtime->FlushPendingSubmissions();
}
return !submissionFailed;
}
void ServerState::Start(Ptr<ServerState> retainedState, IAsyncSocketServerCallback* value)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ServerState::Start(Ptr<ServerState>, IAsyncSocketServerCallback*)#"
CHECK_ERROR(value != nullptr, ERROR_MESSAGE_PREFIX L"Requires a callback.");
CS_LOCK(lockState)
{
CHECK_ERROR(!startCalled && !stopping, ERROR_MESSAGE_PREFIX L"Can only be called once before stopping.");
startCalled = true;
starting = true;
callback = value;
eventStartFinished.Unsignal();
}
StartScope startScope(this);
OwnedFileDescriptor createdListener(socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP));
vint setupError = createdListener.Get() < 0 ? errno : 0;
if (setupError == 0)
{
vint reuseAddress = 1;
if (setsockopt(createdListener.Get(), SOL_SOCKET, SO_REUSEADDR, &reuseAddress, sizeof(reuseAddress)) != 0)
{
setupError = errno;
}
}
sockaddr_in address = {};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
address.sin_port = htons((vuint16_t)port);
if (setupError == 0 && bind(createdListener.Get(), (sockaddr*)&address, sizeof(address)) != 0)
{
setupError = errno;
}
if (setupError == 0 && listen(createdListener.Get(), SOMAXCONN) != 0)
{
setupError = errno;
}
if (setupError != 0)
{
CS_LOCK(lockState)
{
stopping = true;
stopped = true;
callback = nullptr;
}
runtime->Stop();
auto failure = setupError == EADDRINUSE ? AsyncSocketServerStartFailure::AddressInUse : AsyncSocketServerStartFailure::Other;
throw AsyncSocketServerStartException(failure, LinuxSocketErrorMessage(L"AsyncSocketServer listener setup", setupError));
}
bool committed = false;
try
{
CS_LOCK(lockState)
{
if (!stopping)
{
runtime->Start(runtime);
listener = createdListener.Detach();
started = true;
committed = true;
}
}
}
catch (...)
{
CS_LOCK(lockState)
{
started = false;
stopping = true;
stopped = true;
callback = nullptr;
}
runtime->Stop();
throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, ERROR_MESSAGE_PREFIX L"Failed to start the listener runtime.");
}
if (!committed)
{
throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, ERROR_MESSAGE_PREFIX L"The listener was stopped during startup.");
}
if (!PostAccept(retainedState))
{
CS_LOCK(lockState)
{
started = false;
stopping = true;
stopped = true;
callback = nullptr;
if (listener >= 0)
{
shutdown((int)listener, SHUT_RDWR);
if (targetOperations == 0)
{
CloseFileDescriptor(listener);
listener = -1;
}
}
}
runtime->Stop();
throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, ERROR_MESSAGE_PREFIX L"Failed to submit the first accept operation.");
}
#undef ERROR_MESSAGE_PREFIX
}
void ServerState::Stop(Ptr<ServerState> retainedState)
{
bool waitForStart = false;
bool cancellationSubmitted = false;
CS_LOCK(lockState)
{
if (!stopping)
{
stopping = true;
stopped = true;
started = false;
}
waitForStart = starting;
}
if (waitForStart)
{
eventStartFinished.Wait();
}
CS_LOCK(lockState)
{
if (acceptOperationId != 0 && !acceptCancelRequested)
{
auto operation = new CancelOperation(runtime->ReserveOperationId(), retainedState, acceptOperationId);
acceptCancelRequested = true;
BeginCancelLocked();
if (!runtime->Submit(operation))
{
acceptCancelRequested = false;
RollbackCancelLocked();
}
else
{
cancellationSubmitted = true;
}
}
if (listener >= 0)
{
shutdown((int)listener, SHUT_RDWR);
if (targetOperations == 0)
{
CloseFileDescriptor(listener);
listener = -1;
}
}
}
if (cancellationSubmitted)
{
runtime->FlushPendingSubmissions();
}
if (currentRingRuntime != runtime.Obj())
{
operationDrain->Wait();
}
auto callbackDepth = CurrentCallbackDepth();
CS_LOCK(lockState)
{
while (activeCallbacks > callbackDepth)
{
cvCallbacks.SleepWith(lockState);
}
}
List<Ptr<AsyncSocketConnection>> retainedConnections;
CS_LOCK(lockState)
{
for (auto connection : connections)
{
retainedConnections.Add(connection);
}
}
for (auto connection : retainedConnections)
{
connection->Stop();
}
runtime->Stop();
if (callbackDepth == 0)
{
CS_LOCK(lockState)
{
callback = nullptr;
}
}
}
bool ServerState::IsStopped()
{
bool result = false;
CS_LOCK(lockState)
{
result = stopped;
}
return result;
}
/***********************************************************************
AsyncSocketServer::Impl
***********************************************************************/
class AsyncSocketServer::Impl : public Object
{
private:
vint port = 0;
Ptr<RingRuntime> runtime;
Ptr<ServerState> state;
public:
Impl(vint _port)
: port(_port)
, runtime(RingRuntime::Create(false))
, state(Ptr(new ServerState(runtime, _port)))
{
}
~Impl()
{
Stop();
}
vint GetPort()
{
return port;
}
void Start(IAsyncSocketServerCallback* callback)
{
state->Start(state, callback);
}
void Stop()
{
state->Stop(state);
}
bool IsStopped()
{
return state->IsStopped();
}
};
/***********************************************************************
AsyncSocketServer
***********************************************************************/
AsyncSocketServer::AsyncSocketServer(vint port)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::AsyncSocketServer::AsyncSocketServer(vint)#"
CHECK_ERROR(1 <= port && port <= 65535, ERROR_MESSAGE_PREFIX L"The port must be in 1..65535.");
#undef ERROR_MESSAGE_PREFIX
impl = new Impl(port);
}
AsyncSocketServer::~AsyncSocketServer()
{
delete impl;
}
vint AsyncSocketServer::GetPort()
{
return impl->GetPort();
}
void AsyncSocketServer::Start(IAsyncSocketServerCallback* callback)
{
impl->Start(callback);
}
void AsyncSocketServer::Stop()
{
impl->Stop();
}
bool AsyncSocketServer::IsStopped()
{
return impl->IsStopped();
}
/***********************************************************************
AsyncSocketClient::Impl
***********************************************************************/
class AsyncSocketClient::Impl : public Object
{
private:
vint port = 0;
Ptr<RingRuntime> runtime;
Ptr<ConnectionState> state;
Ptr<AsyncSocketConnection> connection;
public:
Impl(vint _port)
: port(_port)
, runtime(RingRuntime::Create(true))
, state(Ptr(new ConnectionState(runtime, true, _port)))
, connection(Ptr(new AsyncSocketConnection(state)))
{
}
~Impl()
{
Stop();
}
vint GetPort()
{
return port;
}
void Stop()
{
connection->Stop();
runtime->Stop();
}
IAsyncSocketConnection* GetConnection()
{
return connection.Obj();
}
void WaitForServer()
{
state->WaitForServer(state);
}
ClientStatus GetStatus()
{
return state->GetStatus();
}
};
/***********************************************************************
AsyncSocketClient
***********************************************************************/
AsyncSocketClient::AsyncSocketClient(vint port)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::AsyncSocketClient::AsyncSocketClient(vint)#"
CHECK_ERROR(1 <= port && port <= 65535, ERROR_MESSAGE_PREFIX L"The port must be in 1..65535.");
#undef ERROR_MESSAGE_PREFIX
impl = new Impl(port);
}
AsyncSocketClient::~AsyncSocketClient()
{
delete impl;
}
vint AsyncSocketClient::GetPort()
{
return impl->GetPort();
}
Ptr<IAsyncSocketClient> AsyncSocketClient::CreateSameEndpointClient()
{
return Ptr(new AsyncSocketClient(GetPort()));
}
IAsyncSocketConnection* AsyncSocketClient::GetConnection()
{
return impl->GetConnection();
}
void AsyncSocketClient::WaitForServer()
{
impl->WaitForServer();
}
ClientStatus AsyncSocketClient::GetStatus()
{
return impl->GetStatus();
}
}
namespace vl::inter_process::async_tcp_socket
{
Ptr<IAsyncSocketServer> CreateDefaultAsyncSocketServer(vint port)
{
return Ptr(new linux_socket::AsyncSocketServer(port));
}
Ptr<IAsyncSocketClient> CreateDefaultAsyncSocketClient(vint port)
{
return Ptr(new linux_socket::AsyncSocketClient(port));
}
}
#endif
/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET.MACOS.CPP
***********************************************************************/
#if defined VCZH_GCC && defined VCZH_APPLE
#include <Network/Network.h>
#include <dispatch/dispatch.h>
#if !defined VCZH_GCC || !defined VCZH_APPLE
static_assert(false, "Do not build this file for non-macOS applications.");
#endif
namespace vl::inter_process::async_tcp_socket::macos_socket
{
using namespace collections;
class ConnectionState;
class AsyncSocketConnection;
struct CallbackFrame
{
ConnectionState* connection = nullptr;
CallbackFrame* previous = nullptr;
};
static thread_local CallbackFrame* currentCallbackFrame = nullptr;
static char connectionQueueKey;
static char serverQueueKey;
/***********************************************************************
NativeConnectionContext
***********************************************************************/
class NativeConnectionContext : public Object
{
friend class ConnectionState;
private:
Ptr<ConnectionState> state;
nw_connection_t connection = nullptr;
vint generation = 0;
bool started = false;
bool cancelRequested = false;
bool waitingHandled = false;
bool cancelledHandled = false;
public:
NativeConnectionContext(Ptr<ConnectionState> _state, nw_connection_t _connection);
~NativeConnectionContext();
};
NativeConnectionContext::NativeConnectionContext(Ptr<ConnectionState> _state, nw_connection_t _connection)
: state(_state)
, connection(_connection)
{
}
NativeConnectionContext::~NativeConnectionContext()
{
if (connection)
{
nw_release(connection);
}
}
/***********************************************************************
ConnectionState
***********************************************************************/
class ConnectionState : public Object
{
friend class NativeConnectionContext;
friend class AsyncSocketConnection;
private:
// covers all fields below
CriticalSection lockState;
ConditionVariable cvState;
dispatch_queue_t queue = nullptr;
IAsyncSocketConnection* owner = nullptr;
IAsyncSocketCallback* callback = nullptr;
bool clientMode = false;
vint port = 0;
bool logicalConnected = false;
bool nativeReady = false;
bool stopping = false;
bool terminalPending = false;
bool disconnectedNotified = false;
vint activeCallbacks = 0;
bool callbackExecuting = false;
bool readingRequested = false;
bool receivePending = false;
bool receiveHandling = false;
vint receiveGeneration = -1;
Ptr<AsyncSocketBuffer> pendingReadBuffer;
bool writePending = false;
bool writeIssued = false;
vint writeGeneration = -1;
Ptr<AsyncSocketBuffer> writeBuffer;
dispatch_data_t writeData = nullptr;
vint pendingLocalTasks = 0;
Ptr<NativeConnectionContext> nativeContext;
vint pendingNativeContexts = 0;
vint nextGeneration = 0;
ClientStatus clientStatus = ClientStatus::Ready;
vint clientAttempts = 0;
vint retryAfterGeneration = -1;
dispatch_source_t retryTimer = nullptr;
vint retryTimerGeneration = -1;
bool retryTimerFired = false;
bool retryTimerCancelRequested = false;
vint pendingRetryTimers = 0;
EventObject eventWaitForServer;
vint CountCurrentCallbackFrames()
{
vint count = 0;
for (auto frame = currentCallbackFrame; frame; frame = frame->previous)
{
if (frame->connection == this)
{
count++;
}
}
return count;
}
bool IsOnQueue()
{
return dispatch_get_specific(&connectionQueueKey) == this;
}
enum class CallbackType
{
Ordinary,
FatalError,
Disconnected,
};
bool CanInvokeCallbackLocked(CallbackType type)
{
if (!callback)
{
return false;
}
switch (type)
{
case CallbackType::Ordinary:
return !stopping && !terminalPending;
case CallbackType::FatalError:
return !stopping;
case CallbackType::Disconnected:
return true;
default:
return false;
}
}
IAsyncSocketCallback* BeginCallback(CallbackType type, bool& ownsExecution)
{
IAsyncSocketCallback* installed = nullptr;
auto nested = CountCurrentCallbackFrames() > 0;
ownsExecution = false;
lockState.Enter();
while (!nested && callbackExecuting && CanInvokeCallbackLocked(type))
{
cvState.SleepWith(lockState);
}
if (CanInvokeCallbackLocked(type))
{
installed = callback;
activeCallbacks++;
if (!nested)
{
callbackExecuting = true;
ownsExecution = true;
}
}
lockState.Leave();
return installed;
}
void EndCallback(bool ownsExecution)
{
CS_LOCK(lockState)
{
activeCallbacks--;
if (ownsExecution)
{
callbackExecuting = false;
}
cvState.WakeAllPendings();
}
}
template<typename TCallback>
bool InvokeCallback(CallbackType type, TCallback&& invoke)
{
bool ownsExecution = false;
auto installed = BeginCallback(type, ownsExecution);
if (!installed)
{
return false;
}
CallbackFrame frame{ this, currentCallbackFrame };
currentCallbackFrame = &frame;
try
{
invoke(installed);
}
catch (...)
{
}
currentCallbackFrame = frame.previous;
EndCallback(ownsExecution);
return true;
}
void WaitForOtherCallbacks(vint currentFrames)
{
lockState.Enter();
while (activeCallbacks > currentFrames)
{
cvState.SleepWith(lockState);
}
lockState.Leave();
}
void NotifyDisconnected()
{
bool notify = false;
lockState.Enter();
if (!disconnectedNotified)
{
disconnectedNotified = true;
notify = callback != nullptr;
}
lockState.Leave();
if (notify)
{
InvokeCallback(CallbackType::Disconnected, [](IAsyncSocketCallback* installed)
{
installed->OnDisconnected();
});
}
}
void WaitForDrain()
{
lockState.Enter();
while (
activeCallbacks > 0 ||
pendingNativeContexts > 0 ||
pendingRetryTimers > 0 ||
pendingLocalTasks > 0
)
{
cvState.SleepWith(lockState);
}
lockState.Leave();
}
void RequestNativeCancelLocked(Ptr<NativeConnectionContext> context)
{
if (!context || !context->connection || context->cancelRequested)
{
return;
}
if (!context->started)
{
context->started = true;
nw_connection_start(context->connection);
}
context->cancelRequested = true;
nw_connection_cancel(context->connection);
}
void RequestRetryTimerCancelLocked()
{
if (retryTimer && !retryTimerCancelRequested)
{
retryTimerCancelRequested = true;
dispatch_source_cancel(retryTimer);
}
}
WString NetworkErrorMessage(const wchar_t* operation, nw_error_t error)
{
if (!error)
{
return WString(operation) + L" failed without a Network.framework error.";
}
return WString(operation) + L" failed with Network.framework domain "
+ itow((vint)nw_error_get_error_domain(error)) + L", code "
+ itow((vint)nw_error_get_error_code(error)) + L".";
}
void ConfigureNativeLocked(Ptr<NativeConnectionContext> context, bool start)
{
nativeContext = context;
pendingNativeContexts++;
context->generation = ++nextGeneration;
nw_connection_set_queue(context->connection, queue);
auto retainedContext = context;
nw_connection_set_state_changed_handler(context->connection, ^(nw_connection_state_t state, nw_error_t error)
{
retainedContext->state->OnNativeState(retainedContext, state, error);
});
if (start)
{
context->started = true;
nw_connection_start(context->connection);
}
}
void IssueReceiveLocked(Ptr<NativeConnectionContext> context)
{
if (
!context || context != nativeContext || !context->connection ||
!logicalConnected || !nativeReady || stopping || terminalPending ||
!callback || !readingRequested || receivePending || receiveHandling || pendingReadBuffer
)
{
return;
}
receivePending = true;
receiveGeneration = context->generation;
auto retainedContext = context;
nw_connection_receive(context->connection, 1, 65536, ^(dispatch_data_t content, nw_content_context_t, bool isComplete, nw_error_t error)
{
retainedContext->state->OnReceive(retainedContext, content, isComplete, error);
});
}
void IssueWriteLocked(Ptr<NativeConnectionContext> context)
{
if (
!context || context != nativeContext || !context->connection ||
!logicalConnected || !nativeReady || stopping || terminalPending ||
!writePending || writeIssued
)
{
return;
}
writeIssued = true;
writeGeneration = context->generation;
if (writeBuffer->data.Count() == 0)
{
pendingLocalTasks++;
auto retainedState = context->state;
auto generation = context->generation;
dispatch_async(queue, ^
{
retainedState->CompleteEmptyWrite(generation);
});
}
else
{
auto retainedContext = context;
nw_connection_send(
context->connection,
writeData,
NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT,
true,
^(nw_error_t error)
{
retainedContext->state->OnSendCompleted(retainedContext, error);
}
);
}
}
void EnterTerminal(Ptr<NativeConnectionContext> context, const WString& error, bool reportError)
{
bool claim = false;
CS_LOCK(lockState)
{
if (!stopping && !terminalPending && (!context || context == nativeContext))
{
terminalPending = true;
claim = true;
}
}
if (!claim)
{
return;
}
if (reportError)
{
InvokeCallback(CallbackType::FatalError, [&](IAsyncSocketCallback* installed)
{
installed->OnError(error, true);
});
}
Stop();
}
void StartClientAttempt(Ptr<ConnectionState> retainedState)
{
bool canStart = false;
CS_LOCK(lockState)
{
canStart = clientMode && !stopping && clientStatus == ClientStatus::WaitingForServer && !nativeContext;
}
if (!canStart)
{
return;
}
auto portText = itoa(port);
auto endpoint = nw_endpoint_create_host("127.0.0.1", portText.Buffer());
auto parameters = nw_parameters_create_secure_tcp(
NW_PARAMETERS_DISABLE_PROTOCOL,
NW_PARAMETERS_DEFAULT_CONFIGURATION
);
nw_connection_t connection = nullptr;
if (endpoint && parameters)
{
connection = nw_connection_create(endpoint, parameters);
}
if (endpoint)
{
nw_release(endpoint);
}
if (parameters)
{
nw_release(parameters);
}
if (!connection)
{
EnterTerminal(nullptr, L"AsyncSocketClient failed to create a Network.framework connection.", true);
return;
}
auto context = Ptr(new NativeConnectionContext(retainedState, connection));
bool installed = false;
lockState.Enter();
if (clientMode && !stopping && clientStatus == ClientStatus::WaitingForServer && !nativeContext)
{
clientAttempts++;
ConfigureNativeLocked(context, true);
installed = true;
}
lockState.Leave();
if (!installed)
{
return;
}
}
void ScheduleRetry(Ptr<ConnectionState> retainedState, vint generation)
{
auto source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (!source)
{
EnterTerminal(nullptr, L"AsyncSocketClient failed to create its retry timer.", true);
return;
}
auto retainedForEvent = retainedState;
dispatch_source_set_event_handler(source, ^
{
bool cancel = false;
retainedForEvent->lockState.Enter();
if (retainedForEvent->retryTimer == source && !retainedForEvent->retryTimerCancelRequested)
{
retainedForEvent->retryTimerFired = true;
retainedForEvent->retryTimerCancelRequested = true;
cancel = true;
}
retainedForEvent->lockState.Leave();
if (cancel)
{
dispatch_source_cancel(source);
}
});
auto retainedForCancel = retainedState;
dispatch_source_set_cancel_handler(source, ^
{
auto retainedForSentinel = retainedForCancel;
dispatch_async(retainedForCancel->queue, ^
{
retainedForSentinel->FinishRetryTimer(retainedForSentinel, source, generation);
});
});
dispatch_source_set_timer(
source,
dispatch_time(DISPATCH_TIME_NOW, (int64_t)AsyncSocketClientRetryDelay * NSEC_PER_MSEC),
DISPATCH_TIME_FOREVER,
0
);
bool installed = false;
lockState.Enter();
if (
clientMode && !stopping && clientStatus == ClientStatus::WaitingForServer &&
retryAfterGeneration == generation && !retryTimer
)
{
retryTimer = source;
retryTimerGeneration = generation;
retryTimerFired = false;
retryTimerCancelRequested = false;
pendingRetryTimers++;
installed = true;
}
lockState.Leave();
if (installed)
{
dispatch_activate(source);
}
else
{
dispatch_source_set_event_handler(source, nullptr);
dispatch_source_set_cancel_handler(source, nullptr);
dispatch_activate(source);
dispatch_source_cancel(source);
dispatch_release(source);
}
}
void FinishRetryTimer(Ptr<ConnectionState> retainedState, dispatch_source_t source, vint generation)
{
bool start = false;
lockState.Enter();
if (retryTimer == source)
{
start = retryTimerFired && !stopping && clientStatus == ClientStatus::WaitingForServer && retryTimerGeneration == generation;
retryTimer = nullptr;
retryTimerGeneration = -1;
retryTimerFired = false;
retryTimerCancelRequested = false;
}
lockState.Leave();
dispatch_release(source);
if (start)
{
StartClientAttempt(retainedState);
}
CS_LOCK(lockState)
{
pendingRetryTimers--;
cvState.WakeAllPendings();
}
}
void OnNativeReady(Ptr<NativeConnectionContext> context)
{
bool notifyConnected = false;
lockState.Enter();
if (
context == nativeContext && !context->cancelRequested &&
retryAfterGeneration != context->generation &&
!stopping && !terminalPending
)
{
nativeReady = true;
logicalConnected = true;
if (clientMode && clientStatus == ClientStatus::WaitingForServer)
{
clientStatus = ClientStatus::Connected;
notifyConnected = true;
}
}
lockState.Leave();
if (notifyConnected)
{
InvokeCallback(CallbackType::Ordinary, [](IAsyncSocketCallback* installed)
{
installed->OnConnected();
});
eventWaitForServer.Signal();
}
lockState.Enter();
if (context == nativeContext && nativeReady && logicalConnected && !stopping && !terminalPending)
{
IssueReceiveLocked(context);
IssueWriteLocked(context);
}
lockState.Leave();
}
void OnNativeWaiting(Ptr<NativeConnectionContext> context, nw_error_t error)
{
bool retryable = false;
bool exhausted = false;
lockState.Enter();
if (
clientMode && context == nativeContext && !context->waitingHandled &&
!stopping && clientStatus == ClientStatus::WaitingForServer
)
{
context->waitingHandled = true;
exhausted = clientAttempts >= AsyncSocketClientRetryCount;
retryable = !exhausted;
if (retryable)
{
retryAfterGeneration = context->generation;
}
}
lockState.Leave();
if (!retryable && !exhausted)
{
return;
}
auto message = NetworkErrorMessage(L"AsyncSocketClient connection attempt", error);
if (exhausted)
{
EnterTerminal(context, message, true);
return;
}
InvokeCallback(CallbackType::Ordinary, [&](IAsyncSocketCallback* installed)
{
installed->OnError(message, false);
});
lockState.Enter();
if (
context == nativeContext && retryAfterGeneration == context->generation &&
!stopping && clientStatus == ClientStatus::WaitingForServer
)
{
RequestNativeCancelLocked(context);
}
lockState.Leave();
}
void OnNativeCancelled(Ptr<NativeConnectionContext> context)
{
bool schedule = false;
lockState.Enter();
if (!context->cancelledHandled)
{
context->cancelledHandled = true;
if (context->connection)
{
nw_connection_set_state_changed_handler(context->connection, nullptr);
nw_release(context->connection);
context->connection = nullptr;
}
schedule = true;
}
lockState.Leave();
if (schedule)
{
auto retainedContext = context;
dispatch_async(queue, ^
{
retainedContext->state->FinishNativeCancellation(retainedContext);
});
}
}
void FinishNativeCancellation(Ptr<NativeConnectionContext> context)
{
bool retry = false;
lockState.Enter();
if (nativeContext == context)
{
retry = clientMode && !stopping && clientStatus == ClientStatus::WaitingForServer && retryAfterGeneration == context->generation;
nativeContext = nullptr;
nativeReady = false;
receivePending = false;
receiveGeneration = -1;
}
lockState.Leave();
if (retry)
{
ScheduleRetry(context->state, context->generation);
}
CS_LOCK(lockState)
{
pendingNativeContexts--;
cvState.WakeAllPendings();
}
}
void OnNativeState(Ptr<NativeConnectionContext> context, nw_connection_state_t state, nw_error_t error)
{
switch (state)
{
case nw_connection_state_waiting:
OnNativeWaiting(context, error);
break;
case nw_connection_state_ready:
OnNativeReady(context);
break;
case nw_connection_state_failed:
{
bool intentionalCancellation = false;
CS_LOCK(lockState)
{
intentionalCancellation =
context != nativeContext || context->cancelRequested ||
retryAfterGeneration == context->generation || stopping;
}
if (!intentionalCancellation)
{
EnterTerminal(context, NetworkErrorMessage(L"Asynchronous socket connection", error), true);
}
}
break;
case nw_connection_state_cancelled:
OnNativeCancelled(context);
break;
default:
break;
}
}
void ResumeReading()
{
while (true)
{
Ptr<AsyncSocketBuffer> buffered;
lockState.Enter();
if (
pendingReadBuffer && callback && !receiveHandling &&
logicalConnected && !stopping && !terminalPending
)
{
buffered = pendingReadBuffer;
pendingReadBuffer = nullptr;
receiveHandling = true;
}
else
{
IssueReceiveLocked(nativeContext);
}
lockState.Leave();
if (!buffered)
{
return;
}
auto invoked = InvokeCallback(CallbackType::Ordinary, [&](IAsyncSocketCallback* installed)
{
installed->OnRead(&buffered->data[0], buffered->data.Count());
});
bool continueReading = false;
lockState.Enter();
if (
!invoked && !pendingReadBuffer && logicalConnected &&
!stopping && !terminalPending
)
{
pendingReadBuffer = buffered;
}
receiveHandling = false;
continueReading = invoked && logicalConnected && !stopping && !terminalPending;
lockState.Leave();
if (!continueReading)
{
return;
}
}
}
void OnReceive(Ptr<NativeConnectionContext> context, dispatch_data_t content, bool isComplete, nw_error_t error)
{
bool claimed = false;
bool deliver = false;
lockState.Enter();
if (context == nativeContext && receivePending && receiveGeneration == context->generation)
{
receivePending = false;
receiveHandling = true;
receiveGeneration = -1;
claimed = true;
deliver = !stopping && !terminalPending && logicalConnected;
}
lockState.Leave();
if (!deliver)
{
if (claimed)
{
CS_LOCK(lockState)
{
receiveHandling = false;
}
}
return;
}
auto undelivered = Ptr(new AsyncSocketBuffer);
bool buffering = false;
auto bufferingRef = &buffering;
if (content)
{
dispatch_data_apply(content, ^bool(dispatch_data_t, size_t, const void* buffer, size_t size)
{
if (size == 0)
{
return true;
}
if (!*bufferingRef)
{
auto invoked = InvokeCallback(CallbackType::Ordinary, [&](IAsyncSocketCallback* installed)
{
installed->OnRead((const vuint8_t*)buffer, (vint)size);
});
if (invoked)
{
return true;
}
*bufferingRef = true;
}
auto oldSize = undelivered->data.Count();
undelivered->data.Resize(oldSize + (vint)size);
std::memcpy(&undelivered->data[oldSize], buffer, size);
return true;
});
}
if (error)
{
EnterTerminal(context, NetworkErrorMessage(L"Asynchronous socket receive", error), true);
}
else if (isComplete)
{
EnterTerminal(context, WString::Empty, false);
}
bool resume = false;
lockState.Enter();
if (
!error && !isComplete && context == nativeContext &&
!stopping && !terminalPending && logicalConnected
)
{
if (undelivered->data.Count() > 0)
{
pendingReadBuffer = undelivered;
}
resume = true;
}
receiveHandling = false;
lockState.Leave();
if (resume)
{
ResumeReading();
}
}
void OnSendCompleted(Ptr<NativeConnectionContext> context, nw_error_t error)
{
Ptr<AsyncSocketBuffer> completedBuffer;
dispatch_data_t completedData = nullptr;
bool reportCompletion = false;
bool reportError = false;
lockState.Enter();
if (writePending && writeIssued && writeGeneration == context->generation)
{
completedBuffer = writeBuffer;
completedData = writeData;
writeBuffer = nullptr;
writeData = nullptr;
writePending = false;
writeIssued = false;
writeGeneration = -1;
reportCompletion = !error && !stopping && !terminalPending && context == nativeContext;
reportError = error && !stopping && !terminalPending && context == nativeContext;
}
lockState.Leave();
if (completedData)
{
dispatch_release(completedData);
}
if (reportCompletion)
{
InvokeCallback(CallbackType::Ordinary, [&](IAsyncSocketCallback* installed)
{
installed->OnWriteCompleted(completedBuffer);
});
}
else if (reportError)
{
EnterTerminal(context, NetworkErrorMessage(L"Asynchronous socket send", error), true);
}
}
void CompleteEmptyWrite(vint generation)
{
Ptr<AsyncSocketBuffer> completedBuffer;
bool reportCompletion = false;
lockState.Enter();
if (writePending && writeIssued && writeGeneration == generation)
{
completedBuffer = writeBuffer;
writeBuffer = nullptr;
writePending = false;
writeIssued = false;
writeGeneration = -1;
reportCompletion = !stopping && !terminalPending && nativeContext && nativeContext->generation == generation;
}
lockState.Leave();
if (reportCompletion)
{
InvokeCallback(CallbackType::Ordinary, [&](IAsyncSocketCallback* installed)
{
installed->OnWriteCompleted(completedBuffer);
});
}
CS_LOCK(lockState)
{
pendingLocalTasks--;
cvState.WakeAllPendings();
}
}
public:
ConnectionState(bool _clientMode, vint _port)
: clientMode(_clientMode)
, port(_port)
{
queue = dispatch_queue_create("vlppos.async-socket.connection", DISPATCH_QUEUE_SERIAL);
CHECK_ERROR(queue != nullptr, L"IAsyncSocketConnection failed to create its dispatch queue.");
try
{
CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), L"IAsyncSocketClient failed to create its wait event.");
dispatch_queue_set_specific(queue, &connectionQueueKey, this, nullptr);
}
catch (...)
{
dispatch_release(queue);
queue = nullptr;
throw;
}
}
~ConnectionState()
{
if (writeData)
{
dispatch_release(writeData);
}
dispatch_queue_set_specific(queue, &connectionQueueKey, nullptr, nullptr);
dispatch_release(queue);
}
void AttachOwner(IAsyncSocketConnection* value)
{
CS_LOCK(lockState)
{
owner = value;
}
}
void DetachOwner(IAsyncSocketConnection* value)
{
CS_LOCK(lockState)
{
if (owner == value)
{
owner = nullptr;
}
}
}
bool RequiresDeferredDrain()
{
return CountCurrentCallbackFrames() > 0 || IsOnQueue();
}
void ConfigureServerNative(Ptr<ConnectionState> retainedState, nw_connection_t connection)
{
auto context = Ptr(new NativeConnectionContext(retainedState, connection));
CS_LOCK(lockState)
{
CHECK_ERROR(!clientMode && !nativeContext && !stopping, L"The accepted async socket connection is already configured.");
logicalConnected = true;
ConfigureNativeLocked(context, false);
}
}
void StartAcceptedConnection()
{
CS_LOCK(lockState)
{
if (nativeContext && !nativeContext->started && !stopping)
{
nativeContext->started = true;
nw_connection_start(nativeContext->connection);
}
}
}
void InstallCallback(IAsyncSocketCallback* value)
{
if (!value)
{
auto currentFrames = CountCurrentCallbackFrames();
lockState.Enter();
callback = nullptr;
cvState.WakeAllPendings();
while (activeCallbacks > currentFrames)
{
cvState.SleepWith(lockState);
}
lockState.Leave();
return;
}
IAsyncSocketConnection* installedOwner = nullptr;
bool canInstall = false;
bool ownsExecution = false;
auto nested = CountCurrentCallbackFrames() > 0;
lockState.Enter();
while (!nested && callbackExecuting && !stopping)
{
cvState.SleepWith(lockState);
}
if (!callback && !stopping && owner)
{
callback = value;
installedOwner = owner;
activeCallbacks++;
if (!nested)
{
callbackExecuting = true;
ownsExecution = true;
}
canInstall = true;
}
lockState.Leave();
CHECK_ERROR(canInstall, L"IAsyncSocketConnection::InstallCallback cannot replace a callback or install one on a stopped connection.");
CallbackFrame frame{ this, currentCallbackFrame };
currentCallbackFrame = &frame;
try
{
value->OnInstalled(installedOwner);
}
catch (...)
{
}
currentCallbackFrame = frame.previous;
EndCallback(ownsExecution);
ResumeReading();
}
void BeginReading()
{
CS_LOCK(lockState)
{
CHECK_ERROR(logicalConnected && !stopping && !terminalPending, L"IAsyncSocketConnection::BeginReadingLoopUnsafe requires a connected connection.");
CHECK_ERROR(callback != nullptr, L"IAsyncSocketConnection::BeginReadingLoopUnsafe requires an installed callback.");
CHECK_ERROR(!readingRequested, L"IAsyncSocketConnection::BeginReadingLoopUnsafe can only be called once.");
readingRequested = true;
IssueReceiveLocked(nativeContext);
}
}
void Write(Ptr<AsyncSocketBuffer> buffer)
{
CHECK_ERROR(buffer, L"IAsyncSocketConnection::WriteAsync requires a buffer.");
dispatch_data_t data = nullptr;
if (buffer->data.Count() > 0)
{
auto bytes = std::malloc((size_t)buffer->data.Count());
CHECK_ERROR(bytes != nullptr, L"IAsyncSocketConnection::WriteAsync failed to allocate native write storage.");
std::memcpy(bytes, &buffer->data[0], (size_t)buffer->data.Count());
data = dispatch_data_create(bytes, (size_t)buffer->data.Count(), nullptr, DISPATCH_DATA_DESTRUCTOR_FREE);
if (!data)
{
std::free(bytes);
}
CHECK_ERROR(data != nullptr, L"IAsyncSocketConnection::WriteAsync failed to create native write data.");
}
bool canWrite = false;
lockState.Enter();
if (logicalConnected && !stopping && !terminalPending && !writePending)
{
writePending = true;
writeBuffer = buffer;
writeData = data;
canWrite = true;
IssueWriteLocked(nativeContext);
}
lockState.Leave();
if (!canWrite && data)
{
dispatch_release(data);
}
CHECK_ERROR(canWrite, L"IAsyncSocketConnection::WriteAsync requires a connected connection with no outstanding write.");
}
void Stop()
{
auto currentFrames = CountCurrentCallbackFrames();
auto deferredDrain = RequiresDeferredDrain();
Ptr<AsyncSocketBuffer> cancelledBuffer;
dispatch_data_t cancelledData = nullptr;
lockState.Enter();
if (!stopping)
{
stopping = true;
terminalPending = true;
logicalConnected = false;
nativeReady = false;
readingRequested = false;
pendingReadBuffer = nullptr;
if (clientMode)
{
clientStatus = ClientStatus::Disconnected;
}
if (writePending && !writeIssued)
{
cancelledBuffer = writeBuffer;
cancelledData = writeData;
writeBuffer = nullptr;
writeData = nullptr;
writePending = false;
writeGeneration = -1;
}
}
RequestRetryTimerCancelLocked();
RequestNativeCancelLocked(nativeContext);
cvState.WakeAllPendings();
lockState.Leave();
if (cancelledData)
{
dispatch_release(cancelledData);
}
if (clientMode)
{
eventWaitForServer.Signal();
}
WaitForOtherCallbacks(currentFrames);
NotifyDisconnected();
if (!deferredDrain)
{
WaitForDrain();
}
}
void WaitForServer(Ptr<ConnectionState> retainedState)
{
bool begin = false;
CS_LOCK(lockState)
{
if (clientMode && clientStatus == ClientStatus::Ready && !stopping)
{
clientStatus = ClientStatus::WaitingForServer;
begin = true;
}
}
CHECK_ERROR(begin, L"IAsyncSocketClient::WaitForServer can only be called once while the client is ready.");
StartClientAttempt(retainedState);
eventWaitForServer.Wait();
}
ClientStatus GetStatus()
{
ClientStatus result;
CS_LOCK(lockState)
{
result = clientStatus;
}
return result;
}
};
/***********************************************************************
AsyncSocketConnection
***********************************************************************/
class AsyncSocketConnection : public Object, public virtual IAsyncSocketConnection
{
private:
Ptr<ConnectionState> state;
public:
AsyncSocketConnection(Ptr<ConnectionState> _state)
: state(_state)
{
state->AttachOwner(this);
}
~AsyncSocketConnection()
{
state->Stop();
state->DetachOwner(this);
}
Ptr<ConnectionState> GetState()
{
return state;
}
void StartAcceptedConnection()
{
state->StartAcceptedConnection();
}
bool RequiresDeferredDrain()
{
return state->RequiresDeferredDrain();
}
void InstallCallback(IAsyncSocketCallback* callback) override
{
state->InstallCallback(callback);
}
void BeginReadingLoopUnsafe() override
{
state->BeginReading();
}
void WriteAsync(Ptr<AsyncSocketBuffer> buffer) override
{
state->Write(buffer);
}
void Stop() override
{
state->Stop();
}
};
/***********************************************************************
ServerState
***********************************************************************/
class ServerState : public Object
{
private:
// covers all fields below
CriticalSection lockState;
ConditionVariable cvState;
dispatch_queue_t queue = nullptr;
IAsyncSocketServerCallback* callback = nullptr;
vint port = 0;
bool startCalled = false;
bool starting = false;
bool startupResolved = false;
bool startupReady = false;
AsyncSocketServerStartFailure startupFailure = AsyncSocketServerStartFailure::Other;
vint startupError = 0;
bool started = false;
bool stopping = false;
bool stopped = false;
bool unexpectedStopNotified = false;
bool stopFinalizing = false;
bool stopCompleted = false;
nw_listener_t listener = nullptr;
bool listenerCancelRequested = false;
bool listenerCancelledHandled = false;
vint pendingListener = 0;
List<Ptr<AsyncSocketConnection>> connections;
bool IsOnQueue()
{
return dispatch_get_specific(&serverQueueKey) == this;
}
void RequestListenerCancelLocked()
{
if (listener && !listenerCancelRequested)
{
listenerCancelRequested = true;
nw_listener_cancel(listener);
}
}
void OnListenerCancelled(Ptr<ServerState> retainedState)
{
bool schedule = false;
lockState.Enter();
if (!listenerCancelledHandled)
{
listenerCancelledHandled = true;
if (listener)
{
nw_listener_set_new_connection_handler(listener, nullptr);
nw_listener_set_state_changed_handler(listener, nullptr);
nw_release(listener);
listener = nullptr;
}
schedule = true;
}
lockState.Leave();
if (schedule)
{
dispatch_async(queue, ^
{
retainedState->FinishListenerCancellation();
});
}
}
void FinishListenerCancellation()
{
CS_LOCK(lockState)
{
pendingListener = 0;
cvState.WakeAllPendings();
}
}
void OnListenerState(Ptr<ServerState> retainedState, nw_listener_state_t state, nw_error_t error)
{
switch (state)
{
case nw_listener_state_waiting:
{
bool startupFailed = false;
CS_LOCK(lockState)
{
if (starting && !startupResolved)
{
starting = false;
startupResolved = true;
startupReady = false;
startupError = error ? (vint)nw_error_get_error_code(error) : 0;
startupFailure = error && nw_error_get_error_domain(error) == nw_error_domain_posix && startupError == EADDRINUSE
? AsyncSocketServerStartFailure::AddressInUse
: AsyncSocketServerStartFailure::Other;
startupFailed = true;
cvState.WakeAllPendings();
}
}
if (startupFailed)
{
Stop();
}
}
break;
case nw_listener_state_ready:
CS_LOCK(lockState)
{
if (starting && !startupResolved && !stopping)
{
starting = false;
startupResolved = true;
startupReady = true;
started = true;
cvState.WakeAllPendings();
}
}
break;
case nw_listener_state_failed:
{
IAsyncSocketServerCallback* installedCallback = nullptr;
lockState.Enter();
if (starting && !startupResolved)
{
starting = false;
startupResolved = true;
startupReady = false;
startupError = error ? (vint)nw_error_get_error_code(error) : 0;
startupFailure = error && nw_error_get_error_domain(error) == nw_error_domain_posix && startupError == EADDRINUSE
? AsyncSocketServerStartFailure::AddressInUse
: AsyncSocketServerStartFailure::Other;
cvState.WakeAllPendings();
}
else if (started && !stopping && !unexpectedStopNotified)
{
unexpectedStopNotified = true;
installedCallback = callback;
}
lockState.Leave();
if (installedCallback)
{
try
{
installedCallback->OnServerStopped();
}
catch (...)
{
}
}
}
Stop();
break;
case nw_listener_state_cancelled:
CS_LOCK(lockState)
{
if (starting && !startupResolved)
{
starting = false;
startupResolved = true;
startupReady = false;
startupFailure = AsyncSocketServerStartFailure::Other;
cvState.WakeAllPendings();
}
}
OnListenerCancelled(retainedState);
break;
default:
break;
}
}
Ptr<AsyncSocketConnection> CreateConnection(nw_connection_t connection)
{
nw_retain(connection);
auto connectionState = Ptr(new ConnectionState(false, 0));
auto wrapper = Ptr(new AsyncSocketConnection(connectionState));
connectionState->ConfigureServerNative(connectionState, connection);
return wrapper;
}
void OnNewConnection(nw_connection_t connection)
{
auto wrapper = CreateConnection(connection);
IAsyncSocketServerCallback* installedCallback = nullptr;
CS_LOCK(lockState)
{
if (started && !stopping)
{
installedCallback = callback;
}
}
WaitForClientResult result = WaitForClientResult::Reject;
if (installedCallback)
{
try
{
result = installedCallback->OnClientConnected(wrapper.Obj());
}
catch (...)
{
}
}
bool accepted = false;
lockState.Enter();
if (result == WaitForClientResult::Accept && started && !stopping)
{
connections.Add(wrapper);
accepted = true;
}
lockState.Leave();
if (accepted)
{
wrapper->StartAcceptedConnection();
}
else
{
wrapper->Stop();
}
}
public:
ServerState(vint _port)
: port(_port)
{
queue = dispatch_queue_create("vlppos.async-socket.listener", DISPATCH_QUEUE_SERIAL);
CHECK_ERROR(queue != nullptr, L"AsyncSocketServer failed to create its dispatch queue.");
dispatch_queue_set_specific(queue, &serverQueueKey, this, nullptr);
}
~ServerState()
{
dispatch_queue_set_specific(queue, &serverQueueKey, nullptr, nullptr);
dispatch_release(queue);
}
void Start(Ptr<ServerState> retainedState, IAsyncSocketServerCallback* value)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::macos_socket::ServerState::Start(Ptr<ServerState>, IAsyncSocketServerCallback*)#"
CHECK_ERROR(value != nullptr, ERROR_MESSAGE_PREFIX L"Requires a callback.");
bool begin = false;
CS_LOCK(lockState)
{
if (!startCalled && !stopping)
{
startCalled = true;
callback = value;
begin = true;
}
}
CHECK_ERROR(begin, ERROR_MESSAGE_PREFIX L"Can only be called once before stopping.");
auto portText = itoa(port);
auto endpoint = nw_endpoint_create_host("127.0.0.1", portText.Buffer());
auto parameters = nw_parameters_create_secure_tcp(
NW_PARAMETERS_DISABLE_PROTOCOL,
NW_PARAMETERS_DEFAULT_CONFIGURATION
);
nw_listener_t createdListener = nullptr;
if (endpoint && parameters)
{
nw_parameters_set_local_endpoint(parameters, endpoint);
createdListener = nw_listener_create(parameters);
}
if (endpoint)
{
nw_release(endpoint);
}
if (parameters)
{
nw_release(parameters);
}
if (!createdListener)
{
CS_LOCK(lockState)
{
stopping = true;
stopped = true;
callback = nullptr;
}
throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, ERROR_MESSAGE_PREFIX L"Failed to create the Network.framework listener.");
}
bool installed = false;
lockState.Enter();
if (!stopping)
{
listener = createdListener;
pendingListener = 1;
starting = true;
nw_listener_set_queue(listener, queue);
auto retainedForState = retainedState;
nw_listener_set_state_changed_handler(listener, ^(nw_listener_state_t state, nw_error_t error)
{
retainedForState->OnListenerState(retainedForState, state, error);
});
auto retainedForConnection = retainedState;
nw_listener_set_new_connection_handler(listener, ^(nw_connection_t connection)
{
retainedForConnection->OnNewConnection(connection);
});
nw_listener_start(listener);
installed = true;
}
lockState.Leave();
if (!installed)
{
nw_release(createdListener);
throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, ERROR_MESSAGE_PREFIX L"The listener was stopped during startup.");
}
AsyncSocketServerStartFailure failure = AsyncSocketServerStartFailure::Other;
vint error = 0;
bool ready = false;
lockState.Enter();
while (!startupResolved)
{
cvState.SleepWith(lockState);
}
ready = startupReady;
failure = startupFailure;
error = startupError;
lockState.Leave();
if (!ready)
{
Stop();
throw AsyncSocketServerStartException(failure, ERROR_MESSAGE_PREFIX L"Network.framework listener startup failed with error " + itow(error) + L".");
}
#undef ERROR_MESSAGE_PREFIX
}
void Stop()
{
auto deferredDrain = IsOnQueue();
List<Ptr<AsyncSocketConnection>> stoppingConnections;
lockState.Enter();
if (!stopping)
{
stopping = true;
started = false;
stopped = true;
if (starting && !startupResolved)
{
starting = false;
startupResolved = true;
startupReady = false;
startupFailure = AsyncSocketServerStartFailure::Other;
cvState.WakeAllPendings();
}
}
RequestListenerCancelLocked();
for (auto connection : connections)
{
stoppingConnections.Add(connection);
}
lockState.Leave();
if (!deferredDrain)
{
for (auto connection : stoppingConnections)
{
if (connection->RequiresDeferredDrain())
{
deferredDrain = true;
break;
}
}
}
if (deferredDrain)
{
for (auto connection : stoppingConnections)
{
connection->Stop();
}
return;
}
bool finalizeHere = false;
lockState.Enter();
if (!stopCompleted && !stopFinalizing)
{
stopFinalizing = true;
finalizeHere = true;
}
while (!finalizeHere && !stopCompleted)
{
cvState.SleepWith(lockState);
}
lockState.Leave();
if (!finalizeHere)
{
return;
}
lockState.Enter();
while (pendingListener > 0)
{
cvState.SleepWith(lockState);
}
lockState.Leave();
for (auto connection : stoppingConnections)
{
connection->Stop();
}
CS_LOCK(lockState)
{
connections.Clear();
callback = nullptr;
stopFinalizing = false;
stopCompleted = true;
cvState.WakeAllPendings();
}
}
bool IsStopped()
{
bool result = false;
CS_LOCK(lockState)
{
result = stopped;
}
return result;
}
};
/***********************************************************************
AsyncSocketServer::Impl
***********************************************************************/
class AsyncSocketServer::Impl : public Object
{
private:
vint port = 0;
Ptr<ServerState> state;
public:
Impl(vint _port)
: port(_port)
, state(Ptr(new ServerState(_port)))
{
}
~Impl()
{
state->Stop();
}
vint GetPort()
{
return port;
}
void Start(IAsyncSocketServerCallback* callback)
{
state->Start(state, callback);
}
void Stop()
{
state->Stop();
}
bool IsStopped()
{
return state->IsStopped();
}
};
/***********************************************************************
AsyncSocketServer
***********************************************************************/
AsyncSocketServer::AsyncSocketServer(vint port)
{
CHECK_ERROR(1 <= port && port <= 65535, L"AsyncSocketServer requires a port in 1..65535.");
impl = new Impl(port);
}
AsyncSocketServer::~AsyncSocketServer()
{
delete impl;
}
vint AsyncSocketServer::GetPort()
{
return impl->GetPort();
}
void AsyncSocketServer::Start(IAsyncSocketServerCallback* callback)
{
impl->Start(callback);
}
void AsyncSocketServer::Stop()
{
impl->Stop();
}
bool AsyncSocketServer::IsStopped()
{
return impl->IsStopped();
}
/***********************************************************************
AsyncSocketClient::Impl
***********************************************************************/
class AsyncSocketClient::Impl : public Object
{
private:
vint port = 0;
Ptr<ConnectionState> state;
Ptr<AsyncSocketConnection> connection;
public:
Impl(vint _port)
: port(_port)
, state(Ptr(new ConnectionState(true, _port)))
, connection(Ptr(new AsyncSocketConnection(state)))
{
}
~Impl()
{
connection->Stop();
}
vint GetPort()
{
return port;
}
IAsyncSocketConnection* GetConnection()
{
return connection.Obj();
}
void WaitForServer()
{
state->WaitForServer(state);
}
ClientStatus GetStatus()
{
return state->GetStatus();
}
};
/***********************************************************************
AsyncSocketClient
***********************************************************************/
AsyncSocketClient::AsyncSocketClient(vint port)
{
CHECK_ERROR(1 <= port && port <= 65535, L"AsyncSocketClient requires a port in 1..65535.");
impl = new Impl(port);
}
AsyncSocketClient::~AsyncSocketClient()
{
delete impl;
}
vint AsyncSocketClient::GetPort()
{
return impl->GetPort();
}
Ptr<IAsyncSocketClient> AsyncSocketClient::CreateSameEndpointClient()
{
return Ptr(new AsyncSocketClient(GetPort()));
}
IAsyncSocketConnection* AsyncSocketClient::GetConnection()
{
return impl->GetConnection();
}
void AsyncSocketClient::WaitForServer()
{
impl->WaitForServer();
}
ClientStatus AsyncSocketClient::GetStatus()
{
return impl->GetStatus();
}
}
namespace vl::inter_process::async_tcp_socket
{
Ptr<IAsyncSocketServer> CreateDefaultAsyncSocketServer(vint port)
{
return Ptr(new macos_socket::AsyncSocketServer(port));
}
Ptr<IAsyncSocketClient> CreateDefaultAsyncSocketClient(vint port)
{
return Ptr(new macos_socket::AsyncSocketClient(port));
}
}
#endif
/***********************************************************************
.\TUI\TUI.LINUX.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifdef VCZH_GCC
#include <csignal>
#include <poll.h>
#include <string>
#include <sys/ioctl.h>
#include <termios.h>
#include <wchar.h>
using namespace vl;
using namespace vl::collections;
namespace vl
{
namespace console
{
vint TUI::MeasureChar(char32_t code)
{
if (!tui_internal::IsScalar(code)) return 0;
auto width = wcwidth((wchar_t)code);
return width < 0 ? 0 : width;
}
namespace tui_internal
{
int resizePipe[2] = { -1, -1 };
void ResizeSignalHandler(int)
{
auto savedError = errno;
char byte = 1;
while (write(resizePipe[1], &byte, 1) == -1 && errno == EINTR)
{
}
errno = savedError;
}
void EnsureResizePipe()
{
if (resizePipe[0] != -1) return;
CHECK_ERROR(pipe(resizePipe) == 0, L"vl::console::TUI POSIX backend failed to create its resize pipe.");
for (vint i = 0; i < 2; i++)
{
auto descriptorFlags = fcntl(resizePipe[i], F_GETFD);
auto statusFlags = fcntl(resizePipe[i], F_GETFL);
CHECK_ERROR(descriptorFlags != -1 && statusFlags != -1, L"vl::console::TUI POSIX backend failed to query resize-pipe flags.");
CHECK_ERROR(fcntl(resizePipe[i], F_SETFD, descriptorFlags | FD_CLOEXEC) != -1, L"vl::console::TUI POSIX backend failed to make the resize pipe close-on-exec.");
CHECK_ERROR(fcntl(resizePipe[i], F_SETFL, statusFlags | O_NONBLOCK) != -1, L"vl::console::TUI POSIX backend failed to make the resize pipe nonblocking.");
}
}
void WriteAll(int descriptor, const char* text, vint length)
{
vint written = 0;
while (written < length)
{
auto count = write(descriptor, text + written, (size_t)(length - written));
if (count == -1 && errno == EINTR) continue;
CHECK_ERROR(count > 0, L"vl::console::TUI POSIX backend failed while writing terminal output.");
written += count;
}
}
void AppendUtf8(std::string& output, char32_t code)
{
if (code <= 0x7F)
{
output.push_back((char)code);
}
else if (code <= 0x7FF)
{
output.push_back((char)(0xC0 | (code >> 6)));
output.push_back((char)(0x80 | (code & 0x3F)));
}
else if (code <= 0xFFFF)
{
output.push_back((char)(0xE0 | (code >> 12)));
output.push_back((char)(0x80 | ((code >> 6) & 0x3F)));
output.push_back((char)(0x80 | (code & 0x3F)));
}
else
{
output.push_back((char)(0xF0 | (code >> 18)));
output.push_back((char)(0x80 | ((code >> 12) & 0x3F)));
output.push_back((char)(0x80 | ((code >> 6) & 0x3F)));
output.push_back((char)(0x80 | (code & 0x3F)));
}
}
void AppendNumber(std::string& output, vint value)
{
output += std::to_string((long long)value);
}
class PosixTuiBackend : public unittest::ITuiBackend
{
private:
termios savedTermios = {};
struct sigaction savedAction = {};
sigset_t savedMask = {};
List<vuint8_t> inputBytes;
List<unittest::TuiBackendEvent> pendingEvents;
vuint64_t escapeDeadline = 0;
vuint64_t lastClickTime = 0;
vint lastClickX = -1;
vint lastClickY = -1;
TuiMouseButton lastClickButton = TuiMouseButton::Left;
bool left = false;
bool middle = false;
bool right = false;
bool started = false;
bool termiosChanged = false;
bool signalInstalled = false;
void QueueChar(wchar_t code, bool alt = false)
{
unittest::TuiBackendEvent event;
event.type = unittest::TuiBackendEventType::Char;
event.charInfo.code = code;
event.charInfo.alt = alt;
pendingEvents.Add(event);
}
void DrainResizePipe()
{
char buffer[64];
for (;;)
{
auto count = read(resizePipe[0], buffer, sizeof(buffer));
if (count > 0) continue;
if (count == -1 && errno == EINTR) continue;
break;
}
}
void QueueResize()
{
vint width = 0;
vint height = 0;
if (TryGetConsoleSize(width, height))
{
unittest::TuiBackendEvent event;
event.type = unittest::TuiBackendEventType::Resize;
event.width = width;
event.height = height;
pendingEvents.Add(event);
}
}
bool TryParseMouse()
{
if (inputBytes.Count() < 4 || inputBytes[0] != 0x1B || inputBytes[1] != '[' || inputBytes[2] != '<') return false;
vint end = -1;
for (vint i = 3; i < inputBytes.Count(); i++)
{
if (inputBytes[i] == 'M' || inputBytes[i] == 'm')
{
end = i;
break;
}
if (!(inputBytes[i] == ';' || (inputBytes[i] >= '0' && inputBytes[i] <= '9')))
{
inputBytes.RemoveRange(0, i + 1);
QueueChar(L'\uFFFD');
return true;
}
}
if (end == -1) return false;
vint values[3] = {};
vint valueIndex = 0;
for (vint i = 3; i < end; i++)
{
if (inputBytes[i] == ';')
{
valueIndex++;
if (valueIndex >= 3) break;
}
else
{
values[valueIndex] = values[valueIndex] * 10 + inputBytes[i] - '0';
}
}
auto final = inputBytes[end];
inputBytes.RemoveRange(0, end + 1);
escapeDeadline = 0;
if (valueIndex != 2) return true;
auto cb = values[0];
TuiMouseInfo info;
info.x = values[1] - 1;
info.y = values[2] - 1;
info.shift = (cb & 4) != 0;
info.alt = (cb & 8) != 0;
info.ctrl = (cb & 16) != 0;
auto motion = (cb & 32) != 0;
auto base = cb & ~(4 | 8 | 16 | 32);
info.left = left;
info.middle = middle;
info.right = right;
unittest::TuiBackendEvent event;
event.mouseInfo = info;
if (base >= 64 && base <= 67)
{
if (base == 64 || base == 65)
{
event.type = unittest::TuiBackendEventType::MouseVerticalWheel;
event.mouseInfo.wheel = base == 64 ? 120 : -120;
}
else
{
event.type = unittest::TuiBackendEventType::MouseHorizontalWheel;
event.mouseInfo.wheel = base == 66 ? 120 : -120;
}
pendingEvents.Add(event);
return true;
}
if (motion)
{
event.type = unittest::TuiBackendEventType::MouseMove;
pendingEvents.Add(event);
return true;
}
auto released = final == 'm' || base == 3;
auto button = base == 0 ? TuiMouseButton::Left : base == 1 ? TuiMouseButton::Middle : TuiMouseButton::Right;
event.mouseButton = button;
if (released)
{
event.type = unittest::TuiBackendEventType::MouseUp;
if (button == TuiMouseButton::Left) left = false;
if (button == TuiMouseButton::Middle) middle = false;
if (button == TuiMouseButton::Right) right = false;
}
else
{
auto now = GetMonotonicTime();
auto doubleClick = lastClickTime != 0 && now - lastClickTime <= 500 && lastClickX == info.x && lastClickY == info.y && lastClickButton == button;
event.type = doubleClick ? unittest::TuiBackendEventType::MouseDoubleClick : unittest::TuiBackendEventType::MouseDown;
if (button == TuiMouseButton::Left) left = true;
if (button == TuiMouseButton::Middle) middle = true;
if (button == TuiMouseButton::Right) right = true;
if (doubleClick)
{
lastClickTime = 0;
}
else
{
lastClickTime = now;
lastClickX = info.x;
lastClickY = info.y;
lastClickButton = button;
}
}
event.mouseInfo.left = left;
event.mouseInfo.middle = middle;
event.mouseInfo.right = right;
pendingEvents.Add(event);
return true;
}
bool TryConsumeSpecialSequence()
{
if (inputBytes.Count() < 2 || inputBytes[0] != 0x1B || inputBytes[1] != '[') return false;
for (vint i = 2; i < inputBytes.Count(); i++)
{
auto byte = inputBytes[i];
if (byte >= 0x40 && byte <= 0x7E)
{
inputBytes.RemoveRange(0, i + 1);
escapeDeadline = 0;
return true;
}
}
return false;
}
bool TryDecodeUtf8(bool alt = false)
{
auto offset = alt ? 1 : 0;
if (inputBytes.Count() <= offset) return false;
auto first = inputBytes[offset];
vint length = 0;
char32_t code = 0;
if (first < 0x80)
{
length = 1;
code = first;
}
else if (first >= 0xC2 && first <= 0xDF)
{
length = 2;
code = first & 0x1F;
}
else if (first >= 0xE0 && first <= 0xEF)
{
length = 3;
code = first & 0x0F;
}
else if (first >= 0xF0 && first <= 0xF4)
{
length = 4;
code = first & 0x07;
}
else
{
inputBytes.RemoveRange(0, offset + 1);
QueueChar(L'\uFFFD', alt);
return true;
}
if (inputBytes.Count() < offset + length) return false;
for (vint i = 1; i < length; i++)
{
auto next = inputBytes[offset + i];
if ((next & 0xC0) != 0x80)
{
inputBytes.RemoveRange(0, offset + 1);
QueueChar(L'\uFFFD', alt);
return true;
}
code = (code << 6) | (next & 0x3F);
}
auto minimum = length == 1 ? 0 : length == 2 ? 0x80 : length == 3 ? 0x800 : 0x10000;
if (code < minimum || code > 0x10FFFF || (code >= 0xD800 && code <= 0xDFFF))
{
inputBytes.RemoveRange(0, offset + length);
QueueChar(L'\uFFFD', alt);
return true;
}
inputBytes.RemoveRange(0, offset + length);
escapeDeadline = 0;
QueueChar((wchar_t)code, alt);
return true;
}
bool ParseInput()
{
if (inputBytes.Count() == 0) return false;
if (inputBytes[0] == 0x1B)
{
if (TryParseMouse()) return true;
if (TryConsumeSpecialSequence()) return true;
if (inputBytes.Count() > 1 && inputBytes[1] != '[')
{
return TryDecodeUtf8(true);
}
if (escapeDeadline == 0) escapeDeadline = GetMonotonicTime() + 30;
if (GetMonotonicTime() >= escapeDeadline)
{
inputBytes.RemoveAt(0);
escapeDeadline = 0;
QueueChar((wchar_t)0x1B);
return true;
}
return false;
}
return TryDecodeUtf8();
}
void AppendColor(std::string& output, TuiColor foreground, TuiColor background, TuiColorMode colorMode)
{
output += "\x1B[";
if (colorMode == TuiColorMode::TrueColor)
{
output += "38;2;";
AppendNumber(output, foreground.r);
output += ";";
AppendNumber(output, foreground.g);
output += ";";
AppendNumber(output, foreground.b);
output += ";48;2;";
AppendNumber(output, background.r);
output += ";";
AppendNumber(output, background.g);
output += ";";
AppendNumber(output, background.b);
}
else if (colorMode == TuiColorMode::Color256)
{
output += "38;5;";
AppendNumber(output, QuantizeColor(foreground, colorMode));
output += ";48;5;";
AppendNumber(output, QuantizeColor(background, colorMode));
}
else
{
auto foregroundIndex = QuantizeColor(foreground, colorMode);
auto backgroundIndex = QuantizeColor(background, colorMode);
AppendNumber(output, foregroundIndex < 8 ? 30 + foregroundIndex : 90 + foregroundIndex - 8);
output += ";";
AppendNumber(output, backgroundIndex < 8 ? 40 + backgroundIndex : 100 + backgroundIndex - 8);
}
output += "m";
}
public:
TuiColorMode Start(const TuiStartOptions& options) override
{
CHECK_ERROR(!started, L"vl::console::TUI POSIX backend is already active.");
CHECK_ERROR(isatty(STDIN_FILENO) && isatty(STDOUT_FILENO), L"vl::console::TUI requires interactive terminal descriptors.");
EnsureResizePipe();
DrainResizePipe();
CHECK_ERROR(tcgetattr(STDIN_FILENO, &savedTermios) == 0, L"vl::console::TUI failed to query terminal attributes.");
started = true;
try
{
auto raw = savedTermios;
cfmakeraw(&raw);
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 0;
CHECK_ERROR(tcsetattr(STDIN_FILENO, TCSANOW, &raw) == 0, L"vl::console::TUI failed to activate raw terminal input.");
termiosChanged = true;
sigset_t blocked;
sigemptyset(&blocked);
sigaddset(&blocked, SIGWINCH);
CHECK_ERROR(pthread_sigmask(SIG_BLOCK, &blocked, &savedMask) == 0, L"vl::console::TUI failed to block SIGWINCH during handler installation.");
struct sigaction action = {};
action.sa_handler = ResizeSignalHandler;
sigemptyset(&action.sa_mask);
auto signalResult = sigaction(SIGWINCH, &action, &savedAction);
signalInstalled = signalResult == 0;
auto maskResult = pthread_sigmask(SIG_SETMASK, &savedMask, nullptr);
CHECK_ERROR(signalResult == 0, L"vl::console::TUI failed to install its SIGWINCH handler.");
CHECK_ERROR(maskResult == 0, L"vl::console::TUI failed to restore the owner thread signal mask.");
const char sequence[] = "\x1B[?1049h\x1B[?25l\x1B[?1003h\x1B[?1006h";
WriteAll(STDOUT_FILENO, sequence, sizeof(sequence) - 1);
if (options.colorMode != TuiColorMode::Auto) return options.colorMode;
auto colorTerm = getenv("COLORTERM");
if (colorTerm && (strstr(colorTerm, "truecolor") || strstr(colorTerm, "24bit"))) return TuiColorMode::TrueColor;
auto term = getenv("TERM");
if (term && strstr(term, "256color")) return TuiColorMode::Color256;
return TuiColorMode::Color16;
}
catch (...)
{
Stop();
throw;
}
}
void Stop() override
{
if (!started) return;
const char sequence[] = "\x1B[?1006l\x1B[?1003l\x1B[0m\x1B[?25h\x1B[?1049l";
auto ignored = write(STDOUT_FILENO, sequence, sizeof(sequence) - 1);
(void)ignored;
if (termiosChanged) tcsetattr(STDIN_FILENO, TCSANOW, &savedTermios);
if (signalInstalled)
{
sigset_t blocked;
sigemptyset(&blocked);
sigaddset(&blocked, SIGWINCH);
pthread_sigmask(SIG_BLOCK, &blocked, nullptr);
sigaction(SIGWINCH, &savedAction, nullptr);
pthread_sigmask(SIG_SETMASK, &savedMask, nullptr);
}
inputBytes.Clear();
pendingEvents.Clear();
escapeDeadline = 0;
left = middle = right = false;
termiosChanged = false;
signalInstalled = false;
started = false;
}
bool TryGetConsoleSize(vint& width, vint& height) override
{
winsize size = {};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) != 0) return false;
width = size.ws_col;
height = size.ws_row;
return width > 0 && height > 0;
}
vuint64_t GetMonotonicTime() override
{
timespec time = {};
clock_gettime(CLOCK_MONOTONIC, &time);
return (vuint64_t)time.tv_sec * 1000 + time.tv_nsec / 1000000;
}
bool ReadEvent(vint milliseconds, unittest::TuiBackendEvent& event) override
{
if (pendingEvents.Count() == 0) ParseInput();
if (pendingEvents.Count() == 0)
{
auto wait = milliseconds;
if (escapeDeadline != 0)
{
auto now = GetMonotonicTime();
auto remaining = escapeDeadline <= now ? 0 : (vint)(escapeDeadline - now);
if (wait < 0 || remaining < wait) wait = remaining;
}
pollfd descriptors[] =
{
{ STDIN_FILENO, POLLIN, 0 },
{ resizePipe[0], POLLIN, 0 },
};
vint result = 0;
do
{
result = poll(descriptors, sizeof(descriptors) / sizeof(*descriptors), wait);
} while (result == -1 && errno == EINTR);
CHECK_ERROR(result >= 0, L"vl::console::TUI POSIX backend failed while waiting for terminal input.");
if (descriptors[1].revents & POLLIN)
{
DrainResizePipe();
QueueResize();
}
if (descriptors[0].revents & POLLIN)
{
vuint8_t bytes[256];
auto count = read(STDIN_FILENO, bytes, sizeof(bytes));
if (count > 0)
{
for (vint i = 0; i < count; i++) inputBytes.Add(bytes[i]);
}
}
if (pendingEvents.Count() == 0) ParseInput();
}
if (pendingEvents.Count() == 0) return false;
event = pendingEvents[0];
pendingEvents.RemoveAt(0);
return true;
}
void Render(const TuiPixel* buffer, vint width, vint height, TuiColorMode colorMode) override
{
std::string output;
TuiColor lastForeground;
TuiColor lastBackground;
bool hasLastColor = false;
for (vint y = 0; y < height; y++)
{
output += "\x1B[";
AppendNumber(output, y + 1);
output += ";1H";
for (vint x = 0; x < width; x++)
{
auto& pixel = buffer[y * width + x];
if (pixel.glyph == TuiPixelGlyph::WideCharContinuation) continue;
if (!hasLastColor || pixel.foregroundColor != lastForeground || pixel.backgroundColor != lastBackground)
{
AppendColor(output, pixel.foregroundColor, pixel.backgroundColor, colorMode);
lastForeground = pixel.foregroundColor;
lastBackground = pixel.backgroundColor;
hasLastColor = true;
}
auto code = pixel.GetChar32();
AppendUtf8(output, code == 0 ? U' ' : code);
}
}
output += "\x1B[0m";
WriteAll(STDOUT_FILENO, output.data(), (vint)output.size());
}
};
Ptr<unittest::ITuiBackend> CreateTuiBackend()
{
return Ptr(new PosixTuiBackend);
}
}
}
}
#endif