Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play Framework: File uploads - blocking or non-blocking?

Given this example code from Play documentation:

def upload = Action(parse.temporaryFile) { request =>
  request.body.moveTo(new File("/tmp/picture/uploaded"))
  Ok("File uploaded")
}
  1. How 100 simultaneous slow upload requests will be handled (number of threads)?
  2. Will be uploaded file buffered in memory or streamed directly to disk?
like image 408
Sokolov Avatar asked Mar 01 '15 16:03

Sokolov


1 Answers

How 100 simultaneous slow upload requests will be handled (number of threads)?

It depends. The number of actual threads being used isn't really relevant. By default, Play uses a number of threads equal to the number of CPU cores available. But this doesn't mean that if you have 4 cores, you're limited to 4 concurrent processes at once. HTTP requests in Play are processed asynchronously in a special internal ExecutionContext provisioned by Akka. Processes running in an ExecutionContext can share threads, so long as they are non-blocking--which is abstracted away by Akka. All of this can be configured in different ways. See Understanding Play Thread Pools.

The Iteratee that consumes the client data must do some blocking in order to write the file chunks to disk, but done in small (and fast) enough chunks, this shouldn't cause other file uploads to become blocked.

What I would be more worried about is the amount of disk I/O your server can handle. 100 slow uploads might be okay, but you can't really say without benchmarking. At some point you will run into trouble when the client input exceeds the rate that your server can write to disk. This will also not work in a distributed environment. I almost always choose to bypass the Play server entirely and direct uploads to Amazon S3.

Will be uploaded file buffered in memory or streamed directly to disk?

All temporary files are streamed to disk. Under the hood, all data sent from the client to the server is read using the iteratee library asynchronously. For multipart uploads, it is no different. The client data is consumed by an Iteratee, which streams the file chunks to a temporary file on disk. So when using the parse.temporaryFile BodyParser, request.body is just a handle to a temporary file on disk, and not the file stored in memory.


It is worth noting that while Play can handle those requests in a non-blocking manner, moving the file once complete will block. That is, request.body.moveTo(...) will block the controller function until the move is complete. This means that if several of the 100 uploads complete at about the same time, Play's internal ExecutionContext for handling requests can quickly become overloaded. The underlying API of moveTo is also deprecated in Play 2.3, as it uses FileInputStream and FileOutputStream to copy the TemporaryFile to a permanent location. The docs advise you to use the Java 7 File API, instead, as it is much more efficient.

This may be a little crude, but something more like this should do it:

import java.io.File
import java.nio.file.Files

def upload = Action(parse.temporaryFile) { request =>
    Files.copy(request.body.file.toPath, new File("/tmp/picture/uploaded").toPath)
    Ok("File uploaded")
}
like image 84
Michael Zajac Avatar answered Oct 21 '22 03:10

Michael Zajac