''int:delayer' with id='delayRetry''' produced no reply for request Message - java

Getting produced no reply for request Message from element "int:delayer" implemented inside chain ("int:chain").
Inside delayer expression and default-delay evaluated successfully after that getting below error due to which retry is not working in case of any failure.
from source: ''int:delayer' with id='delayRetry''' produced no reply for request Message
Tried setting value for requires-reply='false' for the calling parent component. but still getting same issue
Code Snippet:
<int:header-enricher>
<int:header name="delay" expression="headers.containsKey('SomeVariable')?0:(headers['adviceDelay']?:1000)*1.2"
type="java.lang.Integer" overwrite="true" />
</int:header-enricher>
<int:delayer id=delayRetry " expression="headers['delay']" default-delay="100" ignore-expression-failures="false"
message-store="adviceRetryMessageStore" scheduler="delayerTaskScheduler">
<int:transactionaltransaction-manager = jdbcTransactionManager " />
</int:delayer>
<int:routerexpression = "headers.someChannel" />
Here we are setting delay value based on condition defined in expression headers.containsKey('SomeVariable')
If this condition satisfies then, adding 0 delay in retry. In this case retry is working fine and flow routed back to headers.someChannel for retry.
But in another scenario when header not contains SomeVariable, so setting delay of 1200. In this case code enters in error flow and retry is not working.

Hm. That is correct. The delayer logic is like this:
protected Object handleRequestMessage(Message<?> requestMessage) {
boolean delayed = requestMessage.getPayload() instanceof DelayedMessageWrapper;
if (!delayed) {
long delay = determineDelayForMessage(requestMessage);
if (delay > 0) {
releaseMessageAfterDelay(requestMessage, delay);
return null;
}
}
// no delay
return delayed
? ((DelayedMessageWrapper) requestMessage.getPayload()).getOriginal()
: requestMessage;
}
So, if we delay message, we return null. The super class AbstractReplyProducingMessageHandler has this logic:
protected final void handleMessageInternal(Message<?> message) {
Object result;
if (this.advisedRequestHandler == null) {
result = handleRequestMessage(message);
}
else {
result = doInvokeAdvisedRequestHandler(message);
}
if (result != null) {
sendOutputs(result, message);
}
else if (this.requiresReply && !isAsync()) {
throw new ReplyRequiredException(message, "No reply produced by handler '" +
getComponentName() + "', and its 'requiresReply' property is set to true.");
}
else if (!isAsync()) {
logger.debug(LogMessage.format("handler '%s' produced no reply for request Message: %s", this, message));
}
}
so, it does emit that debug message when we no reply to produce. Well, it is there somewhere after delay.
I think it will be safe to mark a DelayHandler as an async component to avoid that debug message. Please, raise a GH issue and we will see what we can do to fix such a confusion. Meanwhile you can just don't use a DEBUG login level for framework categories.

Related

How to get the ConsumerRecord object in StreamListener Consumer code

I wanted to enable the manual commit for my consumer and for that i have below code + configuration. Here i am trying to manually commit the offset in case signIn client throws exception and till manually comitting offet itw works fine but with this code the message which failed to process is not being consumed again so for that what i want to do is calling seek method and consume same failed offset again -
consumer.seek(newTopicPartition(atCommunityTopic,communityFeed.partition()),communityFeed.offset());
But the actual problem is here how do i get partition and offset details from. If somehow i can get ConsumerRecord object along with message then it will work.
spring.cloud.stream.kafka.bindings.atcommnity.consumer.autoCommitOffset=false
And Below is the consumer code through StreamListener
#StreamListener(ConsumerConstants.COMMUNITY_IN)
public void handleCommFeedConsumer(
#Payload Account consumerRecords,
#Header(KafkaHeaders.CONSUMER) Consumer<?, ?> consumer,
#Header(KafkaHeaders.ACKNOWLEDGMENT) Acknowledgment acknowledgment) {
consumerRecords.forEach(communityFeed -> {
try{
AccountClient.signIn(
AccountIn.builder()
.Id(atCommunityEvent.getId())
.build());
log.debug("Calling Client for Id : "
+ communityEvent.getId());
}catch(RuntimeException ex){
log.info("");
//consumer.seek(new TopicPartition(communityTopic,communityFeed.partition()),communityFeed.offset());
return;
}
acknowledgment.acknowledge();
});
}
See https://docs.spring.io/spring-kafka/docs/current/reference/html/#consumer-record-metadata
#Header(KafkaHeaders.PARTITION_ID) int partition
#Header(KafkaHeaders.OFFSET) long offset
IMPORTANT
Seeking the consumer yourself might not do what you want because the container may already have other records after this one; it's best to throw an exception and the error handler will do the seeks for you.

