Archiviert
13
0

Attempt to fix memory leaks with the ChannelInjector

Addresses https://github.com/aadnk/ProtocolLib/issues/70
Dieser Commit ist enthalten in:
Dan Mulloy 2014-11-15 14:56:57 -05:00
Ursprung 592874fd5e
Commit ca2bc3ecc5

Datei anzeigen

@ -61,16 +61,16 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet."); public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
public static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread."); public static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread.");
public static final ReportType REPORT_CANNOT_FIND_GET_VERSION = new ReportType("Cannot find getVersion() in NetworkMananger"); public static final ReportType REPORT_CANNOT_FIND_GET_VERSION = new ReportType("Cannot find getVersion() in NetworkMananger");
/** /**
* Indicates that a packet has bypassed packet listeners. * Indicates that a packet has bypassed packet listeners.
*/ */
private static final PacketEvent BYPASSED_PACKET = new PacketEvent(ChannelInjector.class); private static final PacketEvent BYPASSED_PACKET = new PacketEvent(ChannelInjector.class);
// The login packet // The login packet
private static Class<?> PACKET_LOGIN_CLIENT = null; private static Class<?> PACKET_LOGIN_CLIENT = null;
private static FieldAccessor LOGIN_GAME_PROFILE = null; private static FieldAccessor LOGIN_GAME_PROFILE = null;
// Saved accessors // Saved accessors
private static MethodAccessor DECODE_BUFFER; private static MethodAccessor DECODE_BUFFER;
private static MethodAccessor ENCODE_BUFFER; private static MethodAccessor ENCODE_BUFFER;
@ -78,17 +78,17 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
// For retrieving the protocol // For retrieving the protocol
private static FieldAccessor PROTOCOL_ACCESSOR; private static FieldAccessor PROTOCOL_ACCESSOR;
// The factory that created this injector // The factory that created this injector
private InjectionFactory factory; private InjectionFactory factory;
// The player, or temporary player // The player, or temporary player
private Player player; private Player player;
private Player updated; private Player updated;
// The player connection // The player connection
private Object playerConnection; private Object playerConnection;
// The current network manager and channel // The current network manager and channel
private final Object networkManager; private final Object networkManager;
private final Channel originalChannel; private final Channel originalChannel;
@ -96,32 +96,33 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
// Known network markers // Known network markers
private ConcurrentMap<Object, NetworkMarker> packetMarker = new MapMaker().weakKeys().makeMap(); private ConcurrentMap<Object, NetworkMarker> packetMarker = new MapMaker().weakKeys().makeMap();
/** /**
* Indicate that this packet has been processed by event listeners. * Indicate that this packet has been processed by event listeners.
* <p> * <p>
* This must never be set outside the channel pipeline's thread. * This must never be set outside the channel pipeline's thread.
*/ */
private PacketEvent currentEvent; private PacketEvent currentEvent;
/** /**
* A packet event that should be processed by the write method. * A packet event that should be processed by the write method.
*/ */
private PacketEvent finalEvent; private PacketEvent finalEvent;
/** /**
* A flag set by the main thread to indiciate that a packet should not be processed. * A flag set by the main thread to indiciate that a packet should not be processed.
*/ */
private final ThreadLocal<Boolean> scheduleProcessPackets = new ThreadLocal<Boolean>() { private final ThreadLocal<Boolean> scheduleProcessPackets = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() { protected Boolean initialValue() {
return true; return true;
}; };
}; };
// Other handlers // Other handlers
private ByteToMessageDecoder vanillaDecoder; private ByteToMessageDecoder vanillaDecoder;
private MessageToByteEncoder<Object> vanillaEncoder; private MessageToByteEncoder<Object> vanillaEncoder;
// Our extra handlers // Our extra handlers
private MessageToByteEncoder<Object> protocolEncoder; private MessageToByteEncoder<Object> protocolEncoder;
private ChannelInboundHandler finishHandler; private ChannelInboundHandler finishHandler;
@ -129,14 +130,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
// The channel listener // The channel listener
private ChannelListener channelListener; private ChannelListener channelListener;
// Processing network markers // Processing network markers
private NetworkProcessor processor; private NetworkProcessor processor;
// Closed // Closed
private boolean injected; private boolean injected;
private boolean closed; private boolean closed;
/** /**
* Construct a new channel injector. * Construct a new channel injector.
* @param player - the current player, or temporary player. * @param player - the current player, or temporary player.
@ -152,14 +153,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL"); this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL");
this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL"); this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL");
this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter()); this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter());
// Get the channel field // Get the channel field
this.channelField = new VolatileField( this.channelField = new VolatileField(
FuzzyReflection.fromObject(networkManager, true). FuzzyReflection.fromObject(networkManager, true).
getFieldByType("channel", Channel.class), getFieldByType("channel", Channel.class),
networkManager, true); networkManager, true);
} }
/** /**
* Get the version of the current protocol. * Get the version of the current protocol.
* @return The version. * @return The version.
@ -168,7 +169,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
public int getProtocolVersion() { public int getProtocolVersion() {
return MinecraftProtocolVersion.getCurrentVersion(); return MinecraftProtocolVersion.getCurrentVersion();
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public boolean inject() { public boolean inject() {
@ -179,8 +180,8 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
return false; return false;
if (!originalChannel.isActive()) if (!originalChannel.isActive())
return false; return false;
// Main thread? We should synchronize with the channel thread, otherwise we might see a // Main thread? We should synchronize with the channel thread, otherwise we might see a
// pipeline with only some of the handlers removed // pipeline with only some of the handlers removed
if (Bukkit.isPrimaryThread()) { if (Bukkit.isPrimaryThread()) {
// Just like in the close() method, we'll avoid blocking the main thread // Just like in the close() method, we'll avoid blocking the main thread
@ -192,43 +193,43 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
}); });
return false; // We don't know return false; // We don't know
} }
// Don't inject the same channel twice // Don't inject the same channel twice
if (findChannelHandler(originalChannel, ChannelInjector.class) != null) { if (findChannelHandler(originalChannel, ChannelInjector.class) != null) {
return false; return false;
} }
// Get the vanilla decoder, so we don't have to replicate the work // Get the vanilla decoder, so we don't have to replicate the work
vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder"); vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder");
vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder"); vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder");
if (vanillaDecoder == null) if (vanillaDecoder == null)
throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline() ); throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline() );
if (vanillaEncoder == null) if (vanillaEncoder == null)
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline() ); throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline() );
patchEncoder(vanillaEncoder); patchEncoder(vanillaEncoder);
if (DECODE_BUFFER == null) if (DECODE_BUFFER == null)
DECODE_BUFFER = Accessors.getMethodAccessor(vanillaDecoder.getClass(), DECODE_BUFFER = Accessors.getMethodAccessor(vanillaDecoder.getClass(),
"decode", ChannelHandlerContext.class, ByteBuf.class, List.class); "decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
if (ENCODE_BUFFER == null) if (ENCODE_BUFFER == null)
ENCODE_BUFFER = Accessors.getMethodAccessor(vanillaEncoder.getClass(), ENCODE_BUFFER = Accessors.getMethodAccessor(vanillaEncoder.getClass(),
"encode", ChannelHandlerContext.class, Object.class, ByteBuf.class); "encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
// Intercept sent packets // Intercept sent packets
protocolEncoder = new MessageToByteEncoder<Object>() { protocolEncoder = new MessageToByteEncoder<Object>() {
@Override @Override
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
ChannelInjector.this.encode(ctx, packet, output); ChannelInjector.this.encode(ctx, packet, output);
} }
@Override @Override
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
super.write(ctx, packet, promise); super.write(ctx, packet, promise);
ChannelInjector.this.finalWrite(ctx, packet, promise); ChannelInjector.this.finalWrite(ctx, packet, promise);
} }
}; };
// Intercept recieved packets // Intercept recieved packets
finishHandler = new ChannelInboundHandlerAdapter() { finishHandler = new ChannelInboundHandlerAdapter() {
@Override @Override
@ -238,27 +239,27 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
ChannelInjector.this.finishRead(ctx, msg); ChannelInjector.this.finishRead(ctx, msg);
} }
}; };
// Insert our handlers - note that we effectively replace the vanilla encoder/decoder // Insert our handlers - note that we effectively replace the vanilla encoder/decoder
originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this); originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this);
originalChannel.pipeline().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler); originalChannel.pipeline().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler);
originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder); originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder);
// Intercept all write methods // Intercept all write methods
channelField.setValue(new ChannelProxy(originalChannel, MinecraftReflection.getPacketClass()) { channelField.setValue(new ChannelProxy(originalChannel, MinecraftReflection.getPacketClass()) {
@Override @Override
protected <T> Callable<T> onMessageScheduled(final Callable<T> callable, FieldAccessor packetAccessor) { protected <T> Callable<T> onMessageScheduled(final Callable<T> callable, FieldAccessor packetAccessor) {
final PacketEvent event = handleScheduled(callable, packetAccessor); final PacketEvent event = handleScheduled(callable, packetAccessor);
// Handle cancelled events // Handle cancelled events
if (event != null && event.isCancelled()) if (event != null && event.isCancelled())
return null; return null;
return new Callable<T>() { return new Callable<T>() {
@Override @Override
public T call() throws Exception { public T call() throws Exception {
T result = null; T result = null;
// This field must only be updated in the pipeline thread // This field must only be updated in the pipeline thread
currentEvent = event; currentEvent = event;
result = callable.call(); result = callable.call();
@ -267,11 +268,11 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
}; };
} }
@Override @Override
protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) { protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) {
final PacketEvent event = handleScheduled(runnable, packetAccessor); final PacketEvent event = handleScheduled(runnable, packetAccessor);
// Handle cancelled events // Handle cancelled events
if (event != null && event.isCancelled()) if (event != null && event.isCancelled())
return null; return null;
@ -285,15 +286,15 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
}; };
} }
protected PacketEvent handleScheduled(Object instance, FieldAccessor accessor) { protected PacketEvent handleScheduled(Object instance, FieldAccessor accessor) {
// Let the filters handle this packet // Let the filters handle this packet
Object original = accessor.get(instance); Object original = accessor.get(instance);
// See if we've been instructed not to process packets // See if we've been instructed not to process packets
if (!scheduleProcessPackets.get()) { if (!scheduleProcessPackets.get()) {
NetworkMarker marker = getMarker(original); NetworkMarker marker = getMarker(original);
if (marker != null) { if (marker != null) {
PacketEvent result = new PacketEvent(ChannelInjector.class); PacketEvent result = new PacketEvent(ChannelInjector.class);
result.setNetworkMarker(marker); result.setNetworkMarker(marker);
@ -306,7 +307,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
if (event != null && !event.isCancelled()) { if (event != null && !event.isCancelled()) {
Object changed = event.getPacket().getHandle(); Object changed = event.getPacket().getHandle();
// Change packet to be scheduled // Change packet to be scheduled
if (original != changed) if (original != changed)
accessor.set(instance, changed); accessor.set(instance, changed);
@ -314,7 +315,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
return event != null ? event : BYPASSED_PACKET; return event != null ? event : BYPASSED_PACKET;
} }
}); });
injected = true; injected = true;
return true; return true;
} }
@ -328,7 +329,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
private PacketEvent processSending(Object message) { private PacketEvent processSending(Object message) {
return channelListener.onPacketSending(ChannelInjector.this, message, getMarker(message)); return channelListener.onPacketSending(ChannelInjector.this, message, getMarker(message));
} }
/** /**
* This method patches the encoder so that it skips already created packets. * This method patches the encoder so that it skips already created packets.
* @param encoder - the encoder to patch. * @param encoder - the encoder to patch.
@ -339,17 +340,17 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass())); ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass()));
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (channelListener.isDebug()) if (channelListener.isDebug())
cause.printStackTrace(); cause.printStackTrace();
super.exceptionCaught(ctx, cause); super.exceptionCaught(ctx, cause);
} }
/** /**
* Encode a packet to a byte buffer, taking over for the standard Minecraft encoder. * Encode a packet to a byte buffer, taking over for the standard Minecraft encoder.
* @param ctx - the current context. * @param ctx - the current context.
* @param packet - the packet to encode to a byte array. * @param packet - the packet to encode to a byte array.
* @param output - the output byte array. * @param output - the output byte array.
* @throws Exception If anything went wrong. * @throws Exception If anything went wrong.
@ -357,26 +358,26 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
NetworkMarker marker = null; NetworkMarker marker = null;
PacketEvent event = currentEvent; PacketEvent event = currentEvent;
try { try {
// Skip every kind of non-filtered packet // Skip every kind of non-filtered packet
if (!scheduleProcessPackets.get()) { if (!scheduleProcessPackets.get()) {
return; return;
} }
// This packet has not been seen by the main thread // This packet has not been seen by the main thread
if (event == null) { if (event == null) {
Class<?> clazz = packet.getClass(); Class<?> clazz = packet.getClass();
// Schedule the transmission on the main thread instead // Schedule the transmission on the main thread instead
if (channelListener.hasMainThreadListener(clazz)) { if (channelListener.hasMainThreadListener(clazz)) {
// Delay the packet // Delay the packet
scheduleMainThread(packet); scheduleMainThread(packet);
packet = null; packet = null;
} else { } else {
event = processSending(packet); event = processSending(packet);
// Handle the output // Handle the output
if (event != null) { if (event != null) {
packet = !event.isCancelled() ? event.getPacket().getHandle() : null; packet = !event.isCancelled() ? event.getPacket().getHandle() : null;
@ -385,9 +386,9 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
if (event != null) { if (event != null) {
// Retrieve marker without accidentally constructing it // Retrieve marker without accidentally constructing it
marker = NetworkMarker.getNetworkMarker(event); marker = NetworkMarker.getNetworkMarker(event);
} }
// Process output handler // Process output handler
if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) { if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) {
ByteBuf packetBuffer = ctx.alloc().buffer(); ByteBuf packetBuffer = ctx.alloc().buffer();
@ -395,17 +396,17 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
// Let each handler prepare the actual output // Let each handler prepare the actual output
byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer)); byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer));
// Write the result // Write the result
output.writeBytes(data); output.writeBytes(data);
packet = null; packet = null;
// Sent listeners? // Sent listeners?
finalEvent = event; finalEvent = event;
return; return;
} }
} catch (Exception e) { } catch (Exception e) {
channelListener.getReporter().reportDetailed(this, channelListener.getReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build()); Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build());
} finally { } finally {
// Attempt to handle the packet nevertheless // Attempt to handle the packet nevertheless
@ -424,12 +425,12 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
*/ */
protected void finalWrite(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) { protected void finalWrite(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) {
PacketEvent event = finalEvent; PacketEvent event = finalEvent;
if (event != null) { if (event != null) {
// Necessary to prevent infinite loops // Necessary to prevent infinite loops
finalEvent = null; finalEvent = null;
currentEvent = null; currentEvent = null;
processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event)); processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event));
} }
} }
@ -443,30 +444,30 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
}); });
} }
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception {
byteBuffer.markReaderIndex(); byteBuffer.markReaderIndex();
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets); DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
try { try {
// Reset queue // Reset queue
finishQueue.clear(); finishQueue.clear();
for (ListIterator<Object> it = packets.listIterator(); it.hasNext(); ) { for (ListIterator<Object> it = packets.listIterator(); it.hasNext(); ) {
Object input = it.next(); Object input = it.next();
Class<?> packetClass = input.getClass(); Class<?> packetClass = input.getClass();
NetworkMarker marker = null; NetworkMarker marker = null;
// Special case! // Special case!
handleLogin(packetClass, input); handleLogin(packetClass, input);
if (channelListener.includeBuffer(packetClass)) { if (channelListener.includeBuffer(packetClass)) {
byteBuffer.resetReaderIndex(); byteBuffer.resetReaderIndex();
marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer)); marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
} }
PacketEvent output = channelListener.onPacketReceiving(this, input, marker); PacketEvent output = channelListener.onPacketReceiving(this, input, marker);
// Handle packet changes // Handle packet changes
if (output != null) { if (output != null) {
if (output.isCancelled()) { if (output.isCancelled()) {
@ -475,16 +476,16 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} else if (output.getPacket().getHandle() != input) { } else if (output.getPacket().getHandle() != input) {
it.set(output.getPacket().getHandle()); it.set(output.getPacket().getHandle());
} }
finishQueue.addLast(output); finishQueue.addLast(output);
} }
} }
} catch (Exception e) { } catch (Exception e) {
channelListener.getReporter().reportDetailed(this, channelListener.getReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build()); Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build());
} }
} }
/** /**
* Invoked after our decoder. * Invoked after our decoder.
* @param ctx - current context. * @param ctx - current context.
@ -493,16 +494,16 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
protected void finishRead(ChannelHandlerContext ctx, Object msg) { protected void finishRead(ChannelHandlerContext ctx, Object msg) {
// Assume same order // Assume same order
PacketEvent event = finishQueue.pollFirst(); PacketEvent event = finishQueue.pollFirst();
if (event != null) { if (event != null) {
NetworkMarker marker = NetworkMarker.getNetworkMarker(event); NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
if (marker != null) { if (marker != null) {
processor.invokePostEvent(event, marker); processor.invokePostEvent(event, marker);
} }
} }
} }
/** /**
* Invoked when we may need to handle the login packet. * Invoked when we may need to handle the login packet.
* @param packetClass - the packet class. * @param packetClass - the packet class.
@ -511,7 +512,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
protected void handleLogin(Class<?> packetClass, Object packet) { protected void handleLogin(Class<?> packetClass, Object packet) {
Class<?> loginClass = PACKET_LOGIN_CLIENT; Class<?> loginClass = PACKET_LOGIN_CLIENT;
FieldAccessor loginClient = LOGIN_GAME_PROFILE; FieldAccessor loginClient = LOGIN_GAME_PROFILE;
// Initialize packet class and login // Initialize packet class and login
if (loginClass == null) { if (loginClass == null) {
loginClass = PacketType.Login.Client.START.getPacketClass(); loginClass = PacketType.Login.Client.START.getPacketClass();
@ -521,26 +522,26 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, GameProfile.class, true); loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, GameProfile.class, true);
LOGIN_GAME_PROFILE = loginClient; LOGIN_GAME_PROFILE = loginClient;
} }
// See if we are dealing with the login packet // See if we are dealing with the login packet
if (loginClass.equals(packetClass)) { if (loginClass.equals(packetClass)) {
GameProfile profile = (GameProfile) loginClient.get(packet); GameProfile profile = (GameProfile) loginClient.get(packet);
// Save the channel injector // Save the channel injector
factory.cacheInjector(profile.getName(), this); factory.cacheInjector(profile.getName(), this);
} }
} }
@Override @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx); super.channelActive(ctx);
// See NetworkManager.channelActive(ChannelHandlerContext) for why // See NetworkManager.channelActive(ChannelHandlerContext) for why
if (channelField != null) { if (channelField != null) {
channelField.refreshValue(); channelField.refreshValue();
} }
} }
/** /**
* Retrieve every byte in the given byte buffer. * Retrieve every byte in the given byte buffer.
* @param buffer - the buffer. * @param buffer - the buffer.
@ -548,11 +549,11 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
*/ */
private byte[] getBytes(ByteBuf buffer){ private byte[] getBytes(ByteBuf buffer){
byte[] data = new byte[buffer.readableBytes()]; byte[] data = new byte[buffer.readableBytes()];
buffer.readBytes(data); buffer.readBytes(data);
return data; return data;
} }
/** /**
* Disconnect the current player. * Disconnect the current player.
* @param message - the disconnect message, if possible. * @param message - the disconnect message, if possible.
@ -575,7 +576,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
@Override @Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) { public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
saveMarker(packet, marker); saveMarker(packet, marker);
try { try {
scheduleProcessPackets.set(filtered); scheduleProcessPackets.set(filtered);
invokeSendPacket(packet); invokeSendPacket(packet);
@ -583,7 +584,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
scheduleProcessPackets.set(true); scheduleProcessPackets.set(true);
} }
} }
/** /**
* Invoke the sendPacket method in Minecraft. * Invoke the sendPacket method in Minecraft.
* @param packet - the packet to send. * @param packet - the packet to send.
@ -600,11 +601,11 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
throw new RuntimeException("Unable to send server packet " + packet, e); throw new RuntimeException("Unable to send server packet " + packet, e);
} }
} }
@Override @Override
public void recieveClientPacket(final Object packet) { public void recieveClientPacket(final Object packet) {
// TODO: Ensure the packet listeners are executed in the channel thread. // TODO: Ensure the packet listeners are executed in the channel thread.
// Execute this in the channel thread // Execute this in the channel thread
Runnable action = new Runnable() { Runnable action = new Runnable() {
@Override @Override
@ -617,7 +618,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
} }
}; };
// Execute in the worker thread // Execute in the worker thread
if (originalChannel.eventLoop().inEventLoop()) { if (originalChannel.eventLoop().inEventLoop()) {
action.run(); action.run();
@ -625,7 +626,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
originalChannel.eventLoop().execute(action); originalChannel.eventLoop().execute(action);
} }
} }
@Override @Override
public Protocol getCurrentProtocol() { public Protocol getCurrentProtocol() {
if (PROTOCOL_ACCESSOR == null) { if (PROTOCOL_ACCESSOR == null) {
@ -634,7 +635,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
return Protocol.fromVanilla((Enum<?>) PROTOCOL_ACCESSOR.get(networkManager)); return Protocol.fromVanilla((Enum<?>) PROTOCOL_ACCESSOR.get(networkManager));
} }
/** /**
* Retrieve the player connection of the current player. * Retrieve the player connection of the current player.
* @return The player connection. * @return The player connection.
@ -645,45 +646,47 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
return playerConnection; return playerConnection;
} }
@Override @Override
public NetworkMarker getMarker(Object packet) { public NetworkMarker getMarker(Object packet) {
return packetMarker.get(packet); return packetMarker.get(packet);
} }
@Override @Override
public void saveMarker(Object packet, NetworkMarker marker) { public void saveMarker(Object packet, NetworkMarker marker) {
if (marker != null) { if (marker != null) {
packetMarker.put(packet, marker); packetMarker.put(packet, marker);
} }
} }
@Override @Override
public Player getPlayer() { public Player getPlayer() {
return player; return player;
} }
/** /**
* Set the player instance. * Set the player instance.
* @param player - current instance. * @param player - current instance.
*/ */
@Override
public void setPlayer(Player player) { public void setPlayer(Player player) {
this.player = player; this.player = player;
} }
/** /**
* Set the updated player instance. * Set the updated player instance.
* @param updated - updated instance. * @param updated - updated instance.
*/ */
@Override
public void setUpdatedPlayer(Player updated) { public void setUpdatedPlayer(Player updated) {
this.updated = updated; this.updated = updated;
} }
@Override @Override
public boolean isInjected() { public boolean isInjected() {
return injected; return injected;
} }
/** /**
* Determine if this channel has been closed and cleaned up. * Determine if this channel has been closed and cleaned up.
* @return TRUE if it has, FALSE otherwise. * @return TRUE if it has, FALSE otherwise.
@ -692,29 +695,29 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
public boolean isClosed() { public boolean isClosed() {
return closed; return closed;
} }
@Override @Override
public void close() { public void close() {
if (!closed) { if (!closed) {
closed = true; closed = true;
if (injected) { if (injected) {
channelField.revertValue(); channelField.revertValue();
// Calling remove() in the main thread will block the main thread, which may lead // Calling remove() in the main thread will block the main thread, which may lead
// to a deadlock: // to a deadlock:
// http://pastebin.com/L3SBVKzp // http://pastebin.com/L3SBVKzp
// //
// ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread, // ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread,
// which has implicitly aquired a lock on SimplePluginManager (see SimplePluginManager.callEvent(Event)). // which has implicitly aquired a lock on SimplePluginManager (see SimplePluginManager.callEvent(Event)).
// Unfortunately, the remove() method will schedule the removal on one of the Netty worker threads if // Unfortunately, the remove() method will schedule the removal on one of the Netty worker threads if
// it's called from a different thread, blocking until the removal has been confirmed. // it's called from a different thread, blocking until the removal has been confirmed.
// //
// This is bad enough (Rule #1: Don't block the main thread), but the real trouble starts if the same // This is bad enough (Rule #1: Don't block the main thread), but the real trouble starts if the same
// worker thread happens to be handling a server ping connection when this removal task is scheduled. // worker thread happens to be handling a server ping connection when this removal task is scheduled.
// In that case, it may attempt to invoke an asynchronous ServerPingEvent (see PacketStatusListener) // In that case, it may attempt to invoke an asynchronous ServerPingEvent (see PacketStatusListener)
// using SimplePluginManager.callEvent(). But, since this has already been locked by the main thread, // using SimplePluginManager.callEvent(). But, since this has already been locked by the main thread,
// we end up with a deadlock. The main thread is waiting for the worker thread to process the task, and // we end up with a deadlock. The main thread is waiting for the worker thread to process the task, and
// the worker thread is waiting for the main thread to finish executing PlayerQuitEvent. // the worker thread is waiting for the main thread to finish executing PlayerQuitEvent.
// //
// TLDR: Concurrenty is hard. // TLDR: Concurrenty is hard.
@ -730,10 +733,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
} }
}); });
// Clear cache // Clear cache
factory.invalidate(player); factory.invalidate(player);
} }
// dmulloy2 - attempt to fix memory leakage
this.player = null;
this.updated = null;
} }
} }
@ -750,13 +757,13 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
try { try {
command.run(); command.run();
} catch (Exception e) { } catch (Exception e) {
ProtocolLibrary.getErrorReporter().reportDetailed(ChannelInjector.this, ProtocolLibrary.getErrorReporter().reportDetailed(ChannelInjector.this,
Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build()); Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build());
} }
} }
}); });
} }
/** /**
* Find the first channel handler that is assignable to a given type. * Find the first channel handler that is assignable to a given type.
* @param channel - the channel. * @param channel - the channel.
@ -771,14 +778,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
return null; return null;
} }
/** /**
* Represents a socket injector that foreards to the current channel injector. * Represents a socket injector that foreards to the current channel injector.
* @author Kristian * @author Kristian
*/ */
static class ChannelSocketInjector implements SocketInjector { static class ChannelSocketInjector implements SocketInjector {
private final ChannelInjector injector; private final ChannelInjector injector;
public ChannelSocketInjector(ChannelInjector injector) { public ChannelSocketInjector(ChannelInjector injector) {
this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL"); this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL");
} }
@ -822,7 +829,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
public void setUpdatedPlayer(Player updatedPlayer) { public void setUpdatedPlayer(Player updatedPlayer) {
injector.player = updatedPlayer; injector.player = updatedPlayer;
} }
public ChannelInjector getChannelInjector() { public ChannelInjector getChannelInjector() {
return injector; return injector;
} }