AuroraRuntime/Include/Aurora/IO/IAsyncTransaction.hpp

177 lines
7.6 KiB
C++

/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: IAsyncTransaction.hpp
Date: 2022-2-14
Author: Reece
***/
#pragma once
namespace Aurora::IO::Loop
{
struct ILoopSource;
}
namespace Aurora::IO::CompletionGroup
{
struct ICompletionGroup;
}
namespace Aurora::Async
{
AUKN_SYM AuSPtr<IO::CompletionGroup::ICompletionGroup> GetSelfIOGroup();
}
#include "CompletionGroup/ICompletionGroupWorkHandle.hpp"
#include <Aurora/Debug/Debug.hpp>
namespace Aurora::IO
{
/**
* @brief Cross-platform reusable overlapped-based IO transaction
*
* @warning this object is bound by the current thread at time of creation.
* Do not access this object across threads as there is no MT guarantee on this particular object
* Using the synchronization primitives, loop or authreading, is fine.
*/
// Thread-Local!
struct IAsyncTransaction
{
virtual bool StartRead(AuUInt64 uOffset, const Memory::MemoryViewWrite &memoryView) = 0;
virtual bool StartWrite(AuUInt64 uOffset, const Memory::MemoryViewRead &memoryView) = 0;
/**
* @brief "Non-blocking" is-signaled and call any callback routines (similar to nt alertable sleeps of a period zero)
*/
virtual bool Complete() = 0;
/**
* @brief Block for completion and call any callback routines (similar to nt alertable sleeps)
*/
virtual bool Wait(AuUInt32 uTimeout) = 0;
/**
* @brief Non-blocking has-failed (no callbacks, no alert sleep)
* @warning Also note that we generally try to suppress end of stream "errors"
* @warning If the underlying stream is empty or at the EoS/F point, expect a successful read of zero at least once.
* Further reads may fail.
*
* This design philosophy extends to networking whereby various socket shutdown conditions that most applications would
* consider to be an unsafe shutdown are suppressed as non-fatal. Not that you can't manually query the last error,
* it's just that we shouldn't be generating massive errors logs and presenting stupid warnings to the client, over so
* called "errors" that merely constitute somebodys mother vacuuming over an ethernet cable or a cell tower falling out of
* range. In my estimation, IO errors and other high level errors as presented to an application should only be that of
* logical errors, or that of us bailing out of an unsafe condition, or perhaps the kernel crying about IO resources. Some
* smart-ass networking protocol being aware of an incomplete socket shutdown isn't worth noting, unless the user has
* explicit wishes to log the exact reason of socket shutdown (ie: ISocket::GetError() under ISocketDriver::OnEnd)
*
* Likewise, this reasoning extends to IPC pipes; pipe breakage or other EoS condition should not result in an error.
* @return
*/
virtual bool HasFailed() = 0;
/**
* @brief Non-blocking is-signaled (...and dispatched via ::Complete() or other IO yield) (no callbacks, no alert sleep)
* @return
*/
virtual bool HasCompleted() = 0;
/**
* @brief
* @return
*/
virtual AuUInt GetOSErrorCode() = 0;
/**
* @brief Returns the last packets length assuming ::Complete() is true or you are within the registered callback
*/
virtual AuUInt32 GetLastPacketLength() = 0;
/**
* @brief Registers an NT-like APC callback for the IO transaction.
* Can be executed under any Aurora loop subsystem sleep
*/
virtual void SetCallback(const AuSPtr<IAsyncFinishedSubscriber> &pSubscriber) = 0;
/**
* @brief Provides a loop source that becomes signaled once the transaction is complete.
* Polling the transaction may result in the execution of the callback.
*
* @warning This function needs refactoring from "New..." to "To..."
* @warning This loop source may be that of the CompletionGroup::ICompletionGroup::ToAnyLoopSource(), if provided via TryAttachToCompletionGroup
* @warning Remember to never provide the same loop source twice to the sam LoopQueue or to sleep twice on the same loop source using AuAsync
*
* To prevent logicial errors, you should only be sleeping on this loop source within your scope rather than delegating it a ILoopQueue or AuAsync to prevent double attaches
* EG: pThat->NewLoopSource()->WaitOn(5 /\*ms*\/);
* This is just a recommendation. If you are yielding on only a few file streams and can guarantee you aren't registering the source twice on a single LoopQueue, it's fine.
*
* If you want to ensure proper batching with minimal IO resources, and safe AuAsync interop, you should probably just use CompletionGroups
*/
virtual AuSPtr<IO::Loop::ILoopSource> NewLoopSource() = 0;
/**
* @brief Resets ::Complete() and NewLoopSource()->IsSignaled() to false
*/
virtual void Reset() = 0;
virtual void SetBaseOffset(AuUInt64 uBaseOffset) = 0;
virtual bool TryAttachToCompletionGroup(const AuSPtr<CompletionGroup::ICompletionGroup> &pCompletionGroup) = 0;
virtual CompletionGroup::ICompletionGroupWorkHandle *ToCompletionGroupHandle() = 0;
virtual AuSPtr<CompletionGroup::ICompletionGroup> GetCompletionGroup() = 0;
AURT_ADD_USR_DATA_EXP(this->ToCompletionGroupHandle());
};
inline bool ReadAsync(const AuSPtr<IAsyncTransaction> &pTransaction,
AuUInt64 uOffset,
const Memory::MemoryViewWrite &memoryView,
AuOptional<AuConsumer<AuUInt /*offset*/, AuUInt64 /*length*/>> callback = {},
AuOptional<AuSPtr<CompletionGroup::ICompletionGroup>> pCompletionGroup = { Aurora::Async::GetSelfIOGroup() })
{
SysCheckArgNotNull(pTransaction, false);
if (pCompletionGroup && pCompletionGroup.Value())
{
(void)pTransaction->TryAttachToCompletionGroup(pCompletionGroup.Value());
}
if (callback)
{
auto pRet = AuMakeShared<IAsyncFinishedSubscriberFunctional>();
SysCheckNotNullMemory(pRet, false);
pRet->OnAsyncFileOpFinishedFunctional = callback.Value();
pTransaction->SetCallback(pRet);
}
return pTransaction->StartRead(uOffset, memoryView);
}
inline bool WriteAsync(const AuSPtr<IAsyncTransaction> &pTransaction,
AuUInt64 uOffset,
const Memory::MemoryViewRead &memoryView,
AuOptional<AuConsumer<AuUInt /*offset*/, AuUInt64 /*length*/>> callback = {},
AuOptional<AuSPtr<CompletionGroup::ICompletionGroup>> pCompletionGroup = { Aurora::Async::GetSelfIOGroup() })
{
SysCheckArgNotNull(pTransaction, false);
if (pCompletionGroup && pCompletionGroup.Value())
{
(void)pTransaction->TryAttachToCompletionGroup(pCompletionGroup.Value());
}
if (callback)
{
auto pRet = AuMakeShared<IAsyncFinishedSubscriberFunctional>();
SysCheckNotNullMemory(pRet, false);
pRet->OnAsyncFileOpFinishedFunctional = callback.Value();
pTransaction->SetCallback(pRet);
}
return pTransaction->StartWrite(uOffset, memoryView);
}
}