Hmm, another blog entry on the Play framework. You must be sooo surprised!

Play allows you to suspend requests while doing heavy computation – this frees up your application to continue processing new requests, regardless of how long the computation takes. However, you may be deploying to an environment with a fixed request timeout – in the case that prompted me to come up with this code, I was deploying to a server that required requests to return within 120 seconds.

When generating PDFs, Excel files, images or whatever, that may take longer than the available window I needed to be able to push the computation into the background using a job. This solves the immediate problem, but leads to another issue – what if the job actually completes in a couple of seconds? I would then be forcing the user to then go to another link to download the generated content. Not so good for the user experience.

A third way was clearly needed, and this is the solution I came up with:

  • All file generation computation runs in a job. This job is responsible for saving the content into the database.
  • A promise is requested from the job, and given a certain amount of time to complete in:
    F.Promise promise = downloadJob.now();
    UserContent userContent = promise.get(5,
                                                          TimeUnit.SECONDS);
    
  • If the object returned from the promise is not null, it’s sent to the browser. If it is null, a message is returned to the browser indicating the generation is on-going.
    if (userContent != null)
    {
        ...
        renderBinary(new ByteArrayInputStream(userContent.content.byteContent),
                          userContent.fileName,
                          userContent.content.byteContent.length,
                          MimeTypes.getMimeType(userContent.fileName),
                          false);
    }
    else
    {
        flash.success("Content is being generated in the background");
        index();
    }
    
  • A download manager is provided to download any generated content (even content that returned a binary response).
  • A job runs every night to remove any generated content older than a week.
  • The result is an application that will guarantee to return within the alloted timeframe, and also allows users to keep working while their content is generated asynchronously. It also reduces concurrent load on the server because the generation job is executed from within the jobs pool, which can be limited using the application.conf.

    An example implementation can be found here: variable-downloads. The only thing missing (apart from comments – it’s pretty self-explanatory) is polling of the server to update the downloads table when a new download is available.

Leave a Reply

Your email address will not be published. Required fields are marked *