org.elasticsearch.common.io.stream.DelayableWriteable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.common.io.stream;
import org.elasticsearch.Version;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.bytes.ReleasableBytesReference;
import org.elasticsearch.core.Releasable;
import java.io.IOException;
import java.io.UncheckedIOException;
/**
* A holder for {@link Writeable}s that delays reading the underlying object
* on the receiving end. To be used for objects whose deserialized
* representation is inefficient to keep in memory compared to their
* corresponding serialized representation.
* The node that produces the {@link Writeable} calls {@link #referencing(Writeable)}
* to create a {@link DelayableWriteable} that serializes the inner object
* first to a buffer and writes the content of the buffer to the {@link StreamOutput}.
* The receiver node calls {@link #delayed(Reader, StreamInput)} to create a
* {@link DelayableWriteable} that reads the buffer from the @link {@link StreamInput}
* but delays creating the actual object by calling {@link #expand()} when needed.
* Multiple {@link DelayableWriteable}s coming from different nodes may be buffered
* on the receiver end, which may hold a mix of {@link DelayableWriteable}s that were
* produced locally (hence expanded) as well as received form another node (hence subject
* to delayed expansion). When such objects are buffered for some time it may be desirable
* to force their buffering in serialized format by calling
* {@link #asSerialized(Reader, NamedWriteableRegistry)}.
*/
public abstract class DelayableWriteable implements Writeable, Releasable {
/**
* Build a {@linkplain DelayableWriteable} that wraps an existing object
* but is serialized so that deserializing it can be delayed.
*/
public static DelayableWriteable referencing(T reference) {
return new Referencing<>(reference);
}
/**
* Build a {@linkplain DelayableWriteable} that copies a buffer from
* the provided {@linkplain StreamInput} and deserializes the buffer
* when {@link #expand()} is called.
*/
public static DelayableWriteable delayed(Writeable.Reader reader, StreamInput in) throws IOException {
return new Serialized<>(reader, in.getVersion(), in.namedWriteableRegistry(), in.readReleasableBytesReference());
}
public static DelayableWriteable referencing(Writeable.Reader reader, StreamInput in) throws IOException {
try (ReleasableBytesReference serialized = in.readReleasableBytesReference()) {
return new Referencing<>(deserialize(reader, in.getVersion(), in.namedWriteableRegistry(), serialized));
}
}
private DelayableWriteable() {}
/**
* Returns a {@linkplain DelayableWriteable} that stores its contents
* in serialized form.
*/
public abstract Serialized asSerialized(Writeable.Reader reader, NamedWriteableRegistry registry);
/**
* Expands the inner {@link Writeable} to its original representation and returns it
*/
public abstract T expand();
/**
* {@code true} if the {@linkplain Writeable} is being stored in
* serialized form, {@code false} otherwise.
*/
public abstract boolean isSerialized();
/**
* Returns the serialized size of the inner {@link Writeable}.
*/
public abstract long getSerializedSize();
private static class Referencing extends DelayableWriteable {
private final T reference;
private Referencing(T reference) {
this.reference = reference;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeBytesReference(writeToBuffer(out.getVersion()).bytes());
}
@Override
public T expand() {
return reference;
}
@Override
public Serialized asSerialized(Reader reader, NamedWriteableRegistry registry) {
BytesStreamOutput buffer;
try {
buffer = writeToBuffer(Version.CURRENT);
} catch (IOException e) {
throw new RuntimeException("unexpected error writing writeable to buffer", e);
}
// TODO: this path is currently not used in production code, if it ever is this should start using pooled buffers
return new Serialized<>(reader, Version.CURRENT, registry, ReleasableBytesReference.wrap(buffer.bytes()));
}
@Override
public boolean isSerialized() {
return false;
}
@Override
public long getSerializedSize() {
return DelayableWriteable.getSerializedSize(reference);
}
private BytesStreamOutput writeToBuffer(Version version) throws IOException {
try (BytesStreamOutput buffer = new BytesStreamOutput()) {
buffer.setVersion(version);
reference.writeTo(buffer);
return buffer;
}
}
@Override
public void close() {
// noop
}
}
/**
* A {@link Writeable} stored in serialized form backed by a {@link ReleasableBytesReference}. Once an instance is no longer used its
* backing memory must be manually released by invoking {@link #close()} on it.
*/
public static class Serialized extends DelayableWriteable {
private final Writeable.Reader reader;
private final Version serializedAtVersion;
private final NamedWriteableRegistry registry;
private final ReleasableBytesReference serialized;
private Serialized(
Writeable.Reader reader,
Version serializedAtVersion,
NamedWriteableRegistry registry,
ReleasableBytesReference serialized
) {
this.reader = reader;
this.serializedAtVersion = serializedAtVersion;
this.registry = registry;
this.serialized = serialized;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
if (out.getVersion() == serializedAtVersion) {
/*
* If the version *does* line up we can just copy the bytes
* which is good because this is how shard request caching
* works.
*/
out.writeBytesReference(serialized);
} else {
/*
* If the version doesn't line up then we have to deserialize
* into the Writeable and re-serialize it against the new
* output stream so it can apply any backwards compatibility
* differences in the wire protocol. This ain't efficient but
* it should be quite rare.
*/
referencing(expand()).writeTo(out);
}
}
@Override
public T expand() {
try {
return deserialize(reader, serializedAtVersion, registry, serialized);
} catch (IOException e) {
throw new RuntimeException("unexpected error expanding serialized delayed writeable", e);
}
}
@Override
public Serialized asSerialized(Reader reader, NamedWriteableRegistry registry) {
return this; // We're already serialized
}
@Override
public boolean isSerialized() {
return true;
}
@Override
public long getSerializedSize() {
// We're already serialized
return serialized.length();
}
@Override
public void close() {
serialized.close();
}
}
/**
* Returns the serialized size in bytes of the provided {@link Writeable}.
*/
public static long getSerializedSize(Writeable ref) {
try (CountingStreamOutput out = new CountingStreamOutput()) {
out.setVersion(Version.CURRENT);
ref.writeTo(out);
return out.size;
} catch (IOException exc) {
throw new UncheckedIOException(exc);
}
}
private static T deserialize(
Reader reader,
Version serializedAtVersion,
NamedWriteableRegistry registry,
BytesReference serialized
) throws IOException {
try (
StreamInput in = registry == null
? serialized.streamInput()
: new NamedWriteableAwareStreamInput(serialized.streamInput(), registry)
) {
in.setVersion(serializedAtVersion);
return reader.read(in);
}
}
private static class CountingStreamOutput extends StreamOutput {
long size = 0;
@Override
public void writeByte(byte b) throws IOException {
++size;
}
@Override
public void writeBytes(byte[] b, int offset, int length) throws IOException {
size += length;
}
@Override
public void flush() throws IOException {}
@Override
public void close() throws IOException {}
@Override
public void reset() throws IOException {
size = 0;
}
public long length() {
return size;
}
}
}