Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.joynr.messaging.routing.AbstractMessageRouter Maven / Gradle / Ivy
/*
* #%L
* %%
* Copyright (C) 2011 - 2017 BMW Car IT GmbH
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package io.joynr.messaging.routing;
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.Reference;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.inject.Singleton;
import org.apache.commons.lang.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.joynr.exceptions.JoynrDelayMessageException;
import io.joynr.exceptions.JoynrIllegalStateException;
import io.joynr.exceptions.JoynrMessageNotSentException;
import io.joynr.exceptions.JoynrRuntimeException;
import io.joynr.exceptions.JoynrShutdownException;
import io.joynr.messaging.ConfigurableMessagingSettings;
import io.joynr.messaging.FailureAction;
import io.joynr.messaging.IMessagingMulticastSubscriber;
import io.joynr.messaging.IMessagingSkeleton;
import io.joynr.messaging.IMessagingStub;
import io.joynr.messaging.MessagingSkeletonFactory;
import io.joynr.messaging.SuccessAction;
import io.joynr.runtime.ShutdownListener;
import io.joynr.runtime.ShutdownNotifier;
import io.joynr.statusmetrics.MessageWorkerStatus;
import io.joynr.statusmetrics.StatusReceiver;
import joynr.ImmutableMessage;
import joynr.Message;
import joynr.system.RoutingTypes.Address;
import joynr.system.RoutingTypes.RoutingTypesUtil;
import joynr.system.RoutingTypes.WebSocketClientAddress;
abstract public class AbstractMessageRouter implements MessageRouter, ShutdownListener {
private static final Logger logger = LoggerFactory.getLogger(AbstractMessageRouter.class);
private static final FastDateFormat dateFormatter = FastDateFormat.getInstance("dd/MM/yyyy HH:mm:ss:sss z",
TimeZone.getTimeZone("UTC"));
protected final RoutingTable routingTable;
private ScheduledExecutorService scheduler;
private long sendMsgRetryIntervalMs;
private long routingTableCleanupIntervalMs;
@Inject(optional = true)
@Named(ConfigurableMessagingSettings.PROPERTY_ROUTING_MAX_RETRY_COUNT)
private long maxRetryCount = ConfigurableMessagingSettings.DEFAULT_ROUTING_MAX_RETRY_COUNT;
@Inject(optional = true)
@Named(ConfigurableMessagingSettings.PROPERTY_MAX_DELAY_WITH_EXPONENTIAL_BACKOFF_MS)
private long maxDelayMs = ConfigurableMessagingSettings.DEFAULT_MAX_DELAY_WITH_EXPONENTIAL_BACKOFF;
private MessagingStubFactory messagingStubFactory;
private final MessagingSkeletonFactory messagingSkeletonFactory;
private AddressManager addressManager;
protected final MulticastReceiverRegistry multicastReceiverRegistry;
private final MessageQueue messageQueue;
private final StatusReceiver statusReceiver;
private List messageProcessedListeners;
private List messageWorkers;
// Map weak reference to proxy object -> proxyParticipantId
private final ConcurrentHashMap, String> proxyMap;
private final ReferenceQueue garbageCollectedProxiesQueue;
@Inject
@Singleton
// CHECKSTYLE:OFF
public AbstractMessageRouter(RoutingTable routingTable,
@Named(SCHEDULEDTHREADPOOL) ScheduledExecutorService scheduler,
@Named(ConfigurableMessagingSettings.PROPERTY_SEND_MSG_RETRY_INTERVAL_MS) long sendMsgRetryIntervalMs,
@Named(ConfigurableMessagingSettings.PROPERTY_MESSAGING_MAXIMUM_PARALLEL_SENDS) int maxParallelSends,
@Named(ConfigurableMessagingSettings.PROPERTY_ROUTING_TABLE_CLEANUP_INTERVAL_MS) long routingTableCleanupIntervalMs,
MessagingStubFactory messagingStubFactory,
MessagingSkeletonFactory messagingSkeletonFactory,
AddressManager addressManager,
MulticastReceiverRegistry multicastReceiverRegistry,
MessageQueue messageQueue,
ShutdownNotifier shutdownNotifier,
StatusReceiver statusReceiver) {
// CHECKSTYLE:ON
this.routingTable = routingTable;
this.scheduler = scheduler;
this.sendMsgRetryIntervalMs = sendMsgRetryIntervalMs;
this.routingTableCleanupIntervalMs = routingTableCleanupIntervalMs;
this.messagingStubFactory = messagingStubFactory;
this.messagingSkeletonFactory = messagingSkeletonFactory;
this.addressManager = addressManager;
this.multicastReceiverRegistry = multicastReceiverRegistry;
this.messageQueue = messageQueue;
this.statusReceiver = statusReceiver;
this.proxyMap = new ConcurrentHashMap, String>();
this.garbageCollectedProxiesQueue = new ReferenceQueue();
shutdownNotifier.registerForShutdown(this);
messageProcessedListeners = new ArrayList();
startMessageWorkerThreads(maxParallelSends);
startRoutingTableCleanupThread();
}
private void startMessageWorkerThreads(int numberOfWorkThreads) {
messageWorkers = new ArrayList(numberOfWorkThreads);
for (int i = 0; i < numberOfWorkThreads; i++) {
MessageWorker messageWorker = new MessageWorker(i);
scheduler.schedule(messageWorker, 0, TimeUnit.MILLISECONDS);
messageWorkers.add(messageWorker);
}
}
private void startRoutingTableCleanupThread() {
scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
routingTable.purge();
// remove Routing table entries for proxies which have been garbage collected
Reference extends Object> r;
synchronized (garbageCollectedProxiesQueue) {
r = garbageCollectedProxiesQueue.poll();
}
while (r != null) {
String proxyParticipantId = proxyMap.get(r);
logger.debug("removing garbage collected proxy participantId {}", proxyParticipantId);
removeNextHop(proxyParticipantId);
proxyMap.remove(r);
synchronized (garbageCollectedProxiesQueue) {
r = garbageCollectedProxiesQueue.poll();
}
}
}
}, routingTableCleanupIntervalMs, routingTableCleanupIntervalMs, TimeUnit.MILLISECONDS);
}
@Override
public void registerMessageProcessedListener(MessageProcessedListener messageProcessedListener) {
synchronized (messageProcessedListeners) {
messageProcessedListeners.add(messageProcessedListener);
}
}
@Override
public void unregisterMessageProcessedListener(MessageProcessedListener messageProcessedListener) {
synchronized (messageProcessedListeners) {
messageProcessedListeners.remove(messageProcessedListener);
}
}
@Override
public void removeNextHop(String participantId) {
routingTable.remove(participantId);
}
@Override
public boolean resolveNextHop(String participantId) {
return routingTable.containsKey(participantId);
}
@Override
public void addMulticastReceiver(final String multicastId,
String subscriberParticipantId,
String providerParticipantId) {
logger.trace("Adding multicast receiver {} for multicast {} on provider {}",
subscriberParticipantId,
multicastId,
providerParticipantId);
multicastReceiverRegistry.registerMulticastReceiver(multicastId, subscriberParticipantId);
performSubscriptionOperation(multicastId, providerParticipantId, new SubscriptionOperation() {
@Override
public void perform(IMessagingMulticastSubscriber messagingMulticastSubscriber) {
messagingMulticastSubscriber.registerMulticastSubscription(multicastId);
}
});
}
@Override
public void removeMulticastReceiver(final String multicastId,
String subscriberParticipantId,
String providerParticipantId) {
multicastReceiverRegistry.unregisterMulticastReceiver(multicastId, subscriberParticipantId);
performSubscriptionOperation(multicastId, providerParticipantId, new SubscriptionOperation() {
@Override
public void perform(IMessagingMulticastSubscriber messagingMulticastSubscriber) {
messagingMulticastSubscriber.unregisterMulticastSubscription(multicastId);
}
});
}
private static interface SubscriptionOperation {
void perform(IMessagingMulticastSubscriber messagingMulticastSubscriber);
}
private void performSubscriptionOperation(String multicastId,
String providerParticipantId,
SubscriptionOperation operation) {
Address providerAddress = routingTable.get(providerParticipantId);
IMessagingSkeleton messagingSkeleton = messagingSkeletonFactory.getSkeleton(providerAddress);
if (messagingSkeleton != null && messagingSkeleton instanceof IMessagingMulticastSubscriber) {
operation.perform((IMessagingMulticastSubscriber) messagingSkeleton);
} else {
logger.trace("No messaging skeleton found for address {}, not performing multicast subscription.",
providerAddress);
}
}
@Override
public void addNextHop(String participantId, Address address, boolean isGloballyVisible) {
final long expiryDateMs = Long.MAX_VALUE;
final boolean isSticky = false;
final boolean allowUpdate = false;
addToRoutingTable(participantId, address, isGloballyVisible, expiryDateMs, isSticky, allowUpdate);
}
public void addToRoutingTable(String participantId,
Address address,
boolean isGloballyVisible,
long expiryDateMs,
boolean isSticky,
boolean allowUpdate) {
routingTable.put(participantId, address, isGloballyVisible, expiryDateMs, isSticky, allowUpdate);
}
@Override
public void route(final ImmutableMessage message) {
checkExpiry(message);
registerGlobalRoutingEntryIfRequired(message);
routeInternal(message, 0, 0);
}
protected Set getAddresses(ImmutableMessage message) {
return addressManager.getAddresses(message);
}
private void checkFoundAddresses(Set foundAddresses, ImmutableMessage message) {
if (foundAddresses.isEmpty()) {
if (Message.VALUE_MESSAGE_TYPE_MULTICAST.equals(message.getType())) {
// discard msg
throw new JoynrMessageNotSentException("Failed to route multicast publication: No address found for given message: "
+ message);
} else if (Message.VALUE_MESSAGE_TYPE_PUBLICATION.equals(message.getType())) {
// discard msg
throw new JoynrMessageNotSentException("Failed to route publication: No address found for given message: "
+ message);
} else if (message.isReply()) {
// discard msg
throw new JoynrMessageNotSentException("Failed to route reply: No address found for given message: "
+ message);
} else {
// any kind of request; retry routing
throw new JoynrIllegalStateException("Unable to find address for recipient with participant ID "
+ message.getRecipient());
}
}
}
private void registerGlobalRoutingEntryIfRequired(final ImmutableMessage message) {
if (!message.isReceivedFromGlobal()) {
return;
}
String messageType = message.getType();
if (!messageType.equals(Message.VALUE_MESSAGE_TYPE_REQUEST)
&& !messageType.equals(Message.VALUE_MESSAGE_TYPE_SUBSCRIPTION_REQUEST)
&& !messageType.equals(Message.VALUE_MESSAGE_TYPE_BROADCAST_SUBSCRIPTION_REQUEST)
&& !messageType.equals(Message.VALUE_MESSAGE_TYPE_MULTICAST_SUBSCRIPTION_REQUEST)) {
return;
}
String replyTo = message.getReplyTo();
if (replyTo != null && !replyTo.isEmpty()) {
Address address = RoutingTypesUtil.fromAddressString(replyTo);
// If the message was received from global, the sender is globally visible by definition.
final boolean isGloballyVisible = true;
final long expiryDateMs = message.getTtlMs();
final boolean isSticky = false;
final boolean allowUpdate = false;
routingTable.put(message.getSender(), address, isGloballyVisible, expiryDateMs, isSticky, allowUpdate);
}
}
private void routeInternal(final ImmutableMessage message, long delayMs, final int retriesCount) {
logger.trace("Scheduling message {} with delay {} and retries {}",
new Object[]{ message, delayMs, retriesCount });
Set addresses = getAddresses(message);
try {
checkFoundAddresses(addresses, message);
} catch (JoynrMessageNotSentException error) {
logger.error(" ERROR SENDING: aborting send of messageId: {}. Error: {}",
new Object[]{ message.getId(), error.getMessage() });
callMessageProcessedListeners(message.getId());
return;
} catch (Exception error) {
logger.warn("PROBLEM SENDING, will retry. messageId: {}. Error: {} Message: {}",
new Object[]{ message.getId(), error.getClass().getName(), error.getMessage() });
delayMs = createDelayWithExponentialBackoff(sendMsgRetryIntervalMs, retriesCount);
logger.error("Rescheduling messageId: {} with delay {} ms, TTL is: {}",
message.getId(),
delayMs,
dateFormatter.format(message.getTtlMs()));
}
DelayableImmutableMessage delayableMessage = new DelayableImmutableMessage(message,
delayMs,
addresses,
retriesCount);
scheduleMessage(delayableMessage);
}
private void scheduleMessage(final DelayableImmutableMessage delayableMessage) {
final String messageId = delayableMessage.getMessage().getId();
if (maxRetryCount > -1) {
final int retriesCount = delayableMessage.getRetriesCount();
if (retriesCount > maxRetryCount) {
logger.error("Max-retry-count (" + maxRetryCount + ") reached. Dropping message " + messageId);
callMessageProcessedListeners(messageId);
return;
}
if (retriesCount > 0) {
logger.debug("Retry {}/{} sending message {}", retriesCount, maxRetryCount, messageId);
}
}
messageQueue.put(delayableMessage);
}
private void checkExpiry(final ImmutableMessage message) {
if (!message.isTtlAbsolute()) {
callMessageProcessedListeners(message.getId());
throw new JoynrRuntimeException("Relative ttl not supported");
}
long currentTimeMillis = System.currentTimeMillis();
long ttlExpirationDateMs = message.getTtlMs();
if (ttlExpirationDateMs <= currentTimeMillis) {
String errorMessage = MessageFormat.format("ttl must be greater than 0 / ttl timestamp must be in the future: now: {0} ({1}) abs_ttl: {2} ({3}) msg_id: {4}",
currentTimeMillis,
dateFormatter.format(currentTimeMillis),
ttlExpirationDateMs,
dateFormatter.format(ttlExpirationDateMs),
message.getId());
logger.error(errorMessage);
callMessageProcessedListeners(message.getId());
throw new JoynrMessageNotSentException(errorMessage);
}
}
private FailureAction createFailureAction(final DelayableImmutableMessage delayableMessage) {
final FailureAction failureAction = new FailureAction() {
final String messageId = delayableMessage.getMessage().getId();
private boolean failureActionExecutedOnce = false;
@Override
public void execute(Throwable error) {
synchronized (this) {
if (failureActionExecutedOnce) {
logger.trace("Failure action for message with id {} already executed once. Ignoring further call.",
messageId);
return;
}
failureActionExecutedOnce = true;
}
if (error instanceof JoynrShutdownException) {
logger.warn("{}", error.getMessage());
return;
} else if (error instanceof JoynrMessageNotSentException) {
logger.error(" ERROR SENDING: aborting send of messageId: {}. Error: {}",
new Object[]{ messageId, error.getMessage() });
callMessageProcessedListeners(messageId);
return;
}
logger.warn("PROBLEM SENDING, will retry. messageId: {}. Error: {} Message: {}",
new Object[]{ messageId, error.getClass().getName(), error.getMessage() });
long delayMs;
if (error instanceof JoynrDelayMessageException) {
delayMs = ((JoynrDelayMessageException) error).getDelayMs();
} else {
delayMs = createDelayWithExponentialBackoff(sendMsgRetryIntervalMs,
delayableMessage.getRetriesCount());
}
if (Message.VALUE_MESSAGE_TYPE_MULTICAST.equals(delayableMessage.getMessage().getType())
|| delayableMessage.getDestinationAddresses()
.removeIf(address -> address instanceof WebSocketClientAddress)) {
// WebSocketClientAddresses have to be determined again in every retry because they change when a
// client is restarted, e.g. after a crash.
routeInternal(delayableMessage.getMessage(), delayMs, delayableMessage.getRetriesCount() + 1);
return;
}
delayableMessage.setDelay(delayMs);
delayableMessage.setRetriesCount(delayableMessage.getRetriesCount() + 1);
logger.error("Rescheduling messageId: {} with delay {} ms, TTL: {}, retries: {}",
messageId,
delayMs,
dateFormatter.format(delayableMessage.getMessage().getTtlMs()),
delayableMessage.getRetriesCount());
try {
scheduleMessage(delayableMessage);
} catch (Exception e) {
logger.warn("Rescheduling of message failed (messageId {})", messageId);
callMessageProcessedListeners(messageId);
}
return;
}
};
return failureAction;
}
private void callMessageProcessedListeners(final String messageId) {
synchronized (messageProcessedListeners) {
for (MessageProcessedListener messageProcessedListener : messageProcessedListeners) {
messageProcessedListener.messageProcessed(messageId);
}
}
}
private SuccessAction createMessageProcessedAction(final String messageId, final int numberOfCalls) {
final SuccessAction successAction = new SuccessAction() {
private int callCount = numberOfCalls;
@Override
public void execute() {
callCount--;
if (callCount == 0) {
callMessageProcessedListeners(messageId);
}
}
};
return successAction;
}
@Override
public void prepareForShutdown() {
messageQueue.waitForQueueToDrain();
}
@Override
public void shutdown() {
CountDownLatch countDownLatch = new CountDownLatch(messageWorkers.size());
for (MessageWorker worker : messageWorkers) {
worker.stopWorker(countDownLatch);
}
try {
countDownLatch.await(1500, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
logger.error("Interrupted while waiting for message workers to stop.", e);
}
}
@Override
public void registerProxy(Object proxy, String proxyParticipantId) {
synchronized (garbageCollectedProxiesQueue) {
proxyMap.putIfAbsent(new WeakReference(proxy, garbageCollectedProxiesQueue), proxyParticipantId);
}
}
private long createDelayWithExponentialBackoff(long sendMsgRetryIntervalMs, int retries) {
logger.trace("TRIES: " + retries);
long millis = sendMsgRetryIntervalMs + (long) ((2 ^ (retries)) * sendMsgRetryIntervalMs * Math.random());
if (maxDelayMs >= sendMsgRetryIntervalMs && millis > maxDelayMs) {
millis = maxDelayMs;
logger.trace("set MILLIS to " + millis + " since maxDelayMs is " + maxDelayMs);
}
logger.trace("MILLIS: " + millis);
return millis;
}
class MessageWorker implements Runnable {
private Logger logger = LoggerFactory.getLogger(MessageWorker.class);
private int number;
private volatile CountDownLatch countDownLatch;
private volatile boolean stopped;
public MessageWorker(int number) {
this.number = number;
countDownLatch = null;
stopped = false;
}
void stopWorker(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
stopped = true;
}
@Override
public void run() {
Thread.currentThread().setName("joynrMessageWorker-" + number);
while (!stopped) {
ImmutableMessage message;
DelayableImmutableMessage delayableMessage = null;
try {
statusReceiver.updateMessageWorkerStatus(number,
new MessageWorkerStatus(System.currentTimeMillis(), true));
delayableMessage = messageQueue.poll(1000, TimeUnit.MILLISECONDS);
if (delayableMessage == null) {
continue;
}
statusReceiver.updateMessageWorkerStatus(number,
new MessageWorkerStatus(System.currentTimeMillis(),
false));
message = delayableMessage.getMessage();
logger.trace("Starting processing of message {}", message);
checkExpiry(message);
Set addresses = delayableMessage.getDestinationAddresses();
if (addresses.isEmpty()) {
// try again immediately (message has been scheduled with delay for retry)
final int delayMs = 0;
routeInternal(message, delayMs, delayableMessage.getRetriesCount() + 1);
continue;
}
SuccessAction messageProcessedAction = createMessageProcessedAction(message.getId(),
addresses.size());
// If multiple stub calls for a multicast to multiple destination addresses fail, the failure
// action is called for each failing stub call. Hence, the same failureAction has to be used.
// Otherwise, the message is rescheduled multiple times and the message queue is flooded with
// entries for the same message until the transmission of every entry is successful for all
// recipients. Also the recipients are flooded with the same message.
// Open issue:
// If only some stub calls fail, the rescheduled message will be sent to all its recipients again,
// no matter if an earlier transmission attempt was already successful or not.
FailureAction failureAction = createFailureAction(delayableMessage);
for (Address address : addresses) {
logger.trace(">>>>> SEND message {} to address {}", message.getId(), address);
IMessagingStub messagingStub = messagingStubFactory.create(address);
messagingStub.transmit(message, messageProcessedAction, failureAction);
}
} catch (InterruptedException e) {
logger.trace("Message Worker interrupted. Stopping.");
Thread.currentThread().interrupt();
return;
} catch (Exception error) {
logger.error("error in scheduled message router thread: {}", error.getMessage());
FailureAction failureAction = createFailureAction(delayableMessage);
failureAction.execute(error);
}
}
countDownLatch.countDown();
}
}
}