What settings required to retry message to from GCP pub/sub in java

I am new to GCP pub/sub and trying to resend a message which is not acknowledged (ack/nack). In subscription at GCP console dashboard, I have mentioned:
In my java code, I have created a subscriber
public Subscriber createSubscriber(String subscriptionId, MessageReceiver receiver) throws MessagingException {
Subscriber subscriber = null;
ProjectSubscriptionName subscriptionName = null;
String projectId = getProjectId();
if (Objects.isNull(projectId) || Objects.isNull(subscriptionId)) {
throw new MessagingException(MessagingErrorCodes.MIX90810
+ " Project Id/Subscription Id is null for subscriptionId = " + subscriptionId + " projectId= "
+ projectId, MessagingErrorCodes.MIX90810);
}
try {
subscriptionName = ProjectSubscriptionName.of(projectId, subscriptionId);
subscriber = Subscriber.newBuilder(subscriptionName, receiver).setExecutorProvider(getExecutorProvider()).build();
} catch (Exception e) {
throw new MessagingException(MessagingErrorCodes.MAX34540
+ " Error occurred while creating the subscriber for the subscriptionId = " + subscriptionId
+ "projectId " + projectId + "subscriptionName= " + subscriptionName,
MessagingErrorCodes.MAX34540, e);
}
enter code here
return subscriber;
}
I am getting messages on my receiveMessage(PubsubMessage message, AckReplyConsumer consumer) the first time but not getting again if I am not acknowledging the message. But if sending nack it's sending the message again.
#Service
public class MyMessageReceiver implements MessageReceiver {
#Override
public void receiveMessage(PubsubMessage message, AckReplyConsumer consumer) {
System.out.println(message.getMessageId());
}
}
should I need to mention other configuration to enable retry in case of not acknowledging the message as well?
Regarding the retry policy, the documentation says that Pub/Sub tries to redeliver the message, only if the acknowledgement deadline expires or if the subscriber nacks the message. Once the acknowledgement deadline passes, the message becomes a candidate to be redelivered. The redelivery may not be immediate as redelivery is performed on a best effort basis.
As already mentioned in the comments, the DEFAULT_MAX_ACK_EXTENSION_PERIOD is set to 60 minutes in Subscriber.java, which is the cause of this delay. The Ack deadline will continue to be extended (by the client library as a native functionality) until this duration is reached. Meaning, the unacked message is leased by the subscriber for 60 minutes and is not eligible to be redelivered within this period. The setMaxAckExtensionPeriod(Duration maxAckExtensionPeriod) is used to set a custom value to the maximum period a message’s acknowledgement deadline will be extended to.
Please also note that none of these values is a guarantee that the message will not be redelivered within that time frame. It is possible for the message to be redelivered before maxAckExtensionPeriod due to network or server blips.

How to avoid multiple if else block so that the code does not smell?

