Skip to content

Commit 8c4962b

Browse files
Reduce allocations in ProtoClient codec
Signed-off-by: Adrian Cole <[email protected]>
1 parent 880b3d8 commit 8c4962b

File tree

1 file changed

+32
-23
lines changed

1 file changed

+32
-23
lines changed

util/src/main/java/io/kubernetes/client/ProtoClient.java

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
package io.kubernetes.client;
1414

15+
import com.google.protobuf.CodedOutputStream;
1516
import com.google.protobuf.Message;
1617
import com.google.protobuf.Message.Builder;
1718
import io.kubernetes.client.openapi.ApiClient;
@@ -22,17 +23,15 @@
2223
import io.kubernetes.client.proto.Meta.Status;
2324
import io.kubernetes.client.proto.Runtime.TypeMeta;
2425
import io.kubernetes.client.proto.Runtime.Unknown;
25-
import io.kubernetes.client.util.Streams;
2626
import java.io.IOException;
27-
import java.io.InputStream;
2827
import java.util.ArrayList;
29-
import java.util.Arrays;
3028
import java.util.HashMap;
3129
import okhttp3.MediaType;
3230
import okhttp3.Request;
3331
import okhttp3.RequestBody;
3432
import okhttp3.Response;
35-
import org.apache.commons.codec.binary.Hex;
33+
import okio.BufferedSource;
34+
import okio.ByteString;
3635

3736
public class ProtoClient {
3837
/**
@@ -62,9 +61,10 @@ public String toString() {
6261
// Magic number for the beginning of proto encoded.
6362
// https://github.com/kubernetes/apimachinery/blob/release-1.13/pkg/runtime/serializer/protobuf/protobuf.go#L44
6463
private static final byte[] MAGIC = new byte[] {0x6b, 0x38, 0x73, 0x00};
64+
private static final ByteString MAGIC_BYTESTRING = ByteString.of(MAGIC);
6565
private static final String MEDIA_TYPE = "application/vnd.kubernetes.protobuf";
6666

67-
/** Simple Protocol Budder API client constructor, uses default configuration */
67+
/** Simple Protocol Buffers API client constructor, uses default configuration */
6868
public ProtoClient() {
6969
this(Configuration.getDefaultApiClient());
7070
}
@@ -280,17 +280,19 @@ public <T extends Message> ObjectOrStatus<T> request(
280280

281281
private <T extends Message> ObjectOrStatus<T> getObjectOrStatusFromServer(
282282
Builder builder, Request request) throws IOException, ApiException {
283-
Response resp = apiClient.getHttpClient().newCall(request).execute();
284-
Unknown u = parse(resp.body().byteStream());
285-
resp.body().close();
283+
Unknown u;
284+
try (Response resp = apiClient.getHttpClient().newCall(request).execute()) {
285+
// Note: closing the response, closes the body and the underlying source.
286+
u = parse(resp.body().source());
287+
}
286288

287289
if (u.getTypeMeta().getApiVersion().equals("v1")
288290
&& u.getTypeMeta().getKind().equals("Status")) {
289291
Status status = Status.newBuilder().mergeFrom(u.getRaw()).build();
290-
return new ObjectOrStatus(null, status);
292+
return new ObjectOrStatus<>(null, status);
291293
}
292294

293-
return new ObjectOrStatus((T) builder.mergeFrom(u.getRaw()).build(), null);
295+
return new ObjectOrStatus<>((T) builder.mergeFrom(u.getRaw()).build(), null);
294296
}
295297

296298
// This isn't really documented anywhere except the code, but
@@ -301,7 +303,7 @@ private <T extends Message> ObjectOrStatus<T> getObjectOrStatusFromServer(
301303
// encoding of the actual object.
302304
// TODO: Document this somewhere proper.
303305

304-
private byte[] encode(Message msg, String apiVersion, String kind) {
306+
private static byte[] encode(Message msg, String apiVersion, String kind) throws IOException {
305307
// It is unfortunate that we have to include apiVersion and kind,
306308
// since we should be able to extract it from the Message, but
307309
// for now at least, those fields are missing from the proto-buffer.
@@ -310,22 +312,29 @@ private byte[] encode(Message msg, String apiVersion, String kind) {
310312
.setTypeMeta(TypeMeta.newBuilder().setApiVersion(apiVersion).setKind(kind))
311313
.setRaw(msg.toByteString())
312314
.build();
313-
return concat(MAGIC, u.toByteArray());
314-
}
315315

316-
private static byte[] concat(byte[] one, byte[] two) {
317-
byte[] result = new byte[one.length + two.length];
318-
System.arraycopy(one, 0, result, 0, one.length);
319-
System.arraycopy(two, 0, result, one.length, two.length);
316+
// Encode directly to an array, to reduce buffering. CodedOutputStream will
317+
// still allocate arrays internally, but this is the best we can do without
318+
// something that quickly looks like square/wire.
319+
int serializedSize = u.getSerializedSize();
320+
byte[] result = new byte[MAGIC.length + u.getSerializedSize()];
321+
System.arraycopy(MAGIC, 0, result, 0, MAGIC.length);
322+
u.writeTo(CodedOutputStream.newInstance(result, MAGIC.length, serializedSize));
320323
return result;
321324
}
322325

323-
private Unknown parse(InputStream stream) throws ApiException, IOException {
324-
byte[] magic = new byte[4];
325-
Streams.readFully(stream, magic);
326-
if (!Arrays.equals(magic, MAGIC)) {
327-
throw new ApiException("Unexpected magic number: " + Hex.encodeHexString(magic));
326+
private static Unknown parse(BufferedSource responseBody) throws ApiException, IOException {
327+
if (!responseBody.request(MAGIC.length)) {
328+
throw new ApiException("Truncated reading magic number");
329+
}
330+
331+
// Check the magic without allocating a byte array
332+
if (responseBody.rangeEquals(0, MAGIC_BYTESTRING)) {
333+
responseBody.skip(MAGIC.length);
334+
} else {
335+
ByteString badMagic = responseBody.readByteString(MAGIC.length);
336+
throw new ApiException("Unexpected magic number: " + badMagic.hex());
328337
}
329-
return Unknown.parseFrom(stream);
338+
return Unknown.parseFrom(responseBody.inputStream());
330339
}
331340
}

0 commit comments

Comments
 (0)