Identify player connections by socket address.

It's the only thing that will not not be removed when a
network manager closes, making it relatively safe to block on.
This commit is contained in:
Kristian S. Stangeland 2013-02-28 01:39:49 +01:00
parent fbfbd28bea
commit d60ab3e953
7 changed files with 202 additions and 186 deletions

View File

@ -19,15 +19,20 @@ package com.comphenix.protocol.concurrency;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.google.common.collect.MapMaker; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
/** /**
* A map that supports blocking on read operations. Null keys are not supported. * A map that supports blocking on read operations. Null keys are not supported.
* <p> * <p>
* Keys are stored as weak references, and will be automatically removed once they've all been dereferenced. * Values are stored as weak references, and will be automatically removed once they've all been dereferenced.
* <p> * <p>
* @author Kristian * @author Kristian
* *
@ -35,8 +40,8 @@ import com.google.common.collect.MapMaker;
* @param <TValue> - type of the value. * @param <TValue> - type of the value.
*/ */
public class BlockingHashMap<TKey, TValue> { public class BlockingHashMap<TKey, TValue> {
// Map of values // Map of values
private final Cache<TKey, TValue> backingCache;
private final ConcurrentMap<TKey, TValue> backingMap; private final ConcurrentMap<TKey, TValue> backingMap;
// Map of locked objects // Map of locked objects
@ -46,8 +51,24 @@ public class BlockingHashMap<TKey, TValue> {
* Initialize a new map. * Initialize a new map.
*/ */
public BlockingHashMap() { public BlockingHashMap() {
backingMap = new MapMaker().weakKeys().makeMap(); backingCache = CacheBuilder.newBuilder().weakValues().removalListener(
locks = new MapMaker().weakKeys().makeMap(); new RemovalListener<TKey, TValue>() {
@Override
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
// Clean up locks too
locks.remove(entry.getKey());
}
}).build(
new CacheLoader<TKey, TValue>() {
@Override
public TValue load(TKey key) throws Exception {
throw new IllegalStateException("Illegal use. Access the map directly instead.");
}
});
backingMap = backingCache.asMap();
// Normal concurrent hash map
locks = new ConcurrentHashMap<TKey, Object>();
} }
/** /**
@ -94,21 +115,40 @@ public class BlockingHashMap<TKey, TValue> {
* @throws InterruptedException If the current thread got interrupted while waiting. * @throws InterruptedException If the current thread got interrupted while waiting.
*/ */
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException { public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
return get(key, timeout, unit, false);
}
/**
* Waits until a value has been associated with the given key, and then retrieves that value.
* <p>
* If timeout is zero, this method will return immediately if it can't find an socket injector.
*
* @param key - the key whose associated value is to be returned
* @param timeout - the amount of time to wait until an association has been made.
* @param unit - unit of timeout.
* @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise.
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
* @throws InterruptedException If the current thread got interrupted while waiting.
*/
public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException {
if (key == null) if (key == null)
throw new IllegalArgumentException("key cannot be NULL."); throw new IllegalArgumentException("key cannot be NULL.");
if (unit == null) if (unit == null)
throw new IllegalArgumentException("Unit cannot be NULL."); throw new IllegalArgumentException("Unit cannot be NULL.");
if (timeout < 0)
throw new IllegalArgumentException("Timeout cannot be less than zero.");
TValue value = backingMap.get(key); TValue value = backingMap.get(key);
// Only lock if no value is available // Only lock if no value is available
if (value == null) { if (value == null && timeout > 0) {
final Object lock = getLock(key); final Object lock = getLock(key);
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout); final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
// Don't exceed the timeout // Don't exceed the timeout
synchronized (lock) { synchronized (lock) {
while (value == null) { while (value == null) {
try {
long remainingTime = stopTimeNS - System.nanoTime(); long remainingTime = stopTimeNS - System.nanoTime();
if (remainingTime > 0) { if (remainingTime > 0) {
@ -118,10 +158,14 @@ public class BlockingHashMap<TKey, TValue> {
// Timeout elapsed // Timeout elapsed
break; break;
} }
} catch (InterruptedException e) {
// This is fairly dangerous - but we might HAVE to block the thread
if (!ignoreInterrupted)
throw e;
}
} }
} }
} }
return value; return value;
} }

View File

@ -17,18 +17,14 @@
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field;
import java.net.SocketAddress;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -40,22 +36,20 @@ import com.google.common.collect.Maps;
class NetLoginInjector { class NetLoginInjector {
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap(); private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
private static Field networkManagerField;
private static Field socketAddressField;
// Handles every hook // Handles every hook
private ProxyPlayerInjectionHandler injectionHandler; private ProxyPlayerInjectionHandler injectionHandler;
// Associate input streams and injectors // Create temporary players
private AbstractInputStreamLookup inputStreamLookup; private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
// The current error rerporter // The current error reporter
private ErrorReporter reporter; private ErrorReporter reporter;
private Server server;
public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, AbstractInputStreamLookup inputStreamLookup) { public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
this.reporter = reporter; this.reporter = reporter;
this.server = server;
this.injectionHandler = injectionHandler; this.injectionHandler = injectionHandler;
this.inputStreamLookup = inputStreamLookup;
} }
/** /**
@ -69,23 +63,17 @@ class NetLoginInjector {
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
return inserting; return inserting;
Object networkManager = getNetworkManager(inserting); Player temporary = playerFactory.createTemporaryPlayer(server);
SocketAddress address = getAddress(networkManager); PlayerInjector injector = injectionHandler.injectPlayer(temporary, inserting, GamePhase.LOGIN);
// Get the underlying socket
SocketInjector socketInjector = inputStreamLookup.getSocketInjector(address);
// This is the case if we're dealing with a connection initiated by the injected server socket
if (socketInjector != null) {
PlayerInjector injector = injectionHandler.injectPlayer(socketInjector.getPlayer(), inserting, GamePhase.LOGIN);
if (injector != null) { if (injector != null) {
// Update injector as well
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
injector.updateOnLogin = true; injector.updateOnLogin = true;
// Save the login // Save the login
injectedLogins.putIfAbsent(inserting, injector); injectedLogins.putIfAbsent(inserting, injector);
} }
}
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting; return inserting;
@ -98,36 +86,6 @@ class NetLoginInjector {
} }
} }
/**
* Retrieve the network manager from a given pending connection.
* @param inserting - the pending connection.
* @return The referenced network manager.
* @throws IllegalAccessException If we are unable to read the network manager.
*/
private Object getNetworkManager(Object inserting) throws IllegalAccessException {
if (networkManagerField == null) {
networkManagerField = FuzzyReflection.fromObject(inserting, true).
getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass());
}
return FieldUtils.readField(networkManagerField, inserting, true);
}
/**
* Retrieve the socket address stored in a network manager.
* @param networkManager - the network manager.
* @return The associated socket address.
* @throws IllegalAccessException If we are unable to read the address.
*/
private SocketAddress getAddress(Object networkManager) throws IllegalAccessException {
if (socketAddressField == null) {
socketAddressField = FuzzyReflection.fromObject(networkManager, true).
getFieldByType("socketAddress", SocketAddress.class);
}
return (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true);
}
/** /**
* Invoked when a NetLoginHandler should be reverted. * Invoked when a NetLoginHandler should be reverted.
* @param inserting - the original NetLoginHandler. * @param inserting - the original NetLoginHandler.

View File

@ -180,6 +180,7 @@ class NetworkServerInjector extends PlayerInjector {
return; return;
if (!tryInjectManager()) { if (!tryInjectManager()) {
Class<?> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass();
// Try to override the proxied object // Try to override the proxied object
if (proxyServerField != null) { if (proxyServerField != null) {
@ -188,6 +189,8 @@ class NetworkServerInjector extends PlayerInjector {
if (serverHandler == null) if (serverHandler == null)
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL."); throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
else
serverHandlerClass = serverHandler.getClass();
// Try again // Try again
if (tryInjectManager()) { if (tryInjectManager()) {
@ -198,7 +201,7 @@ class NetworkServerInjector extends PlayerInjector {
throw new RuntimeException( throw new RuntimeException(
"Cannot hook player: Unable to find a valid constructor for the " "Cannot hook player: Unable to find a valid constructor for the "
+ MinecraftReflection.getNetServerHandlerClass().getName() + " object."); + serverHandlerClass.getName() + " object.");
} }
} }

View File

@ -60,6 +60,7 @@ abstract class PlayerInjector implements SocketInjector {
protected static Field networkManagerField; protected static Field networkManagerField;
protected static Field netHandlerField; protected static Field netHandlerField;
protected static Field socketField; protected static Field socketField;
protected static Field socketAddressField;
private static Field inputField; private static Field inputField;
private static Field entityPlayerField; private static Field entityPlayerField;
@ -87,8 +88,9 @@ abstract class PlayerInjector implements SocketInjector {
protected Object serverHandler; protected Object serverHandler;
protected Object netHandler; protected Object netHandler;
// Current socket // Current socket and address
protected Socket socket; protected Socket socket;
protected SocketAddress socketAddress;
// The packet manager and filters // The packet manager and filters
protected ListenerInvoker invoker; protected ListenerInvoker invoker;
@ -250,7 +252,8 @@ abstract class PlayerInjector implements SocketInjector {
public Socket getSocket() throws IllegalAccessException { public Socket getSocket() throws IllegalAccessException {
try { try {
if (socketField == null) if (socketField == null)
socketField = FuzzyReflection.fromObject(networkManager, true).getFieldListByType(Socket.class).get(0); socketField = FuzzyReflection.fromObject(networkManager, true).
getFieldListByType(Socket.class).get(0);
if (socket == null) if (socket == null)
socket = (Socket) FieldUtils.readField(socketField, networkManager, true); socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
return socket; return socket;
@ -261,19 +264,23 @@ abstract class PlayerInjector implements SocketInjector {
} }
/** /**
* Retrieve the associated address of this player. * Retrieve the associated remote address of a player.
* @return The associated address. * @return The associated remote address..
* @throws IllegalAccessException If we're unable to read the socket field. * @throws IllegalAccessException If we're unable to read the socket address field.
*/ */
@Override @Override
public SocketAddress getAddress() throws IllegalAccessException { public SocketAddress getAddress() throws IllegalAccessException {
Socket socket = getSocket(); try {
if (socketAddressField == null)
socketAddressField = FuzzyReflection.fromObject(networkManager, true).
getFieldListByType(SocketAddress.class).get(0);
if (socketAddress == null)
socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true);
return socketAddress;
// Guard against NULL } catch (IndexOutOfBoundsException e) {
if (socket != null) throw new IllegalAccessException("Unable to read the socket address field.");
return socket.getRemoteSocketAddress(); }
else
return null;
} }
/** /**

View File

@ -20,9 +20,12 @@ package com.comphenix.protocol.injector.player;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.SocketAddress;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -39,7 +42,7 @@ import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.InputStreamLookupBuilder; import com.comphenix.protocol.injector.server.InputStreamLookupBuilder;
import com.comphenix.protocol.injector.server.SocketInjector; import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -105,7 +108,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
build(); build();
// Create net login injectors and the server connection injector // Create net login injectors and the server connection injector
this.netLoginInjector = new NetLoginInjector(reporter, this, inputStreamLookup); this.netLoginInjector = new NetLoginInjector(reporter, server, this);
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector); this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
serverInjection.injectList(); serverInjection.injectList();
} }
@ -216,7 +219,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
@Override @Override
public Player getPlayerByConnection(DataInputStream inputStream) { public Player getPlayerByConnection(DataInputStream inputStream) {
// Wait until the connection owner has been established // Wait until the connection owner has been established
SocketInjector injector = inputStreamLookup.getSocketInjector(inputStream); SocketInjector injector = inputStreamLookup.waitSocketInjector(inputStream);
if (injector != null) { if (injector != null) {
return injector.getPlayer(); return injector.getPlayer();
@ -309,24 +312,18 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
injector.initialize(injectionPoint); injector.initialize(injectionPoint);
// Get socket and socket injector // Get socket and socket injector
Socket socket = injector.getSocket(); SocketAddress address = injector.getAddress();
SocketInjector previous = null; SocketInjector previous = inputStreamLookup.peekSocketInjector(address);
// Due to a race condition, the main server "accept connections" thread may
// get a closed network manager with a NULL input stream,
if (socket == null) {
}
// Close any previously associated hooks before we proceed // Close any previously associated hooks before we proceed
if (previous != null && previous instanceof PlayerInjector) { if (previous != null && !(player instanceof Factory)) {
uninjectPlayer(previous.getPlayer(), true); uninjectPlayer(previous.getPlayer(), true);
} }
injector.injectManager(); injector.injectManager();
// Save injector // Save injector
inputStreamLookup.setSocketInjector(socket, injector); inputStreamLookup.setSocketInjector(address, injector);
break; break;
} }
@ -453,7 +450,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
@Override @Override
public boolean uninjectPlayer(InetSocketAddress address) { public boolean uninjectPlayer(InetSocketAddress address) {
if (!hasClosed && address != null) { if (!hasClosed && address != null) {
SocketInjector injector = inputStreamLookup.getSocketInjector(address); SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
// Clean up // Clean up
if (injector != null) if (injector != null)
@ -495,7 +492,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
*/ */
@Override @Override
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
PlayerInjector injector = getInjector(player); PlayerInjector injector = getInjector(player);
// Process the given packet, or simply give up // Process the given packet, or simply give up
@ -518,16 +514,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
if (injector == null) { if (injector == null) {
// Try getting it from the player itself // Try getting it from the player itself
SocketInjector socket = TemporaryPlayerFactory.getInjectorFromPlayer(player); SocketAddress address = player.getAddress();
// Look that up without blocking
SocketInjector result = inputStreamLookup.peekSocketInjector(address);
// Only accept it if it's a player injector // Ensure that it is non-null and a player injector
if (!(socket instanceof PlayerInjector)) { if (result instanceof PlayerInjector)
socket = inputStreamLookup.getSocketInjector(player.getAddress()); return (PlayerInjector) result;
}
// Ensure that it is a player injector
if (socket instanceof PlayerInjector)
return (PlayerInjector) socket;
else else
return null; return null;
} else { } else {

View File

@ -63,43 +63,40 @@ public abstract class AbstractInputStreamLookup {
*/ */
public abstract void postWorldLoaded(); public abstract void postWorldLoaded();
/**
* Retrieve the associated socket injector for a player.
* @param filtered - the indentifying filtered input stream.
* @return The socket injector we have associated with this player.
* @throws FieldAccessException Unable to access input stream.
*/
public SocketInjector getSocketInjector(FilterInputStream filtered) {
return getSocketInjector(getInputStream(filtered));
}
/** /**
* Retrieve the associated socket injector for a player. * Retrieve the associated socket injector for a player.
* @param input - the indentifying filtered input stream. * @param input - the indentifying filtered input stream.
* @return The socket injector we have associated with this player. * @return The socket injector we have associated with this player.
*/ */
public abstract SocketInjector getSocketInjector(InputStream input); public abstract SocketInjector waitSocketInjector(InputStream input);
/** /**
* Retrieve an injector by its socket. * Retrieve an injector by its socket.
* @param socket - the socket. * @param socket - the socket.
* @return The socket injector. * @return The socket injector.
*/ */
public abstract SocketInjector getSocketInjector(Socket socket); public abstract SocketInjector waitSocketInjector(Socket socket);
/** /**
* Retrieve a injector by its address. * Retrieve a injector by its address.
* @param address - the address of the socket. * @param address - the address of the socket.
* @return The socket injector, or NULL if not found. * @return The socket injector, or NULL if not found.
*/ */
public abstract SocketInjector getSocketInjector(SocketAddress address); public abstract SocketInjector waitSocketInjector(SocketAddress address);
/** /**
* Associate a given socket the provided socket injector. * Attempt to get a socket injector without blocking the thread.
* @param input - the socket to associate. * @param address - the address to lookup.
* @return The socket injector, or NULL if not found.
*/
public abstract SocketInjector peekSocketInjector(SocketAddress address);
/**
* Associate a given socket address to the provided socket injector.
* @param input - the socket address to associate.
* @param injector - the injector. * @param injector - the injector.
*/ */
public abstract void setSocketInjector(Socket socket, SocketInjector injector); public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
/** /**
* If a player can hold a reference to its parent injector, this method will update that reference. * If a player can hold a reference to its parent injector, this method will update that reference.
@ -111,8 +108,7 @@ public abstract class AbstractInputStreamLookup {
// Default implementation // Default implementation
if (player instanceof InjectorContainer) { if (player instanceof InjectorContainer) {
InjectorContainer container = (InjectorContainer) player; TemporaryPlayerFactory.setInjectorInPlayer(player, current);
container.setInjector(current);
} }
} }

View File

@ -6,10 +6,11 @@ import java.lang.reflect.Field;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
@ -17,16 +18,31 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.collect.MapMaker; import com.google.common.collect.MapMaker;
class InputStreamReflectLookup extends AbstractInputStreamLookup { class InputStreamReflectLookup extends AbstractInputStreamLookup {
// Using weak keys and values ensures that we will not hold up garbage collection // The default lookup timeout
protected ConcurrentMap<Socket, SocketInjector> ownerSocket = new MapMaker().weakKeys().makeMap(); private static final long DEFAULT_TIMEOUT = 2000; // ms
protected ConcurrentMap<SocketAddress, Socket> addressLookup = new MapMaker().weakValues().makeMap();
protected ConcurrentMap<InputStream, Socket> inputLookup = new MapMaker().weakValues().makeMap();
// Used to create fake players // Using weak keys and values ensures that we will not hold up garbage collection
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); protected BlockingHashMap<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
protected ConcurrentMap<InputStream, SocketAddress> inputLookup = new MapMaker().weakValues().makeMap();
// The timeout
private final long injectorTimeout;
public InputStreamReflectLookup(ErrorReporter reporter, Server server) { public InputStreamReflectLookup(ErrorReporter reporter, Server server) {
this(reporter, server, DEFAULT_TIMEOUT);
}
/**
* Initialize a reflect lookup with a given default injector timeout.
* <p>
* This timeout defines the maximum amount of time to wait until an injector has been discovered.
* @param reporter - the error reporter.
* @param server - the current Bukkit server.
* @param injectorTimeout - the injector timeout.
*/
public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) {
super(reporter, server); super(reporter, server);
this.injectorTimeout = injectorTimeout;
} }
@Override @Override
@ -40,32 +56,43 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup {
} }
@Override @Override
public SocketInjector getSocketInjector(Socket socket) { public SocketInjector peekSocketInjector(SocketAddress address) {
SocketInjector result = ownerSocket.get(socket); try {
return addressLookup.get(address, 0, TimeUnit.MILLISECONDS);
if (result == null) { } catch (InterruptedException e) {
Player player = tempPlayerFactory.createTemporaryPlayer(server); // Whatever
SocketInjector created = new TemporarySocketInjector(player, socket); return null;
result = ownerSocket.putIfAbsent(socket, created);
if (result == null) {
// We won - use our created injector
TemporaryPlayerFactory.setInjectorInPlayer(player, created);
result = created;
} }
} }
return result;
}
@Override @Override
public SocketInjector getSocketInjector(InputStream input) { public SocketInjector waitSocketInjector(SocketAddress address) {
try { try {
Socket socket = getSocket(input); // Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to
// periodically wake up waiting readers and writers. We have to wait for the dedicated server thread
// to catch up, so we'll swallow these interrupts.
//
// TODO: Consider if we should raise the thread priority of the dedicated server listener thread.
return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true);
} catch (InterruptedException e) {
// This cannot be!
throw new IllegalStateException("Impossible exception occured!", e);
}
}
@Override
public SocketInjector waitSocketInjector(Socket socket) {
return waitSocketInjector(socket.getRemoteSocketAddress());
}
@Override
public SocketInjector waitSocketInjector(InputStream input) {
try {
SocketAddress address = getSocketAddress(input);
// Guard against NPE // Guard against NPE
if (socket != null) if (address != null)
return getSocketInjector(socket); return waitSocketInjector(address);
else else
return null; return null;
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
@ -74,38 +101,36 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup {
} }
/** /**
* Use reflection to get the underlying socket from an input stream. * Use reflection to get the underlying socket address from an input stream.
* @param stream - the socket stream to lookup. * @param stream - the socket stream to lookup.
* @return The underlying socket, or NULL if not found. * @return The underlying socket address, or NULL if not found.
* @throws IllegalAccessException Unable to access socket field. * @throws IllegalAccessException Unable to access socket field.
*/ */
private Socket getSocket(InputStream stream) throws IllegalAccessException { private SocketAddress getSocketAddress(InputStream stream) throws IllegalAccessException {
// Extra check, just in case // Extra check, just in case
if (stream instanceof FilterInputStream) if (stream instanceof FilterInputStream)
return getSocket(getInputStream((FilterInputStream) stream)); return getSocketAddress(getInputStream((FilterInputStream) stream));
Socket result = inputLookup.get(stream); SocketAddress result = inputLookup.get(stream);
if (result == null) { if (result == null) {
result = lookupSocket(stream); Socket socket = lookupSocket(stream);
// Save it // Save it
result = socket.getRemoteSocketAddress();
inputLookup.put(stream, result); inputLookup.put(stream, result);
} }
return result; return result;
} }
@Override @Override
public void setSocketInjector(Socket socket, SocketInjector injector) { public void setSocketInjector(SocketAddress address, SocketInjector injector) {
if (socket == null) if (address == null)
throw new IllegalArgumentException("socket cannot be NULL"); throw new IllegalArgumentException("address cannot be NULL");
if (injector == null) if (injector == null)
throw new IllegalArgumentException("injector cannot be NULL."); throw new IllegalArgumentException("injector cannot be NULL.");
SocketInjector previous = ownerSocket.put(socket, injector); SocketInjector previous = addressLookup.put(address, injector);
// Save the address lookup too
addressLookup.put(socket.getRemoteSocketAddress(), socket);
// Any previous temporary players will also be associated // Any previous temporary players will also be associated
if (previous != null) { if (previous != null) {
@ -114,16 +139,6 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup {
} }
} }
@Override
public SocketInjector getSocketInjector(SocketAddress address) {
Socket socket = addressLookup.get(address);
if (socket != null)
return getSocketInjector(socket);
else
return null;
}
@Override @Override
public void cleanupAll() { public void cleanupAll() {
// Do nothing // Do nothing