Coverage Summary for Class: MessageQueue (org.ethereum.net)
Class |
Method, %
|
Line, %
|
MessageQueue |
0%
(0/14)
|
0%
(0/47)
|
MessageQueue$1 |
0%
(0/2)
|
0%
(0/3)
|
MessageQueue$2 |
0%
(0/2)
|
0%
(0/6)
|
Total |
0%
(0/18)
|
0%
(0/56)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 RSK Labs Ltd.
4 * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package org.ethereum.net;
21
22 import io.netty.channel.ChannelFutureListener;
23 import io.netty.channel.ChannelHandlerContext;
24 import co.rsk.panic.PanicProcessor;
25 import org.ethereum.net.eth.message.EthMessage;
26 import org.ethereum.net.message.Message;
27 import org.ethereum.net.message.ReasonCode;
28 import org.ethereum.net.p2p.DisconnectMessage;
29 import org.ethereum.net.p2p.PingMessage;
30 import org.ethereum.net.server.Channel;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import java.util.Queue;
35 import java.util.concurrent.*;
36 import java.util.concurrent.atomic.AtomicInteger;
37
38 import static org.ethereum.net.message.StaticMessages.DISCONNECT_MESSAGE;
39
40 /**
41 * This class contains the logic for sending messages in a queue
42 *
43 * Messages open by send and answered by receive of appropriate message
44 * PING by PONG
45 * GET_PEERS by PEERS
46 * GET_TRANSACTIONS by TRANSACTIONS
47 * GET_BLOCK_HASHES by BLOCK_HASHES
48 * GET_BLOCKS by BLOCKS
49 *
50 * The following messages will not be answered:
51 * PONG, PEERS, HELLO, STATUS, TRANSACTIONS, BLOCKS
52 *
53 * @author Roman Mandeleil
54 */
55 public class MessageQueue {
56
57 private static final Logger logger = LoggerFactory.getLogger("net");
58 private static final PanicProcessor panicProcessor = new PanicProcessor();
59
60 private static final ScheduledExecutorService timer = Executors.newScheduledThreadPool(4, new ThreadFactory() {
61 private AtomicInteger cnt = new AtomicInteger(0);
62
63 public Thread newThread(Runnable r) {
64 return new Thread(r, "MessageQueueTimer-" + cnt.getAndIncrement());
65 }
66 });
67
68 private Queue<MessageRoundtrip> requestQueue = new LinkedBlockingQueue<>();
69 private Queue<MessageRoundtrip> respondQueue = new LinkedBlockingQueue<>();
70 private ChannelHandlerContext ctx = null;
71
72 boolean hasPing = false;
73 private ScheduledFuture<?> timerTask;
74 private Channel channel;
75
76 public MessageQueue() {
77 }
78
79 public void activate(ChannelHandlerContext ctx) {
80 this.ctx = ctx;
81 timerTask = timer.scheduleAtFixedRate(new Runnable() {
82 public void run() {
83 try {
84 nudgeQueue();
85 } catch (Throwable t) {
86 logger.error("Unhandled exception", t);
87 panicProcessor.panic("messagequeue", String.format("Unhandled exception %s", t.toString()));
88 }
89 }
90 }, 10, 10, TimeUnit.MILLISECONDS);
91 }
92
93 public void setChannel(Channel channel) {
94 this.channel = channel;
95 }
96
97 public void sendMessage(Message msg) {
98 if (msg instanceof PingMessage) {
99 if (hasPing) {
100 return;
101 }
102 logger.trace("Sending Ping Message to {}", channel);
103 hasPing = true;
104 }
105
106 Queue<MessageRoundtrip> queue = msg.getAnswerMessage() != null ? requestQueue : respondQueue;
107 queue.add(new MessageRoundtrip(msg));
108 }
109
110 public void disconnect() {
111 disconnect(DISCONNECT_MESSAGE);
112 }
113
114 public void disconnect(ReasonCode reason) {
115 disconnect(new DisconnectMessage(reason));
116 }
117
118 private void disconnect(DisconnectMessage msg) {
119 ctx.writeAndFlush(msg);
120 ctx.close();
121 }
122
123 public void receivedMessage(Message msg) throws InterruptedException {
124
125 MessageRoundtrip messageRoundtrip = requestQueue.peek();
126 if (messageRoundtrip != null) {
127 Message waitingMessage = messageRoundtrip.getMsg();
128
129 if (waitingMessage instanceof PingMessage) {
130 hasPing = false;
131 }
132
133 if (waitingMessage.getAnswerMessage() != null
134 && msg.getClass() == waitingMessage.getAnswerMessage()) {
135 messageRoundtrip.answer();
136 if (waitingMessage instanceof EthMessage) {
137 channel.getPeerStats().pong(messageRoundtrip.lastTimestamp);
138 }
139 logger.trace("Message round trip covered: [{}] ",
140 messageRoundtrip.getMsg().getClass());
141 }
142 }
143 }
144
145 private void removeAnsweredMessage(MessageRoundtrip messageRoundtrip) {
146 if (messageRoundtrip != null && messageRoundtrip.isAnswered()) {
147 requestQueue.remove();
148 }
149 }
150
151 private void nudgeQueue() {
152 // remove last answered message on the queue
153 removeAnsweredMessage(requestQueue.peek());
154 // Now send the next message
155 sendToWire(respondQueue.poll());
156 sendToWire(requestQueue.peek());
157 }
158
159 private void sendToWire(MessageRoundtrip messageRoundtrip) {
160
161 if (messageRoundtrip != null && messageRoundtrip.getRetryTimes() == 0) {
162 // TODO: retry logic. See messageRoundtrip.hasToRetry
163
164 Message msg = messageRoundtrip.getMsg();
165
166 ctx.writeAndFlush(msg).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
167
168 if (msg.getAnswerMessage() != null) {
169 messageRoundtrip.incRetryTimes();
170 messageRoundtrip.saveTime();
171 }
172 }
173 }
174
175 public void close() {
176 if (timerTask != null) {
177 timerTask.cancel(false);
178 }
179 }
180 }