Why Play 2.5 Akka chunk response getting loaded all at once
Asked Answered
T

3

6

I'm trying to implement chunk response in webapp using PLay 2 with Akka. However, instead of load the response by chunk by chunk all the response is coming as once. Below is the code by which I'm creating chunk in the controller:

/**
 * 
 */    
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import akka.stream.javadsl.Source;
import akka.util.ByteString;
import org.pmw.tinylog.Logger;
import play.cache.CacheApi;
import play.cache.Cached;
import play.filters.csrf.AddCSRFToken;
import play.filters.csrf.CSRF;
import play.libs.Json;
import play.libs.concurrent.HttpExecutionContext;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Http.Cookie;
import play.mvc.Result;

import akka.NotUsed;
import akka.actor.Status;
import akka.stream.OverflowStrategy;
import akka.stream.javadsl.Source;
import akka.util.ByteString;

/**
 * @author Abhinabyte
 *
 */
@Singleton
@AddCSRFToken
public class GetHandler extends Controller {

    @Inject
    private CacheApi cache;

    @Inject
    private HttpExecutionContext httpExecutionContext;

    public CompletionStage<Result> index() {

return CompletableFuture.supplyAsync( () ->
            Source.<ByteString>actorRef(256, OverflowStrategy.dropNew())
                    .mapMaterializedValue(sourceActor -> {

                        CompletableFuture.runAsync(() -> {
                            sourceActor.tell(ByteString.fromString("1"), null);
                            sourceActor.tell(ByteString.fromString("2"), null);
                            sourceActor.tell(ByteString.fromString("3"), null);
                            try {
                                Thread.sleep(3000);//intentional delay
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            sourceActor.tell(ByteString.fromString("444444444444444444444444444444444444444444444444444444444444444444444444"), null);
                            sourceActor.tell(new Status.Success(NotUsed.getInstance()), null);
                        });

                        return sourceActor;
                    })
    ).thenApplyAsync( chunks -> ok().chunked(chunks).as("text/html"));  

    }

}


And below is the Akka thread pool configuration at application.conf :

akka {
  jvm-exit-on-fatal-error = on
  actor {
    default-dispatcher {
      fork-join-executor {
        parallelism-factor = 1.0
        parallelism-max = 64
        task-peeking-mode = LIFO
      }
    }
  }
}

play.server.netty {
  eventLoopThreads = 0
  maxInitialLineLength = 4096
  log.wire = false
  transport = "native"
}

As you can see before sending last to last chunk I'm intentionally delaying the response time. So logically, all chunked data before it should be delivered before it.
However, in my case whole bunch of data is getting loaded. I've tested in all browser(even have tried to CURL).
What I'm missing in here?

Torn answered 9/8, 2016 at 5:33 Comment(0)
I
1

Blocking in mapMaterializedValue will do that because it runs in the Akka default-dispatcher thread, thus preventing message routing for the duration (see this answer for details). You want to dispatch your slow, blocking code asynchronously, with the actor reference for it to post messages to. Your example will do what you expect if you run it in a future:

public CompletionStage<Result> test() {
    return CompletableFuture.supplyAsync( () ->
            Source.<ByteString>actorRef(256, OverflowStrategy.dropNew())
                    .mapMaterializedValue(sourceActor -> {

                        CompletableFuture.runAsync(() -> {

                            for (int i = 0; i < 20; i++) {
                                sourceActor.tell(ByteString.fromString(String.valueOf(i) + "<br/>\n"), null);
                                try {
                                    Thread.sleep(500);//intentional delay
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            sourceActor.tell(new Status.Success(NotUsed.getInstance()), null);
                        });

                        return sourceActor;
                    })
    ).thenApplyAsync( chunks -> ok().chunked(chunks).as("text/html"));
}
Interferon answered 11/8, 2016 at 19:20 Comment(10)
not working :( same thing is happening just like beforeTorn
Are you sure? I tested it myself... post a Gist of your whole controller.Interferon
updated code snippet with with whole controller code. Please have a look if I'm missing somethingTorn
I've expanded the explanation, but I'm stumped as to why it's still not working for you. Can you try this alternative, which uses an Actor for the blocking code: gist.github.com/mikesname/5b7124f51a8705eb7bdb0ff329255390Interferon
Thanks for your effort. However, it's still not working resulting the same. I guess some silly mistake is happeing. Do I need to add any param in application.conf?Also, I've tried with all kinda browser but result is According to both play and scala guideline, the both of the codes you and I using should work. So what's the issue in here?Torn
Have you changed the default thread pool settings at all? And to confirm, even the actor version is outputting everything at once, yes?Interferon
Updated code snippet with thread pool config details. And, actor version is outputting everything at once.Torn
find out that the same code is working only in the Chrome 40+. Not working in any firefox or IE version. Do i need to do something for cross browser effectiveness?Torn
Hello @AbhinabKanrar , I can confirm, it works in Chrome, but not Safari. Have you found a solution yet? Thank you!Cusco
@Cusco I found that most of the latest version of web-browser support chunk-encoding. Not sure about mobile based browser. There is nothing wrong from the application side since after monitoring the network packet, I found that there is not a single packet lossTorn
D
1

If you check the Source code, you can see that the first parameter is bufferSize

public static <T> Source<T,ActorRef> actorRef(int bufferSize,
                                               OverflowStrategy overflowStrategy)

all your elements that you generate in the stream probably have less then 256 bytes, hence only one http chunk is generated. Try to add more elements like in @Mikesname example.

Dever answered 16/8, 2016 at 14:52 Comment(4)
buffersize didn't solve the issue, but find out that the same code is working only in the Chrome 40+. Not working in any firefox or IE version. Do i need to do something for cross browser effectiveness?Torn
for testing you could use simple curl command with --no-buffer to show the data as it arrives, presumably some browsers have a local buffer that may output the data independent of how the data arrives. Still try to add way more data in the stream to properly see the chunking mechanism at work(http chunking makes sense only for large response entities)Dever
as per my knowledge, firefox, chrome etc use 1024 buffer size. so if it's working in chrome it should also work in firefox and so on. Please correct me if I am wrongTorn
another thing to worry would the browser cache, this is why I pointed out testing via a third party tool like curlDever
A
0

It might me useful, if you need chunked response by using other approach.

public Result test() {
    try {
        // Finite list
        List<String> sourceList = Arrays.asList("kiki", "foo", "bar");
        Source<String, ?> source =  Source.from(sourceList);

    /*  Following DB call, which fetch a record at a time, and send it as chunked response.
        final Iterator<String> sourceIterator = Person.fetchAll();
        Source<String, ?> source =  Source.from(() -> sourceIterator);  */

        return ok().chunked(source.via(Flow.of(String.class).map(ByteString::fromString))).as(Http.MimeTypes.TEXT);

    } catch (Exception e) {
        return badRequest(e.getMessage());
    }
}
Afire answered 18/8, 2016 at 6:47 Comment(3)
nope....same issue. Moreover, as per my knowledge, this seems not be a good design since you are copying all chunked data in the list first. Hence, there won't be any meaning of sending chunked data as the computational time already wasted :(Torn
This one might useful for you.Afire
Thanks for the link....the link you provided is an example of comet. I don't think chunk encoding would work as same in this case. The issue is in the browser side which needs some fixesTorn

© 2022 - 2024 — McMap. All rights reserved.