diff options
4 files changed, 52 insertions, 14 deletions
diff --git a/documentapi/abi-spec.json b/documentapi/abi-spec.json index d00c89ae737..02d764deab8 100644 --- a/documentapi/abi-spec.json +++ b/documentapi/abi-spec.json @@ -3015,6 +3015,7 @@ ], "methods" : [ "public abstract boolean encode(com.yahoo.messagebus.Routable, com.yahoo.document.serialization.DocumentSerializer)", + "public byte[] encode(int, com.yahoo.messagebus.Routable)", "public abstract com.yahoo.messagebus.Routable decode(com.yahoo.document.serialization.DocumentDeserializer)" ], "fields" : [ ] diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories80.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories80.java index 83392679f11..4712f6d2442 100644 --- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories80.java +++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories80.java @@ -57,17 +57,31 @@ abstract class RoutableFactories80 { } @Override - public boolean encode(Routable obj, DocumentSerializer out) { + public byte[] encode(int msgType, Routable obj) { try { var protoMsg = encoderFn.apply(apiClass.cast(obj)); - // TODO avoid this buffer indirection by directly exposing an OutputStream to write into...! - // ... or at the very least have a way to preallocate buffer output of protoMsg.getSerializedSize() bytes! - out.getBuf().put(protoMsg.toByteArray()); - } catch (RuntimeException e) { + int protoSize = protoMsg.getSerializedSize(); + // The message payload contains a 4-byte header int which specifies the type of the message + // that follows. We want to write this header and the subsequence message bytes using a single + // allocation and without unneeded copying, so we create one array for both purposes and encode + // directly into it. Aside from the header, this is pretty much a mirror image of what the + // toByteArray() method on Protobuf message objects already does. + var buf = new byte[4 + protoSize]; + ByteBuffer.wrap(buf).putInt(msgType); // In network order (default setting) + var protoStream = CodedOutputStream.newInstance(buf, 4, protoSize); + protoMsg.writeTo(protoStream); // Writing straight to array, no need to flush + protoStream.checkNoSpaceLeft(); + return buf; + } catch (IOException | RuntimeException e) { logCodecError("encoding", e); - return false; + return null; } - return true; + } + + @Override + public boolean encode(Routable obj, DocumentSerializer out) { + // Legacy encode; not supported + return false; } @Override diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactory.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactory.java index e98c9ab3a40..c635aa1581e 100755 --- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactory.java +++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactory.java @@ -3,6 +3,8 @@ package com.yahoo.documentapi.messagebus.protocol; import com.yahoo.document.serialization.DocumentDeserializer; import com.yahoo.document.serialization.DocumentSerializer; +import com.yahoo.document.serialization.DocumentSerializerFactory; +import com.yahoo.io.GrowableByteBuffer; import com.yahoo.messagebus.Routable; /** @@ -31,6 +33,32 @@ public interface RoutableFactory { boolean encode(Routable obj, DocumentSerializer out); /** + * <p>Encode a message type and object payload to a byte array. This is an alternative, + * optional method to {@link #encode(Routable, DocumentSerializer)}, but which defers all + * buffer management to the callee. This allows protocol implementations to make more + * efficient use of memory, as they do not have to deal with DocumentSerializer indirections.</p> + * + * <p>Implementations <strong>must</strong> ensure that the first 4 bytes of the returned + * byte array contain a 32-bit integer (in network order) equal to the provided msgType value.</p> + * + * @param msgType A positive integer indicating the concrete message type of obj. + * @param obj The message to encode. + * @return A byte buffer encapsulating the message type and the serialized representation + * of obj, or null if encoding failed. + */ + default byte[] encode(int msgType, Routable obj) { + var out = DocumentSerializerFactory.createHead(new GrowableByteBuffer(8192)); + out.putInt(null, msgType); + if (!encode(obj, out)) { + return null; + } + byte[] ret = new byte[out.getBuf().position()]; + out.getBuf().rewind(); + out.getBuf().get(ret); + return ret; + } + + /** * <p>This method decodes the given byte buffer to a routable.</p> <p>Return false to signal failure.</p> <p>This * method is NOT exception safe.</p> * diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableRepository.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableRepository.java index 47117471615..56d23d36811 100755 --- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableRepository.java +++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableRepository.java @@ -93,17 +93,12 @@ final class RoutableRepository { log.log(Level.SEVERE,"Can not encode routable type " + type + " (version " + version + "). Only major version 5 and up supported."); return new byte[0]; } - DocumentSerializer out= DocumentSerializerFactory.createHead(new GrowableByteBuffer(8192)); - - out.putInt(null, type); - if (!factory.encode(obj, out)) { + byte[] ret = factory.encode(type, obj); + if (ret == null) { log.log(Level.SEVERE, "Routable factory " + factory.getClass().getName() + " failed to serialize " + "routable of type " + type + " (version " + version + ")."); return new byte[0]; } - byte[] ret = new byte[out.getBuf().position()]; - out.getBuf().rewind(); - out.getBuf().get(ret); return ret; } |