/******************************************************************************* * Copyright 2015 Klaus Pfeiffer <klaus@allpiper.com> * * 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. ******************************************************************************/ package com.jfastnet; import com.jfastnet.events.DisabledStackedMessagesEvent; import com.jfastnet.messages.*; import com.jfastnet.processors.IMessageReceiverPostProcessor; import com.jfastnet.processors.IMessageReceiverPreProcessor; import com.jfastnet.processors.IMessageSenderPostProcessor; import com.jfastnet.processors.IMessageSenderPreProcessor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; /** @author Klaus Pfeiffer - klaus@allpiper.com */ @Slf4j public class PeerController implements IPeerController { /** Last timestamp. Needed for evaluation of passed time. */ private long lastTimestamp; /** Increases until next queued message is sent. */ private long queueDelayIncrease; /** List of queued messages. A FIFO queue. */ private final List<Message> queuedMessages = new ArrayList<>(); /** Current state information. */ @Getter protected State state; @Getter protected Config config; public PeerController(Config config) { assert config != null : "Config may not be null!"; this.config = config; this.state = new State(config); if (config.internalReceiver == null) { config.internalReceiver = this; } if (config.internalSender == null) { config.internalSender = this; } } @Override public boolean start() { return this.state.getUdpPeer().start(); } @Override public void stop() { log.info("Stopping UDP peer controller."); send(new LeaveRequest()); this.state.getUdpPeer().stop(); } @Override public void process() { retrieveTimeDelta(); if (queueDelayIncrease > config.queuedMessagesDelay && !queuedMessages.isEmpty()) { final Message firstMessage = queuedMessages.remove(0); send(firstMessage); queueDelayIncrease = 0; } state.getProcessables().forEach(ISimpleProcessable::process); state.getUdpPeer().process(); } private void retrieveTimeDelta() { final long timestamp = config.timeProvider.get(); queueDelayIncrease += (timestamp - lastTimestamp); lastTimestamp = timestamp; } @Override public boolean queue(Message message) { return queuedMessages.add(message); } @Override public boolean send(Message message) { if (!resolveMessage(message)) { return false; } if (!createPayload(message)) { return false; } if (!beforeSend(message)) { return false; } if (!checkPayloadSize(message)) { return false; } state.getUdpPeer().send(message); log.trace("Sent message: {}", message); if (!afterSend(message)) { return false; } return true; } private boolean hasSmallEnoughPayloadSizeToSend(Message message) { if (message.isResendMessage()) { // Don't try to split up resent messages, because if they were too // big, they already got split up. Also new ids would be generated // and the reliable sequence ids from the clients could diverge, if // for some reason the resent message is a bit bigger and would // have to be split up. return true; } if (message.payload instanceof byte[]) { byte[] payload = (byte[]) message.payload; if (payload.length > config.maximumUdpPacketSize && !(message instanceof MessagePart)) { return false; } } else { log.error("Payload is no byte array."); } return true; } boolean checkPayloadSize(Message message) { if (!hasSmallEnoughPayloadSizeToSend(message)) { getState().idProvider.stepBack(message); if (config.autoSplitTooBigMessages && !Message.ReliableMode.UNRELIABLE.equals(message.getReliableMode())) { autoSplitMessage(message); } else { if (message instanceof StackedMessage) { StackedMessage stackedMessage = (StackedMessage) message; log.info(" -> Message is a stacked message: DISABLING stacked messages now"); disableStackedMessages(stackedMessage); downsizeStackedMessage(stackedMessage); if (hasSmallEnoughPayloadSizeToSend(stackedMessage)) { return true; } else { // Still not small enough Message lastMessage = stackedMessage.getLastMessage(); if (lastMessage != null) { this.send(lastMessage); } } } // Write error message // OS could prevent too big messages from being sent. log.error("Message {} exceeds configured maximumUdpPacketSize of {}. Payload size is {}.", new Object[]{message, config.maximumUdpPacketSize, message.payloadLength()}); } return false; } return true; } private void autoSplitMessage(Message message) { log.info("Auto splitting message: {}", message); final List<MessagePart> parts = MessagePart.createFromMessage(state, message.getMsgId(), message, config.maximumUdpPacketSize - MessagePart.MESSAGE_HEADER_SIZE, message.getReliableMode()); if (parts.size() > 0) { parts.forEach(this::queue); } else { log.error("Message {} exceeds configured maximumUdpPacketSize of {}. Payload size is {}.", new Object[]{message, config.maximumUdpPacketSize, message.payloadLength()}); log.error(" -> Parts couldn't be created for message {}", message); } } private void disableStackedMessages(StackedMessage stackedMessage) { state.getEventLog().add(new DisabledStackedMessagesEvent(stackedMessage)); state.setEnableStackedMessages(false); } private void downsizeStackedMessage(StackedMessage stackedMessage) { List<Message> messages = stackedMessage.getMessages(); if (messages.size() > 0) { // Reduce messages while (!hasSmallEnoughPayloadSizeToSend(stackedMessage) && messages.size() > 1) { // Choose removal strategy based on stackKeepAliveMessages config // because when stackKeepAliveMessages is true, we can safely remove // the newest messages without the risk of getting stuck. if (config.stackKeepAliveMessages) { // Remove newest message messages.remove(messages.size() - 1); } else { // Remove oldest message messages.remove(0); } // Recreate payload createPayload(stackedMessage); } } } protected boolean afterSend(Message message) { for (IMessageSenderPostProcessor processor : state.getMessageSenderPostProcessors()) { if (processor.afterSend(message) == null) { log.trace("Processor {} discarded message {} at afterSend", processor, message); return false; } } return true; } /** Run pre-processors and congestion control. * @param message message about to send * @return true if we are ready to send the message, false otherwise */ public boolean beforeSend(Message message) { for (IMessageSenderPreProcessor processor : state.getMessageSenderPreProcessors()) { if (processor.beforeSend(message) == null) { log.trace("Processor {} discarded message {} at beforeSend", processor, message); return false; } } return true; } /** Set id in message and prepare to send. */ public boolean resolveMessage(Message message) { message.resolve(config, state); message.prepareToSend(); if (message.getMsgId() == 0L) { message.resolveId(); if (log.isTraceEnabled()) { log.trace("Message id {} resolved for: {}", message.getMsgId(), message.getClass().getSimpleName()); } } return true; } /** Create payload for message. */ public boolean createPayload(Message message) { if (!state.getUdpPeer().createPayload(message)) { log.error("Creation of payload for {} failed.", message); return false; } return true; } @Override public void receive(Message message) { message.getFeatures().resolve(); for (IMessageReceiverPreProcessor processor : state.getMessageReceiverPreProcessors()) { if (processor.beforeReceive(message) == null) { log.trace("Processor {} discarded message {} at beforeReceive", processor, message); return; } } log.trace("Received message: {}", message); if (message instanceof IInstantProcessable) { message.process(config.context); } else { config.externalReceiver.receive(message); } for (IMessageReceiverPostProcessor processor : state.getMessageReceiverPostProcessors()) { if (processor.afterReceive(message) == null) { log.trace("Processor {} discarded message {} at afterReceive", processor, message); return; } } } }