// Copyright 2016 Twitter. All rights reserved.
//
// 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.twitter.heron.common.network;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.protobuf.Message;
/**
* Defines OutgoingPacket
* <p>
* TODO -- Sanjeev will add a detailed description of this application level protocol later
* <p>
* When allocating the ByteBuffer, we have two options:
* 1. Normal java heap buffer by invoking ByteBuffer.allocate(...),
* 2. Native heap buffer by invoking ByteBuffer.allocateDirect(...),
* Though it would require extra memory copies in java heap buffer, after experiments trying to use
* both of them, we choose to use normal java heap buffer, since:
* 1. It is unsafe to use direct buffer:
* -- Direct buffer would not trigger gc;
* -- We could not control when to release the resources of direct buffer explicitly;
* -- It is hard to guarantee direct buffer would not break limitation of native heap,
* i.e. not throw OutOfMemoryError.
* <p>
* 2. Experiments are done by using direct buffer and the resources saving is negligible:
* -- Direct buffer would save, in our scenarios, less than 1% of RAM;
* -- Direct buffer could save 30%~50% cpu of Gateway thread.
* However, the cpu used by Gateway thread is negligible,
* less than 2% out of the whole usage in worst case.
* -- The extra copy is within JVM boundary; it is pretty fast.
*/
public class OutgoingPacket {
private static final Logger LOG = Logger.getLogger(OutgoingPacket.class.getName());
private ByteBuffer buffer;
public OutgoingPacket(REQID reqid, Message message) {
assert message.isInitialized();
// First calculate the total size of the packet
// including the header
int headerSize = 4;
String typename = message.getDescriptorForType().getFullName();
int dataSize = sizeRequiredToPackString(typename)
+ REQID.REQID_SIZE
+ sizeRequiredToPackMessage(message);
buffer = ByteBuffer.allocate(headerSize + dataSize);
// First write out how much data is there as the header
buffer.putInt(dataSize);
// Next write the type string
buffer.putInt(typename.length());
buffer.put(typename.getBytes());
// now the reqid
reqid.pack(buffer);
// finally the proto
// Double copy but it is designed, see the comments on top
buffer.putInt(message.getSerializedSize());
buffer.put(message.toByteArray());
// Make the buffer ready for writing out
buffer.flip();
}
public static int sizeRequiredToPackString(String str) {
return 4 + str.length();
}
public static int sizeRequiredToPackMessage(Message msg) {
return 4 + msg.getSerializedSize();
}
public int writeToChannel(SocketChannel channel) {
int remaining = buffer.remaining();
assert remaining > 0;
int wrote = 0;
try {
wrote = channel.write(buffer);
} catch (IOException e) {
LOG.log(Level.SEVERE, "Error writing to channel ", e);
return -1;
}
return remaining - wrote;
}
public int size() {
return buffer.capacity();
}
}