I am using the IAsyncReadRequest::SizeRequest() to determine whether a file exists during async read operations.
The very first time this stack of functions runs, it works properly. But the second time, it gets stuck without the IAsyncReadFileHandle::ReadRequest processing. You can see from the print statement
Here are the data structures used in the attached code:
struct FReadRequest
{
void* Buffer;
uint32 SizeToRead;
int64 Offset;
IAsyncReadRequest* ReadRequestPtr;
};
struct FFileRef
{
IAsyncReadFileHandle* ReadFileHandle;
int32 StreamId;
TMap<int32, FReadRequest> ReadRequests;
};
enum EAsyncFileLookupState
{
FILE_LOOKUP_PENDING = 0,
FILE_LOOKUP_FAILED = 1,
FILE_LOOKUP_SUCCESS = 2,
};
Steps to Reproduce
static TMap<int32, EAsyncFileLookupState> AsyncStreamFileSizeLookup;
static I32 FileAsyncOpen(const char* filePath, StreamUserFileAsyncHandle* pReturnedAsyncHandle)
{
UE_LOG(LogTemp, Warning, TEXT("AsyncTest: AsyncOpen filepath: %s, streamId: %d"), *FString(filePath), streamId);
if (AsyncStreamFileSizeLookup.Contains(streamId) == false)
{
FFileRef* handle = new FFileRef();
handle->ReadFileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenAsyncRead(ANSI_TO_TCHAR(filePath));
if (handle->ReadFileHandle == nullptr)
{
delete handle;
return FILE_ERROR_OPEN;
}
handle->StreamId = streamId;
handle->ReadRequests.Empty(SND_STREAM_FILE_MAX_BUFFER_IDS);
AsyncStreamFileSizeLookup.Add(TPair<int32, EAsyncFileLookupState>(streamId, EAsyncFileLookupState::FILE_LOOKUP_PENDING));
int32 CapturedStreamId = streamId;
FAsyncFileCallBack SizeCallbackFunction = [CapturedStreamId](bool bWasCancelled, IAsyncReadRequest* Request)
{
FCriticalSection SizeRequestMutexLock;
if (!bWasCancelled && Request && AsyncStreamFileSizeLookup.Contains(CapturedStreamId))
{
int64 FileSize = Request->GetSizeResults();
FScopeLock Lock(&SizeRequestMutexLock);
AsyncStreamFileSizeLookup[CapturedStreamId] = FileSize > 0 ? EAsyncFileLookupState::FILE_LOOKUP_SUCCESS : EAsyncFileLookupState::FILE_LOOKUP_FAILED;
}
delete Request;
};
IAsyncReadRequest* SizeReq = handle->ReadFileHandle->SizeRequest(&SizeCallbackFunction);
check(SizeReq);
}
EAsyncFileLookupState LookupState = AsyncStreamFileSizeLookup.Contains(streamId) ? AsyncStreamFileSizeLookup[streamId] : EAsyncFileLookupState::FILE_LOOKUP_FAILED;
return LookupState == EAsyncFileLookupState::FILE_LOOKUP_PENDING ? FILE_OPEN_PENDING :
LookupState == EAsyncFileLookupState::FILE_LOOKUP_SUCCESS ? FILE_OPEN_COMPLETE : FILE_ERROR_OPEN;
}
static I32 FileAsyncRead(
StreamUserFileAsyncHandle asyncFileHandle
, void* pDestBuffer
, U32 sizeToRead
, I64 offset
, I32 streamId
, I32 bufferId)
{
FFileRef* handle = static_cast<FFileRef*>(asyncFileHandle);
UE_LOG(LogTemp, Warning, TEXT("AsyncTest: AsyncRead streamId: %d"), streamId);
FReadRequest readRequest;
readRequest.Buffer = pDestBuffer;
readRequest.SizeToRead = sizeToRead;
readRequest.Offset = offset;
readRequest.ReadRequestPtr = handle->ReadFileHandle->ReadRequest( offset, sizeToRead, AIOP_Normal);
handle->ReadRequests.Add( streamId * SubBufferCountInitializationValue + bufferId, readRequest );
return SND_STREAM_FILE_ERROR_OK;
}
/*
*/
static I32 FileAsyncIsReadComplete(
StreamUserFileAsyncHandle asyncFileHandle
, I32 streamId
, I32 bufferId
)
{
FFileRef* handle = static_cast<FFileRef*>(asyncFileHandle);
// Get async read request
FReadRequest* readRequest = handle->ReadRequests.Find( streamId * SubBufferCountInitializationValue + bufferId );
// Check if last async read is done
if( readRequest->ReadRequestPtr->PollCompletion() == false )
{
UE_LOG(LogTemp, Warning, TEXT("AsyncTest: AsyncIsReadComplete is pending streamId: %d"), streamId);
// Still pending
return SND_STREAM_FILE_READ_PENDING;
}
uint8* readResults = readRequest->ReadRequestPtr->GetReadResults();
FMemory::Memcpy(readRequest->Buffer, readResults, readRequest->SizeToRead);
FMemory::Free(readResults);
delete readRequest->ReadRequestPtr;
readRequest->ReadRequestPtr = nullptr;
handle->ReadRequests.Remove( streamId * SubBufferCountInitializationValue + bufferId );
UE_LOG(LogTemp, Warning, TEXT("AsyncTest: AsyncIsReadComplete is finished streamId: %d"), streamId);
return SND_STREAM_FILE_READ_COMPLETE;
}
UE_Seb.Tho
(Sebastian Thomeczek)
October 27, 2025, 10:37am
3
Hey!
Thanks for reporting this.
Could you please provide us with a simple project that contains your code and runs it at startup?
A repro would help us a lot in eliminating any differences in setup and would avoid unnecessary back-and-forths.
Thanks a lot!
Kind Regards,
Sebastian
Hi [mention removed],
I’ve found the fix for this problem. I misread the comment above IAsyncReadRequest::SizeRequest() that says “@return A request for the size. This is owned by the caller and must be deleted by the caller.”
Inside my size callback, I am mistakenly deleting the IAsyncReadRequest* Request, which causes subsequent read requests to fail. Duh!
FAsyncFileCallBack SizeCallbackFunction = [CapturedId](bool bWasCancelled, IAsyncReadRequest* Request)
{
FCriticalSection SizeRequestMutexLock;
if (!bWasCancelled && Request && AsyncStreamFileSizeLookup.Contains(CapturedStreamId))
{
int64 FileSize = Request->GetSizeResults();
FScopeLock Lock(&SizeRequestMutexLock);
AsyncStreamFileSizeLookup[CapturedId] = FileSize > 0 ? EAsyncFileLookupState::FILE_LOOKUP_SUCCESS : EAsyncFileLookupState::FILE_LOOKUP_FAILED;
}
delete Request; // THIS LINE IS A MISTAKE
};
UE_Seb.Tho
(Sebastian Thomeczek)
October 31, 2025, 10:37am
5
Glad to hear that, thanks for letting us know!
I’ll mark this as resolved then.