Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ public void clear(PrintWriter w) {
// parse() invalidates the stale buffer reference for fields present on the wire.
}

@Override
public void clearRelease(PrintWriter w) {
w.format("%s = null;\n", ccName);
}

@Override
public boolean needsRelease() {
return true;
}

@Override
public void serializedSize(PrintWriter w) {
w.format("_size += %s_SIZE;\n", tagName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ public void has(PrintWriter w) {

abstract public void clear(PrintWriter w);

/**
* Emit this field's contribution to the generated {@code _clearAndRelease()}:
* like {@link #clear(PrintWriter)} but also releasing retained data references
* (cached Strings, ByteBuf refs), recursing into nested messages via their
* {@code _clearAndRelease()}. Fields that retain no references inherit this
* default, which emits the plain clear code.
*/
public void clearRelease(PrintWriter w) {
clear(w);
}

/**
* Whether this field retains data references that {@link #clearRelease(PrintWriter)}
* must drop. Messages where no field does skip the clear() size gate entirely:
* their release path is behaviorally identical to the plain clear.
*/
public boolean needsRelease() {
return false;
}

public void fieldClear(PrintWriter w, String enclosingType) {
w.format(" /** Clear the {@code %s} field. */\n", field.getName());
w.format(" public %s %s() {\n", enclosingType, Util.camelCase("clear", field.getName()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,36 @@ public void clear(PrintWriter w) {
w.format("_%sIndex = null;\n", ccName);
}

@Override
public void clearRelease(PrintWriter w) {
if (isStringKey()) {
w.format("for (int i = 0; i < _%sCount; i++) {\n", ccName);
w.format(" _%sKeys[i].s = null;\n", ccName);
w.format("}\n");
}
if (isStringValue()) {
w.format("for (int i = 0; i < _%sCount; i++) {\n", ccName);
w.format(" _%sValues[i].s = null;\n", ccName);
w.format("}\n");
} else if (isBytesValue()) {
w.format("for (int i = 0; i < _%sCount; i++) {\n", ccName);
w.format(" _%sValues[i].b = null;\n", ccName);
w.format("}\n");
} else if (isMessageValue()) {
// Forced recursion — see LightProtoMessageField#clearRelease.
w.format("for (int i = 0; i < _%sCount; i++) {\n", ccName);
w.format(" _%sValues[i]._clearAndRelease();\n", ccName);
w.format("}\n");
}
w.format("_%sCount = 0;\n", ccName);
w.format("_%sIndex = null;\n", ccName);
}

@Override
public boolean needsRelease() {
return true;
}

@Override
public void materialize(PrintWriter w) {
if (isStringKey()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public void generate(PrintWriter w) {
generateParseFrom(w);
generateCheckRequiredFields(w);
generateClear(w);
generateClearAndRelease(w);
generateCopyFrom(w);

if (generateJson) {
Expand Down Expand Up @@ -285,6 +286,18 @@ private void emitBitDrivenTraversal(PrintWriter w, java.util.function.Consumer<L
private void generateClear(PrintWriter w) {
w.println(" /** Reset all fields to their default values, allowing this instance to be reused. */");
w.format(" public %s clear() {\n", message.getName());
// Only messages that can retain data references need the release gate;
// for the rest _clearAndRelease() is behaviorally identical to clear().
if (fields.stream().anyMatch(LightProtoField::needsRelease)) {
// _cachedSize is the previous message's size at this point (parseFrom()
// and getSerializedSize() maintain it), so the gate is O(1) — a single
// unsigned compare: -1 (mutated since, unknown fields on the wire, or
// already cleared) is huge unsigned, taking the release path
// conservatively; over cleared fields it walks nothing.
w.format(" if (Integer.compareUnsigned(_cachedSize, LightProtoCodec.CLEAR_RETAIN_MAX) > 0) {\n");
w.format(" return _clearAndRelease();\n");
w.format(" }\n");
}
boolean bitDriven = useBitDrivenClear();
for (LightProtoField f : fields) {
if (bitDriven && f instanceof LightProtoMessageField && !f.isOneofMember()) {
Expand Down Expand Up @@ -335,6 +348,33 @@ private void generateClear(PrintWriter w) {
w.format(" }\n");
}

private void generateClearAndRelease(PrintWriter w) {
w.println(" /**");
w.println(" * clear() variant for messages above CLEAR_RETAIN_MAX (or of unknown");
w.println(" * size) that also releases the data references the O(1) clear() leaves");
w.println(" * in place, so a reused (pooled or per-connection) instance doesn't pin");
w.println(" * the last message's data. Recursion into nested messages is forced —");
w.println(" * children don't re-check the size gate, or a large message spread over");
w.println(" * many small children would release nothing. Public only so that");
w.println(" * generated messages in other packages can release nested fields of");
w.println(" * this type.");
w.println(" */");
w.format(" public %s _clearAndRelease() {\n", message.getName());
for (LightProtoField f : fields) {
f.clearRelease(w);
}
w.format(" _parsedBuffer = null;\n");
w.format(" _cachedSize = -1;\n");
for (int i = 0; i < bitFieldsCount(); i++) {
w.format(" _bitField%d = 0;\n", i);
}
for (ProtoOneofDescriptor oneof : oneofs) {
w.format(" _%sCase = 0;\n", Util.camelCase(oneof.getName()));
}
w.format(" return this;\n");
w.format(" }\n");
}

private void generateCopyFrom(PrintWriter w) {
w.println(" /** Copy all fields from another message of the same type. */");
w.format("public %s copyFrom(%s _other) {\n", message.getName(), message.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ public void clear(PrintWriter w) {
w.format("}\n");
}

@Override
public void clearRelease(PrintWriter w) {
// Forced recursion: the child must not re-check its own size gate, or a
// large parent spread across many small children would release nothing.
w.format("if (%s()){\n", Util.camelCase("has", ccName));
w.format(" %s._clearAndRelease();\n", ccName);
w.format("}\n");
}

@Override
public boolean needsRelease() {
return true;
}

@Override
public void materialize(PrintWriter w) {
w.format("if (%s()) {\n", Util.camelCase("has", ccName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,19 @@ public void clear(PrintWriter w) {
w.format("_%sCount = 0;\n", pluralName);
}

@Override
public void clearRelease(PrintWriter w) {
w.format("for (int i = 0; i < _%sCount; i++) {\n", pluralName);
w.format(" %s[i].b = null;\n", pluralName);
w.format("}\n");
w.format("_%sCount = 0;\n", pluralName);
}

@Override
public boolean needsRelease() {
return true;
}

@Override
public void materialize(PrintWriter w) {
w.format("for (int i = 0; i < _%sCount; i++) {\n", pluralName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,20 @@ public void clear(PrintWriter w) {
w.format("_%sCount = 0;\n", pluralName);
}

@Override
public void clearRelease(PrintWriter w) {
// Forced recursion — see LightProtoMessageField#clearRelease.
w.format("for (int i = 0; i < _%sCount; i++) {\n", pluralName);
w.format(" %s[i]._clearAndRelease();\n", pluralName);
w.format("}\n");
w.format("_%sCount = 0;\n", pluralName);
}

@Override
public boolean needsRelease() {
return true;
}

@Override
public void materialize(PrintWriter w) {
w.format("for (int i = 0; i < _%sCount; i++) {\n", pluralName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,19 @@ public void clear(PrintWriter w) {
w.format("_%sCount = 0;\n", pluralName);
}

@Override
public void clearRelease(PrintWriter w) {
w.format("for (int i = 0; i < _%sCount; i++) {\n", pluralName);
w.format(" %s[i].s = null;\n", pluralName);
w.format("}\n");
w.format("_%sCount = 0;\n", pluralName);
}

@Override
public boolean needsRelease() {
return true;
}

@Override
public void materialize(PrintWriter w) {
w.format("for (int i = 0; i < _%sCount; i++) {\n", pluralName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ public void clear(PrintWriter w) {
// parse() invalidates the cached decoded String for fields present on the wire.
}

@Override
public void clearRelease(PrintWriter w) {
w.format("%s = null;\n", ccName);
}

@Override
public boolean needsRelease() {
return true;
}

@Override
public void serializedSize(PrintWriter w) {
w.format("_size += %s_SIZE;\n", tagName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,13 @@ static void writeString(ByteBuf b, String s, int bytesCount) {
// so outlier messages don't pin large allocations.
static final int SCRATCH_RETAIN_MAX = 1024 * 1024;

// clear() of a message larger than this (or of unknown size) releases the
// data references retained by the O(1) clear design, so a reused (pooled or
// per-connection) instance pins at most this much of the last message's
// data. The release walk costs O(element count), which is noise for any
// message this large; below the threshold the walk is skipped entirely.
static final int CLEAR_RETAIN_MAX = 64 * 1024;

/** Returns current if it can hold size bytes, otherwise a larger replacement. */
static byte[] scratchFor(byte[] current, int size) {
if (current != null && current.length >= size) {
Expand Down
Loading
Loading