There are a lot of if-else statements in my code what is the best way that I can avoid so many if-else statements. Below is the code snippet. Now again I need to check if WWW-Authenticate header returns value
signature_invalid
then I need to log and return a different error message and if WWW-Authenticate header returns value
token_exppured
then I need to log a different error message which will again add 2 more ifs. Can anyone Help me how can I avoid this??
if (e.getRawStatusCode() == NOT_FOUND) {
logger.log(
log,
LogLevel.ERROR,
MISSING_VALID_ID_ERROR_MSG + " : " + e.toString(),
viewRequest);
String errorDetails = createNotFoundDetails(appl, transactionId);
updateIdentifier(rcvLog, true, request, identifier);
return createErrorViewResponseReply(MISSING_VALID_ID_ERROR_MSG, errorDetails);
} else if (e.getRawStatusCode() == UNAUTHORIZED) {
logger.log(
log,
LogLevel.ERROR,
UNABLE_TO_REACH_ERROR_MSG + " : " + e.toString(),
viewRequest);
if (e.getResponseHeaders() != null && e.getResponseHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE != null)) {
logger.log(
log,
LogLevel.ERROR,
INVALID_TOKEN_ERROR_MSG + " : " + e.getResponseHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE),
viewRequest);
}
updateIdentifier(rcvLog, false, request, identifier);
return createErrorViewResponseReply(
UNABLE_TO_REACH_ERROR_MSG,
INVALID_TOKEN_ERROR_DETAILS);
}
The basic approaches one can take with this is:
Create a class hierarchy with a factory to instantiate the right instance to respond to the action
Do the same but in short hand with Enums
Use a map of this::method() and call your methods that do the work for you
For your case, since you can't really control the types of code an API sends you, and a factory method may be overkill, a map approach may be best:
map.put(NOT_FOUND, this::methodA);
map.put(UNAUTHORIZED, this::methodB);
map.put(OTHER, this::methodC);
map.computeIfAbsent(
e.getRawStatusCode(),
(e, rc, req, id) -> {/** NOTHING */}
).apply(e, rcvLog, request, identifier);
computeIfAbsent() allows you to handle the unhandled case with basically a no-op.

Conversation ID leads to unkown path in graph-api

I have a code that fetches conversations and the messages inside them (a specific number of pages). It works most of the time, but for certain conversations it throws an exception, such as:
Exception in thread "main" com.restfb.exception.FacebookOAuthException: Received Facebook error response of type OAuthException: Unknown path components: /[id of the message]/messages (code 2500, subcode null)
at com.restfb.DefaultFacebookClient$DefaultGraphFacebookExceptionMapper.exceptionForTypeAndMessage(DefaultFacebookClient.java:1192)
at com.restfb.DefaultFacebookClient.throwFacebookResponseStatusExceptionIfNecessary(DefaultFacebookClient.java:1118)
at com.restfb.DefaultFacebookClient.makeRequestAndProcessResponse(DefaultFacebookClient.java:1059)
at com.restfb.DefaultFacebookClient.makeRequest(DefaultFacebookClient.java:970)
at com.restfb.DefaultFacebookClient.makeRequest(DefaultFacebookClient.java:932)
at com.restfb.DefaultFacebookClient.fetchConnection(DefaultFacebookClient.java:356)
at test.Test.main(Test.java:40)
After debugging I found the ID that doesn't work and tried to access it from graph-api, which results in an "unknown path components" error. I also attempted to manually find the conversation in me/conversations and click the next page link in the graph api explorer which also lead to the same error.
Is there a different way to retrieve a conversation than by ID? And if not, could someone show me an example to verify first if the conversation ID is valid, so if there are conversations I can't retrieve I could skip them instead of getting an error. Here's my current code:
Connection<Conversation> fetchedConversations = fbClient.fetchConnection("me/Conversations", Conversation.class);
int pageCnt = 2;
for (List<Conversation> conversationPage : fetchedConversations) {
for (Conversation aConversation : conversationPage) {
String id = aConversation.getId();
//The line of code which causes the exception
Connection<Message> messages = fbClient.fetchConnection(id + "/messages", Message.class, Parameter.with("fields", "message,created_time,from,id"));
int tempCnt = 0;
for (List<Message> messagePage : messages) {
for (Message msg : messagePage) {
System.out.println(msg.getFrom().getName());
System.out.println(msg.getMessage());
}
if (tempCnt == pageCnt) {
break;
}
tempCnt++;
}
}
}
Thanks in advance!
Update: Surrounded the problematic part with a try catch as a temporary solution, also counted the number of occurrences and it only effects 3 out of 53 conversations. I also printed all the IDs, and it seems that these 3 IDs are the only ones that contain a "/" symbol, I'm guessing it has something to do with the exception.
The IDs that work look something like this: t_[text] (sometimes a "." or a ":" symbol) and the ones that cause an exception are always t_[text]/[text]
conv_id/messages is not a valid graph api call.
messages is a field of conversation.
Here is what you do (single call to api):
Connection<Conversation> conversations = facebookClient.fetchConnection("me/conversations", Conversation.class);
for (Conversation conv : conversations.getData()) {
// To get list of messages for given conversation
LinkedList<Message> allConvMessagesStorage = new LinkedList<Message>();
Connection<Message> messages25 = facebookClient.fetchConnection(conv.getId()+"/messages", Message.class);
//Add messages returned
allConvMessagesStorage.addAll(messages25.getData());
//Check if there is next page to fetch
boolean progress = messages25.hasNext();
while(progress){
messages25 = facebookClient.fetchConnectionPage(messages25.getNextPageUrl(), Message.class);
//Append next page of messages
allConvMessagesStorage.addAll(messages25.getData());
progress = messages25.hasNext();
}
}

Retrieve multiple messages from SQS

I have multiple messages in SQS. The following code always returns only one, even if there are dozens visible (not in flight). setMaxNumberOfMessages I thought would allow multiple to be consumed at once .. have i misunderstood this?
CreateQueueRequest createQueueRequest = new CreateQueueRequest().withQueueName(queueName);
String queueUrl = sqs.createQueue(createQueueRequest).getQueueUrl();
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest(queueUrl);
receiveMessageRequest.setMaxNumberOfMessages(10);
List<Message> messages = sqs.receiveMessage(receiveMessageRequest).getMessages();
for (Message message : messages) {
// i'm a message from SQS
}
I've also tried using withMaxNumberOfMessages without any such luck:
receiveMessageRequest.withMaxNumberOfMessages(10);
How do I know there are messages in the queue? More than 1?
Set<String> attrs = new HashSet<String>();
attrs.add("ApproximateNumberOfMessages");
CreateQueueRequest createQueueRequest = new CreateQueueRequest().withQueueName(queueName);
GetQueueAttributesRequest a = new GetQueueAttributesRequest().withQueueUrl(sqs.createQueue(createQueueRequest).getQueueUrl()).withAttributeNames(attrs);
Map<String,String> result = sqs.getQueueAttributes(a).getAttributes();
int num = Integer.parseInt(result.get("ApproximateNumberOfMessages"));
The above always is run prior and gives me an int that is >1
Thanks for your input
AWS API Reference Guide: Query/QueryReceiveMessage
Due to the distributed nature of the queue, a weighted random set of machines is sampled on a ReceiveMessage call. That means only the messages on the sampled machines are returned. If the number of messages in the queue is small (less than 1000), it is likely you will get fewer messages than you requested per ReceiveMessage call. If the number of messages in the queue is extremely small, you might not receive any messages in a particular ReceiveMessage response; in which case you should repeat the request.
and
MaxNumberOfMessages: Maximum number of messages to return. SQS never returns more messages than this value but might return fewer.
There is a comprehensive explanation for this (arguably rather idiosyncratic) behaviour in the SQS reference documentation.
SQS stores copies of messages on multiple servers and receive message requests are made to these servers with one of two possible strategies,
Short Polling : The default behaviour, only a subset of the servers (based on a weighted random distribution) are queried.
Long Polling : Enabled by setting the WaitTimeSeconds attribute to a non-zero value, all of the servers are queried.
In practice, for my limited tests, I always seem to get one message with short polling just as you did.
I had the same problem. What is your Receive Message Wait Time for your queue set to? When mine was at 0, it only returned 1 message even if there were 8 in the queue. When I increased the Receive Message Wait Time, then I got all of them. Seems kind of buggy to me.
I was just trying the same and with the help of these two attributes setMaxNumberOfMessages and setWaitTimeSeconds i was able to get 10 messages.
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest(myQueueUrl);
receiveMessageRequest.setMaxNumberOfMessages(10);
receiveMessageRequest.setWaitTimeSeconds(20);
Snapshot of o/p:
Receiving messages from TestQueue.
Number of messages:10
Message
MessageId: 31a7c669-1f0c-4bf1-b18b-c7fa31f4e82d
...
receiveMessageRequest.withMaxNumberOfMessages(10);
Just to be clear, the more practical use of this would be to add to your constructor like this:
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest(queueUrl).withMaxNumberOfMessages(10);
Otherwise, you might as well just do:
receiveMessageRequest.setMaxNumberOfMessages(10);
That being said, changing this won't help the original problem.
Thanks Caoilte!
I faced this issue also. Finally solved by using long polling follow the configuration here:
https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-long-polling-for-queue.html
Unfortunately, to use long polling, you must create your queue as FIFO one. I tried standard queue with no luck.
And when receiving, need also set MaxNumberOfMessages. So my code is like:
ReceiveMessageRequest receive_request = new ReceiveMessageRequest()
.withQueueUrl(QUEUE_URL)
.withWaitTimeSeconds(20)
.withMaxNumberOfMessages(10);
Although solved, still feel too wired. AWS should definitely provide a more neat API for this kind of basic receiving operation.
From my point, AWS has many many cool features but not good APIs. Like those guys are rushing out all the time.
For small task list I use FIFO queue like stackoverflow.com/a/55149351/13678017
for example modified AWS tutorial
// Create a queue.
System.out.println("Creating a new Amazon SQS FIFO queue called " + "MyFifoQueue.fifo.\n");
final Map<String, String> attributes = new HashMap<>();
// A FIFO queue must have the FifoQueue attribute set to true.
attributes.put("FifoQueue", "true");
/*
* If the user doesn't provide a MessageDeduplicationId, generate a
* MessageDeduplicationId based on the content.
*/
attributes.put("ContentBasedDeduplication", "true");
// The FIFO queue name must end with the .fifo suffix.
final CreateQueueRequest createQueueRequest = new CreateQueueRequest("MyFifoQueue4.fifo")
.withAttributes(attributes);
final String myQueueUrl = sqs.createQueue(createQueueRequest).getQueueUrl();
// List all queues.
System.out.println("Listing all queues in your account.\n");
for (final String queueUrl : sqs.listQueues().getQueueUrls()) {
System.out.println(" QueueUrl: " + queueUrl);
}
System.out.println();
// Send a message.
System.out.println("Sending a message to MyQueue.\n");
for (int i = 0; i < 4; i++) {
var request = new SendMessageRequest()
.withQueueUrl(myQueueUrl)
.withMessageBody("message " + i)
.withMessageGroupId("userId1");
;
sqs.sendMessage(request);
}
for (int i = 0; i < 6; i++) {
var request = new SendMessageRequest()
.withQueueUrl(myQueueUrl)
.withMessageBody("message " + i)
.withMessageGroupId("userId2");
;
sqs.sendMessage(request);
}
// Receive messages.
System.out.println("Receiving messages from MyQueue.\n");
var receiveMessageRequest = new ReceiveMessageRequest(myQueueUrl);
receiveMessageRequest.setMaxNumberOfMessages(10);
receiveMessageRequest.setWaitTimeSeconds(20);
// what receive?
receiveMessageRequest.withMessageAttributeNames("userId2");
final List<Message> messages = sqs.receiveMessage(receiveMessageRequest).getMessages();
for (final Message message : messages) {
System.out.println("Message");
System.out.println(" MessageId: "
+ message.getMessageId());
System.out.println(" ReceiptHandle: "
+ message.getReceiptHandle());
System.out.println(" MD5OfBody: "
+ message.getMD5OfBody());
System.out.println(" Body: "
+ message.getBody());
for (final Entry<String, String> entry : message.getAttributes()
.entrySet()) {
System.out.println("Attribute");
System.out.println(" Name: " + entry
.getKey());
System.out.println(" Value: " + entry
.getValue());
}
}
Here's a workaround, you can call receiveMessageFromSQS method asynchronously.
bulkReceiveFromSQS (queueUrl, totalMessages, asyncLimit, batchSize, visibilityTimeout, waitTime, callback) {
batchSize = Math.min(batchSize, 10);
let self = this,
noOfIterations = Math.ceil(totalMessages / batchSize);
async.timesLimit(noOfIterations, asyncLimit, function(n, next) {
self.receiveMessageFromSQS(queueUrl, batchSize, visibilityTimeout, waitTime,
function(err, result) {
if (err) {
return next(err);
}
return next(null, _.get(result, 'Messages'));
});
}, function (err, listOfMessages) {
if (err) {
return callback(err);
}
listOfMessages = _.flatten(listOfMessages).filter(Boolean);
return callback(null, listOfMessages);
});
}
It will return you an array with a given number of messages

Categories

Resources