Improve Quality of Channel Close Code

* Don't double disconnect due to client exceptions
* Add generic delayed close method
* Properly format imports in changed files
This commit is contained in:
md_5 2016-10-19 21:08:31 +11:00
parent 24a65d8fa9
commit 5c551fd899
3 changed files with 68 additions and 52 deletions

View File

@ -11,17 +11,14 @@ import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.PlatformDependent;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
@ -59,8 +56,6 @@ import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter; import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
import net.md_5.bungee.protocol.packet.PluginMessage; import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.SetCompression; import net.md_5.bungee.protocol.packet.SetCompression;
import net.md_5.bungee.tab.Global;
import net.md_5.bungee.tab.GlobalPing;
import net.md_5.bungee.tab.ServerUnique; import net.md_5.bungee.tab.ServerUnique;
import net.md_5.bungee.tab.TabList; import net.md_5.bungee.tab.TabList;
import net.md_5.bungee.util.CaseInsensitiveSet; import net.md_5.bungee.util.CaseInsensitiveSet;
@ -364,31 +359,26 @@ public final class UserConnection implements ProxiedPlayer
public void disconnect0(final BaseComponent... reason) public void disconnect0(final BaseComponent... reason)
{ {
if ( !ch.isClosed() ) if ( !ch.isClosing() )
{ {
bungee.getLogger().log( Level.INFO, "[{0}] disconnected with: {1}", new Object[] bungee.getLogger().log( Level.INFO, "[{0}] disconnected with: {1}", new Object[]
{ {
getName(), BaseComponent.toLegacyText( reason ) getName(), BaseComponent.toLegacyText( reason )
} ); } );
// Why do we have to delay this you might ask? Well the simple reason is MOJANG. ch.delayedClose( new Runnable()
// Despite many a bug report posted, ever since the 1.7 protocol rewrite, the client STILL has a race condition upon switching protocols.
// As such, despite the protocol switch packets already having been sent, there is the possibility of a client side exception
// To help combat this we will wait half a second before actually sending the disconnected packet so that whoever is on the other
// end has a somewhat better chance of receiving the proper packet.
ch.getHandle().eventLoop().schedule( new Runnable()
{ {
@Override @Override
public void run() public void run()
{ {
unsafe().sendPacket( new Kick( ComponentSerializer.toString( reason ) ) ); unsafe().sendPacket( new Kick( ComponentSerializer.toString( reason ) ) );
ch.close();
} }
}, 500, TimeUnit.MILLISECONDS ); } );
if ( server != null ) if ( server != null )
{ {
server.setObsolete( true );
server.disconnect( "Quitting" ); server.disconnect( "Quitting" );
} }
} }

View File

@ -2,19 +2,24 @@ package net.md_5.bungee.connection;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import com.google.gson.Gson;
import java.util.concurrent.TimeUnit;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.md_5.bungee.*; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.BungeeServerInfo;
import net.md_5.bungee.EncryptionUtil;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.AbstractReconnectHandler;
import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.Favicon; import net.md_5.bungee.api.Favicon;
@ -23,37 +28,37 @@ import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.Connection.Unsafe;
import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.ProxyPingEvent; import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.chat.ComponentSerializer; import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.http.HttpClient; import net.md_5.bungee.http.HttpClient;
import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.jni.cipher.BungeeCipher;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PacketHandler; import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.netty.cipher.CipherDecoder; import net.md_5.bungee.netty.cipher.CipherDecoder;
import net.md_5.bungee.netty.cipher.CipherEncoder; import net.md_5.bungee.netty.cipher.CipherEncoder;
import net.md_5.bungee.protocol.DefinedPacket; import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.EncryptionResponse;
import net.md_5.bungee.protocol.packet.EncryptionRequest;
import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.api.AbstractReconnectHandler;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.jni.cipher.BungeeCipher;
import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.EncryptionRequest;
import net.md_5.bungee.protocol.packet.EncryptionResponse;
import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.protocol.packet.LegacyHandshake; import net.md_5.bungee.protocol.packet.LegacyHandshake;
import net.md_5.bungee.protocol.packet.LegacyPing; import net.md_5.bungee.protocol.packet.LegacyPing;
import net.md_5.bungee.protocol.packet.LoginRequest; import net.md_5.bungee.protocol.packet.LoginRequest;
import net.md_5.bungee.protocol.packet.LoginSuccess; import net.md_5.bungee.protocol.packet.LoginSuccess;
import net.md_5.bungee.protocol.packet.PingPacket; import net.md_5.bungee.protocol.packet.PingPacket;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.StatusRequest; import net.md_5.bungee.protocol.packet.StatusRequest;
import net.md_5.bungee.protocol.packet.StatusResponse; import net.md_5.bungee.protocol.packet.StatusResponse;
import net.md_5.bungee.util.BoundedArrayList; import net.md_5.bungee.util.BoundedArrayList;
@ -96,12 +101,11 @@ public class InitialHandler extends PacketHandler implements PendingConnection
private boolean legacy; private boolean legacy;
@Getter @Getter
private String extraDataInHandshake = ""; private String extraDataInHandshake = "";
private boolean disconnecting;
@Override @Override
public boolean shouldHandle(PacketWrapper packet) throws Exception public boolean shouldHandle(PacketWrapper packet) throws Exception
{ {
return !disconnecting; return !ch.isClosing();
} }
private enum State private enum State
@ -534,28 +538,18 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override @Override
public void disconnect(final BaseComponent... reason) public void disconnect(final BaseComponent... reason)
{ {
if ( !disconnecting || !ch.isClosed() ) ch.delayedClose( new Runnable()
{ {
disconnecting = true;
// Why do we have to delay this you might ask? Well the simple reason is MOJANG.
// Despite many a bug report posted, ever since the 1.7 protocol rewrite, the client STILL has a race condition upon switching protocols.
// As such, despite the protocol switch packets already having been sent, there is the possibility of a client side exception
// To help combat this we will wait half a second before actually sending the disconnected packet so that whoever is on the other
// end has a somewhat better chance of receiving the proper packet.
ch.getHandle().eventLoop().schedule( new Runnable()
{
@Override @Override
public void run() public void run()
{
if ( thisState != State.STATUS && thisState != State.PING )
{ {
if ( thisState != State.STATUS && thisState != State.PING ) unsafe().sendPacket( new Kick( ComponentSerializer.toString( reason ) ) );
{
unsafe().sendPacket( new Kick( ComponentSerializer.toString( reason ) ) );
}
ch.close();
} }
}, 500, TimeUnit.MILLISECONDS ); }
} } );
} }
@Override @Override

View File

@ -1,15 +1,16 @@
package net.md_5.bungee.netty; package net.md_5.bungee.netty;
import net.md_5.bungee.compress.PacketCompressor;
import net.md_5.bungee.compress.PacketDecompressor;
import net.md_5.bungee.protocol.PacketWrapper;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import java.util.concurrent.TimeUnit;
import lombok.Getter; import lombok.Getter;
import net.md_5.bungee.compress.PacketCompressor;
import net.md_5.bungee.compress.PacketDecompressor;
import net.md_5.bungee.protocol.MinecraftDecoder; import net.md_5.bungee.protocol.MinecraftDecoder;
import net.md_5.bungee.protocol.MinecraftEncoder; import net.md_5.bungee.protocol.MinecraftEncoder;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.Protocol;
public class ChannelWrapper public class ChannelWrapper
@ -18,6 +19,8 @@ public class ChannelWrapper
private final Channel ch; private final Channel ch;
@Getter @Getter
private volatile boolean closed; private volatile boolean closed;
@Getter
private volatile boolean closing;
public ChannelWrapper(ChannelHandlerContext ctx) public ChannelWrapper(ChannelHandlerContext ctx)
{ {
@ -56,12 +59,41 @@ public class ChannelWrapper
{ {
if ( !closed ) if ( !closed )
{ {
closed = true; closed = closing = true;
ch.flush(); ch.flush();
ch.close(); ch.close();
} }
} }
public void delayedClose(final Runnable runnable)
{
Preconditions.checkArgument( runnable != null, "runnable" );
if ( !closing )
{
closing = true;
// Minecraft client can take some time to switch protocols.
// Sending the wrong disconnect packet whilst a protocol switch is in progress will crash it.
// Delay 500ms to ensure that the protocol switch (if any) has definitely taken place.
ch.eventLoop().schedule( new Runnable()
{
@Override
public void run()
{
try
{
runnable.run();
} finally
{
ChannelWrapper.this.close();
}
}
}, 500, TimeUnit.MILLISECONDS );
}
}
public void addBefore(String baseName, String name, ChannelHandler handler) public void addBefore(String baseName, String name, ChannelHandler handler)
{ {
Preconditions.checkState( ch.eventLoop().inEventLoop(), "cannot add handler outside of event loop" ); Preconditions.checkState( ch.eventLoop().inEventLoop(), "cannot add handler outside of event loop" );