Responding to resource requests asynchronously
On this page
Overview
Cohtml sends resource requests immediately as resources are encountered while executing Cohtml work, for example during page parsing or script execution. These requests can occur from multiple threads executing Cohtml work simultaneously.
While your IAsyncResourceHandler can respond to resource requests immediately within the same call stack, doing so for large assets can stall the thread executing the work. To alleviate that, Prysm allows you to respond to requests from any thread at any point in time.
How to use
On a high level, you must:
Implement
IAsyncResourceHandler::OnResourceRequest()so that the call schedules the resource response work to happen on a different thread.Since multiple threads can send resource requests simultaneously, you must ensure theIAsyncResourceHandler::OnResourceRequest()call is thread-safe.Execute the work and respond to the resource by calling
IAsyncResourceResponse::Finish().You should not respond to resource requests by callingIAsyncResourceResponse::Finish()during/after Cohtml uninitialization. Doing so leads to undefined behavior, most likely a crash.
To avoid doing unnecessary work, you can also implement IAsyncResourceHandler::OnAbortResourceRequest() so that you are notified when a resource won’t be used and can drop scheduled resource work for it.
IAsyncResourceHandler::OnAbortResourceRequest() callback is only a notification. You can still respond with success to an aborted resource request and Cohtml will handle that gracefully. Thus, implementing the call to handle dropping asynchronous resource work is an optimization, not a requirement.IAsyncResourceHandler::OnAbortResourceRequest() call is thread-safe.Example
Here is a pseudo code implementation of IAsyncResourceHandler that reads local resources from disk asynchronously.
void MyResourceHandler::OnResourceRequest(const cohtml::IAsyncResourceRequest* request, cohtml::IAsyncResourceResponse* response)
{
auto requestId = request->GetId();
myEngine->ScheduleResourceWorkOnAnotherThread(requestId, [myEngine, request, response]() {
// Get URL of resource and use it to read the file from disk
auto path = request->GetURL();
auto fileContents = myEngine->ReadFileFromDisk(path);
// Fill the body of the response with the contents of the file.
auto responseBody = response->GetSpace(fileContents.size());
myEngine->FillResponseBody(responseBody, fileContents);
// Respond to the request
response->Finish(cohtml::IAsyncResourceResponse::Success);
});
}
void MyResourceHandler::OnAbortResourceRequest(unsigned id)
{
// cohtml::IAsyncResourceResponse::Finish() must be called exactly once, after which it becomes unsafe to call any methods for the response.
// For that reason, we call finish here only if we successfully aborted the resource work.
if (myEngine->TryAbortResourceWorkFromAnotherThread(id))
{
cohtml::IAsyncResourceResponse* response = myEngine->GetResourceResponseById(id);
response->Finish(cohtml::IAsyncResourceResponse::Failure);
}
}
// Before you begin uninitializing Cohtml, stop all in-flight resource requests
void Unitialize()
{
myEngine->StopAndJoinResourceWorkerThreads();
// Uninitialize rest of Cohtml
cohtmlSystem->Destroy();
// ...
}