3

I wrote a spring-boot application that recieves an object named Calc from the user, which contains two parameters, and returns an answer that consists of a complex calculation (the calculation itself is not relevant to the question). Because the system may be busy, each object is entered into the queue, and there is a scheduler that passes by order on the queue, and preforms the calculation.

My problem is how to return the result of the item's calculation to the correct request.

I've included the code I wrote:

controller:

@RestController
public class CalcController {

    @Autowired
    private CalculateService calculateService;

    @RequestMapping("/")
    public int calculate(@RequestBody Calc calc) {
        return calculateService.calculate(calc);
    }
}

Calc Object:

@Data
public class Calc {
    private int paramA;
    private int paramB;
}

CalculateService:

@Service
public class CalculateService {
    private BlockingQueue<Calc> calcQueue;

    @PostConstruct
    private void init() {
        calcQueue = new LinkedBlockingDeque<>();
    }

    public int calculate(Calc calc) {
        calcQueue.add(calc);

        // TODO: Return calculation result.
        return 0;
    }

    @Scheduled(fixedRate = 2000)
    public void calculateQueue() throws InterruptedException {
        while (!calcQueue.isEmpty()) {
            Calc calc = calcQueue.take();
            int result = Calculator.calculate(calc);
            // TODO: Return calculation result to the right request.
        }
    }
}

Thanks

Zag Gol
  • 1,038
  • 1
  • 18
  • 43
  • 2
    Using a scheduler makes little sense. Why not create an `ExecutorService` with multiple threads, submit calculation to that, and then return the result of the returned `Future`. – daniu Mar 17 '19 at 10:51
  • Thanks, if you can, please add a link to a example of a ExecutorService with multiple threads on a queue, that returns values. – Zag Gol Mar 17 '19 at 10:57
  • 1
    I think you need a messaging service with a request-reply capability rather than a REST Service. Or you could use frameworks like Node.js or Vert.X to build non-blocking rest service, but non-blocking rest services are best if you just want to post something to service and only care about the acknowledgement from the service – Arvind Gangam Mar 17 '19 at 15:07

2 Answers2

0

You can use an ExecutorService which essentially maintains an internal queue to dispatch your work requests to a number of threads.

class Service {
    // use 4 threads; optimal is the amount of processor cores available
    private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(4);

    public int calculate(int input) {
        Future<Integer> future = EXECUTOR.submit(() -> Calculator.calculate(input));
        return future.get(); // this actually thrown an Exception you have to catch
    }
}

The submit will call Calculator.calculate() when the executor has a thread available, and future.get() will extract the actual result of that call.

Note that this code does block until the result has been calculated, only the calculation itself will be parallel. If you do want to return immediately and provide the result later, that's a different story, but it doesn't really fit the REST controller concept.

You can also make this code simpler by using CompletableFuture

class Service {
    public int calculate(int input) {
        return CompletableFuture.supplyAsync(() -> Calculator.calculate(input)).get();
    }
}
daniu
  • 14,137
  • 4
  • 32
  • 53
0

You can use Spring's @Async ability

  1. Create a Thread pool
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@EnableAsync
@Configuration
public class ThreadConfig {
    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        return new ThreadPoolTaskExecutor();
    }
}
  1. Update calculateService, you do not need to store objects in Queue, it will be handled by Spring's async utility
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class CalculateService {
    @Async("threadPoolTaskExecutor")
    public CompletableFuture<Integer> calculate(Calc calc) {
        int result = Calculator.calculate(calc);
        return CompletableFuture.completedFuture(result);
    }
}
  1. Update Controller method to
import com.example.service.CalculateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

@RestController
public class CalcController {

    @Autowired
    private CalculateService calculateService;

    @RequestMapping("/")
    public int calculate(@RequestBody Calc calc) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> resultFut = calculateService.calculate(calc);
        // some other calls/logic
        return resultFut.get();
    }
}

If you want to store sessions per request, refer this SO post

dkb
  • 4,389
  • 4
  • 36
  • 54
  • A `ThreadPoolExecutor` is not the best idea; it will create as many Threads as there are tasks being submitted, it just reuses them after they are finished. Ie it will not limit parallelism of the execution, and is most probably less efficient. – daniu Mar 18 '19 at 07:55