Jamie Reece Wilson
48aa0f01b1
[+] IFileStream::ToStreamSeekingWriter [*] Mitigation for Linshids god awful IO subsystems
172 lines
7.4 KiB
C++
172 lines
7.4 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;
|
|
}
|
|
|
|
#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 AuSPtr<Memory::MemoryViewWrite> &memoryView) = 0;
|
|
virtual bool StartWrite(AuUInt64 uOffset, const AuSPtr<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 AuSPtr<Memory::MemoryViewWrite> &memoryView,
|
|
AuOptional<AuConsumer<AuUInt /*offset*/, AuUInt64 /*length*/>> callback = {},
|
|
AuOptional<AuSPtr<CompletionGroup::ICompletionGroup>> pCompletionGroup = {})
|
|
{
|
|
SysCheckArgNotNull(pTransaction, false);
|
|
|
|
if (pCompletionGroup)
|
|
{
|
|
(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 AuSPtr<Memory::MemoryViewRead> &memoryView,
|
|
AuOptional<AuConsumer<AuUInt /*offset*/, AuUInt64 /*length*/>> callback = {},
|
|
AuOptional<AuSPtr<CompletionGroup::ICompletionGroup>> pCompletionGroup = {})
|
|
{
|
|
SysCheckArgNotNull(pTransaction, false);
|
|
|
|
if (pCompletionGroup)
|
|
{
|
|
(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);
|
|
}
|
|
} |