The bug
Before 23w41a, tail-recursive functions, which call themselves with /function
at the end of their body, worked quite efficiently. They consume only a constant amount of memory resources regardless of the depth of the recursion.
Since 23w41a, however, even tail-recursive functions consume more memory resources for each recursive call.
How to reproduce
/gamerule maxCommandChainLength 2147483647
/function mc-265805:f
→ Eventually, the game crashes due to
java.lang.OutOfMemoryError: Java heap space
data/mc-265805/functions/f.mcfunction
function mc-265805:f
Code analysis
net/minecraft/server/commands/FunctionCommand.java
public static <T extends ExecutionCommandSource<T>> void queueFunctions(
Collection<CommandFunction<T>> functions,
@Nullable CompoundTag arguments,
T sender,
T senderForExecution,
ExecutionControl<T> control,
FunctionCommand.Callbacks<T> callbacks
) throws CommandSyntaxException {
CommandDispatcher<T> dispatcher = sender.dispatcher();
MutableInt accumulatedReturnValue = new MutableInt();
for(CommandFunction<T> function : functions) {
ResourceLocation id = function.id();
try {
T listener = senderForExecution.clearCallbacks().withReturnValueConsumer(returnValue -> {
int value = accumulatedReturnValue.addAndGet(returnValue);
callbacks.signalResult(sender, id, value);
sender.storeResults(true, value);
});
InstantiatedFunction<T> instantiatedFunction = function.instantiate(arguments, dispatcher, listener);
control.queueNext(new CallFunction<>(instantiatedFunction).bind(listener));
} catch (FunctionInstantiationException e) {
throw ERROR_FUNCTION_INSTANTATION_FAILURE.create(id, e.messageComponent());
}
}
}
Because listener
's return value consumer captures the original sender
, JVM cannot release sender
while listener
is alive. In the case of recursive calls, a listener
becomes the sender
of another function call, chaining to the initial sender. Although calling a tail-recursive function still consumes only a constant amount of the command queue space, it now consumes a linear amount of memory resources due to the chain of execution command sources.
Heap dump analysis
Using VisualVM and a deobfuscated server.jar of 23w41a, a heap dump was obtained when the heap was almost full. According to the heap dump, the heap was almost fully occupied by the following three objects:
net.minecraft.commands.CommandSourceStack
: 30.6%net.minecraft.server.commands.FunctionCommand$$Lambda+0x000001c05ebefc78
: 30.6%org.apache.commons.lang3.mutable.MutableInt
: 30.6%
Attachments
Comments 0
No comments.