Significantly reduce the possibility of a race condition.
The vanilla server bootstrap (ServerConnectionChannel) is executed asynchronously when a new channel object has been registered in an event loop, much before it is ready to accept a new client connection. It is here the whole channel handler pipeline is set up, along with a NetworkManager responsible for reading and writing packets. The trouble starts when the bootstrap class adds the created NetworkManager to a list (f) of managers in ServerConnection. This list is regularly inspected by the main thread (in order to process packets on the main thread) and includes a clean up procedure (ServerConnection#61) in case it detects a disconnected network manager. Unfortunately, the network manager IS considered disconnected for a moment when its added to the list, so the main thread MAY end up getting to the network manager before Netty has connected the channel. This is still very rare under normal circumstances, but because ProtocolLib does a lot of initial processing in the channel handler, the asynchronous thread gets hold up for a long while the first time a player connects to the server, allowing the main thread sufficient time to catch up and evict the network manager. The partial solution here is to synchronize on the network manager list, stopping the main thread from processing network managers when we are preparing our new connection. But I suspect the best solution would be to correct the root of the problem, and correct the race condition in the server itself. This could be done by only adding the network manager when the channel is active (see ChannelInboundHandler.channelActive).
Dieser Commit ist enthalten in:
Ursprung
c568481da3
Commit
9b349299a0
@ -27,9 +27,7 @@ class BootstrapList implements List<Object> {
|
|||||||
|
|
||||||
// Process all existing bootstraps
|
// Process all existing bootstraps
|
||||||
for (Object item : this) {
|
for (Object item : this) {
|
||||||
if (item instanceof ChannelFuture) {
|
processElement(item);
|
||||||
processBootstrap((ChannelFuture) item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import com.comphenix.protocol.injector.spigot.AbstractPacketInjector;
|
|||||||
import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler;
|
import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.reflect.VolatileField;
|
import com.comphenix.protocol.reflect.VolatileField;
|
||||||
|
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
@ -45,10 +46,12 @@ public class NettyProtocolInjector implements ChannelListener {
|
|||||||
// The temporary player factory
|
// The temporary player factory
|
||||||
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
|
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
|
||||||
private List<VolatileField> bootstrapFields = Lists.newArrayList();
|
private List<VolatileField> bootstrapFields = Lists.newArrayList();
|
||||||
|
private BootstrapList networkManagers;
|
||||||
|
|
||||||
// Different sending filters
|
// Different sending filters
|
||||||
private PacketTypeSet sendingFilters = new PacketTypeSet();
|
private PacketTypeSet sendingFilters = new PacketTypeSet();
|
||||||
private PacketTypeSet reveivedFilters = new PacketTypeSet();
|
private PacketTypeSet reveivedFilters = new PacketTypeSet();
|
||||||
|
|
||||||
// Packets that must be executed on the main thread
|
// Packets that must be executed on the main thread
|
||||||
private PacketTypeSet mainThreadFilters = new PacketTypeSet();
|
private PacketTypeSet mainThreadFilters = new PacketTypeSet();
|
||||||
|
|
||||||
@ -88,7 +91,11 @@ public class NettyProtocolInjector implements ChannelListener {
|
|||||||
if (closed)
|
if (closed)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ChannelInjector.fromChannel(channel, NettyProtocolInjector.this, playerFactory).inject();
|
|
||||||
|
// This can take a while, so we need to stop the main thread from interfering
|
||||||
|
synchronized (networkManagers) {
|
||||||
|
ChannelInjector.fromChannel(channel, NettyProtocolInjector.this, playerFactory).inject();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -104,16 +111,25 @@ public class NettyProtocolInjector implements ChannelListener {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get the current NetworkMananger list
|
||||||
|
Object networkManagerList = FuzzyReflection.fromObject(serverConnection, true).
|
||||||
|
invokeMethod(null, "getNetworkManagers", List.class, serverConnection);
|
||||||
|
|
||||||
// Insert ProtocolLib's connection interceptor
|
// Insert ProtocolLib's connection interceptor
|
||||||
bootstrapFields = getBootstrapFields(serverConnection);
|
bootstrapFields = getBootstrapFields(serverConnection);
|
||||||
|
|
||||||
for (VolatileField field : bootstrapFields) {
|
for (VolatileField field : bootstrapFields) {
|
||||||
final List<Object> list = (List<Object>) field.getValue();
|
final List<Object> list = (List<Object>) field.getValue();
|
||||||
|
final BootstrapList bootstrap = new BootstrapList(list, connectionHandler);
|
||||||
|
|
||||||
// Synchronize with each list before we attempt to replace them.
|
// Synchronize with each list before we attempt to replace them.
|
||||||
field.setValue(new BootstrapList(
|
field.setValue(bootstrap);
|
||||||
list, connectionHandler
|
|
||||||
));
|
if (list == networkManagerList) {
|
||||||
|
// Save it for later
|
||||||
|
networkManagers = bootstrap;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
injected = true;
|
injected = true;
|
||||||
|
@ -279,6 +279,26 @@ public class FuzzyReflection {
|
|||||||
throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName());
|
throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a method by return type and parameters alone.
|
||||||
|
* <p>
|
||||||
|
* The parameters must be non-null for this to work.
|
||||||
|
* @param target - the instance.
|
||||||
|
* @param name - the name of the method - for debugging.
|
||||||
|
* @param returnType - the expected return type.
|
||||||
|
* @param parameters - the parameters.
|
||||||
|
* @return The return value, or NULL.
|
||||||
|
*/
|
||||||
|
public Object invokeMethod(Object target, String name, Class<?> returnType, Object... parameters) {
|
||||||
|
Class<?>[] types = new Class<?>[parameters.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < types.length; i++) {
|
||||||
|
types[i] = parameters[i].getClass();
|
||||||
|
}
|
||||||
|
return Accessors.getMethodAccessor(getMethodByParameters(name, returnType, types)).
|
||||||
|
invoke(target, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean matchParameters(Pattern[] parameterMatchers, Class<?>[] argTypes) {
|
private boolean matchParameters(Pattern[] parameterMatchers, Class<?>[] argTypes) {
|
||||||
if (parameterMatchers.length != argTypes.length)
|
if (parameterMatchers.length != argTypes.length)
|
||||||
throw new IllegalArgumentException("Arrays must have the same cardinality.");
|
throw new IllegalArgumentException("Arrays must have the same cardinality.");
|
||||||
|
@ -136,6 +136,17 @@ public final class Accessors {
|
|||||||
* @return The method accessor.
|
* @return The method accessor.
|
||||||
*/
|
*/
|
||||||
public static MethodAccessor getMethodAccessor(final Method method) {
|
public static MethodAccessor getMethodAccessor(final Method method) {
|
||||||
|
return getMethodAccessor(method, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a method accessor for a particular method, avoding checked exceptions.
|
||||||
|
* @param method - the method to access.
|
||||||
|
* @param forceAccess - whether or not to skip Java access checking.
|
||||||
|
* @return The method accessor.
|
||||||
|
*/
|
||||||
|
public static MethodAccessor getMethodAccessor(final Method method, boolean forceAccess) {
|
||||||
|
method.setAccessible(forceAccess);
|
||||||
return new DefaultMethodAccessor(method);
|
return new DefaultMethodAccessor(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren