From 55553318fa334e84498ceca871a3d2f8d32ebd41 Mon Sep 17 00:00:00 2001 From: vczh Date: Tue, 14 Jul 2026 22:15:11 -0700 Subject: [PATCH] Refresh release artifacts and fix HTTP tutorial --- Import/GacUI.Windows.cpp | 2 +- Import/VlppOS.Linux.cpp | 4143 ++++++++++++++++- Import/VlppOS.Linux.h | 118 + Import/VlppOS.Windows.cpp | 1762 ++++++- Import/VlppOS.Windows.h | 83 +- Import/VlppOS.h | 1295 +++++- .../CodePack/Codepack_CategorizeCodeFiles.cpp | 23 +- Tools/Executables/vl/makefile-cpp | 34 +- .../GacUI_Controls/ProgressAndAsync/Main.cpp | 2 +- 9 files changed, 7374 insertions(+), 88 deletions(-) create mode 100644 Import/VlppOS.Linux.h diff --git a/Import/GacUI.Windows.cpp b/Import/GacUI.Windows.cpp index e696a5fb..8e34de6f 100644 --- a/Import/GacUI.Windows.cpp +++ b/Import/GacUI.Windows.cpp @@ -2368,7 +2368,7 @@ WindowsAutomationServiceRenderer HttpAutomationService ***********************************************************************/ - class HttpAutomationService : public inter_process::HttpServerApi + class HttpAutomationService : public inter_process::windows_http::HttpServerApi { protected: WString urlControls; diff --git a/Import/VlppOS.Linux.cpp b/Import/VlppOS.Linux.cpp index 9ec29d37..be881e99 100644 --- a/Import/VlppOS.Linux.cpp +++ b/Import/VlppOS.Linux.cpp @@ -2,8 +2,7 @@ THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY DEVELOPER: Zihan Chen(vczh) ***********************************************************************/ -#include "VlppOS.h" -#include "Vlpp.h" +#include "VlppOS.Linux.h" /*********************************************************************** .\FILESYSTEM.LINUX.CPP @@ -316,6 +315,7 @@ Licensed under https://github.com/vczh-libraries/License #include #include #include +#include #if defined VCZH_APPLE #include #endif @@ -340,6 +340,7 @@ Thread { pthread_t id; EventObject ev; + bool deleteAfterStopped = false; }; class ProceduredThread : public Thread @@ -347,36 +348,18 @@ Thread private: Thread::ThreadProcedure procedure; void* argument; - bool deleteAfterStopped; protected: void Run() { - bool deleteAfterStopped = this->deleteAfterStopped; - ThreadLocalStorage::FixStorages(); - try - { - procedure(this, argument); - threadState=Thread::Stopped; - internalData->ev.Signal(); - ThreadLocalStorage::ClearStorages(); - } - catch (...) - { - ThreadLocalStorage::ClearStorages(); - throw; - } - if(deleteAfterStopped) - { - delete this; - } + procedure(this, argument); } public: ProceduredThread(Thread::ThreadProcedure _procedure, void* _argument, bool _deleteAfterStopped) :procedure(_procedure) ,argument(_argument) - ,deleteAfterStopped(_deleteAfterStopped) { + internalData->deleteAfterStopped = _deleteAfterStopped; } }; @@ -384,42 +367,41 @@ Thread { private: Func procedure; - bool deleteAfterStopped; protected: void Run() { - bool deleteAfterStopped = this->deleteAfterStopped; - ThreadLocalStorage::FixStorages(); - try - { - procedure(); - threadState=Thread::Stopped; - internalData->ev.Signal(); - ThreadLocalStorage::ClearStorages(); - } - catch (...) - { - ThreadLocalStorage::ClearStorages(); - throw; - } - if(deleteAfterStopped) - { - delete this; - } + procedure(); } public: LambdaThread(const Func& _procedure, bool _deleteAfterStopped) :procedure(_procedure) - ,deleteAfterStopped(_deleteAfterStopped) { + internalData->deleteAfterStopped = _deleteAfterStopped; } }; } void InternalThreadProc(Thread* thread) { - thread->Run(); + 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) @@ -505,11 +487,12 @@ Thread { if(threadState==Thread::NotStarted) { + threadState=Thread::Running; if(pthread_create(&internalData->id, nullptr, &InternalThreadProcWrapper, this)==0) { - threadState=Thread::Running; return true; } + threadState=Thread::NotStarted; } return false; } @@ -744,6 +727,8 @@ EventObject CriticalSection mutex; ConditionVariable cond; atomic_vint counter = 0; + vint releasedWaiters = 0; + vuint64_t signalVersion = 0; }; } @@ -787,15 +772,23 @@ EventObject if (!internalData) return false; internalData->mutex.Enter(); - internalData->signaled = true; - if (internalData->counter) + if (internalData->autoReset) { - if (internalData->autoReset) + if (internalData->counter > internalData->releasedWaiters) { + internalData->releasedWaiters++; internalData->cond.WakeOnePending(); - internalData->signaled = false; } else + { + internalData->signaled = true; + } + } + else + { + internalData->signaled = true; + internalData->signalVersion++; + if (internalData->counter) { internalData->cond.WakeAllPendings(); } @@ -818,6 +811,7 @@ EventObject { if (!internalData) return false; + bool result = true; internalData->mutex.Enter(); if (internalData->signaled) { @@ -828,12 +822,93 @@ EventObject } else { + auto signalVersion = internalData->signalVersion; INCRC(&internalData->counter); - internalData->cond.SleepWith(internalData->mutex); + 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 true; + 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; } /*********************************************************************** @@ -1137,8 +1212,30 @@ ConditionVariable ConditionVariable::ConditionVariable() { +#define ERROR_MESSAGE_PREFIX L"vl::ConditionVariable::ConditionVariable()#" internalData = new ConditionVariableData; - pthread_cond_init(&internalData->cond, nullptr); +#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() @@ -1152,6 +1249,37 @@ ConditionVariable 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); @@ -1294,3 +1422,3916 @@ TestEncoding } } + +/*********************************************************************** +.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET.LINUX.CPP +***********************************************************************/ +#if defined VCZH_GCC && !defined VCZH_APPLE + +#include +#include +#include +#include +#include +#include +#include + +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); + } + } + + 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; + } + }; + + 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(); + } + }; + + class RingOperationOwner : public Object + { + friend class RingRuntime; + protected: + Ptr operationDrain; + + RingOperationOwner() + : operationDrain(Ptr(new OperationDrain)) + { + } + + virtual void EndTargetOperation() = 0; + virtual void HandleOperationFailure(Ptr retainedOwner) = 0; + }; + + class RingOperation + { + friend class RingRuntime; + private: + Ptr operationOwner; + bool targetOperation = false; + + public: + vuint64_t id = 0; + + RingOperation(vuint64_t _id, Ptr _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; + }; + + class RingRuntime : public Object + { + private: + enum class FlushResult + { + Done, + ConsumeCompletion, + }; + + io_uring ring = {}; + CriticalSection lockRuntime; + ConditionVariable cvRuntimeProgress; + Dictionary 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, ¶meters); + 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 Create(bool startWorker) + { + auto result = Ptr(new RingRuntime); + if (startWorker) + { + result->Start(result); + } + return result; + } + + void Start(Ptr retainedRuntime) + { +#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::RingRuntime::Start(Ptr)#" + 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([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(); + }; + + 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 + { + } + }; + + 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 + } + + class ConnectionState : public RingOperationOwner + { + friend class AsyncSocketConnection; + private: + class ReceiveOperation; + class WriteOperation; + class ConnectOperation; + class RetryOperation; + class CancelOperation; + + Ptr runtime; + AsyncSocketConnection* 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 retainedOwner) override + { + auto retainedState = retainedOwner.Cast(); + 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 + 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 retainedState); + void BeginTerminal(Ptr retainedState, vint error, bool reportError); + void StartConnectAttempt(Ptr retainedState); + void DeliverConnectFailure(Ptr retainedState, vint error, bool fatal); + void ScheduleRetry(Ptr retainedState); + + public: + ConnectionState(Ptr _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, bool, vint)#" + CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), ERROR_MESSAGE_PREFIX L"Failed to create the client wait event."); +#undef ERROR_MESSAGE_PREFIX + } + + ConnectionState(Ptr _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, vint)#" + CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), ERROR_MESSAGE_PREFIX L"Failed to create the client wait event."); +#undef ERROR_MESSAGE_PREFIX + } + + void InstallCallback(Ptr retainedState, IAsyncSocketCallback* value); + void BeginReading(Ptr retainedState); + void Write(Ptr retainedState, Ptr buffer); + void Stop(Ptr retainedState); + void WaitForServer(Ptr retainedState); + ClientStatus GetStatus(); + }; + + class AsyncSocketConnection : public Object, public virtual IAsyncSocketConnection + { + private: + Ptr state; + + public: + AsyncSocketConnection(Ptr _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 buffer) override + { + state->Write(state, buffer); + } + + void Stop() override + { + state->Stop(state); + } + }; + + class ConnectionState::CancelOperation : public RingOperation + { + public: + Ptr connection; + vuint64_t targetId = 0; + + CancelOperation(vuint64_t id, Ptr _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) + { + } + } + }; + + class ConnectionState::ReceiveOperation : public RingOperation + { + public: + Ptr connection; + vint fileDescriptor = -1; + Array buffer; + + ReceiveOperation(vuint64_t id, Ptr _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); + } + } + }; + + class ConnectionState::WriteOperation : public RingOperation + { + public: + Ptr connection; + vint fileDescriptor = -1; + Ptr buffer; + vint offset = 0; + bool empty = false; + + WriteOperation(vuint64_t id, Ptr _connection, vint _fileDescriptor, Ptr _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); + } + } + } + }; + + class ConnectionState::ConnectOperation : public RingOperation + { + public: + Ptr connection; + OwnedFileDescriptor ownedSocket; + sockaddr_in address = {}; + + ConnectOperation(vuint64_t id, Ptr _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); + } + } + }; + + class ConnectionState::RetryOperation : public RingOperation + { + public: + Ptr connection; + __kernel_timespec timeout = {}; + + RetryOperation(vuint64_t id, Ptr _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); + } + } + }; + + void ConnectionState::InstallCallback(Ptr, IAsyncSocketCallback* value) + { +#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::InstallCallback(Ptr, 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 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 retainedState) + { +#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::BeginReading(Ptr)#" + 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 retainedState, Ptr buffer) + { +#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::Write(Ptr, Ptr)#" + 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 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 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 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 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 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 retainedState) + { +#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ConnectionState::WaitForServer(Ptr)#" + 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; + } + + class ServerState : public RingOperationOwner + { + private: + class AcceptOperation; + class CancelOperation; + + Ptr runtime; + AsyncSocketServer* owner = nullptr; + vint port = 0; + + // covers all fields below, callback counts, and target operation counts + CriticalSection lockState; + ConditionVariable cvCallbacks; + vint listener = -1; + bool startCalled = false; + bool starting = false; + bool started = false; + bool stopping = false; + bool stopped = false; + EventObject eventStartFinished; + vuint64_t acceptOperationId = 0; + bool acceptCancelRequested = false; + vint targetOperations = 0; + vint activeCallbacks = 0; + List> 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 retainedOwner) override + { + auto retainedState = retainedOwner.Cast(); + if (!retainedState) + { + std::abort(); + } + 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 retainedState); + + public: + ServerState(Ptr _runtime, AsyncSocketServer* _owner, vint _port) + : runtime(_runtime) + , owner(_owner) + , port(_port) + { +#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ServerState::ServerState(Ptr, AsyncSocketServer*, 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 retainedState); + void Stop(Ptr retainedState); + bool IsStopped(); + }; + + class ServerState::CancelOperation : public RingOperation + { + public: + Ptr server; + vuint64_t targetId = 0; + + CancelOperation(vuint64_t id, Ptr _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) + { + } + } + }; + + class ServerState::AcceptOperation : public RingOperation + { + public: + Ptr server; + vint listener = -1; + sockaddr_storage address = {}; + socklen_t addressSize = sizeof(address); + + AcceptOperation(vuint64_t id, Ptr _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->Stop(server); + } + } + else if (running && result != -ECANCELED) + { + server->Stop(server); + } + return; + } + + OwnedFileDescriptor acceptedSocket(result); + if (!running) + { + return; + } + + // Keep exactly one accept pending, and rearm before invoking user code. + if (!server->PostAccept(server)) + { + server->Stop(server); + return; + } + auto connectionState = Ptr(new ConnectionState(server->runtime, acceptedSocket.Get())); + auto connection = Ptr(new AsyncSocketConnection(connectionState)); + acceptedSocket.Detach(); + bool invoke = false; + CS_LOCK(server->lockState) + { + if (server->started && !server->stopping && server->owner) + { + server->connections.Add(connection); + server->activeCallbacks++; + invoke = true; + } + } + if (!invoke) + { + connection->Stop(); + return; + } + + WaitForClientResult acceptResult = WaitForClientResult::Reject; + ServerCallbackFrame frame{ server.Obj(), currentServerCallbackFrame }; + currentServerCallbackFrame = &frame; + try + { + acceptResult = server->owner->OnClientConnected(connection.Obj()); + } + catch (...) + { + } + currentServerCallbackFrame = frame.previous; + server->EndCallback(); + + bool stillRunning = false; + CS_LOCK(server->lockState) + { + stillRunning = server->started && !server->stopping; + } + if (acceptResult == WaitForClientResult::Reject || !stillRunning) + { + connection->Stop(); + } + } + }; + + bool ServerState::PostAccept(Ptr 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 retainedState) + { +#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::linux_socket::ServerState::Start(Ptr)#" + CS_LOCK(lockState) + { + CHECK_ERROR(!startCalled && !stopping, ERROR_MESSAGE_PREFIX L"Can only be called once before stopping."); + startCalled = true; + starting = true; + 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; + } + runtime->Stop(); + CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Failed to create the loopback listener."); + } + + 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; + } + runtime->Stop(); + throw; + } + if (committed) + { + if (!PostAccept(retainedState)) + { + CS_LOCK(lockState) + { + started = false; + stopping = true; + stopped = true; + CloseFileDescriptor(listener); + listener = -1; + } + runtime->Stop(); + CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Failed to submit the first accept operation."); + } + } +#undef ERROR_MESSAGE_PREFIX + } + + void ServerState::Stop(Ptr 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> retainedConnections; + CS_LOCK(lockState) + { + for (auto connection : connections) + { + retainedConnections.Add(connection); + } + } + for (auto connection : retainedConnections) + { + connection->Stop(); + } + runtime->Stop(); + } + + bool ServerState::IsStopped() + { + bool result = false; + CS_LOCK(lockState) + { + result = stopped; + } + return result; + } + + class AsyncSocketServer::Impl : public Object + { + private: + Ptr runtime; + Ptr state; + + public: + Impl(AsyncSocketServer* owner, vint port) + : runtime(RingRuntime::Create(false)) + , state(Ptr(new ServerState(runtime, owner, port))) + { + } + + ~Impl() + { + Stop(); + } + + void Start() + { + state->Start(state); + } + + void Stop() + { + state->Stop(state); + } + + bool IsStopped() + { + return state->IsStopped(); + } + }; + + 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(this, port); + } + + AsyncSocketServer::~AsyncSocketServer() + { + delete impl; + } + + WaitForClientResult AsyncSocketServer::OnClientConnected(IAsyncSocketConnection*) + { + return WaitForClientResult::Accept; + } + + void AsyncSocketServer::Start() + { + impl->Start(); + } + + void AsyncSocketServer::Stop() + { + impl->Stop(); + } + + bool AsyncSocketServer::IsStopped() + { + return impl->IsStopped(); + } + + class AsyncSocketClient::Impl : public Object + { + private: + Ptr runtime; + Ptr state; + Ptr connection; + + public: + Impl(vint port) + : runtime(RingRuntime::Create(true)) + , state(Ptr(new ConnectionState(runtime, true, port))) + , connection(Ptr(new AsyncSocketConnection(state))) + { + } + + ~Impl() + { + Stop(); + } + + void Stop() + { + connection->Stop(); + runtime->Stop(); + } + + IAsyncSocketConnection* GetConnection() + { + return connection.Obj(); + } + + void WaitForServer() + { + state->WaitForServer(state); + } + + ClientStatus GetStatus() + { + return state->GetStatus(); + } + }; + + 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; + } + + IAsyncSocketConnection* AsyncSocketClient::GetConnection() + { + return impl->GetConnection(); + } + + void AsyncSocketClient::WaitForServer() + { + impl->WaitForServer(); + } + + ClientStatus AsyncSocketClient::GetStatus() + { + return impl->GetStatus(); + } +} + +#endif + +/*********************************************************************** +.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET.MACOS.CPP +***********************************************************************/ +#if defined VCZH_GCC && defined VCZH_APPLE + +#include +#include + +#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; + + class NativeConnectionContext : public Object + { + friend class ConnectionState; + private: + Ptr state; + nw_connection_t connection = nullptr; + vint generation = 0; + bool started = false; + bool cancelRequested = false; + bool waitingHandled = false; + bool cancelledHandled = false; + + public: + NativeConnectionContext(Ptr _state, nw_connection_t _connection); + ~NativeConnectionContext(); + }; + + 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 pendingReadBuffer; + bool writePending = false; + bool writeIssued = false; + vint writeGeneration = -1; + Ptr writeBuffer; + dispatch_data_t writeData = nullptr; + vint pendingLocalTasks = 0; + Ptr 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 + 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 context, nw_error_t error) + { + Ptr 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 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 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 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 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 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; + } + }; + + NativeConnectionContext::NativeConnectionContext(Ptr _state, nw_connection_t _connection) + : state(_state) + , connection(_connection) + { + } + + NativeConnectionContext::~NativeConnectionContext() + { + if (connection) + { + nw_release(connection); + } + } + + class AsyncSocketConnection : public Object, public virtual IAsyncSocketConnection + { + private: + Ptr state; + + public: + AsyncSocketConnection(Ptr _state) + : state(_state) + { + state->AttachOwner(this); + } + + ~AsyncSocketConnection() + { + state->Stop(); + state->DetachOwner(this); + } + + Ptr 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 buffer) override + { + state->Write(buffer); + } + + void Stop() override + { + state->Stop(); + } + }; + + class ServerState : public Object + { + private: + // covers all fields below + CriticalSection lockState; + ConditionVariable cvState; + dispatch_queue_t queue = nullptr; + AsyncSocketServer* owner = nullptr; + vint port = 0; + bool startCalled = false; + bool started = false; + bool stopping = false; + bool stopped = false; + bool stopFinalizing = false; + bool stopCompleted = false; + nw_listener_t listener = nullptr; + bool listenerCancelRequested = false; + bool listenerCancelledHandled = false; + vint pendingListener = 0; + List> connections; + + bool IsOnQueue() + { + return dispatch_get_specific(&serverQueueKey) == this; + } + + void RequestListenerCancelLocked() + { + if (listener && !listenerCancelRequested) + { + listenerCancelRequested = true; + nw_listener_cancel(listener); + } + } + + void OnListenerCancelled(Ptr 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 retainedState, nw_listener_state_t state) + { + switch (state) + { + case nw_listener_state_failed: + Stop(); + break; + case nw_listener_state_cancelled: + OnListenerCancelled(retainedState); + break; + default: + break; + } + } + + Ptr 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); + bool offer = false; + AsyncSocketServer* installedOwner = nullptr; + CS_LOCK(lockState) + { + offer = started && !stopping && owner; + installedOwner = owner; + } + + WaitForClientResult result = WaitForClientResult::Reject; + if (offer) + { + try + { + result = installedOwner->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(AsyncSocketServer* _owner, vint _port) + : owner(_owner) + , 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 DetachOwner() + { + CS_LOCK(lockState) + { + owner = nullptr; + } + } + + void Start(Ptr retainedState) + { + bool begin = false; + CS_LOCK(lockState) + { + if (!startCalled && !stopping) + { + startCalled = true; + begin = true; + } + } + CHECK_ERROR(begin, L"AsyncSocketServer::Start can only be called once."); + + 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; + } + CHECK_ERROR(false, L"AsyncSocketServer failed to create its Network.framework listener."); + } + + bool installed = false; + lockState.Enter(); + if (!stopping) + { + listener = createdListener; + pendingListener = 1; + started = 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) + { + retainedForState->OnListenerState(retainedForState, state); + }); + + 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); + } + } + + void Stop() + { + auto deferredDrain = IsOnQueue(); + List> stoppingConnections; + lockState.Enter(); + if (!stopping) + { + stopping = true; + started = false; + stopped = true; + } + 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(); + stopFinalizing = false; + stopCompleted = true; + cvState.WakeAllPendings(); + } + } + + bool IsStopped() + { + bool result = false; + CS_LOCK(lockState) + { + result = stopped; + } + return result; + } + }; + + class AsyncSocketServer::Impl : public Object + { + private: + Ptr state; + + public: + Impl(AsyncSocketServer* owner, vint port) + : state(Ptr(new ServerState(owner, port))) + { + } + + ~Impl() + { + state->Stop(); + state->DetachOwner(); + } + + void Start() + { + state->Start(state); + } + + void Stop() + { + state->Stop(); + } + + bool IsStopped() + { + return state->IsStopped(); + } + }; + + AsyncSocketServer::AsyncSocketServer(vint port) + { + CHECK_ERROR(1 <= port && port <= 65535, L"AsyncSocketServer requires a port in 1..65535."); + impl = new Impl(this, port); + } + + AsyncSocketServer::~AsyncSocketServer() + { + delete impl; + } + + WaitForClientResult AsyncSocketServer::OnClientConnected(IAsyncSocketConnection*) + { + return WaitForClientResult::Accept; + } + + void AsyncSocketServer::Start() + { + impl->Start(); + } + + void AsyncSocketServer::Stop() + { + impl->Stop(); + } + + bool AsyncSocketServer::IsStopped() + { + return impl->IsStopped(); + } + + class AsyncSocketClient::Impl : public Object + { + private: + Ptr state; + Ptr connection; + + public: + Impl(vint port) + : state(Ptr(new ConnectionState(true, port))) + , connection(Ptr(new AsyncSocketConnection(state))) + { + } + + ~Impl() + { + connection->Stop(); + } + + IAsyncSocketConnection* GetConnection() + { + return connection.Obj(); + } + + void WaitForServer() + { + state->WaitForServer(state); + } + + ClientStatus GetStatus() + { + return state->GetStatus(); + } + }; + + 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; + } + + IAsyncSocketConnection* AsyncSocketClient::GetConnection() + { + return impl->GetConnection(); + } + + void AsyncSocketClient::WaitForServer() + { + impl->WaitForServer(); + } + + ClientStatus AsyncSocketClient::GetStatus() + { + return impl->GetStatus(); + } +} +#endif diff --git a/Import/VlppOS.Linux.h b/Import/VlppOS.Linux.h new file mode 100644 index 00000000..d90c13b0 --- /dev/null +++ b/Import/VlppOS.Linux.h @@ -0,0 +1,118 @@ +/*********************************************************************** +THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY +DEVELOPER: Zihan Chen(vczh) +***********************************************************************/ +#include "Vlpp.h" +#include "VlppOS.h" + +/*********************************************************************** +.\ASYNCSOCKET.LINUX.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) + +Linux implementation of IAsyncSocket(Server|Client) + +***********************************************************************/ + +#ifndef VCZH_INTERPROCESS_ASYNCSOCKET_LINUX +#define VCZH_INTERPROCESS_ASYNCSOCKET_LINUX + + +#if defined VCZH_GCC && !defined VCZH_APPLE + +namespace vl::inter_process::async_tcp_socket::linux_socket +{ + class AsyncSocketServer : public Object, public virtual IAsyncSocketServer + { + private: + class Impl; + Impl* impl = nullptr; + + public: + AsyncSocketServer(vint port); + ~AsyncSocketServer(); + + WaitForClientResult OnClientConnected(IAsyncSocketConnection* connection) override; + void Start() override; + void Stop() override; + bool IsStopped() override; + }; + + class AsyncSocketClient : public Object, public virtual IAsyncSocketClient + { + private: + class Impl; + Impl* impl = nullptr; + + public: + AsyncSocketClient(vint port); + ~AsyncSocketClient(); + + IAsyncSocketConnection* GetConnection() override; + void WaitForServer() override; + ClientStatus GetStatus() override; + }; +} + +#endif + +#endif + + +/*********************************************************************** +.\ASYNCSOCKET.MACOS.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) + +macOS implementation of IAsyncSocket(Server|Client) + +***********************************************************************/ + +#ifndef VCZH_INTERPROCESS_ASYNCSOCKET_MACOS +#define VCZH_INTERPROCESS_ASYNCSOCKET_MACOS + + +#if defined VCZH_GCC && defined VCZH_APPLE + +namespace vl::inter_process::async_tcp_socket::macos_socket +{ + class AsyncSocketServer : public Object, public virtual IAsyncSocketServer + { + private: + class Impl; + Impl* impl = nullptr; + + public: + AsyncSocketServer(vint port); + ~AsyncSocketServer(); + + WaitForClientResult OnClientConnected(IAsyncSocketConnection* connection) override; + void Start() override; + void Stop() override; + bool IsStopped() override; + }; + + class AsyncSocketClient : public Object, public virtual IAsyncSocketClient + { + private: + class Impl; + Impl* impl = nullptr; + + public: + AsyncSocketClient(vint port); + ~AsyncSocketClient(); + + IAsyncSocketConnection* GetConnection() override; + void WaitForServer() override; + ClientStatus GetStatus() override; + }; +} + +#endif + +#endif + diff --git a/Import/VlppOS.Windows.cpp b/Import/VlppOS.Windows.cpp index 63c83144..62f32631 100644 --- a/Import/VlppOS.Windows.cpp +++ b/Import/VlppOS.Windows.cpp @@ -13,7 +13,6 @@ Licensed under https://github.com/vczh-libraries/License ***********************************************************************/ #define _WINSOCKAPI_ -#include #include #ifndef VCZH_MSVC @@ -1657,11 +1656,1760 @@ TestEncoding } +/*********************************************************************** +.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET.WINDOWS.CPP +***********************************************************************/ + +#pragma comment(lib, "Ws2_32.lib") + +namespace vl::inter_process::async_tcp_socket::windows_socket +{ + using namespace collections; + + class IocpOperation; + class IocpRuntime; + static thread_local IocpRuntime* currentCompletionRuntime = nullptr; + static thread_local IocpRuntime* currentCallbackRuntime = nullptr; + + struct NativeOverlapped + { + OVERLAPPED overlapped; + IocpOperation* operation = nullptr; + }; + + class IocpOperation + { + public: + NativeOverlapped native; + + IocpOperation() + { + ZeroMemory(&native.overlapped, sizeof(native.overlapped)); + native.operation = this; + } + + virtual ~IocpOperation() = default; + virtual bool Complete(DWORD bytes, DWORD error) = 0; + virtual void EndPending() = 0; + }; + + class IocpRuntime : public Object + { + private: + class CompletionWorker : public Thread + { + private: + IocpRuntime* runtime = nullptr; + protected: + void Run() override + { + currentCompletionRuntime = runtime; + while (true) + { + DWORD bytes = 0; + ULONG_PTR key = 0; + OVERLAPPED* overlapped = nullptr; + auto succeeded = GetQueuedCompletionStatus(runtime->iocp, &bytes, &key, &overlapped, INFINITE); + if (!overlapped) + { + break; + } + + auto native = CONTAINING_RECORD(overlapped, NativeOverlapped, overlapped); + auto operation = native->operation; + auto error = succeeded ? ERROR_SUCCESS : GetLastError(); + bool completed = true; + try + { + completed = operation->Complete(bytes, error); + } + catch (...) + { + completed = true; + } + if (completed) + { + operation->EndPending(); + delete operation; + } + } + currentCompletionRuntime = nullptr; + } + + public: + CompletionWorker(IocpRuntime* _runtime) + : runtime(_runtime) + { + } + }; + + class CallbackWorker : public Thread + { + private: + IocpRuntime* runtime = nullptr; + protected: + void Run() override + { + currentCallbackRuntime = runtime; + runtime->callbackQueue.RunTaskQueue(); + currentCallbackRuntime = nullptr; + } + + public: + CallbackWorker(IocpRuntime* _runtime) + : runtime(_runtime) + { + } + }; + + HANDLE iocp = nullptr; + TaskQueue callbackQueue; + CompletionWorker* completionWorker = nullptr; + CallbackWorker* callbackWorker = nullptr; + SpinLock lockStop; + bool stopRequested = false; + bool finalizing = false; + bool finalized = false; + EventObject eventFinalized; + bool winsockStarted = false; + + public: + IocpRuntime() + { + CHECK_ERROR(eventFinalized.CreateManualUnsignal(false), L"IAsyncSocket failed to create its runtime drain event."); + bool completionStarted = false; + bool callbackStarted = false; + try + { + WSADATA data; + auto error = WSAStartup(MAKEWORD(2, 2), &data); + CHECK_ERROR(error == 0, L"IAsyncSocket failed to initialize Winsock."); + winsockStarted = true; + + iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1); + CHECK_ERROR(iocp != nullptr, L"IAsyncSocket failed to create an IO completion port."); + + completionWorker = new CompletionWorker(this); + callbackWorker = new CallbackWorker(this); + completionStarted = completionWorker->Start(); + CHECK_ERROR(completionStarted, L"IAsyncSocket failed to start its completion worker."); + callbackStarted = callbackWorker->Start(); + CHECK_ERROR(callbackStarted, L"IAsyncSocket failed to start its callback worker."); + } + catch (...) + { + if (callbackStarted) + { + callbackQueue.QueueExitTask(); + callbackWorker->Wait(); + } + if (completionStarted) + { + PostQueuedCompletionStatus(iocp, 0, 0, nullptr); + completionWorker->Wait(); + } + delete callbackWorker; + delete completionWorker; + if (iocp) + { + CloseHandle(iocp); + } + if (winsockStarted) + { + WSACleanup(); + } + throw; + } + } + + ~IocpRuntime() + { + Stop(); + } + + bool Associate(SOCKET socket) + { + return CreateIoCompletionPort((HANDLE)socket, iocp, 0, 0) == iocp; + } + + void QueueCallback(Func callback) + { + callbackQueue.QueueTask(callback); + } + + void Stop() + { + bool requestExit = false; + bool finalizeHere = false; + bool waitForFinalization = false; + auto selfWorker = currentCallbackRuntime == this || currentCompletionRuntime == this; + SPIN_LOCK(lockStop) + { + if (finalized) + { + return; + } + if (!stopRequested) + { + stopRequested = true; + requestExit = true; + } + if (!selfWorker) + { + if (!finalizing) + { + finalizing = true; + finalizeHere = true; + } + else + { + waitForFinalization = true; + } + } + } + + if (requestExit) + { + callbackQueue.QueueExitTask(); + PostQueuedCompletionStatus(iocp, 0, 0, nullptr); + } + if (selfWorker) + { + return; + } + if (waitForFinalization) + { + eventFinalized.Wait(); + return; + } + if (!finalizeHere) + { + return; + } + + if (callbackWorker) + { + callbackWorker->Wait(); + } + if (completionWorker) + { + completionWorker->Wait(); + } + + delete callbackWorker; + callbackWorker = nullptr; + delete completionWorker; + completionWorker = nullptr; + if (iocp) + { + CloseHandle(iocp); + iocp = nullptr; + } + if (winsockStarted) + { + WSACleanup(); + winsockStarted = false; + } + SPIN_LOCK(lockStop) + { + finalized = true; + } + eventFinalized.Signal(); + } + }; + + class ConnectionState; + + struct CallbackFrame + { + ConnectionState* connection = nullptr; + CallbackFrame* previous = nullptr; + }; + + static thread_local CallbackFrame* currentCallbackFrame = nullptr; + + class ReadBlock : public Object + { + public: + Array data; + + ReadBlock() + { + data.Resize(65536); + } + }; + + class AsyncSocketConnection; + + class ConnectionState : public Object + { + friend class AsyncSocketConnection; + private: + class ReadOperation; + class WriteOperation; + class ConnectOperation; + + IocpRuntime* runtime = nullptr; + AsyncSocketConnection* owner = nullptr; + + // covers every field below, pending counts, and their events + CriticalSection lockState; + SOCKET socket = INVALID_SOCKET; + IAsyncSocketCallback* callback = nullptr; + bool connected = false; + bool stopping = false; + bool stopped = false; + bool reading = false; + bool readPending = false; + bool writePending = false; + bool terminalPending = false; + bool disconnectedNotified = false; + vint pendingIo = 0; + vint activeCallbacks = 0; + EventObject eventIoDrained; + EventObject eventCallbacksDrained; + + bool clientMode = false; + vint clientPort = 0; + ClientStatus clientStatus = ClientStatus::Ready; + EventObject eventWaitForServer; + PTP_TIMER clientRetryTimer = nullptr; + vint clientAttempts = 0; + vint clientGeneration = 0; + + void BeginPendingLocked() + { + if (pendingIo++ == 0) + { + eventIoDrained.Unsignal(); + } + } + + void EndPendingLocked() + { + if (--pendingIo == 0) + { + eventIoDrained.Signal(); + } + } + + void EndPending() + { + CS_LOCK(lockState) + { + EndPendingLocked(); + } + } + + IAsyncSocketCallback* BeginCallback(bool terminal) + { + IAsyncSocketCallback* result = nullptr; + CS_LOCK(lockState) + { + if (callback && (terminal || (!stopping && !terminalPending))) + { + result = callback; + if (activeCallbacks++ == 0) + { + eventCallbacksDrained.Unsignal(); + } + } + } + return result; + } + + void EndCallback() + { + CS_LOCK(lockState) + { + if (--activeCallbacks == 0) + { + eventCallbacksDrained.Signal(); + } + } + } + + template + bool InvokeCallback(bool terminal, TCallback&& invoke) + { + auto installed = BeginCallback(terminal); + if (!installed) + { + return false; + } + + CallbackFrame frame; + frame.connection = this; + frame.previous = currentCallbackFrame; + currentCallbackFrame = &frame; + try + { + invoke(installed); + } + catch (...) + { + } + currentCallbackFrame = frame.previous; + EndCallback(); + return true; + } + + bool IsCurrentCallback() + { + for (auto frame = currentCallbackFrame; frame; frame = frame->previous) + { + if (frame->connection == this) + { + return true; + } + } + return false; + } + + void PostRead(Ptr retainedState); + Ptr Retain(); + void DeliverRead(Ptr retainedState, Ptr block, vint bytes); + void DeliverWrite(Ptr buffer); + void QueueTerminal(DWORD error, bool reportError); + void DeliverTerminal(DWORD error, bool reportError); + void StartConnectAttempt(); + void CompleteConnect(SOCKET operationSocket, vint generation, DWORD error); + void QueueConnectFailure(DWORD error); + void DeliverConnectFailure(DWORD error, bool fatal); + void DeliverConnected(); + void ScheduleRetry(); + static VOID CALLBACK RetryTimerCallback(PTP_CALLBACK_INSTANCE, PVOID context, PTP_TIMER) + { + auto self = (ConnectionState*)context; + self->StartConnectAttempt(); + } + + public: + ConnectionState(IocpRuntime* _runtime, bool _clientMode, vint _clientPort) + : runtime(_runtime) + , clientMode(_clientMode) + , clientPort(_clientPort) + { + CHECK_ERROR(eventIoDrained.CreateManualUnsignal(true), L"IAsyncSocket failed to create its I/O drain event."); + CHECK_ERROR(eventCallbacksDrained.CreateManualUnsignal(true), L"IAsyncSocket failed to create its callback drain event."); + CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), L"IAsyncSocket failed to create its client wait event."); + if (clientMode) + { + clientRetryTimer = CreateThreadpoolTimer(&RetryTimerCallback, this, nullptr); + CHECK_ERROR(clientRetryTimer != nullptr, L"IAsyncSocket failed to create its retry timer."); + } + } + + ConnectionState(IocpRuntime* _runtime, SOCKET _socket) + : runtime(_runtime) + , socket(_socket) + , connected(true) + { + CHECK_ERROR(eventIoDrained.CreateManualUnsignal(true), L"IAsyncSocket failed to create its I/O drain event."); + CHECK_ERROR(eventCallbacksDrained.CreateManualUnsignal(true), L"IAsyncSocket failed to create its callback drain event."); + CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), L"IAsyncSocket failed to create its client wait event."); + } + + ~ConnectionState() + { + if (clientRetryTimer) + { + SetThreadpoolTimer(clientRetryTimer, nullptr, 0, 0); + WaitForThreadpoolTimerCallbacks(clientRetryTimer, TRUE); + CloseThreadpoolTimer(clientRetryTimer); + clientRetryTimer = nullptr; + } + } + + void CloseRetryTimer() + { + PTP_TIMER timer = nullptr; + CS_LOCK(lockState) + { + timer = clientRetryTimer; + clientRetryTimer = nullptr; + } + if (timer) + { + SetThreadpoolTimer(timer, nullptr, 0, 0); + WaitForThreadpoolTimerCallbacks(timer, TRUE); + CloseThreadpoolTimer(timer); + } + } + + void InstallCallback(IAsyncSocketCallback* value); + void BeginReading(); + void Write(Ptr buffer); + void Stop(); + void WaitForServer(); + ClientStatus GetStatus(); + }; + + class AsyncSocketConnection : public Object, public virtual IAsyncSocketConnection + { + private: + Ptr state; + + public: + AsyncSocketConnection(Ptr _state) + : state(_state) + { + state->owner = this; + } + + ~AsyncSocketConnection() + { + state->Stop(); + state->owner = nullptr; + } + + Ptr GetState() + { + return state; + } + + void InstallCallback(IAsyncSocketCallback* callback) override + { + state->InstallCallback(callback); + } + + void BeginReadingLoopUnsafe() override + { + state->BeginReading(); + } + + void WriteAsync(Ptr buffer) override + { + state->Write(buffer); + } + + void Stop() override + { + state->Stop(); + } + }; + + Ptr ConnectionState::Retain() + { + CHECK_ERROR(owner != nullptr, L"IAsyncSocketConnection lost its canonical state owner."); + return owner->GetState(); + } + + WString SocketErrorMessage(const wchar_t* operation, DWORD error) + { + return WString::Unmanaged(operation) + L" failed with Windows error " + itow((vint)error) + L"."; + } + + class ConnectionState::ReadOperation : public IocpOperation + { + public: + Ptr connection; + Ptr block; + + ReadOperation(Ptr _connection) + : connection(_connection) + , block(Ptr(new ReadBlock)) + { + } + + bool Complete(DWORD bytes, DWORD error) override + { + bool cancelled = false; + CS_LOCK(connection->lockState) + { + connection->readPending = false; + cancelled = connection->stopping; + } + if (cancelled) + { + return true; + } + if (error != ERROR_SUCCESS) + { + connection->QueueTerminal(error, true); + } + else if (bytes == 0) + { + connection->QueueTerminal(ERROR_SUCCESS, false); + } + else + { + auto state = connection; + auto retainedBlock = block; + connection->runtime->QueueCallback(Func([state, retainedBlock, bytes]() + { + state->DeliverRead(state, retainedBlock, (vint)bytes); + })); + } + return true; + } + + void EndPending() override + { + connection->EndPending(); + } + }; + + class ConnectionState::WriteOperation : public IocpOperation + { + public: + Ptr connection; + Ptr buffer; + vint offset = 0; + + WriteOperation(Ptr _connection, Ptr _buffer) + : connection(_connection) + , buffer(_buffer) + { + } + + DWORD PostLocked() + { + WSABUF nativeBuffer; + nativeBuffer.buf = (CHAR*)&buffer->data[offset]; + nativeBuffer.len = (ULONG)(buffer->data.Count() - offset); + DWORD sent = 0; + auto result = WSASend(connection->socket, &nativeBuffer, 1, &sent, 0, &native.overlapped, nullptr); + if (result == 0) + { + return ERROR_SUCCESS; + } + auto error = WSAGetLastError(); + return error == WSA_IO_PENDING ? ERROR_SUCCESS : (DWORD)error; + } + + bool Complete(DWORD bytes, DWORD error) override + { + bool queueCompleted = false; + DWORD terminalError = ERROR_SUCCESS; + connection->lockState.Enter(); + if (connection->stopping) + { + connection->lockState.Leave(); + return true; + } + if (error != ERROR_SUCCESS || bytes == 0) + { + terminalError = error == ERROR_SUCCESS ? WSAECONNRESET : error; + connection->lockState.Leave(); + connection->QueueTerminal(terminalError, true); + return true; + } + + offset += (vint)bytes; + if (offset < buffer->data.Count()) + { + ZeroMemory(&native.overlapped, sizeof(native.overlapped)); + auto postError = PostLocked(); + connection->lockState.Leave(); + if (postError == ERROR_SUCCESS) + { + return false; + } + connection->QueueTerminal(postError, true); + return true; + } + queueCompleted = true; + connection->lockState.Leave(); + + if (queueCompleted) + { + auto state = connection; + auto retainedBuffer = buffer; + connection->runtime->QueueCallback(Func([state, retainedBuffer]() + { + state->DeliverWrite(retainedBuffer); + })); + } + return true; + } + + void EndPending() override + { + connection->EndPending(); + } + }; + + class ConnectionState::ConnectOperation : public IocpOperation + { + public: + Ptr connection; + SOCKET operationSocket = INVALID_SOCKET; + vint generation = 0; + + ConnectOperation(Ptr _connection, SOCKET _operationSocket, vint _generation) + : connection(_connection) + , operationSocket(_operationSocket) + , generation(_generation) + { + } + + bool Complete(DWORD, DWORD error) override + { + connection->CompleteConnect(operationSocket, generation, error); + return true; + } + + void EndPending() override + { + connection->EndPending(); + } + }; + + void ConnectionState::InstallCallback(IAsyncSocketCallback* value) + { + if (!value) + { + bool selfCallback = IsCurrentCallback(); + CS_LOCK(lockState) + { + callback = nullptr; + } + if (!selfCallback) + { + eventCallbacksDrained.Wait(); + } + return; + } + + bool canInstall = false; + CS_LOCK(lockState) + { + canInstall = callback == nullptr && !stopping; + if (canInstall) + { + callback = value; + if (activeCallbacks++ == 0) + { + eventCallbacksDrained.Unsignal(); + } + } + } + CHECK_ERROR(canInstall, L"IAsyncSocketConnection::InstallCallback cannot replace a callback or install one on a stopped connection."); + + CallbackFrame frame; + frame.connection = this; + frame.previous = currentCallbackFrame; + currentCallbackFrame = &frame; + try + { + value->OnInstalled(owner); + } + catch (...) + { + } + currentCallbackFrame = frame.previous; + EndCallback(); + } + + void ConnectionState::BeginReading() + { + CS_LOCK(lockState) + { + CHECK_ERROR(connected && !stopping && !terminalPending, L"IAsyncSocketConnection::BeginReadingLoopUnsafe requires a connected connection."); + CHECK_ERROR(callback != nullptr, L"IAsyncSocketConnection::BeginReadingLoopUnsafe requires an installed callback."); + CHECK_ERROR(!reading, L"IAsyncSocketConnection::BeginReadingLoopUnsafe can only be called once."); + reading = true; + } + PostRead(Retain()); + } + + void ConnectionState::PostRead(Ptr retainedState) + { + auto operation = new ReadOperation(retainedState); + DWORD immediateError = ERROR_SUCCESS; + lockState.Enter(); + if (!connected || stopping || terminalPending || !reading || readPending) + { + lockState.Leave(); + delete operation; + return; + } + + readPending = true; + BeginPendingLocked(); + WSABUF buffer; + buffer.buf = (CHAR*)&operation->block->data[0]; + buffer.len = (ULONG)operation->block->data.Count(); + DWORD flags = 0; + DWORD received = 0; + auto result = WSARecv(socket, &buffer, 1, &received, &flags, &operation->native.overlapped, nullptr); + if (result == SOCKET_ERROR) + { + auto error = WSAGetLastError(); + if (error != WSA_IO_PENDING) + { + immediateError = (DWORD)error; + readPending = false; + EndPendingLocked(); + } + } + lockState.Leave(); + + if (immediateError != ERROR_SUCCESS) + { + delete operation; + QueueTerminal(immediateError, true); + } + } + + void ConnectionState::DeliverRead(Ptr retainedState, Ptr block, vint bytes) + { + auto invoked = InvokeCallback(false, [&](IAsyncSocketCallback* installed) + { + installed->OnRead(&block->data[0], bytes); + }); + if (invoked) + { + PostRead(retainedState); + } + } + + void ConnectionState::Write(Ptr buffer) + { + CHECK_ERROR(buffer, L"IAsyncSocketConnection::WriteAsync requires a buffer."); + bool empty = false; + bool canWrite = false; + CS_LOCK(lockState) + { + canWrite = connected && !stopping && !terminalPending && !writePending; + if (canWrite) + { + writePending = true; + empty = buffer->data.Count() == 0; + } + } + CHECK_ERROR(canWrite, L"IAsyncSocketConnection::WriteAsync requires a connected connection with no outstanding write."); + + if (empty) + { + auto state = Retain(); + runtime->QueueCallback(Func([state, buffer]() + { + state->DeliverWrite(buffer); + })); + return; + } + + auto operation = new WriteOperation(Retain(), buffer); + DWORD immediateError = ERROR_SUCCESS; + lockState.Enter(); + if (stopping || terminalPending) + { + writePending = false; + lockState.Leave(); + delete operation; + return; + } + BeginPendingLocked(); + immediateError = operation->PostLocked(); + if (immediateError != ERROR_SUCCESS) + { + EndPendingLocked(); + } + lockState.Leave(); + + if (immediateError != ERROR_SUCCESS) + { + delete operation; + QueueTerminal(immediateError, true); + } + } + + void ConnectionState::DeliverWrite(Ptr buffer) + { + bool deliver = false; + CS_LOCK(lockState) + { + if (writePending && !stopping && !terminalPending) + { + writePending = false; + deliver = true; + } + } + if (deliver) + { + InvokeCallback(false, [&](IAsyncSocketCallback* installed) + { + installed->OnWriteCompleted(buffer); + }); + } + } + + void ConnectionState::QueueTerminal(DWORD error, bool reportError) + { + bool queue = false; + CS_LOCK(lockState) + { + if (!stopping && !terminalPending) + { + terminalPending = true; + queue = true; + } + } + if (queue) + { + auto state = Retain(); + runtime->QueueCallback(Func([state, error, reportError]() + { + state->DeliverTerminal(error, reportError); + })); + } + } + + void ConnectionState::DeliverTerminal(DWORD error, bool reportError) + { + IAsyncSocketCallback* installed = nullptr; + bool claimed = false; + lockState.Enter(); + if (!stopping && terminalPending) + { + claimed = true; + if (reportError && callback) + { + installed = callback; + if (activeCallbacks++ == 0) + { + eventCallbacksDrained.Unsignal(); + } + } + } + lockState.Leave(); + if (!claimed) + { + return; + } + + if (installed) + { + CallbackFrame frame{ this, currentCallbackFrame }; + currentCallbackFrame = &frame; + try + { + installed->OnError(SocketErrorMessage(L"Asynchronous socket operation", error), true); + } + catch (...) + { + } + currentCallbackFrame = frame.previous; + EndCallback(); + } + Stop(); + } + + void ConnectionState::StartConnectAttempt() + { + auto retainedState = Retain(); + SOCKET createdSocket = INVALID_SOCKET; + ConnectOperation* operation = nullptr; + DWORD immediateError = ERROR_SUCCESS; + + lockState.Enter(); + if (!clientMode || stopping || clientStatus != ClientStatus::WaitingForServer) + { + lockState.Leave(); + return; + } + clientAttempts++; + + createdSocket = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED); + if (createdSocket == INVALID_SOCKET) + { + immediateError = WSAGetLastError(); + } + + SOCKADDR_IN localAddress = {}; + localAddress.sin_family = AF_INET; + localAddress.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + localAddress.sin_port = 0; + if (immediateError == ERROR_SUCCESS && bind(createdSocket, (SOCKADDR*)&localAddress, sizeof(localAddress)) == SOCKET_ERROR) + { + immediateError = WSAGetLastError(); + } + + LPFN_CONNECTEX connectEx = nullptr; + GUID connectExGuid = WSAID_CONNECTEX; + DWORD transferred = 0; + if (immediateError == ERROR_SUCCESS && WSAIoctl( + createdSocket, + SIO_GET_EXTENSION_FUNCTION_POINTER, + &connectExGuid, + sizeof(connectExGuid), + &connectEx, + sizeof(connectEx), + &transferred, + nullptr, + nullptr + ) == SOCKET_ERROR) + { + immediateError = WSAGetLastError(); + } + if (immediateError == ERROR_SUCCESS && !runtime->Associate(createdSocket)) + { + immediateError = GetLastError(); + } + + if (immediateError == ERROR_SUCCESS) + { + socket = createdSocket; + connected = false; + auto generation = ++clientGeneration; + operation = new ConnectOperation(retainedState, createdSocket, generation); + BeginPendingLocked(); + + SOCKADDR_IN remoteAddress = {}; + remoteAddress.sin_family = AF_INET; + remoteAddress.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + remoteAddress.sin_port = htons((u_short)clientPort); + auto result = connectEx( + createdSocket, + (SOCKADDR*)&remoteAddress, + sizeof(remoteAddress), + nullptr, + 0, + nullptr, + &operation->native.overlapped + ); + if (!result) + { + auto error = WSAGetLastError(); + if (error != WSA_IO_PENDING) + { + immediateError = error; + socket = INVALID_SOCKET; + EndPendingLocked(); + } + } + } + lockState.Leave(); + + if (immediateError != ERROR_SUCCESS) + { + if (createdSocket != INVALID_SOCKET) + { + closesocket(createdSocket); + } + delete operation; + QueueConnectFailure(immediateError); + } + } + + void ConnectionState::CompleteConnect(SOCKET operationSocket, vint generation, DWORD error) + { + if (error == ERROR_SUCCESS) + { + if (setsockopt(operationSocket, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, nullptr, 0) == SOCKET_ERROR) + { + error = WSAGetLastError(); + } + } + + bool accepted = false; + bool failed = false; + lockState.Enter(); + if (!stopping && clientStatus == ClientStatus::WaitingForServer && socket == operationSocket && clientGeneration == generation) + { + if (error == ERROR_SUCCESS) + { + connected = true; + clientStatus = ClientStatus::Connected; + accepted = true; + } + else + { + socket = INVALID_SOCKET; + failed = true; + } + } + lockState.Leave(); + + if (accepted) + { + auto state = Retain(); + runtime->QueueCallback(Func([state]() + { + state->DeliverConnected(); + })); + } + else if (failed) + { + closesocket(operationSocket); + QueueConnectFailure(error); + } + } + + void ConnectionState::DeliverConnected() + { + bool deliver = false; + CS_LOCK(lockState) + { + deliver = connected && !stopping && clientStatus == ClientStatus::Connected; + } + if (deliver) + { + InvokeCallback(false, [](IAsyncSocketCallback* installed) + { + installed->OnConnected(); + }); + } + eventWaitForServer.Signal(); + } + + void ConnectionState::QueueConnectFailure(DWORD error) + { + bool queue = false; + bool fatal = false; + CS_LOCK(lockState) + { + if (!stopping && clientStatus == ClientStatus::WaitingForServer) + { + queue = true; + fatal = clientAttempts >= AsyncSocketClientRetryCount; + } + } + if (queue) + { + auto state = Retain(); + runtime->QueueCallback(Func([state, error, fatal]() + { + state->DeliverConnectFailure(error, fatal); + })); + } + } + + void ConnectionState::DeliverConnectFailure(DWORD error, bool fatal) + { + bool deliver = false; + CS_LOCK(lockState) + { + deliver = !stopping && clientStatus == ClientStatus::WaitingForServer; + } + if (!deliver) + { + return; + } + + InvokeCallback(false, [&](IAsyncSocketCallback* installed) + { + installed->OnError(SocketErrorMessage(L"ConnectEx", error), fatal); + }); + if (fatal) + { + Stop(); + } + else + { + ScheduleRetry(); + } + } + + void ConnectionState::ScheduleRetry() + { + CS_LOCK(lockState) + { + if (!stopping && clientStatus == ClientStatus::WaitingForServer && clientRetryTimer) + { + LARGE_INTEGER dueTime; + dueTime.QuadPart = -(LONGLONG)AsyncSocketClientRetryDelay * 10000; + FILETIME fileTime; + fileTime.dwLowDateTime = dueTime.LowPart; + fileTime.dwHighDateTime = dueTime.HighPart; + SetThreadpoolTimer(clientRetryTimer, &fileTime, 0, 0); + } + } + } + + void ConnectionState::WaitForServer() + { + 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."); + StartConnectAttempt(); + eventWaitForServer.Wait(); + } + + ClientStatus ConnectionState::GetStatus() + { + ClientStatus result; + CS_LOCK(lockState) + { + result = clientStatus; + } + return result; + } + + void ConnectionState::Stop() + { + SOCKET closingSocket = INVALID_SOCKET; + PTP_TIMER timer = nullptr; + CS_LOCK(lockState) + { + if (!stopping) + { + stopping = true; + connected = false; + reading = false; + writePending = false; + terminalPending = false; + closingSocket = socket; + socket = INVALID_SOCKET; + if (clientMode) + { + clientStatus = ClientStatus::Disconnected; + timer = clientRetryTimer; + } + } + else if (clientMode) + { + timer = clientRetryTimer; + } + } + + if (timer) + { + SetThreadpoolTimer(timer, nullptr, 0, 0); + WaitForThreadpoolTimerCallbacks(timer, TRUE); + } + if (closingSocket != INVALID_SOCKET) + { + // Prefer an orderly FIN for the peer while closesocket cancels this + // connection's pending overlapped operations. + shutdown(closingSocket, SD_BOTH); + closesocket(closingSocket); + } + eventIoDrained.Wait(); + + auto selfCallback = IsCurrentCallback(); + if (!selfCallback) + { + eventCallbacksDrained.Wait(); + } + + IAsyncSocketCallback* installed = nullptr; + lockState.Enter(); + if (!disconnectedNotified) + { + disconnectedNotified = true; + if (callback) + { + installed = callback; + if (activeCallbacks++ == 0) + { + eventCallbacksDrained.Unsignal(); + } + } + } + stopped = true; + lockState.Leave(); + + if (installed) + { + CallbackFrame frame{ this, currentCallbackFrame }; + currentCallbackFrame = &frame; + try + { + installed->OnDisconnected(); + } + catch (...) + { + } + currentCallbackFrame = frame.previous; + EndCallback(); + } + + if (!selfCallback) + { + eventCallbacksDrained.Wait(); + } + if (clientMode) + { + eventWaitForServer.Signal(); + } + } + + class AsyncSocketServer::Impl : public Object + { + private: + class AcceptOperation : public IocpOperation + { + public: + Impl* server = nullptr; + SOCKET acceptedSocket = INVALID_SOCKET; + BYTE addresses[(sizeof(SOCKADDR_IN) + 16) * 2]; + + AcceptOperation(Impl* _server, SOCKET _acceptedSocket) + : server(_server) + , acceptedSocket(_acceptedSocket) + { + ZeroMemory(addresses, sizeof(addresses)); + } + + bool Complete(DWORD, DWORD error) override + { + server->CompleteAccept(this, error); + return true; + } + + void EndPending() override + { + server->EndAcceptPending(); + } + }; + + AsyncSocketServer* owner = nullptr; + vint port = 0; + Ptr runtime; + CriticalSection lockState; + bool started = false; + bool stopping = false; + bool stopped = false; + SOCKET listener = INVALID_SOCKET; + LPFN_ACCEPTEX acceptEx = nullptr; + bool acceptPending = false; + vint pendingAccepts = 0; + EventObject eventAcceptDrained; + EventObject eventStopped; + List> connections; + + void BeginAcceptPendingLocked() + { + if (pendingAccepts++ == 0) + { + eventAcceptDrained.Unsignal(); + } + } + + void EndAcceptPending() + { + CS_LOCK(lockState) + { + if (--pendingAccepts == 0) + { + eventAcceptDrained.Signal(); + } + } + } + + bool PostAccept() + { + SOCKET acceptedSocket = INVALID_SOCKET; + AcceptOperation* operation = nullptr; + DWORD immediateError = ERROR_SUCCESS; + + lockState.Enter(); + if (!started || stopping || acceptPending) + { + lockState.Leave(); + return false; + } + acceptedSocket = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED); + if (acceptedSocket == INVALID_SOCKET) + { + immediateError = WSAGetLastError(); + } + else + { + operation = new AcceptOperation(this, acceptedSocket); + acceptPending = true; + BeginAcceptPendingLocked(); + DWORD received = 0; + auto result = acceptEx( + listener, + acceptedSocket, + operation->addresses, + 0, + sizeof(SOCKADDR_IN) + 16, + sizeof(SOCKADDR_IN) + 16, + &received, + &operation->native.overlapped + ); + if (!result) + { + auto error = WSAGetLastError(); + if (error != WSA_IO_PENDING) + { + immediateError = error; + acceptPending = false; + EndPendingAcceptLocked(); + } + } + } + lockState.Leave(); + + if (immediateError != ERROR_SUCCESS) + { + if (acceptedSocket != INVALID_SOCKET) + { + closesocket(acceptedSocket); + } + delete operation; + return false; + } + return true; + } + + void EndPendingAcceptLocked() + { + if (--pendingAccepts == 0) + { + eventAcceptDrained.Signal(); + } + } + + void CompleteAccept(AcceptOperation* operation, DWORD error) + { + bool running = false; + CS_LOCK(lockState) + { + acceptPending = false; + running = started && !stopping; + } + + auto acceptedSocket = operation->acceptedSocket; + if (!running || error != ERROR_SUCCESS) + { + closesocket(acceptedSocket); + if (running) + { + PostAccept(); + } + return; + } + + if (setsockopt(acceptedSocket, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (CHAR*)&listener, sizeof(listener)) == SOCKET_ERROR || !runtime->Associate(acceptedSocket)) + { + closesocket(acceptedSocket); + PostAccept(); + return; + } + + auto state = Ptr(new ConnectionState(runtime.Obj(), acceptedSocket)); + auto connection = Ptr(new AsyncSocketConnection(state)); + bool retain = false; + CS_LOCK(lockState) + { + if (started && !stopping) + { + connections.Add(connection); + retain = true; + } + } + + PostAccept(); + if (!retain) + { + connection->Stop(); + return; + } + + auto self = this; + runtime->QueueCallback(Func([self, connection]() + { + bool invoke = false; + CS_LOCK(self->lockState) + { + invoke = self->started && !self->stopping; + } + if (!invoke) + { + connection->Stop(); + return; + } + + WaitForClientResult result = WaitForClientResult::Reject; + try + { + result = self->owner->OnClientConnected(connection.Obj()); + } + catch (...) + { + } + if (result == WaitForClientResult::Reject) + { + connection->Stop(); + } + })); + } + + public: + Impl(AsyncSocketServer* _owner, vint _port) + : owner(_owner) + , port(_port) + , runtime(Ptr(new IocpRuntime)) + { + CHECK_ERROR(eventAcceptDrained.CreateManualUnsignal(true), L"AsyncSocketServer failed to create its accept drain event."); + CHECK_ERROR(eventStopped.CreateManualUnsignal(false), L"AsyncSocketServer failed to create its stop event."); + } + + ~Impl() + { + Stop(); + } + + void Start() + { + SOCKET createdListener = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED); + CHECK_ERROR(createdListener != INVALID_SOCKET, L"AsyncSocketServer failed to create its listener socket."); + + BOOL exclusive = TRUE; + if (setsockopt(createdListener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (CHAR*)&exclusive, sizeof(exclusive)) == SOCKET_ERROR) + { + closesocket(createdListener); + CHECK_ERROR(false, L"AsyncSocketServer failed to apply SO_EXCLUSIVEADDRUSE."); + } + + SOCKADDR_IN address = {}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = htons((u_short)port); + if (bind(createdListener, (SOCKADDR*)&address, sizeof(address)) == SOCKET_ERROR || listen(createdListener, SOMAXCONN) == SOCKET_ERROR) + { + closesocket(createdListener); + CHECK_ERROR(false, L"AsyncSocketServer failed to bind or listen on 127.0.0.1."); + } + if (!runtime->Associate(createdListener)) + { + closesocket(createdListener); + CHECK_ERROR(false, L"AsyncSocketServer failed to associate its listener with the IO completion port."); + } + + LPFN_ACCEPTEX loadedAcceptEx = nullptr; + GUID acceptExGuid = WSAID_ACCEPTEX; + DWORD transferred = 0; + if (WSAIoctl( + createdListener, + SIO_GET_EXTENSION_FUNCTION_POINTER, + &acceptExGuid, + sizeof(acceptExGuid), + &loadedAcceptEx, + sizeof(loadedAcceptEx), + &transferred, + nullptr, + nullptr + ) == SOCKET_ERROR) + { + closesocket(createdListener); + CHECK_ERROR(false, L"AsyncSocketServer failed to load AcceptEx."); + } + + bool canStart = false; + CS_LOCK(lockState) + { + if (!started && !stopping) + { + listener = createdListener; + acceptEx = loadedAcceptEx; + started = true; + canStart = true; + } + } + if (!canStart) + { + closesocket(createdListener); + } + CHECK_ERROR(canStart, L"AsyncSocketServer::Start can only be called once."); + if (!PostAccept()) + { + Stop(); + CHECK_ERROR(false, L"AsyncSocketServer failed to post AcceptEx."); + } + } + + void Stop() + { + SOCKET closingListener = INVALID_SOCKET; + bool first = false; + auto selfWorker = currentCallbackRuntime == runtime.Obj() || currentCompletionRuntime == runtime.Obj(); + CS_LOCK(lockState) + { + if (!stopping) + { + stopping = true; + started = false; + closingListener = listener; + listener = INVALID_SOCKET; + first = true; + } + } + if (!first) + { + // A runtime callback must not wait for the caller that is draining it. + if (!selfWorker) + { + eventStopped.Wait(); + // A callback-worker caller requests runtime exit but cannot join itself. + // An external repeated Stop completes that deferred finalization here. + runtime->Stop(); + } + return; + } + if (closingListener != INVALID_SOCKET) + { + closesocket(closingListener); + } + eventAcceptDrained.Wait(); + + List> stoppingConnections; + CS_LOCK(lockState) + { + for (auto connection : connections) + { + stoppingConnections.Add(connection); + } + connections.Clear(); + } + for (auto connection : stoppingConnections) + { + connection->Stop(); + } + + runtime->Stop(); + CS_LOCK(lockState) + { + stopped = true; + } + eventStopped.Signal(); + } + + bool IsStopped() + { + bool result = false; + CS_LOCK(lockState) + { + result = stopped; + } + return result; + } + }; + + AsyncSocketServer::AsyncSocketServer(vint port) + { + CHECK_ERROR(1 <= port && port <= 65535, L"AsyncSocketServer requires a port in 1..65535."); + impl = new Impl(this, port); + } + + AsyncSocketServer::~AsyncSocketServer() + { + delete impl; + } + + WaitForClientResult AsyncSocketServer::OnClientConnected(IAsyncSocketConnection*) + { + return WaitForClientResult::Accept; + } + + void AsyncSocketServer::Start() + { + impl->Start(); + } + + void AsyncSocketServer::Stop() + { + impl->Stop(); + } + + bool AsyncSocketServer::IsStopped() + { + return impl->IsStopped(); + } + + class AsyncSocketClient::Impl : public Object + { + private: + Ptr runtime; + Ptr state; + Ptr connection; + SpinLock lockStop; + bool stopped = false; + + public: + Impl(vint port) + : runtime(Ptr(new IocpRuntime)) + , state(Ptr(new ConnectionState(runtime.Obj(), true, port))) + , connection(Ptr(new AsyncSocketConnection(state))) + { + } + + ~Impl() + { + Stop(); + } + + void Stop() + { + bool first = false; + SPIN_LOCK(lockStop) + { + if (!stopped) + { + stopped = true; + first = true; + } + } + if (first) + { + connection->Stop(); + state->CloseRetryTimer(); + runtime->Stop(); + } + } + + IAsyncSocketConnection* GetConnection() + { + return connection.Obj(); + } + + void WaitForServer() + { + state->WaitForServer(); + } + + ClientStatus GetStatus() + { + return state->GetStatus(); + } + }; + + 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; + } + + IAsyncSocketConnection* AsyncSocketClient::GetConnection() + { + return impl->GetConnection(); + } + + void AsyncSocketClient::WaitForServer() + { + impl->WaitForServer(); + } + + ClientStatus AsyncSocketClient::GetStatus() + { + return impl->GetStatus(); + } +} + + /*********************************************************************** .\INTERPROCESS\WINDOWS\HTTPCLIENT.WINDOWS.CPP ***********************************************************************/ -namespace vl::inter_process +namespace vl::inter_process::windows_http { /*********************************************************************** @@ -2044,7 +3792,7 @@ static_assert(false, "Do not build this file for non-Windows applications."); #pragma comment(lib, "WinHttp.lib") -namespace vl::inter_process +namespace vl::inter_process::windows_http { using namespace vl::collections; @@ -2432,7 +4180,7 @@ HttpClientApi::HttpClientApi(const WString& _server, vint _port) CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"HttpClientApi initialization failed on eventPendingCallbacks.CreateManualUnsignal."); httpSession = WinHttpOpen( - L"vl::inter_process::HttpClientApi", + L"vl::inter_process::windows_http::HttpClientApi", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, @@ -2822,7 +4570,7 @@ WString HttpClientApi::UrlDecodeQuery(const WString& query) .\INTERPROCESS\WINDOWS\HTTPSERVER.WINDOWS.CPP ***********************************************************************/ -namespace vl::inter_process +namespace vl::inter_process::windows_http { using namespace vl::collections; @@ -3177,7 +4925,7 @@ static_assert(false, "Do not build this file for non-Windows applications."); #pragma comment(lib, "Httpapi.lib") -namespace vl::inter_process +namespace vl::inter_process::windows_http { using namespace vl::collections; @@ -3695,7 +5443,7 @@ bool HttpServerApi::IsStopped() .\INTERPROCESS\WINDOWS\NAMEDPIPE.WINDOWS.CPP ***********************************************************************/ -namespace vl::inter_process +namespace vl::inter_process::named_pipe { using namespace vl::console; diff --git a/Import/VlppOS.Windows.h b/Import/VlppOS.Windows.h index 5ae73629..056e71ea 100644 --- a/Import/VlppOS.Windows.h +++ b/Import/VlppOS.Windows.h @@ -6,7 +6,68 @@ DEVELOPER: Zihan Chen(vczh) #include "Vlpp.h" /*********************************************************************** -.\NETWORKPROTOCOL.WINDOWS.H +.\ASYNCSOCKET\ASYNCSOCKET.WINDOWS.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) + +Windows implementation of IAsyncSocket(Server|Client) + +***********************************************************************/ + +#ifndef VCZH_INTERPROCESS_ASYNCSOCKET_WINDOWS +#define VCZH_INTERPROCESS_ASYNCSOCKET_WINDOWS + +// Winsock must precede every include that can include windows.h. +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#define _WINSOCKAPI_ +#include + + +namespace vl::inter_process::async_tcp_socket::windows_socket +{ + class AsyncSocketServer : public Object, public virtual IAsyncSocketServer + { + private: + class Impl; + Impl* impl = nullptr; + + public: + AsyncSocketServer(vint port); + ~AsyncSocketServer(); + + WaitForClientResult OnClientConnected(IAsyncSocketConnection* connection) override; + void Start() override; + void Stop() override; + bool IsStopped() override; + }; + + class AsyncSocketClient : public Object, public virtual IAsyncSocketClient + { + private: + class Impl; + Impl* impl = nullptr; + + public: + AsyncSocketClient(vint port); + ~AsyncSocketClient(); + + IAsyncSocketConnection* GetConnection() override; + void WaitForServer() override; + ClientStatus GetStatus() override; + }; +} + +#endif + + +/*********************************************************************** +.\WINDOWS\NETWORKPROTOCOL.WINDOWS.H ***********************************************************************/ /*********************************************************************** Vczh Library++ 3.0 @@ -62,7 +123,7 @@ namespace vl::inter_process /*********************************************************************** -.\HTTPCLIENTAPI.WINDOWS.H +.\WINDOWS\HTTPCLIENTAPI.WINDOWS.H ***********************************************************************/ /*********************************************************************** Vczh Library++ 3.0 @@ -77,7 +138,7 @@ Interfaces: #define VCZH_INTERPROCESS_WINDOWS_HTTPCLIENTAPI -namespace vl::inter_process +namespace vl::inter_process::windows_http { /// An http request. @@ -218,7 +279,7 @@ public: /*********************************************************************** -.\HTTPCLIENT.WINDOWS.H +.\WINDOWS\HTTPCLIENT.WINDOWS.H ***********************************************************************/ /*********************************************************************** Vczh Library++ 3.0 @@ -233,7 +294,7 @@ Interfaces: #define VCZH_INTERPROCESS_WINDOWS_HTTPCLIENT -namespace vl::inter_process +namespace vl::inter_process::windows_http { class HttpClient : public Object, public virtual INetworkProtocolConnection, public virtual INetworkProtocolClient @@ -328,7 +389,7 @@ public: /*********************************************************************** -.\HTTPSERVERAPI.WINDOWS.H +.\WINDOWS\HTTPSERVERAPI.WINDOWS.H ***********************************************************************/ /*********************************************************************** Vczh Library++ 3.0 @@ -343,7 +404,7 @@ Interfaces: #define VCZH_INTERPROCESS_WINDOWS_HTTPSERVERAPI -namespace vl::inter_process +namespace vl::inter_process::windows_http { /// A response to be sent by . @@ -423,7 +484,7 @@ public: /*********************************************************************** -.\HTTPSERVER.WINDOWS.H +.\WINDOWS\HTTPSERVER.WINDOWS.H ***********************************************************************/ /*********************************************************************** Vczh Library++ 3.0 @@ -438,7 +499,7 @@ Interfaces: #define VCZH_INTERPROCESS_WINDOWS_HTTPSERVER -namespace vl::inter_process +namespace vl::inter_process::windows_http { class HttpServer; @@ -525,7 +586,7 @@ public: /*********************************************************************** -.\NAMEDPIPE.WINDOWS.H +.\WINDOWS\NAMEDPIPE.WINDOWS.H ***********************************************************************/ /*********************************************************************** Vczh Library++ 3.0 @@ -541,7 +602,7 @@ Interfaces: #define VCZH_INTERPROCESS_WINDOWS_NAMEDPIPE -namespace vl::inter_process +namespace vl::inter_process::named_pipe { class NamedPipeServer; diff --git a/Import/VlppOS.h b/Import/VlppOS.h index ee6d41ac..62ad9299 100644 --- a/Import/VlppOS.h +++ b/Import/VlppOS.h @@ -622,6 +622,10 @@ Kernel Mode Objects bool Unsignal(); #ifdef VCZH_GCC bool Wait(); + /// Wait for this event to signal for a period of time. + /// Returns true if the event is signaled. Returns false if this operation failed, including time out. + /// Time in milliseconds. + bool WaitForTime(vint ms); #endif }; @@ -788,13 +792,12 @@ Kernel Mode Objects in Process /// Returns true if this operation succeeded. /// The critical section. bool SleepWith(CriticalSection& cs); -#ifdef VCZH_MSVC /// Bind a conditional variable with a owned critical section and release it for a period of time. When the function returns, the condition variable is activated or it is time out, and the current thread owned the critical section again. /// Returns true if this operation succeeded. /// The critical section. /// Time in milliseconds. - /// This function is only available in Windows. bool SleepWithForTime(CriticalSection& cs, vint ms); +#ifdef VCZH_MSVC /// Bind a conditional variable with a owned reader lock and release it. When the function returns, the condition variable is activated, and the current thread owned the reader lock again. /// Returns true if this operation succeeded. /// The reader lock. @@ -1751,6 +1754,1293 @@ INetworkProtocolServer #endif +/*********************************************************************** +.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) + +Interfaces: + IAsyncSocket(Server|Client) + +***********************************************************************/ + +#ifndef VCZH_INTERPROCESS_ASYNCSOCKET +#define VCZH_INTERPROCESS_ASYNCSOCKET + +#include +#include +#include +#include +#include + +namespace vl::inter_process::async_tcp_socket +{ + /// A retained buffer for one asynchronous write. + class AsyncSocketBuffer : public Object + { + public: + collections::Array data; + }; + + class IAsyncSocketConnection; + + /// Callbacks for an asynchronous byte-stream connection. + class IAsyncSocketCallback : public virtual Interface + { + public: + /// Called with one positive borrowed read block. + virtual void OnRead(const vuint8_t* buffer, vint size) = 0; + /// Called after the complete retained write buffer has been sent. + virtual void OnWriteCompleted(Ptr buffer) {} + /// Called when an asynchronous operation fails. + virtual void OnError(const WString& error, bool fatal) {} + /// Called for the client connection after it is established. + virtual void OnConnected() {} + /// Called exactly once when the connection stops. + virtual void OnDisconnected() {} + /// Called synchronously when this callback is installed. + virtual void OnInstalled(IAsyncSocketConnection* connection) = 0; + }; + + /// An ordered, full-duplex asynchronous byte stream. + class IAsyncSocketConnection : public virtual Interface + { + public: + virtual void InstallCallback(IAsyncSocketCallback* callback) = 0; + virtual void BeginReadingLoopUnsafe() = 0; + virtual void WriteAsync(Ptr buffer) = 0; + virtual void Stop() = 0; + }; + + /// An asynchronous TCP client for the local machine. + class IAsyncSocketClient : public virtual Interface + { + public: + virtual IAsyncSocketConnection* GetConnection() = 0; + virtual void WaitForServer() = 0; + virtual ClientStatus GetStatus() = 0; + }; + + /// An asynchronous TCP server for the local machine. + class IAsyncSocketServer : public virtual Interface + { + public: + virtual WaitForClientResult OnClientConnected(IAsyncSocketConnection* connection) = 0; + virtual void Start() = 0; + virtual void Stop() = 0; + virtual bool IsStopped() = 0; + }; + + // This policy is intentionally platform-neutral. Each failed attempt creates + // a fresh native socket and is followed by an asynchronous millisecond delay. + constexpr vint AsyncSocketClientRetryCount = 50; + constexpr vint AsyncSocketClientRetryDelay = 100; + +/*********************************************************************** +NetworkProtocolConnection +***********************************************************************/ + + class NetworkProtocolCallbackDomain : public Object + { + public: + struct CallbackFrame; + + private: + inline static thread_local CallbackFrame* currentCallbackFrame = nullptr; + CriticalSection lockState; + ConditionVariable cvState; + vint activeCallbacks = 0; + + public: + struct CallbackFrame + { + Ptr domain; + CallbackFrame* previous = nullptr; + + CallbackFrame(Ptr _domain) + : domain(_domain) + { + if (domain) + { + previous = currentCallbackFrame; + currentCallbackFrame = this; + CS_LOCK(domain->lockState) + { + domain->activeCallbacks++; + } + } + } + + ~CallbackFrame() + { + if (domain) + { + currentCallbackFrame = previous; + CS_LOCK(domain->lockState) + { + domain->activeCallbacks--; + domain->cvState.WakeAllPendings(); + } + } + } + }; + + vint CurrentCallbackDepth() + { + vint depth = 0; + for (auto frame = currentCallbackFrame; frame; frame = frame->previous) + { + if (frame->domain.Obj() == this) + { + depth++; + } + } + return depth; + } + + void WaitForCallbacks(vint callbackDepth) + { + CS_LOCK(lockState) + { + while (activeCallbacks > callbackDepth) + { + cvState.SleepWith(lockState); + } + } + } + }; + + class NetworkProtocolConnectionLifecycle : public Object + { + public: + IAsyncSocketConnection* socketConnection = nullptr; + Ptr callbackDomain; + Ptr retainedAdapter; + + CriticalSection lockState; + ConditionVariable cvState; + INetworkProtocolCallback* callback = nullptr; + bool callbackInstalling = false; + vint activeCallbacks = 0; + vint activeSocketCallbacks = 0; + vint activeSocketCalls = 0; + bool stopStarted = false; + bool stopFinished = false; + bool terminal = false; + bool disconnectedNotified = false; + bool disconnectDelivering = false; + bool disconnectFinished = false; + collections::List> + queuedWrites; + bool writePending = false; + bool drainWrites = false; + + CriticalSection lockParser; + vuint8_t lengthBytes[sizeof(vint32_t)] = {}; + vint lengthBytesReceived = 0; + vint32_t expectedCharacters = -1; + collections::Array characterBuffer; + vint characterBytesReceived = 0; + bool parserFailed = false; + + void TakeRetainedAdapterIfDrained(Ptr& releasing) + { + if (stopFinished && disconnectFinished && activeCallbacks == 0 && activeSocketCallbacks == 0 && activeSocketCalls == 0) + { + releasing = std::move(retainedAdapter); + } + } + }; + + /// Adapts an asynchronous byte stream to framed network-protocol strings. + class NetworkProtocolConnection + : public Object + , public virtual INetworkProtocolConnection + , public virtual IAsyncSocketCallback + { + private: + using Lifecycle = NetworkProtocolConnectionLifecycle; + static constexpr vint WriteDrainTimeout = 1000; + + struct CallbackFrame; + struct SocketCallbackFrame; + inline static thread_local CallbackFrame* currentCallbackFrame = nullptr; + inline static thread_local SocketCallbackFrame* + currentSocketCallbackFrame = nullptr; + + struct CallbackFrame + { + Ptr state; + CallbackFrame* previous = nullptr; + NetworkProtocolCallbackDomain::CallbackFrame + domainFrame; + + CallbackFrame(Ptr _state) + : state(_state) + , previous(currentCallbackFrame) + , domainFrame(state->callbackDomain) + { + currentCallbackFrame = this; + } + + ~CallbackFrame() + { + currentCallbackFrame = previous; + Ptr releasing; + CS_LOCK(state->lockState) + { + state->activeCallbacks--; + state->TakeRetainedAdapterIfDrained(releasing); + state->cvState.WakeAllPendings(); + } + } + }; + + struct SocketCallbackFrame + { + Ptr state; + SocketCallbackFrame* previous = nullptr; + + SocketCallbackFrame(Ptr _state) + : state(_state) + , previous(currentSocketCallbackFrame) + { + currentSocketCallbackFrame = this; + CS_LOCK(state->lockState) + { + state->activeSocketCallbacks++; + } + } + + ~SocketCallbackFrame() + { + currentSocketCallbackFrame = previous; + Ptr releasing; + CS_LOCK(state->lockState) + { + state->activeSocketCallbacks--; + state->TakeRetainedAdapterIfDrained(releasing); + state->cvState.WakeAllPendings(); + } + } + }; + + Ptr lifecycle; + + static vint CurrentCallbackDepth(Ptr state) + { + vint depth = 0; + for (auto frame = currentCallbackFrame; frame; frame = frame->previous) + { + if (frame->state.Obj() == state.Obj()) + { + depth++; + } + } + return depth; + } + + static vint CurrentSocketCallbackDepth(Ptr state) + { + vint depth = 0; + for (auto frame = currentSocketCallbackFrame; frame; frame = frame->previous) + { + if (frame->state.Obj() == state.Obj()) + { + depth++; + } + } + return depth; + } + + static void FinishSocketCall(Ptr state) + { + CS_LOCK(state->lockState) + { + state->activeSocketCalls--; + state->cvState.WakeAllPendings(); + } + } + + template + static void InvokeProtocolCallback(Ptr state, bool allowTerminal, TCallback&& invoke) + { + INetworkProtocolCallback* installed = nullptr; + auto callbackDepth = CurrentCallbackDepth(state); + state->lockState.Enter(); + while (state->callbackInstalling && callbackDepth == 0 && state->callback) + { + state->cvState.SleepWith(state->lockState); + } + if (state->callback && (allowTerminal || (!state->stopStarted && !state->terminal))) + { + installed = state->callback; + state->activeCallbacks++; + } + state->lockState.Leave(); + + if (installed) + { + CallbackFrame frame(state); + invoke(installed); + } + } + + static void SubmitWrite(Ptr state, IAsyncSocketConnection* connection, Ptr buffer) + { + try + { + connection->WriteAsync(buffer); + } + catch (...) + { + CS_LOCK(state->lockState) + { + state->queuedWrites.Clear(); + state->writePending = false; + state->cvState.WakeAllPendings(); + } + FinishSocketCall(state); + throw; + } + FinishSocketCall(state); + } + + static void NotifyProtocolDisconnected(Ptr state) + { + auto callbackDepth = CurrentCallbackDepth(state); + state->lockState.Enter(); + if (!state->disconnectedNotified) + { + state->disconnectedNotified = true; + state->terminal = true; + state->queuedWrites.Clear(); + state->writePending = false; + state->cvState.WakeAllPendings(); + } + if (state->disconnectFinished) + { + state->lockState.Leave(); + return; + } + + if (state->disconnectDelivering) + { + if (callbackDepth == 0) + { + while (!state->disconnectFinished) + { + state->cvState.SleepWith(state->lockState); + } + } + state->lockState.Leave(); + return; + } + + if (callbackDepth == 0) + { + while (state->activeCallbacks > 0 && !state->disconnectDelivering && !state->disconnectFinished) + { + state->cvState.SleepWith(state->lockState); + } + if (state->disconnectFinished) + { + state->lockState.Leave(); + return; + } + if (state->disconnectDelivering) + { + while (!state->disconnectFinished) + { + state->cvState.SleepWith(state->lockState); + } + state->lockState.Leave(); + return; + } + } + + state->disconnectDelivering = true; + while (state->activeCallbacks > callbackDepth) + { + state->cvState.SleepWith(state->lockState); + } + state->lockState.Leave(); + + try + { + InvokeProtocolCallback(state, true, [](INetworkProtocolCallback* installed) + { + installed->OnDisconnected(); + }); + } + catch (...) + { + Ptr releasing; + CS_LOCK(state->lockState) + { + state->callback = nullptr; + while (state->activeCallbacks > callbackDepth) + { + state->cvState.SleepWith(state->lockState); + } + state->disconnectFinished = true; + state->TakeRetainedAdapterIfDrained(releasing); + state->cvState.WakeAllPendings(); + } + throw; + } + + Ptr releasing; + CS_LOCK(state->lockState) + { + state->callback = nullptr; + while (state->activeCallbacks > callbackDepth) + { + state->cvState.SleepWith(state->lockState); + } + state->disconnectFinished = true; + state->TakeRetainedAdapterIfDrained(releasing); + state->cvState.WakeAllPendings(); + } + } + + static void DetachSocketCallback(Ptr state, IAsyncSocketConnection* connection) + { + if (connection) + { + connection->InstallCallback(nullptr); + } + CS_LOCK(state->lockState) + { + if (state->socketConnection == connection) + { + state->socketConnection = nullptr; + } + state->cvState.WakeAllPendings(); + } + } + + static void StopConnection(Ptr state, Ptr retainedAdapter = nullptr) + { + auto callbackDepth = CurrentCallbackDepth(state); + auto socketCallbackDepth = CurrentSocketCallbackDepth(state); + auto nestedCallback = callbackDepth > 0 || socketCallbackDepth > 0; + IAsyncSocketConnection* connection = nullptr; + bool executeStop = false; + bool nestedFollower = false; + Ptr releasing; + + state->lockState.Enter(); + if (retainedAdapter) + { + state->retainedAdapter = retainedAdapter; + } + if (!state->stopStarted) + { + state->stopStarted = true; + if (!nestedCallback && !state->terminal && state->queuedWrites.Count() > 0) + { + state->drainWrites = true; + auto deadline = DateTime::LocalTime().osMilliseconds + WriteDrainTimeout; + while (state->queuedWrites.Count() > 0 && !state->terminal) + { + auto now = DateTime::LocalTime().osMilliseconds; + if (now >= deadline) + { + break; + } + state->cvState.SleepWithForTime(state->lockState, (vint)(deadline - now)); + } + state->drainWrites = false; + } + state->queuedWrites.Clear(); + state->writePending = false; + while (state->activeSocketCalls > 0) + { + state->cvState.SleepWith(state->lockState); + } + connection = state->socketConnection; + executeStop = true; + } + else if (nestedCallback) + { + connection = state->socketConnection; + nestedFollower = true; + } + else + { + while (!state->stopFinished) + { + state->cvState.SleepWith(state->lockState); + } + while (state->activeCallbacks > 0 || state->activeSocketCallbacks > 0 || state->activeSocketCalls > 0) + { + state->cvState.SleepWith(state->lockState); + } + state->TakeRetainedAdapterIfDrained(releasing); + state->lockState.Leave(); + return; + } + state->lockState.Leave(); + + if (nestedFollower) + { + if (connection && socketCallbackDepth > 0) + { + connection->Stop(); + } + NotifyProtocolDisconnected(state); + return; + } + + if (executeStop && connection) + { + connection->Stop(); + } + NotifyProtocolDisconnected(state); + + CS_LOCK(state->lockState) + { + while (state->activeCallbacks > callbackDepth || state->activeSocketCallbacks > socketCallbackDepth) + { + state->cvState.SleepWith(state->lockState); + } + state->stopFinished = true; + state->TakeRetainedAdapterIfDrained(releasing); + state->cvState.WakeAllPendings(); + } + } + + static void ReportFatalError(Ptr state, const WString& error) + { + bool report = false; + CS_LOCK(state->lockState) + { + if (!state->terminal && !state->stopStarted) + { + state->terminal = true; + state->queuedWrites.Clear(); + state->writePending = false; + state->cvState.WakeAllPendings(); + report = true; + } + } + if (report) + { + try + { + InvokeProtocolCallback(state, true, [&](INetworkProtocolCallback* installed) + { + installed->OnLocalError(error, true); + }); + } + catch (...) + { + StopConnection(state); + throw; + } + StopConnection(state); + } + } + + template + friend class NetworkProtocolServer; + + template + friend class NetworkProtocolClient; + + void StopWithRetainedAdapter(Ptr retainedAdapter) + { + StopConnection(lifecycle, retainedAdapter); + } + + public: + explicit NetworkProtocolConnection(IAsyncSocketConnection* connection, Ptr callbackDomain = nullptr) + : lifecycle(Ptr(new Lifecycle)) + { + CHECK_ERROR(connection, L"NetworkProtocolConnection requires a valid async socket connection."); + lifecycle->socketConnection = connection; + lifecycle->callbackDomain = callbackDomain; + connection->InstallCallback(this); + } + + ~NetworkProtocolConnection() + { + StopConnection(lifecycle); + } + + void InstallCallback(INetworkProtocolCallback* value) override + { + auto state = lifecycle; + if (!value) + { + auto callbackDepth = CurrentCallbackDepth(state); + bool uninstallOwner = false; + CS_LOCK(state->lockState) + { + uninstallOwner = state->callback != nullptr; + state->callback = nullptr; + while ((callbackDepth == 0 || uninstallOwner) && state->activeCallbacks > callbackDepth) + { + state->cvState.SleepWith(state->lockState); + } + } + return; + } + + bool canInstall = false; + CS_LOCK(state->lockState) + { + if (!state->callback && !state->callbackInstalling && !state->stopStarted && !state->terminal) + { + state->callback = value; + state->callbackInstalling = true; + state->activeCallbacks++; + canInstall = true; + } + } + CHECK_ERROR(canInstall, L"NetworkProtocolConnection::InstallCallback cannot replace a callback or install one on a stopped connection."); + + CallbackFrame frame(state); + try + { + value->OnInstalled(this); + } + catch (...) + { + CS_LOCK(state->lockState) + { + if (state->callback == value) + { + state->callback = nullptr; + } + state->callbackInstalling = false; + state->cvState.WakeAllPendings(); + } + throw; + } + + CS_LOCK(state->lockState) + { + state->callbackInstalling = false; + state->cvState.WakeAllPendings(); + } + } + + void BeginReadingLoopUnsafe() override + { + auto state = lifecycle; + IAsyncSocketConnection* connection = nullptr; + CS_LOCK(state->lockState) + { + if (!state->stopStarted && !state->terminal && state->socketConnection) + { + connection = state->socketConnection; + state->activeSocketCalls++; + } + } + if (!connection) + { + return; + } + + try + { + connection->BeginReadingLoopUnsafe(); + } + catch (...) + { + FinishSocketCall(state); + throw; + } + FinishSocketCall(state); + } + + void SendString(const WString& str) override + { + auto state = lifecycle; + auto length = str.Length(); + CHECK_ERROR(length >= 0 && length <= (std::numeric_limits::max)(), L"NetworkProtocolConnection::SendString cannot encode a string longer than vint32_t."); + CHECK_ERROR((size_t)length <= ((size_t)(std::numeric_limits::max)() - sizeof(vint32_t)) / sizeof(wchar_t), L"NetworkProtocolConnection::SendString frame size overflow."); + + auto buffer = Ptr(new AsyncSocketBuffer); + auto characterBytes = (size_t)length * sizeof(wchar_t); + auto frameBytes = sizeof(vint32_t) + characterBytes; + buffer->data.Resize((vint)frameBytes); + auto encodedLength = (vint32_t)length; + std::memcpy(&buffer->data[0], &encodedLength, sizeof(encodedLength)); + if (characterBytes > 0) + { + std::memcpy(&buffer->data[sizeof(encodedLength)], str.Buffer(), characterBytes); + } + + IAsyncSocketConnection* connection = nullptr; + Ptr submitting; + CS_LOCK(state->lockState) + { + if (!state->stopStarted && !state->terminal && state->socketConnection) + { + state->queuedWrites.Add(buffer); + if (!state->writePending) + { + state->writePending = true; + connection = state->socketConnection; + submitting = state->queuedWrites[0]; + state->activeSocketCalls++; + } + } + } + if (submitting) + { + SubmitWrite(state, connection, submitting); + } + } + + void Stop() override + { + StopConnection(lifecycle); + } + + void OnRead(const vuint8_t* buffer, vint size) override + { + auto state = lifecycle; + SocketCallbackFrame socketCallbackFrame(state); + if (!buffer || size <= 0) + { + return; + } + + collections::List completedStrings; + bool malformed = false; + CS_LOCK(state->lockParser) + { + if (state->parserFailed) + { + return; + } + + auto reading = buffer; + auto available = size; + while (available > 0) + { + if (state->expectedCharacters == -1) + { + auto required = (vint)sizeof(vint32_t) - state->lengthBytesReceived; + auto copied = required < available ? required : available; + std::memcpy(state->lengthBytes + state->lengthBytesReceived, reading, (size_t)copied); + state->lengthBytesReceived += copied; + reading += copied; + available -= copied; + if (state->lengthBytesReceived < (vint)sizeof(vint32_t)) + { + continue; + } + + std::memcpy(&state->expectedCharacters, state->lengthBytes, sizeof(state->expectedCharacters)); + state->lengthBytesReceived = 0; + if (state->expectedCharacters < 0 || (size_t)state->expectedCharacters > ((size_t)(std::numeric_limits::max)() - sizeof(vint32_t)) / sizeof(wchar_t)) + { + state->parserFailed = true; + malformed = true; + break; + } + + state->characterBuffer.Resize((vint)state->expectedCharacters); + state->characterBytesReceived = 0; + if (state->expectedCharacters == 0) + { + completedStrings.Add(WString()); + state->expectedCharacters = -1; + } + } + + if (state->expectedCharacters >= 0) + { + auto characterBytes = (vint)((size_t)state->expectedCharacters * sizeof(wchar_t)); + auto required = characterBytes - state->characterBytesReceived; + auto copied = required < available ? required : available; + std::memcpy((vuint8_t*)&state->characterBuffer[0] + state->characterBytesReceived, reading, (size_t)copied); + state->characterBytesReceived += copied; + reading += copied; + available -= copied; + if (state->characterBytesReceived == characterBytes) + { + completedStrings.Add(WString::CopyFrom(&state->characterBuffer[0], state->expectedCharacters)); + state->characterBuffer.Resize(0); + state->characterBytesReceived = 0; + state->expectedCharacters = -1; + } + } + } + } + + for (auto&& str : completedStrings) + { + InvokeProtocolCallback(state, false, [&](INetworkProtocolCallback* installed) + { + installed->OnReadString(str); + }); + } + if (malformed) + { + ReportFatalError(state, L"The async socket protocol received an invalid string length."); + } + } + + void OnWriteCompleted(Ptr buffer) override + { + auto state = lifecycle; + SocketCallbackFrame socketCallbackFrame(state); + IAsyncSocketConnection* connection = nullptr; + Ptr submitting; + bool mismatched = false; + CS_LOCK(state->lockState) + { + if (state->queuedWrites.Count() == 0) + { + return; + } + if (state->queuedWrites[0].Obj() != buffer.Obj()) + { + state->queuedWrites.Clear(); + state->writePending = false; + mismatched = true; + state->cvState.WakeAllPendings(); + } + else + { + state->queuedWrites.RemoveAt(0); + if ((!state->stopStarted || state->drainWrites) && !state->terminal && state->socketConnection && state->queuedWrites.Count() > 0) + { + connection = state->socketConnection; + submitting = state->queuedWrites[0]; + state->activeSocketCalls++; + } + else + { + state->writePending = false; + } + state->cvState.WakeAllPendings(); + } + } + CHECK_ERROR(!mismatched, L"NetworkProtocolConnection received a completion for an unexpected async socket buffer."); + if (submitting) + { + SubmitWrite(state, connection, submitting); + } + } + + void OnError(const WString& error, bool fatal) override + { + auto state = lifecycle; + SocketCallbackFrame socketCallbackFrame(state); + if (fatal) + { + ReportFatalError(state, error); + } + else + { + InvokeProtocolCallback(state, false, [&](INetworkProtocolCallback* installed) + { + installed->OnLocalError(error, false); + }); + } + } + + void OnConnected() override + { + auto state = lifecycle; + SocketCallbackFrame socketCallbackFrame(state); + InvokeProtocolCallback(state, false, [](INetworkProtocolCallback* installed) + { + installed->OnConnected(); + }); + } + + void OnDisconnected() override + { + auto state = lifecycle; + SocketCallbackFrame socketCallbackFrame(state); + IAsyncSocketConnection* connection = nullptr; + CS_LOCK(state->lockState) + { + while (state->activeSocketCalls > 0) + { + state->cvState.SleepWith(state->lockState); + } + connection = state->socketConnection; + } + + try + { + DetachSocketCallback(state, connection); + } + catch (...) + { + CS_LOCK(state->lockState) + { + if (state->socketConnection == connection) + { + state->socketConnection = nullptr; + } + state->cvState.WakeAllPendings(); + } + NotifyProtocolDisconnected(state); + throw; + } + NotifyProtocolDisconnected(state); + } + + void OnInstalled(IAsyncSocketConnection* connection) override + { + auto state = lifecycle; + SocketCallbackFrame socketCallbackFrame(state); + CHECK_ERROR(connection == state->socketConnection, L"NetworkProtocolConnection was installed on an unexpected async socket connection."); + } + }; + +/*********************************************************************** +NetworkProtocolServer +***********************************************************************/ + + template + class NetworkProtocolServer + : public Object + , public virtual INetworkProtocolServer + { + static_assert(std::derived_from); + static_assert(!std::is_final_v); + + private: + class Lifecycle : public Object + { + public: + NetworkProtocolServer* owner = nullptr; + Ptr callbackDomain = Ptr(new NetworkProtocolCallbackDomain); + CriticalSection lockState; + ConditionVariable cvState; + collections::List> + connections; + bool stopStarted = false; + bool stopFinished = false; + bool nativeStopCalling = false; + + Lifecycle(NetworkProtocolServer* _owner) + : owner(_owner) + { + } + }; + + class SocketServerBridge : public TAsyncSocketServer + { + private: + Ptr lifecycle; + CriticalSection lockSelf; + Ptr selfReference; + + public: + template + SocketServerBridge(Ptr _lifecycle, TArgs&&... args) + : TAsyncSocketServer(std::forward(args)...) + , lifecycle(_lifecycle) + { + } + + void InitializeSelf(Ptr self) + { + CS_LOCK(lockSelf) + { + selfReference = self; + } + } + + void ReleaseSelfReference() + { + CS_LOCK(lockSelf) + { + selfReference = nullptr; + } + } + + WaitForClientResult OnClientConnected(IAsyncSocketConnection* connection) override + { + Ptr self; + CS_LOCK(lockSelf) + { + self = selfReference; + } + auto state = lifecycle; + NetworkProtocolServer* owner = nullptr; + CS_LOCK(state->lockState) + { + owner = state->owner; + } + return owner ? owner->OnSocketClientConnected(connection) : WaitForClientResult::Reject; + } + }; + + Ptr lifecycle; + Ptr asyncSocketServer; + + static void StopConnections(Ptr state, bool retainAdapters = false) + { + collections::List> stoppingConnections; + CS_LOCK(state->lockState) + { + for (auto connection : state->connections) + { + stoppingConnections.Add(connection); + } + } + for (auto connection : stoppingConnections) + { + if (retainAdapters) + { + connection->StopWithRetainedAdapter(connection); + } + else + { + connection->Stop(); + } + } + } + + static void QueueDeferredStop(Ptr state, Ptr nativeServer) + { + ThreadPoolLite::QueueLambda([state, nativeServer]() + { + state->lockState.Enter(); + while (!state->stopFinished || state->nativeStopCalling) + { + state->cvState.SleepWith(state->lockState); + } + state->nativeStopCalling = true; + state->lockState.Leave(); + + try + { + nativeServer->Stop(); + StopConnections(state); + state->callbackDomain->WaitForCallbacks(0); + } + catch (...) + { + } + + CS_LOCK(state->lockState) + { + state->nativeStopCalling = false; + state->cvState.WakeAllPendings(); + } + nativeServer->ReleaseSelfReference(); + }); + } + + WaitForClientResult OnSocketClientConnected(IAsyncSocketConnection* connection) + { + auto state = lifecycle; + NetworkProtocolCallbackDomain::CallbackFrame callbackFrame(state->callbackDomain); + bool acceptCallback = false; + CS_LOCK(state->lockState) + { + acceptCallback = !state->stopStarted; + } + if (!acceptCallback) + { + return WaitForClientResult::Reject; + } + + auto protocolConnection = Ptr(new NetworkProtocolConnection(connection, state->callbackDomain)); + CS_LOCK(state->lockState) + { + state->connections.Add(protocolConnection); + acceptCallback = !state->stopStarted; + } + return acceptCallback ? OnClientConnected(protocolConnection.Obj()) : WaitForClientResult::Reject; + } + + public: + template + NetworkProtocolServer(TArgs&&... args) + : lifecycle(Ptr(new Lifecycle(this))) + , asyncSocketServer(new SocketServerBridge(lifecycle, std::forward(args)...)) + { + asyncSocketServer->InitializeSelf(asyncSocketServer); + } + + ~NetworkProtocolServer() + { + Stop(); + auto state = lifecycle; + StopConnections(state, true); + CS_LOCK(state->lockState) + { + state->owner = nullptr; + state->connections.Clear(); + } + } + + virtual WaitForClientResult OnClientConnected(INetworkProtocolConnection*) override + { + return WaitForClientResult::Accept; + } + + void Start() override + { + asyncSocketServer->Start(); + } + + void Stop() override + { + auto state = lifecycle; + auto nativeServer = asyncSocketServer; + auto callbackDepth = state->callbackDomain->CurrentCallbackDepth(); + bool firstStop = false; + bool nestedFollower = false; + bool deferFinalization = false; + state->lockState.Enter(); + if (!state->stopStarted) + { + state->stopStarted = true; + state->nativeStopCalling = true; + firstStop = true; + } + else if (callbackDepth > 0) + { + nestedFollower = true; + } + else + { + while (!state->stopFinished || state->nativeStopCalling) + { + state->cvState.SleepWith(state->lockState); + } + state->nativeStopCalling = true; + } + state->lockState.Leave(); + if (nestedFollower) + { + StopConnections(state); + return; + } + + try + { + nativeServer->Stop(); + StopConnections(state); + state->callbackDomain->WaitForCallbacks(firstStop ? callbackDepth : 0); + deferFinalization = firstStop && callbackDepth > 0; + if (!deferFinalization) + { + nativeServer->ReleaseSelfReference(); + } + } + catch (...) + { + if (!firstStop || callbackDepth == 0) + { + nativeServer->ReleaseSelfReference(); + } + CS_LOCK(state->lockState) + { + state->nativeStopCalling = false; + if (firstStop) + { + state->stopFinished = true; + } + state->cvState.WakeAllPendings(); + } + throw; + } + + CS_LOCK(state->lockState) + { + state->nativeStopCalling = false; + if (firstStop) + { + state->stopFinished = true; + } + state->cvState.WakeAllPendings(); + } + if (deferFinalization) + { + QueueDeferredStop(state, nativeServer); + } + } + + bool IsStopped() override + { + return asyncSocketServer->IsStopped(); + } + }; + +/*********************************************************************** +NetworkProtocolClient +***********************************************************************/ + + template + class NetworkProtocolClient + : public Object + , public virtual INetworkProtocolClient + { + static_assert(std::derived_from); + + private: + Ptr asyncSocketClient; + Ptr connection; + + static void QueueDeferredRelease(Ptr nativeClient) + { + ThreadPoolLite::QueueLambda([nativeClient]() + { + nativeClient->GetConnection()->Stop(); + }); + } + + public: + template + NetworkProtocolClient(TArgs&&... args) + : asyncSocketClient(new TAsyncSocketClient(std::forward(args)...)) + , connection(new NetworkProtocolConnection(asyncSocketClient->GetConnection())) + { + } + + ~NetworkProtocolClient() + { + auto state = connection->lifecycle; + auto deferFinalization = + NetworkProtocolConnection::CurrentCallbackDepth(state) > 0 || + NetworkProtocolConnection::CurrentSocketCallbackDepth(state) > 0; + auto nativeClient = asyncSocketClient; + connection->StopWithRetainedAdapter(connection); + if (deferFinalization) + { + QueueDeferredRelease(nativeClient); + } + } + + INetworkProtocolConnection* GetConnection() override + { + return connection.Obj(); + } + + void WaitForServer() override + { + asyncSocketClient->WaitForServer(); + } + + ClientStatus GetStatus() override + { + return asyncSocketClient->GetStatus(); + } + }; +} + +#endif + + /*********************************************************************** .\INTERPROCESS\CHANNELIMPLS\CHANNELIMPL.H ***********************************************************************/ @@ -2649,7 +3939,6 @@ Interfaces: #ifndef VCZH_INTERPROCESS_CHANNELIMPLS_CHANNELSERVERIMPL #define VCZH_INTERPROCESS_CHANNELIMPLS_CHANNELSERVERIMPL -#include namespace vl::inter_process { diff --git a/Tools/Executables/CodePack/Codepack_CategorizeCodeFiles.cpp b/Tools/Executables/CodePack/Codepack_CategorizeCodeFiles.cpp index baac444b..14468879 100644 --- a/Tools/Executables/CodePack/Codepack_CategorizeCodeFiles.cpp +++ b/Tools/Executables/CodePack/Codepack_CategorizeCodeFiles.cpp @@ -11,6 +11,24 @@ void CategorizeCodeFiles( { auto name = XmlGetAttribute(e, L"name")->value.value; auto pattern = wupper(XmlGetAttribute(e, L"pattern")->value.value); + List patterns; + while (true) + { + auto index = pattern.IndexOf(L';'); + if (index == -1) + { + if (pattern.Length() > 0) + { + patterns.Add(pattern); + } + break; + } + if (index > 0) + { + patterns.Add(pattern.Left(index)); + } + pattern = pattern.Right(pattern.Length() - index - 1); + } List exceptions; CopyFrom( @@ -28,7 +46,10 @@ void CategorizeCodeFiles( From(files).Where([&](const FilePath& f) { auto path = GetCodePackPath(f.GetFullPath()); - return INVLOC.FindFirst(path, pattern, Locale::IgnoreCase).key != -1 + return From(patterns).Any([&](const WString& pattern) + { + return INVLOC.FindFirst(path, pattern, Locale::IgnoreCase).key != -1; + }) && From(exceptions).All([&](const WString& ex) { return INVLOC.FindFirst(path, ex, Locale::IgnoreCase).key == -1; diff --git a/Tools/Executables/vl/makefile-cpp b/Tools/Executables/vl/makefile-cpp index 724c01f2..ecbc99a9 100644 --- a/Tools/Executables/vl/makefile-cpp +++ b/Tools/Executables/vl/makefile-cpp @@ -3,23 +3,31 @@ USE_GCC?=NO # add "--stdlib=libc++" after bug #808086 is fixed +CPP_DEPENDENCY_OPTIONS=-MMD -MP -MF $(@:.o=.d) -MT $@ +CPP_PLATFORM_NAME=$(shell uname -s) +CPP_PLATFORM_COMPILE_OPTIONS= CPP_PLATFORM_LINK_OPTIONS= -ifeq ($(shell uname -s),Darwin) -CPP_PLATFORM_LINK_OPTIONS=-framework CoreFoundation +ifeq ($(CPP_PLATFORM_NAME),Darwin) +CPP_PLATFORM_COMPILE_OPTIONS=-fblocks +CPP_PLATFORM_LINK_OPTIONS=-framework CoreFoundation -framework Network +else ifeq ($(CPP_PLATFORM_NAME),Linux) +CPP_PLATFORM_LINK_OPTIONS=-luring +endif + +-include $(wildcard ./Obj/*.d) + +ifeq ($(USE_GCC), YES) +CPP_LINK=g++ -std=c++20 -g -pthread -o $@ $^ $(CPP_LINK_OPTIONS) $(CPP_PLATFORM_LINK_OPTIONS) +else ifeq ($(COVERAGE),NO) +CPP_LINK=clang++ -std=c++20 -pthread -g $(CPP_COMPILE_OPTIONS) -o $@ $^ $(CPP_LINK_OPTIONS) $(CPP_PLATFORM_LINK_OPTIONS) +else +CPP_LINK=clang++ -std=c++20 -pthread -g --coverage -o $@ $^ $(CPP_LINK_OPTIONS) $(CPP_PLATFORM_LINK_OPTIONS) endif ifeq ($(USE_GCC), YES) -CPP_LINK=g++ -std=c++20 -g -pthread $(CPP_LINK_OPTIONS) $(CPP_PLATFORM_LINK_OPTIONS) -o $@ $^ +CPP_COMPILE=g++ -std=c++20 -g $(CPP_COMPILE_OPTIONS) $(CPP_PLATFORM_COMPILE_OPTIONS) $(CPP_DEPENDENCY_OPTIONS) -o $@ -c $< else ifeq ($(COVERAGE),NO) -CPP_LINK=clang++ -std=c++20 -pthread -g $(CPP_COMPILE_OPTIONS) $(CPP_LINK_OPTIONS) $(CPP_PLATFORM_LINK_OPTIONS) -o $@ $^ +CPP_COMPILE=clang++ -std=c++20 -g $(CPP_COMPILE_OPTIONS) $(CPP_PLATFORM_COMPILE_OPTIONS) $(CPP_DEPENDENCY_OPTIONS) -o $@ -c $< else -CPP_LINK=clang++ -std=c++20 -pthread -g --coverage $(CPP_LINK_OPTIONS) $(CPP_PLATFORM_LINK_OPTIONS) -o $@ $^ -endif - -ifeq ($(USE_GCC), YES) -CPP_COMPILE=g++ -std=c++20 -g $(CPP_COMPILE_OPTIONS) -o $@ -c $< -else ifeq ($(COVERAGE),NO) -CPP_COMPILE=clang++ -std=c++20 -g $(CPP_COMPILE_OPTIONS) -o $@ -c $< -else -CPP_COMPILE=clang++ -std=c++20 -g -fprofile-arcs -ftest-coverage $(CPP_COMPILE_OPTIONS) -o $@ -c $< +CPP_COMPILE=clang++ -std=c++20 -g -fprofile-arcs -ftest-coverage $(CPP_COMPILE_OPTIONS) $(CPP_PLATFORM_COMPILE_OPTIONS) $(CPP_DEPENDENCY_OPTIONS) -o $@ -c $< endif diff --git a/Tutorial/GacUI_Controls/ProgressAndAsync/Main.cpp b/Tutorial/GacUI_Controls/ProgressAndAsync/Main.cpp index 2fb0377b..f687304a 100644 --- a/Tutorial/GacUI_Controls/ProgressAndAsync/Main.cpp +++ b/Tutorial/GacUI_Controls/ProgressAndAsync/Main.cpp @@ -9,7 +9,7 @@ using namespace vl::collections; using namespace vl::stream; #if defined VCZH_MSVC -using namespace vl::inter_process; +using namespace vl::inter_process::windows_http; #endif class ViewModel : public Object, public demo::IViewModel