简体   繁体   中英

AKKA - Spring MVC and service - Timeout Exception

I have a spring service in Spring MVC web app which calls an actor system to compute a value. When I fire multiple times on webapp the application launch an TimeoutException . Only the first computation is done.

Could you give me some help ?

Thanks

@Service
public class Service {

    public static final int processors = Runtime.getRuntime().availableProcessors();
    @Value("${Iterations}")
    long numberOfIterations;
    @Value("${constante}")
    double constante;

    ActorSystem system;
    ActorRef master;

    public Serice() {
        // Create an Akka system
        system = ActorSystem.create("ComputationSystem");

        // create the master
        master = system.actorOf(new Props(new UntypedActorFactory() {
            public UntypedActor create() {
                return new Master(constante);
            }
        }));
    }

    @PreDestroy
    public void cleanUp() throws Exception {
        system.shutdown();
    }

    @Override
    public double calculatePrice(double x, double y, double z,
            double ex) {


        // start the calculation
        Work work = new Work(numberOfIterations, x, y, z,
                ex);

        Timeout timeout = new Timeout(Duration.create(60, "seconds"));
        Future<Object> future = ask(master, work, timeout);

        double total = 0;
        try {
            total = (Double) Await.result(future,
                    timeout.duration());
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {

        }

        return total;
    }

}


public class Master extends UntypedActor {
    private final ActorRef workerRouter;
    private double total = 0;
    private int answerReceived = 0;
    private long nbPerThreads;
    private double x;
    private double constante;
    private ActorRef replayTo;

    public Master(final double constante) {
        workerRouter = this.getContext().actorOf(
                new Props(new UntypedActorFactory() {
                    public UntypedActor create() {
                        return new Worker(constante);
                    }
                }).withRouter(new RoundRobinRouter(Algo.processors)),
                "workerRouter");
        this.constante = constante;
    }

    public void onReceive(Object message) {
        if (message instanceof Work) {
            Work work = (Work) message;

            replayTo = getSender();
            nbPerThreads = work.nbIterations / Algo.processors;
            x = work.x / 360.0;

            // Modify the message to give the right to the workers
            work.nbIterations = nbPerThreads;
            work.x = x;

            for (int i = 0; i < Algo.processors; i++) {

                workerRouter.tell(work, getSelf());
            }
            return;
        }

        if (message instanceof Double) {


            Double result = (Double) message;
            total += result;
            if (++answerReceived == Algo.processors) {
                double meanOfPremiums = total / (nbPerThreads * Algo.processors); 

                double result = Math.exp(-constante * x) * meanOfPremiums;

                System.out.println("returning answer :" + message);
                // Return the answer
                replayTo.tell(result, getSelf());
            }
            return;
        }
        unhandled(message);
    }
}

It is problematic to store the sender in an attribute. If another Work message arrives before the last left, this will get overwritten and your messages will not arrive correctly. My advice would be to create a temporary actor to aggregate the results and reply to the sender. Every time you receive a Work message, you would create this actor, and pass it the sender it needs to reply to as argument. When you send the work to your work router you would just pass this new actor as senders. No changes in your worker code.

This new actor would simply hold the code you currently have for handling Double message in its onReceive method, and would call context().system().stop(self()) after sending the reply to the original sender.

This pattern should lead you to a working solution.

Everything works ! thanks for all, a special thanks to @pushy.

There was 2 errors in my code.

First the reference replyTo is the original sender akka://ComputeSystem/temp/$0d . So when 2 threads (http calls) consumes the service, the first future expires because the master never sends the response.

Second, the calcul has to be done in a temporary Actor. I've created this actor which notifies the master. The master launchs all workers with the temporary Actor reference. When all workers have answered to the this actor the computation is return to the original sender.

I have no ides on how Spring MVC works, but is Service instantiated multiple times ?

If yes, you are creating the actor system multiple times under the same name; that is not a good idea. Instantiate it globally then get a reference to it and its created actors.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM