On Tue, Jun 16, 2020 at 11:18 PM Stuart Douglas <sdouglas@redhat.com> wrote:
On Wed, 17 Jun 2020, 6:07 am Nate, <nate@n4te.com> wrote:
I dispatch to a worker thread, then read from the file using FileInputStream and write to exchange.getOutputStream(). However, I noticed that this writes all the bytes immediately. Digging into why, exchange.getOutputStream() uses UndertowOutputStream which appears to buffer all the data given to it. Not only does this seem inefficient to copy 100MB for every request, it means I don't know when the transfer completes so I can't track concurrent downloads.

I don't understand exactly what you are doing here, can you share a code snippet? The stream will always write everything that is given to it, but it won't allocate 100M of buffers. If you have loaded the whole file into memory then it is your code that has buffered the full 100M.

I was reading one byte[] from the InputStream and writing it before reading the next. I've now stepped through UndertowOutputStream and I see it copies to a ByteBuffer, then writes that. So I apologize -- I misspoke, it doesn't buffer the whole 100MB. The behavior I am seeing still has me spooked though.

I have now switched to transferBlocking, like this:
var rateLimit = RateLimit.obtain(ip); // Throws if the IP has too many concurrent downloads.
try (var input = new FileInputStream(file)) {
     Channels.transferBlocking(exchange.getResponseChannel(), input.getChannel(), position, length);
     exchange.endExchange();
} finally {
     rateLimit.release(ip);
}

My concern is that transferBlocking returns very quickly. For example, I run curl 3 times simultaneously to download the file at 10 bytes/sec. For each request, all the code above completes right away, even though the 3 curl instances are still downloading and won't finish for ~150 hours.

The stream approach you mention at the start will do it. Just be aware that your systems socket buffer will also come into play here too, initially undertow will be able to write a bit without blocking until the buffer fills up.

Could the system's socket buffer be responsible for the behavior I see? If so, maybe it is working correctly and I see the request code complete quickly because I don't have much load on the server?

It's a little unfortunate to read (and buffer at the system level) the whole file when the request may be abandoned. In fact, the reason I want to limit concurrent downloads by IP is that many people in China use downloader software because of their government firewall. That software hits my server every few seconds for hundreds or even thousands of times. It would be nice to avoid doing too much work prematurely, though limiting concurrent downloads by IP should also solve the problem.

Is there a way I can know when the client has received all the data, even if it is buffered at the system level?

Thanks!
-Nate