I'm playing around with reactive patterns in a Java (8) Spring Boot (1.5.2.RELEASE) application with Akka (2.5.1). It's coming along nicely but now I'm stuck trying to run a CompletableFuture from an actor. To simulate this I have created a very simple service that returns a CompletableFuture. However, when I then try to return the result to the calling controller I get errors about dead-letters and no response is returned.
The error I am getting is:
[INFO] [05/05/2017 13:12:25.650] [akka-spring-demo-akka.actor.default-dispatcher-5] [akka://akka-spring-demo/deadLetters] Message [java.lang.String] from Actor[akka://akka-spring-demo/user/$a#-1561144664] to Actor[akka://akka-spring-demo/deadLetters] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
Here is my code. This is the controller calling the actor:
#Component
#Produces(MediaType.TEXT_PLAIN)
#Path("/")
public class AsyncController {
#Autowired
private ActorSystem system;
private ActorRef getGreetingActorRef() {
ActorRef greeter = system.actorOf(SPRING_EXTENSION_PROVIDER.get(system)
.props("greetingActor"));
return greeter;
}
#GET
#Path("/foo")
public void test(#Suspended AsyncResponse asyncResponse, #QueryParam("echo") String echo) {
ask(getGreetingActorRef(), new Greet(echo), 1000)
.thenApply((greet) -> asyncResponse.resume(Response.ok(greet).build()));
}
}
Here is the service:
#Component
public class GreetingService {
public CompletableFuture<String> greetAsync(String name) {
return CompletableFuture.supplyAsync(() -> "Hello, " + name);
}
}
Then here is the actor receiving the call. At first I had this:
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class GreetingActor extends AbstractActor {
#Autowired
private GreetingService greetingService;
#Autowired
private ActorSystem system;
#Override
public Receive createReceive() {
return receiveBuilder()
.match(Greet.class, this::onGreet)
.build();
}
private void onGreet(Greet greet) {
greetingService.greetAsync(greet.getMessage())
.thenAccept((greetingResponse) -> getSender().tell(greetingResponse, getSelf()));
}
}
This resulted in 2 calls being handled correctly but after that I would get dead-letter errors. Then I read here what was probably causing my problems:
http://doc.akka.io/docs/akka/2.5.1/java/actors.html
Warning
When using future callbacks, inside actors you need to carefully avoid closing over the containing actor’s reference, i.e. do not call methods or access mutable state on the enclosing actor from within the callback. This would break the actor encapsulation and may introduce synchronization bugs and race conditions because the callback will be scheduled concurrently to the enclosing actor. Unfortunately there is not yet a way to detect these illegal accesses at compile time. See also: Actors and shared mutable state
So I figured the idea is that you pipe the result to self() after which you can do getSender().tell(response, getSelf()).
So I altered my code to this:
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class GreetingActor extends AbstractActor {
#Autowired
private GreetingService greetingService;
#Autowired
private ActorSystem system;
#Override
public Receive createReceive() {
return receiveBuilder()
.match(Greet.class, this::onGreet)
.match(String.class, this::onGreetingCompleted)
.build();
}
private void onGreet(Greet greet) {
pipe(greetingService.greetAsync(greet.getMessage()), system.dispatcher()).to(getSelf());
}
private void onGreetingCompleted(String greetingResponse) {
getSender().tell(greetingResponse, getSelf());
}
}
The onGreetingCompleted method is being called with the response from the GreetingService but at that time I again get the dead-letters error so for some reason it can't send the response back to the calling controller.
Note that if I change the service to this:
#Component
public class GreetingService {
public String greet(String name) {
return "Hello, " + name;
}
}
And the onGreet in the actor to:
private void onGreet(Greet greet) {
getSender().tell(greetingService.greet(greet.getMessage()), getSelf());
}
Then everything works fine. So it would appear that I have my basic Java/Spring/Akka set up correctly, it's just when trying to call a CompletableFuture from my actor that the problems start.
Any help would be much appreciated, thanks!
The getSender method is only reliably returning the ref of the sender during the synchronous processing of the message.
In your first case, you have:
greetingService.greetAsync(greet.getMessage())
.thenAccept((greetingResponse) -> getSender().tell(greetingResponse, getSelf()));
Which means that getSender() is invoked async once the future completes. Not reliable anymore. You can change that to:
ActorRef sender = getSender();
greetingService.greetAsync(greet.getMessage())
.thenAccept((greetingResponse) -> sender.tell(greetingResponse, getSelf()));
In your second example, you have
pipe(greetingService.greetAsync(greet.getMessage()), system.dispatcher()).to(getSelf());
You are piping the response to "getSelf()", i.e. your worker actor. The original sender will never get anything (thus the ask expires). You can fix that into:
pipe(greetingService.greetAsync(greet.getMessage()), system.dispatcher()).to(getSender());
In the third case, you have getSender() being executed synchronously during the processing of the message, thus it works.
Related
I am having troubles invoking a method asynchronously in Spring, when the invoker is an embedded library receiving notifications from an external system. The code looks as below:
#Service
public class DefaultNotificationProcessor implements NotificationProcessor {
private NotificationClient client;
#Override
public void process(Notification notification) {
processAsync(notification);
}
#PostConstruct
public void startClient() {
client = new NotificationClient(this, clientPort);
client.start();
}
#PreDestroy
public void stopClient() {
client.stop();
}
#Async
private void processAsync(Notification notification) {
// Heavy processing
}
}
The NotificationClient internally has a thread in which it receives notifications from another system. It accepts a NotificationProcessor in its constructor which is basically the object that will do the actual processing of notifications.
In the above code, I have given the Spring bean as the processor and attempted to process the notification asynchronously by using #Async annotation. However, it appears the notification is processed in the same thread as the one used by NotificationClient. Effectively, #Async is ignored.
What am I missing here?
#Async (as well as #Transactional and other similar annotations) will not work when the method is invoked via this (on when #Async is used for private methods*), as long as you do not use real AspectJ compiletime or runtime weaving.
*the private method thing is: when the method is private, then it must been invoked via this - so this is more the consequence then the cause
So change your code:
#Service
public class DefaultNotificationProcessor implements NotificationProcessor {
#Resource
private DefaultNotificationProcessor selfReference;
#Override
public void process(Notification notification) {
selfReference.processAsync(notification);
}
//the method must not been private
//the method must been invoked via a bean reference
#Async
void processAsync(Notification notification) {
// Heavy processing
}
}
See also the answers for: Does Spring #Transactional attribute work on a private method? -- this is the same problem
I currently have implemented in a Spring Boot project running on Fargate an SQS listener.
It's possible that under the hood, the SqsAsyncClient which appears to be a listener, is actually polling though.
Separately, as a PoC, on I implemented a Lambda function trigger on a different queue. This would be invoked when there are items in the queue and would post to my service. This seems unnecessarily complex to me but removes a single point of failure if I were to only have one instance of the service.
I guess my major point of confusion is whether I am needlessly worrying about polling vs listening on a SQS queue and whether it matters.
Code for example purposes:
#Component
#Slf4j
#RequiredArgsConstructor
public class SqsListener {
private final SqsAsyncClient sqsAsyncClient;
private final Environment environment;
private final SmsMessagingServiceImpl smsMessagingService;
#PostConstruct
public void continuousListener() {
String queueUrl = environment.getProperty("aws.sqs.sms.queueUrl");
Mono<ReceiveMessageResponse> responseMono = receiveMessage(queueUrl);
Flux<Message> messages = getItems(responseMono);
messages.subscribe(message -> disposeOfFlux(message, queueUrl));
}
protected Flux<Message> getItems(Mono<ReceiveMessageResponse> responseMono) {
return responseMono.repeat().retry()
.map(ReceiveMessageResponse::messages)
.map(Flux::fromIterable)
.flatMap(messageFlux -> messageFlux);
}
protected void disposeOfFlux(Message message, String queueUrl) {
log.info("Inbound SMS Received from SQS with MessageId: {}", message.messageId());
if (someConditionIsMet())
deleteMessage(queueUrl, message);
}
protected Mono<ReceiveMessageResponse> receiveMessage(String queueUrl) {
return Mono.fromFuture(() -> sqsAsyncClient.receiveMessage(
ReceiveMessageRequest.builder()
.maxNumberOfMessages(5)
.messageAttributeNames("All")
.queueUrl(queueUrl)
.waitTimeSeconds(10)
.visibilityTimeout(30)
.build()));
}
protected void deleteMessage(String queueUrl, Message message) {
sqsAsyncClient.deleteMessage(DeleteMessageRequest.builder()
.queueUrl(queueUrl)
.receiptHandle(message.receiptHandle())
.build())
.thenAccept(deleteMessageResponse -> log.info("deleted message with handle {}", message.receiptHandle()));
}
}
I'm actively using ApplicationEventPublisher in my app and the main result of some methods executions is publishing event with ApplicationEventPublisher.
I am using a simple trap for events in the test environment in order to collect events and verify them:
#Singleton
public class MessageListenerTestHelper {
private ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>();
#Async
#EventListener
public void onEvent(Object event) {
queue.add(event);
}
public Queue getQueue() {
return queue;
}
public <T> Future<T> getEventFromQueue(Class<T> eventClass) {
CompletableFuture<T> future = new CompletableFuture<>();
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
Optional eventOpt = queue.stream()
.filter(eventClass::isInstance)
.findAny();
if (eventOpt.isPresent()) {
future.complete((T) eventOpt.get());
}
}, 100, 100, TimeUnit.MILLISECONDS);
return future;
}
}
But my tests are flaky - its usually fails in github actions, but works at my computer. So I want to fix it by mock ApplicationEventPublisher. But #Replaces annotation doesn't work. I tried it in the test and in factory available only in test environment, but neither of this is worked.
I am going to refuse to use #MicronautTest annotation, and inject mocks manually. But maybe there is another choise?
Context
I've a scenario - I need to expose a rest endpoint and provide a post method , which will be used by a fronend to receive form values (name, email, address).
With these details I need ti call a third party api (that could take upto 10 seconds to respond).
Additionally , I need to store the processed application and the response in DB somewhere to keep a track for monitoring purposes.
Problem
I plan to use Spring #Async functionality for storing details in Queue (the a DB) so that I dont keep the user waiting for response while I do this storing. The #Async seems to be creating new Thread , I can see from the logs but Controller doesn’t send the response back to client (Which is contrary to actual #Async knowledge I have which is not alot) ; So, until the async completes user has to wait for the response.
Is there anything wrong here or missing ?
TIA for your help.
here are some snippets of my classes-
Main
#EnableAsync(proxyTargetClass = true)
#EnableScheduling
#SpringBootApplication
public class CardsApplication {
public static void main(String[] args) {
SpringApplication.run(CardsApplication.class, args);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
Controller
#Validated
#RequiredArgsConstructor
public class CardEligibilityController {
private final CardEligibilityService cardEligibilityService;
#PostMapping("/check-eligibility")
#CrossOrigin(origins = "*")
public EligibilityResponse checkEligibility(#RequestBody #Valid Applicant applicant){
return cardEligibilityService.eligibilityService(applicant);
}
}
Service 1
public interface CardEligibilityService {
EligibilityResponse eligibilityService(Applicant applicant);
}
#Slf4j
#Service
#RequiredArgsConstructor
public class CardEligibilityServiceImpl implements CardEligibilityService {
private final ThirdPartyEligibilityAdapter thirdPartyEligibilityAdapter;
private final QueueService queueService;
private final QueueMessageResponseService queueMessageResponseService;
#Override
public EligibilityResponse eligibilityService(Applicant applicant){
EligibilityResponse eligibilityResponse = checkEligibility(applicant);
queueService.pushMessage(queueMessageResponseService.createQueueResponse(applicant,eligibilityResponse));
return eligibilityResponse;
}
private EligibilityResponse checkEligibility(Applicant applicant) {
return thirdPartyEligibilityAdapter.getEligibility(applicant);
}
}
Service 2
public interface QueueService {
void pushMessage(QueueMessage queueMessage);
void retry();
}
#Service
#RequiredArgsConstructor
#Slf4j
public class QueueServiceImpl implements QueueService{
private final List<QueueMessage> deadQueue = new LinkedList<>();
//TODO check why async gets response stuck
#Override
#Async
public void pushMessage(QueueMessage queueMessage){
try {
//Push message to a queue - Queue settings Rabbit/Kafka - then this could be
//used by listeners to persist the data into DB
log.info("message queued {} ", queueMessage);
} catch (Exception e) {
log.error("Error {} , queueMessage {} ", e, queueMessage);
deadQueue.add(queueMessage);
}
}
**This method is a fault tolerance mechanism in case push to queue had any issues, The Local Method call to pushMessage isn’t the problem I also tried this by deleting retry method method**
#Override
#Scheduled(fixedDelay = 300000)
public void retry() {
log.info("Retrying Message push if there are any failure in enqueueing ");
final List<QueueMessage> temp = new LinkedList<>(deadQueue);
deadQueue.clear();
Collections.reverse(temp);
temp.forEach(this::pushMessage);
}
}
Service 3
public interface QueueMessageResponseService {
QueueMessage createQueueResponse(Applicant applicant, EligibilityResponse eligibilityResponse);
}
#Service
public class QueueMessageResponseServiceServiceImpl implements QueueMessageResponseService {
#Override
public QueueMessage createQueueResponse(Applicant applicant, EligibilityResponse eligibilityResponse) {
return new QueueMessage(applicant,eligibilityResponse);
}
}
EDIT 2
THE MOST STRANGE BEHAVIOUR
If I add Thread.sleep(20); in my async method, This works as expected , the user gets a response back without waiting for async to complete. But Still unable to understand the cause.
#Async
public void pushMessage(QueueMessage queueMessage) {
try {
//Push message to a queue - Queue settings Rabbit/Kafka - then this could be
//used by listeners to persist the data into DB
Thread.sleep(20);
log.info("message queued {} ", queueMessage);
} catch (Exception e) {
log.error("Error {} , queueMessage {} ", e, queueMessage);
deadQueue.add(queueMessage);
}
}
The call to pushMessage in retry is a LOCAL call. So the proxy is not involved an the method is executed synchronously.
You have to move the async method to it's own class.
I have a method in the service class that uses an external wrapper to call the slack api. The wrapper I'm using is this one if it makes any difference. This is how I'm using the wrapper,
//This is the method in my service class
public String sendMess(SlackObj obj) {
//SlackObj contains the channel url, channel name and the message
//build the payload from the SlackObj
//Slack is the name of the wrapper's class that I'm using
Slack slack = Slack.getInstance();
//slack.send is the method that sends the message to slack
WebhookResponse res = slack.send(url, payload);
//other logic
}
//This is what I've tried
#Test
public void slackSendMessageTest(){
//build the slack obj and payload
//build the mock WebhookResponse
Slack slackMock = mock(Slack.class)
when(slackMock.send(channelUrl, payload)).thenReturn(mockWebHookRes);
assertEquals("SUCCESS", testService.sendMessage(testSlackObj);
}
I am trying to write some tests for this method, so my question is, how would i test it without having the message sent every time I run the test? I believe the cause of this is because slack itself is not mocked and I have no idea as how to inject the mock into the mocked service class.
I am open to refactoring the service class if it helps with the testing. Any suggestions and recommendation is appreciated. Thanks.
You are going to have to find a way to mock Slack, which appears to be a singleton, unfortunately.
Here's what I would do:
1) Make Slack available as a bean that can be autowired:
#Configuration
public class SlackConfiguration {
#Bean
public Slack slack() {
return Slack.getInstance();
}
}
2) Change your class to take an injected Slack:
Note that I am totally guessing on the name here, as you just show the method. You would inject the Slack object you turned into a #Bean above, and not use Slack.getInstance() directly anywhere else.
#Component
public class SlackService {
private final Slack slack;
#Autowired
public SlackService(final Slack slack) {
this.slack = slack;
}
public String sendMessage(final Object message) {
final WebhookResponse res = slack.send(url, payload);
// etc
}
}
3) Mock the Slack object and pass it to your SlackService in test:
This allows you to mock out the implementation of Slack, so you can alter its behavior. I won't go into mocking in detail.
public class SlacServiceTest {
private final Slack slack = mock(Slack.class);
private final SlackService serviceUnderTest = new SlackService(slack);
#Test
public void testSomething() {
// TODO: set mock responses here
// Given... when... then...
}
}