IAsyncReadRequest::SizeRequest() Seems to block subsequent AsyncRead operations

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;
}

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
		};

Glad to hear that, thanks for letting us know!

I’ll mark this as resolved then.