/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.node;
import freenet.io.comm.AsyncMessageCallback;
import freenet.io.comm.ByteCounter;
import freenet.io.comm.DMT;
import freenet.io.comm.Message;
import freenet.support.Logger;
/** A queued byte[], maybe including a Message, and a callback, which may be null.
* Note that we always create the byte[] on construction, as almost everywhere
* which uses a MessageItem needs to know its length immediately. */
public class MessageItem {
final Message msg;
final byte[] buf;
final AsyncMessageCallback[] cb;
final long submitted;
/** If true, the buffer may contain several messages, and is formatted
* for sending as a single packet.
*/
final boolean formatted;
final ByteCounter ctrCallback;
private final short priority;
private long cachedID;
private boolean hasCachedID;
final boolean sendLoadRT;
final boolean sendLoadBulk;
private long deadline;
public MessageItem(Message msg2, AsyncMessageCallback[] cb2, ByteCounter ctr, short overridePriority) {
this.msg = msg2;
this.cb = cb2;
formatted = false;
this.ctrCallback = ctr;
this.submitted = System.currentTimeMillis();
if(overridePriority > 0)
priority = overridePriority;
else
priority = msg2.getPriority();
this.sendLoadRT = msg2.needsLoadRT();
this.sendLoadBulk = msg2.needsLoadBulk();
buf = msg.encodeToPacket();
if(buf.length > NewPacketFormat.MAX_MESSAGE_SIZE) {
// This is bad because fairness between UID's happens at the level of message queueing,
// and the window size is frequently very small, so if we have really big messages they
// could cause big problems e.g. starvation of other messages, resulting in timeouts
// (especially if there are retransmits).
Logger.error(this, "WARNING: Message too big: "+buf.length+" for "+msg2, new Exception("error"));
}
}
public MessageItem(Message msg2, AsyncMessageCallback[] cb2, ByteCounter ctr) {
this(msg2, cb2, ctr, (short)-1);
}
public MessageItem(byte[] data, AsyncMessageCallback[] cb2, boolean formatted, ByteCounter ctr, short priority, boolean sendLoadRT, boolean sendLoadBulk) {
this.cb = cb2;
this.msg = null;
this.buf = data;
this.formatted = formatted;
if(formatted && buf == null)
throw new NullPointerException();
this.ctrCallback = ctr;
this.submitted = System.currentTimeMillis();
this.priority = priority;
this.sendLoadRT = sendLoadRT;
this.sendLoadBulk = sendLoadBulk;
}
/**
* Return the data contents of this MessageItem.
*/
public byte[] getData() {
return buf;
}
public int getLength() {
return buf.length;
}
/**
* @param length The actual number of bytes sent to send this message, including our share of the packet overheads,
* *and including alreadyReportedBytes*, which is only used when deciding how many bytes to report to the throttle.
*/
public void onSent(int length) {
//NB: The fact that the bytes are counted before callback notifications is important for load management.
if(ctrCallback != null) {
try {
ctrCallback.sentBytes(length);
} catch (Throwable t) {
Logger.error(this, "Caught "+t+" reporting "+length+" sent bytes on "+this, t);
}
}
}
public short getPriority() {
return priority;
}
@Override
public String toString() {
return super.toString()+":formatted="+formatted+",msg="+msg;
}
public void onDisconnect() {
if(cb != null) {
for(AsyncMessageCallback cbi: cb) {
try {
cbi.disconnected();
} catch (Throwable t) {
Logger.error(this, "Caught "+t+" calling sent() on "+cbi+" for "+this, t);
}
}
}
}
public void onFailed() {
if(cb != null) {
for(AsyncMessageCallback cbi: cb) {
try {
cbi.fatalError();
} catch (Throwable t) {
Logger.error(this, "Caught "+t+" calling sent() on "+cbi+" for "+this, t);
}
}
}
}
public synchronized long getID() {
if(hasCachedID) return cachedID;
cachedID = generateID();
hasCachedID = true;
return cachedID;
}
private long generateID() {
if(msg == null) return -1;
Object o = msg.getObject(DMT.UID);
if(o == null || !(o instanceof Long)) {
return -1;
} else {
return (Long)o;
}
}
/** Called the first time we have sent all of the message. */
public void onSentAll() {
if(cb != null) {
for(AsyncMessageCallback cbi: cb) {
try {
cbi.sent();
} catch (Throwable t) {
Logger.error(this, "Caught "+t+" calling sent() on "+cbi+" for "+this, t);
}
}
}
}
/** Set the deadline for this message. Called when a message is unqueued, when
* we start to send it. Used if the message does not entirely fit in the
* packet, and also if it is retransmitted.
* @param time The time (in the future) to set the deadline to.
*/
public synchronized void setDeadline(long time) {
deadline = time;
}
/** Clear the deadline for this message. */
public synchronized void clearDeadline() {
deadline = 0;
}
/** Get the deadline for this message. 0 means no deadline has been set. */
public synchronized long getDeadline() {
return deadline;
}
}