Archiviert
13
0

A possible fix for a rare but game-breaking deadlock.

Calling remove() in the main thread will block the main thread, which 
may lead to a deadlock:
    http://pastebin.com/L3SBVKzp

ProtocolLib executes this close() method through a PlayerQuitEvent in 
the main thread, 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 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 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) 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 the worker thread is waiting for the main thread to 
finish executing PlayerQuitEvent.

TLDR: Concurrenty is hard.
Dieser Commit ist enthalten in:
Kristian S. Stangeland 2013-12-24 02:15:22 +01:00
Ursprung 16dd2d5d1b
Commit e56c0fec00
2 geänderte Dateien mit 39 neuen und 13 gelöschten Zeilen

Datei anzeigen

@ -4,10 +4,12 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import com.google.common.collect.Lists;
import net.minecraft.util.io.netty.channel.Channel;
// Hopefully, CB won't version these as well
import net.minecraft.util.io.netty.channel.ChannelFuture;
import net.minecraft.util.io.netty.channel.ChannelHandler;
@ -94,11 +96,16 @@ class BootstrapList implements List<Object> {
* @param future - the future.
*/
protected void unprocessBootstrap(ChannelFuture future) {
try {
future.channel().pipeline().remove(handler);
} catch (NoSuchElementException e) {
// Whatever
final Channel channel = future.channel();
// For thread safety - see ChannelInjector.close()
channel.eventLoop().submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
channel.pipeline().remove(handler);
return null;
}
});
}
/**

Datei anzeigen

@ -6,8 +6,8 @@ import java.net.SocketAddress;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import net.minecraft.util.com.mojang.authlib.GameProfile;
@ -510,12 +510,31 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
if (injected) {
channelField.revertValue();
try {
originalChannel.pipeline().remove(this);
// Calling remove() in the main thread will block the main thread, which may lead
// to a deadlock:
// http://pastebin.com/L3SBVKzp
//
// ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread,
// 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
// 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
// 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)
// 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
// the worker thread is waiting for the main thread to finish executing PlayerQuitEvent.
//
// TLDR: Concurrenty is hard.
originalChannel.eventLoop().submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
originalChannel.pipeline().remove(ChannelInjector.this);
originalChannel.pipeline().remove(protocolEncoder);
} catch (NoSuchElementException e) {
// Ignore it - the player has logged out
return null;
}
});
// Clear cache
factory.invalidate(player);
}