Skip to content

Race condition in EnumWrappers#initialize #3120

@diogotcorreia

Description

@diogotcorreia
  • This issue is not solved in a development build

Describe the bug
I'm using an async listener in my plugin, so it frequently happens that two packets that trigger initialization of EnumWrapper are sent at the same time.

However, due to the design of the EnumWrapper#initialize method, in an async context it is possible that the method returns without all fields having been initialized:

private static void initialize() {
    if (INITIALIZED)
        return; // (1)

    INITIALIZED = true; // (2)

    PROTOCOL_CLASS = MinecraftReflection.getEnumProtocolClass();
    // ...
    // (3)
}

If another thread calls initialize after the first thread reaches (2) but not (3), it will return immediately (1).

This error is a side effect of this issue (only happens once, when the first player joins the server):

[12:45:42 ERROR]: [Triton] Unhandled exception occurred in onAsyncPacket() for Triton
java.lang.NullPointerException: Cannot invoke "java.lang.Enum.name()" because "generic" is null
        at ProtocolLib.jar/com.comphenix.protocol.wrappers.EnumWrappers$EnumConverter.getSpecific(EnumWrappers.java:1104) ~[ProtocolLib.jar:?]
        at ProtocolLib.jar/com.comphenix.protocol.wrappers.EnumWrappers$EnumConverter.getSpecific(EnumWrappers.java:1090) ~[ProtocolLib.jar:?]
        at ProtocolLib.jar/com.comphenix.protocol.wrappers.WrappedTeamParameters.getColor(WrappedTeamParameters.java:73) ~[ProtocolLib.jar:?]
        at Triton.jar/com.rexcantor64.triton.packetinterceptor.ProtocolLibListener.handleScoreboardTeam(ProtocolLibListener.java:614) ~[Triton.jar:?]
        at Triton.jar/com.rexcantor64.triton.packetinterceptor.ProtocolLibListener.onPacketSending(ProtocolLibListener.java:755) ~[Triton.jar:?]
        at ProtocolLib.jar/com.comphenix.protocol.async.AsyncListenerHandler.lambda$processPacket$4(AsyncListenerHandler.java:602) ~[ProtocolLib.jar:?]
        at ProtocolLib.jar/com.comphenix.protocol.timing.TimingTracker.lambda$static$0(TimingTracker.java:7) ~[ProtocolLib.jar:?]
        at ProtocolLib.jar/com.comphenix.protocol.async.AsyncListenerHandler.processPacket(AsyncListenerHandler.java:600) ~[ProtocolLib.jar:?]
        at ProtocolLib.jar/com.comphenix.protocol.async.AsyncListenerHandler.listenerLoop(AsyncListenerHandler.java:572) ~[ProtocolLib.jar:?]
        at ProtocolLib.jar/com.comphenix.protocol.async.AsyncListenerHandler.access$100(AsyncListenerHandler.java:48) ~[ProtocolLib.jar:?]
        at ProtocolLib.jar/com.comphenix.protocol.async.AsyncListenerHandler$1.run(AsyncListenerHandler.java:217) ~[ProtocolLib.jar:?]
        at ProtocolLib.jar/com.comphenix.protocol.async.AsyncListenerHandler.lambda$start$1(AsyncListenerHandler.java:286) ~[ProtocolLib.jar:?]
        at org.bukkit.craftbukkit.scheduler.CraftTask.run(CraftTask.java:101) ~[paper-1.21.jar:1.21-68-8b35adc]
        at org.bukkit.craftbukkit.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:57) ~[paper-1.21.jar:1.21-68-8b35adc]
        at com.destroystokyo.paper.ServerSchedulerReportingWrapper.run(ServerSchedulerReportingWrapper.java:22) ~[paper-1.21.jar:?]
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[?:?]
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[?:?]
        at java.base/java.lang.Thread.run(Thread.java:1583) ~[?:?]

To Reproduce

(I'm not providing detailed steps since I'm going to be submitting a PR in a sec)

  1. Setup Triton and TAB plugins in a server (e.g. Paper 1.21)
  2. Join the server
  3. Error on console (might only happen sometimes; it's a race condition after all)

Expected behavior
EnumWrappers#initialize should be thread-safe

Version Info
Built from commit 4aa344b

Additional context
Submitting a PR in a sec

Extra log where I added some print statements for extra clarity of bug:

[12:52:23 INFO]: [ProtocolLib] [STDOUT] INITIALIZED = true
[12:52:23 INFO]: [ProtocolLib] [STDOUT] CHAT_FORMATTING_CLASS = null
[12:52:23 INFO]: [ProtocolLib] [STDOUT] INITIALIZED = true
[12:52:23 INFO]: [ProtocolLib] [STDOUT] CHAT_FORMATTING_CLASS = null
[12:52:23 ERROR]: [Triton] Unhandled exception occurred in onAsyncPacket() for Triton
java.lang.NullPointerException: Cannot invoke "java.lang.Enum.name()" because "generic" is null
// -snip- same error as above
[12:52:23 INFO]: [Triton] [TRACE] Trying to get translation with key 'chat.test.0' in language 'en_GB'
[12:52:23 INFO]: [Triton] [TRACE] Found translation with key 'chat.test.0' in language 'en_GB'
[12:52:23 INFO]: [ProtocolLib] [STDOUT] INITIALIZED = true
[12:52:23 INFO]: [ProtocolLib] [STDOUT] CHAT_FORMATTING_CLASS = class net.minecraft.ChatFormatting
[12:52:23 INFO]: [ProtocolLib] [STDOUT] INITIALIZED = true
[12:52:23 INFO]: [ProtocolLib] [STDOUT] CHAT_FORMATTING_CLASS = class net.minecraft.ChatFormatting

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions