-
Bug
-
Resolution: Fixed
-
23w41a, 23w42a, 23w43a
-
None
-
Confirmed
-
Commands, Performance
-
Important
-
Platform
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
function mc-265805:f
Code analysis
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%