Reworked RMI so it will not crash anything on the "remote" side. There

will be errors emitted, and the "local" side will receive a null object
+ invalid RMI ID.
This commit is contained in:
nathan 2018-01-28 10:23:12 +01:00
parent 10d8cc6061
commit 0404ff408d
8 changed files with 222 additions and 274 deletions

View File

@ -41,8 +41,8 @@ import dorkbox.network.connection.wrapper.ChannelNull;
import dorkbox.network.connection.wrapper.ChannelWrapper; import dorkbox.network.connection.wrapper.ChannelWrapper;
import dorkbox.network.rmi.*; import dorkbox.network.rmi.*;
import dorkbox.network.serialization.CryptoSerializationManager; import dorkbox.network.serialization.CryptoSerializationManager;
import dorkbox.util.collections.IntMap;
import dorkbox.util.collections.LockFreeHashMap; import dorkbox.util.collections.LockFreeHashMap;
import dorkbox.util.collections.LockFreeIntMap;
import dorkbox.util.generics.ClassHelper; import dorkbox.util.generics.ClassHelper;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandler.Sharable;
@ -66,9 +66,6 @@ import io.netty.util.concurrent.Promise;
@Sharable @Sharable
public public
class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConnection, Connection, Listeners, ConnectionBridge { class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConnection, Connection, Listeners, ConnectionBridge {
// default false
public static boolean ENABLE_PROXY_OBJECTS = true;
public static public static
boolean isTcp(Class<? extends Channel> channelClass) { boolean isTcp(Class<? extends Channel> channelClass) {
return channelClass == NioSocketChannel.class || channelClass == EpollSocketChannel.class; return channelClass == NioSocketChannel.class || channelClass == EpollSocketChannel.class;
@ -128,8 +125,8 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
private final Map<Integer, RemoteObject> proxyIdCache; private final Map<Integer, RemoteObject> proxyIdCache;
private final List<Listener.OnMessageReceived<Connection, InvokeMethodResult>> proxyListeners; private final List<Listener.OnMessageReceived<Connection, InvokeMethodResult>> proxyListeners;
private final IntMap<RemoteObjectCallback> rmiRegistrationCallbacks; private final LockFreeIntMap<RemoteObjectCallback> rmiRegistrationCallbacks;
private int rmiCallbackId = 0; // protected by synchronized (rmiRegistrationCallbacks) private volatile int rmiCallbackId = 0;
/** /**
* All of the parameters can be null, when metaChannel wants to get the base class type * All of the parameters can be null, when metaChannel wants to get the base class type
@ -142,9 +139,11 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
if (endPoint != null && endPoint.globalRmiBridge != null) { if (endPoint != null && endPoint.globalRmiBridge != null) {
// rmi is enabled. // rmi is enabled.
// because this is PER CONNECTION, there is no need for synchronize(), since there will not be any issues with concurrent
// access, but there WILL be issues with thread visibility because a different worker thread can be called for different connections
proxyIdCache = new LockFreeHashMap<Integer, RemoteObject>(); proxyIdCache = new LockFreeHashMap<Integer, RemoteObject>();
proxyListeners = new CopyOnWriteArrayList<Listener.OnMessageReceived<Connection, InvokeMethodResult>>(); proxyListeners = new CopyOnWriteArrayList<Listener.OnMessageReceived<Connection, InvokeMethodResult>>();
rmiRegistrationCallbacks = new IntMap<RemoteObjectCallback>(); rmiRegistrationCallbacks = new LockFreeIntMap<RemoteObjectCallback>();
} else { } else {
proxyIdCache = null; proxyIdCache = null;
proxyListeners = null; proxyListeners = null;
@ -699,15 +698,11 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
// proxy listeners are cleared in the removeAll() call // proxy listeners are cleared in the removeAll() call
if (proxyIdCache != null) { if (proxyIdCache != null) {
synchronized (proxyIdCache) { proxyIdCache.clear();
proxyIdCache.clear();
}
} }
if (rmiRegistrationCallbacks != null) { if (rmiRegistrationCallbacks != null) {
synchronized (rmiRegistrationCallbacks) { rmiRegistrationCallbacks.clear();
rmiRegistrationCallbacks.clear();
}
} }
} }
} }
@ -971,13 +966,12 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
throw new IllegalArgumentException("Cannot create a proxy for RMI access. It must be an interface."); throw new IllegalArgumentException("Cannot create a proxy for RMI access. It must be an interface.");
} }
RmiRegistration message; // because this is PER CONNECTION, there is no need for synchronize(), since there will not be any issues with concurrent
// access, but there WILL be issues with thread visibility because a different worker thread can be called for different connections
synchronized (rmiRegistrationCallbacks) { //noinspection NonAtomicOperationOnVolatileField
int nextRmiCallbackId = rmiCallbackId++; int nextRmiCallbackId = rmiCallbackId++;
rmiRegistrationCallbacks.put(nextRmiCallbackId, callback); rmiRegistrationCallbacks.put(nextRmiCallbackId, callback);
message = new RmiRegistration(interfaceClass, RmiBridge.INVALID_RMI, nextRmiCallbackId); RmiRegistration message = new RmiRegistration(interfaceClass, RmiBridge.INVALID_RMI, nextRmiCallbackId);
}
// We use a callback to notify us when the object is ready. We can't "create this on the fly" because we // We use a callback to notify us when the object is ready. We can't "create this on the fly" because we
// have to wait for the object to be created + ID to be assigned on the remote system BEFORE we can create the proxy instance here. // have to wait for the object to be created + ID to be assigned on the remote system BEFORE we can create the proxy instance here.
@ -989,15 +983,21 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
@Override @Override
public final public final
<Iface> void getRemoteObject(final int objectId, final RemoteObjectCallback<Iface> callback) { <Iface> void getRemoteObject(final int objectId, final RemoteObjectCallback<Iface> callback) {
RmiRegistration message; if (objectId < 0) {
throw new IllegalStateException("Object ID cannot be < 0");
}
if (objectId >= RmiBridge.INVALID_RMI) {
throw new IllegalStateException("Object ID cannot be >= " + RmiBridge.INVALID_RMI);
}
Class<?> iFaceClass = ClassHelper.getGenericParameterAsClassForSuperClass(RemoteObjectCallback.class, callback.getClass(), 0); Class<?> iFaceClass = ClassHelper.getGenericParameterAsClassForSuperClass(RemoteObjectCallback.class, callback.getClass(), 0);
synchronized (rmiRegistrationCallbacks) { // because this is PER CONNECTION, there is no need for synchronize(), since there will not be any issues with concurrent
int nextRmiCallbackId = rmiCallbackId++; // access, but there WILL be issues with thread visibility because a different worker thread can be called for different connections
rmiRegistrationCallbacks.put(nextRmiCallbackId, callback); //noinspection NonAtomicOperationOnVolatileField
message = new RmiRegistration(iFaceClass, objectId, nextRmiCallbackId); int nextRmiCallbackId = rmiCallbackId++;
} rmiRegistrationCallbacks.put(nextRmiCallbackId, callback);
RmiRegistration message = new RmiRegistration(iFaceClass, objectId, nextRmiCallbackId);
// We use a callback to notify us when the object is ready. We can't "create this on the fly" because we // We use a callback to notify us when the object is ready. We can't "create this on the fly" because we
// have to wait for the object to be created + ID to be assigned on the remote system BEFORE we can create the proxy instance here. // have to wait for the object to be created + ID to be assigned on the remote system BEFORE we can create the proxy instance here.
@ -1072,55 +1072,61 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
rmiId = rmiBridge.register(object); rmiId = rmiBridge.register(object);
if (rmiId == RmiBridge.INVALID_RMI) {
// this means that there are too many RMI ids (either global or connection specific!)
object = null;
}
else {
// if we are invalid, skip going over fields that might also be RMI objects, BECAUSE our object will be NULL!
// the @Rmi annotation allows an RMI object to have fields with objects that are ALSO RMI objects
LinkedList<Map.Entry<Class<?>, Object>> classesToCheck = new LinkedList<Map.Entry<Class<?>, Object>>();
classesToCheck.add(new AbstractMap.SimpleEntry<Class<?>, Object>(implementationClass, object));
// the @Rmi annotation allows an RMI object to have fields with objects that are ALSO RMI objects Map.Entry<Class<?>, Object> remoteClassObject;
LinkedList<Map.Entry<Class<?>, Object>> classesToCheck = new LinkedList<Map.Entry<Class<?>, Object>>(); while (!classesToCheck.isEmpty()) {
classesToCheck.add(new AbstractMap.SimpleEntry<Class<?>, Object>(implementationClass, object)); remoteClassObject = classesToCheck.removeFirst();
// we have to check the IMPLEMENTATION for any additional fields that will have proxy information.
// we use getDeclaredFields() + walking the object hierarchy, so we get ALL the fields possible (public + private).
for (Field field : remoteClassObject.getKey()
.getDeclaredFields()) {
if (field.getAnnotation(Rmi.class) != null) {
final Class<?> type = field.getType();
if (!type.isInterface()) {
// the type must be an interface, otherwise RMI cannot create a proxy object
logger.error("Error checking RMI fields for: {}.{} -- It is not an interface!",
remoteClassObject.getKey(),
field.getName());
continue;
}
Map.Entry<Class<?>, Object> remoteClassObject; boolean prev = field.isAccessible();
while (!classesToCheck.isEmpty()) { field.setAccessible(true);
remoteClassObject = classesToCheck.removeFirst(); final Object o;
try {
o = field.get(remoteClassObject.getValue());
// we have to check the IMPLEMENTATION for any additional fields that will have proxy information. rmiBridge.register(o);
// we use getDeclaredFields() + walking the object hierarchy, so we get ALL the fields possible (public + private). classesToCheck.add(new AbstractMap.SimpleEntry<Class<?>, Object>(type, o));
for (Field field : remoteClassObject.getKey() } catch (IllegalAccessException e) {
.getDeclaredFields()) { logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e);
if (field.getAnnotation(Rmi.class) != null) { } finally {
final Class<?> type = field.getType(); field.setAccessible(prev);
}
if (!type.isInterface()) {
// the type must be an interface, otherwise RMI cannot create a proxy object
logger.error("Error checking RMI fields for: {}.{} -- It is not an interface!",
remoteClassObject.getKey(),
field.getName());
continue;
}
boolean prev = field.isAccessible();
field.setAccessible(true);
final Object o;
try {
o = field.get(remoteClassObject.getValue());
rmiBridge.register(o);
classesToCheck.add(new AbstractMap.SimpleEntry<Class<?>, Object>(type, o));
} catch (IllegalAccessException e) {
logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e);
} finally {
field.setAccessible(prev);
} }
} }
}
// have to check the object hierarchy as well // have to check the object hierarchy as well
Class<?> superclass = remoteClassObject.getKey() Class<?> superclass = remoteClassObject.getKey()
.getSuperclass(); .getSuperclass();
if (superclass != null && superclass != Object.class) { if (superclass != null && superclass != Object.class) {
classesToCheck.add(new AbstractMap.SimpleEntry<Class<?>, Object>(superclass, remoteClassObject.getValue())); classesToCheck.add(new AbstractMap.SimpleEntry<Class<?>, Object>(superclass, remoteClassObject.getValue()));
}
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -1144,10 +1150,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
public final public final
void runRmiCallback(final Class<?> interfaceClass, final int callbackId, final Object remoteObject) { void runRmiCallback(final Class<?> interfaceClass, final int callbackId, final Object remoteObject) {
RemoteObjectCallback callback; RemoteObjectCallback callback = rmiRegistrationCallbacks.remove(callbackId);
synchronized (rmiRegistrationCallbacks) {
callback = rmiRegistrationCallbacks.remove(callbackId);
}
try { try {
//noinspection unchecked //noinspection unchecked
@ -1173,11 +1176,12 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
} }
int objectId = globalRmiBridge.getRegisteredId(object); int objectId = globalRmiBridge.getRegisteredId(object);
if (objectId == RmiBridge.INVALID_RMI) { if (objectId != RmiBridge.INVALID_RMI) {
return rmiBridge.getRegisteredId(object);
} else {
return objectId; return objectId;
} }
else {
return rmiBridge.getRegisteredId(object);
}
} }
/** /**
@ -1212,30 +1216,29 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
throw new IllegalArgumentException("iface must be an interface."); throw new IllegalArgumentException("iface must be an interface.");
} }
synchronized (proxyIdCache) { // we want to have a connection specific cache of IDs
// we want to have a connection specific cache of IDs, using weak references. // because this is PER CONNECTION, there is no need for synchronize(), since there will not be any issues with concurrent
// because this is PER CONNECTION, this is safe. // access, but there WILL be issues with thread visibility because a different worker thread can be called for different connections
RemoteObject remoteObject = proxyIdCache.get(objectID); RemoteObject remoteObject = proxyIdCache.get(objectID);
if (remoteObject == null) { if (remoteObject == null) {
// duplicates are fine, as they represent the same object (as specified by the ID) on the remote side. // duplicates are fine, as they represent the same object (as specified by the ID) on the remote side.
// remoteObject = rmiBridge.createProxyObject(this, objectID, iFace); // remoteObject = rmiBridge.createProxyObject(this, objectID, iFace);
// the ACTUAL proxy is created in the connection impl. // the ACTUAL proxy is created in the connection impl.
RmiProxyHandler proxyObject = new RmiProxyHandler(this, objectID, iFace); RmiProxyHandler proxyObject = new RmiProxyHandler(this, objectID, iFace);
proxyListeners.add(proxyObject.getListener()); proxyListeners.add(proxyObject.getListener());
Class<?>[] temp = new Class<?>[2]; Class<?>[] temp = new Class<?>[2];
temp[0] = RemoteObject.class; temp[0] = RemoteObject.class;
temp[1] = iFace; temp[1] = iFace;
remoteObject = (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(), temp, proxyObject); remoteObject = (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(), temp, proxyObject);
proxyIdCache.put(objectID, remoteObject); proxyIdCache.put(objectID, remoteObject);
}
return remoteObject;
} }
return remoteObject;
} }
/** /**
@ -1247,9 +1250,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
if (RmiBridge.isGlobal(objectID)) { if (RmiBridge.isGlobal(objectID)) {
RmiBridge globalRmiBridge = endPoint.globalRmiBridge; RmiBridge globalRmiBridge = endPoint.globalRmiBridge;
if (globalRmiBridge == null) { assert globalRmiBridge != null;
throw new NullPointerException("Unable to call 'getRegisteredId' when the gloablRmiBridge is null!");
}
return globalRmiBridge.getRegisteredObject(objectID); return globalRmiBridge.getRegisteredObject(objectID);
} else { } else {

View File

@ -221,8 +221,8 @@ class EndPoint extends Shutdownable {
if (rmiEnabled) { if (rmiEnabled) {
rmiHandler = null; rmiHandler = null;
localRmiHandler = new RmiObjectLocalHandler(); localRmiHandler = new RmiObjectLocalHandler(logger);
networkRmiHandler = new RmiObjectNetworkHandler(); networkRmiHandler = new RmiObjectNetworkHandler(logger);
globalRmiBridge = new RmiBridge(logger, config.rmiExecutor, true); globalRmiBridge = new RmiBridge(logger, config.rmiExecutor, true);
} }
else { else {

View File

@ -21,7 +21,7 @@ package dorkbox.network.rmi;
public public
interface RemoteObjectCallback<Iface> { interface RemoteObjectCallback<Iface> {
/** /**
* @param remoteObject the remote object (as a proxy object) or null if there was an error * @param remoteObject the remote object (as a proxy object) or null if there was an error creating the RMI object
*/ */
void created(Iface remoteObject); void created(Iface remoteObject);
} }

View File

@ -59,10 +59,6 @@ class RemoteObjectSerializer<T> extends Serializer<T> {
void write(Kryo kryo, Output output, T object) { void write(Kryo kryo, Output output, T object) {
KryoExtra kryoExtra = (KryoExtra) kryo; KryoExtra kryoExtra = (KryoExtra) kryo;
int id = kryoExtra.connection.getRegisteredId(object); int id = kryoExtra.connection.getRegisteredId(object);
if (id == RmiBridge.INVALID_RMI) {
throw new IllegalStateException("Object not found in RMI objectSpace: " + object);
}
output.writeInt(id, true); output.writeInt(id, true);
} }

View File

@ -38,20 +38,16 @@ import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.slf4j.Logger; import org.slf4j.Logger;
import com.esotericsoftware.kryo.util.IntMap;
import dorkbox.network.connection.Connection; import dorkbox.network.connection.Connection;
import dorkbox.network.connection.ConnectionImpl; import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.EndPoint; import dorkbox.network.connection.EndPoint;
import dorkbox.network.connection.Listener; import dorkbox.network.connection.Listener;
import dorkbox.network.serialization.RmiSerializationManager; import dorkbox.network.serialization.RmiSerializationManager;
import dorkbox.util.collections.ObjectIntMap; import dorkbox.util.Property;
import dorkbox.util.collections.LockFreeIntBiMap;
/** /**
* Allows methods on objects to be invoked remotely over TCP, UDP, or LOCAL. Local connections ignore TCP/UDP requests, and perform * Allows methods on objects to be invoked remotely over TCP, UDP, or LOCAL. Local connections ignore TCP/UDP requests, and perform
@ -84,34 +80,47 @@ import dorkbox.util.collections.ObjectIntMap;
*/ */
public final public final
class RmiBridge { class RmiBridge {
public static final int INVALID_RMI = 0; /**
* Permits local (in-jvm) connections to bypass creating RMI proxy objects (which is CPU + Memory intensive) in favor of just using the
* object directly. While doing so is considerably faster, it comes at the expense that RMI objects lose the {@link RemoteObject}
* functions.
* <p>
* This will also break logic in a way that "network connection" RMI logic *CAN BE* incompatible.
* <p>
* Default is true
*/
@Property
public static boolean ENABLE_PROXY_OBJECTS = true;
public static final int INVALID_RMI = Integer.MAX_VALUE;
static final int returnValueMask = 1 << 7; static final int returnValueMask = 1 << 7;
static final int returnExceptionMask = 1 << 6; static final int returnExceptionMask = 1 << 6;
static final int responseIdMask = 0xFF & ~returnValueMask & ~returnExceptionMask; static final int responseIdMask = 0xFF & ~returnValueMask & ~returnExceptionMask;
// global RMI objects -> ODD in range 1-16380 (max 2 bytes) throws error on outside of range private static final int INVALID_MAP_ID = -1;
// connection local RMI -> EVEN in range 1-16380 (max 2 bytes) throws error on outside of range
private static final int MAX_RMI_VALUE = 16380;
/** /**
* @return true if the objectId is a "global" id (it's odd) otherwise, false (it's connection local) * @return true if the objectId is global for all connections (even). false if it's connection-only (odd)
*/ */
public static public static
boolean isGlobal(final int objectId) { boolean isGlobal(final int objectId) {
return (objectId & 1) != 0; // global RMI objects -> EVEN in range 0 - (MAX_VALUE-1)
// connection local RMI -> ODD in range 1 - (MAX_VALUE-1)
return (objectId & 1) == 0;
} }
// the name of who created this RmiBridge // the name of who created this RmiBridge
private final org.slf4j.Logger logger; private final org.slf4j.Logger logger;
private final Executor executor;
// we start at 1, because 0 (INVALID_RMI) means we access connection only objects // we start at 1, because 0 (INVALID_RMI) means we access connection only objects
private final AtomicInteger rmiObjectIdCounter; private final AtomicInteger rmiObjectIdCounter;
// can be accessed by DIFFERENT threads. // this is the ID -> Object RMI map. The RMI ID is used (not the kryo ID)
private final ReentrantReadWriteLock objectLock = new ReentrantReadWriteLock(); private final LockFreeIntBiMap<Object> objectMap = new LockFreeIntBiMap<Object>(INVALID_MAP_ID);
private final IntMap<Object> idToObject = new IntMap<Object>();
private final ObjectIntMap<Object> objectToID = new ObjectIntMap<Object>();
private final Executor executor;
private final Listener.OnMessageReceived<ConnectionImpl, InvokeMethod> invokeListener = new Listener.OnMessageReceived<ConnectionImpl, InvokeMethod>() { private final Listener.OnMessageReceived<ConnectionImpl, InvokeMethod> invokeListener = new Listener.OnMessageReceived<ConnectionImpl, InvokeMethod>() {
@SuppressWarnings("AutoBoxing") @SuppressWarnings("AutoBoxing")
@ -175,10 +184,10 @@ class RmiBridge {
this.executor = executor; this.executor = executor;
if (isGlobal) { if (isGlobal) {
rmiObjectIdCounter = new AtomicInteger(1); rmiObjectIdCounter = new AtomicInteger(0);
} }
else { else {
rmiObjectIdCounter = new AtomicInteger(2); rmiObjectIdCounter = new AtomicInteger(1);
} }
} }
@ -291,46 +300,65 @@ class RmiBridge {
// logger.error("{} sent data: {} with id ({})", connection, result, invokeMethod.responseID); // logger.error("{} sent data: {} with id ({})", connection, result, invokeMethod.responseID);
} }
public private
int nextObjectId() { int nextObjectId() {
// always increment by 2 // always increment by 2
// global RMI objects -> ODD in range 1-16380 (max 2 bytes) throws error on outside of range // global RMI objects -> ODD in range 1-16380 (max 2 bytes) throws error on outside of range
// connection local RMI -> EVEN in range 1-16380 (max 2 bytes) throws error on outside of range // connection local RMI -> EVEN in range 1-16380 (max 2 bytes) throws error on outside of range
int value = rmiObjectIdCounter.getAndAdd(2); int value = rmiObjectIdCounter.getAndAdd(2);
if (value > MAX_RMI_VALUE) { if (value >= INVALID_RMI) {
rmiObjectIdCounter.set(MAX_RMI_VALUE); // prevent wrapping by spammy callers rmiObjectIdCounter.set(INVALID_RMI); // prevent wrapping by spammy callers
logger.error("RMI next value has exceeded maximum limits in RmiBridge!"); logger.error("next RMI value '{}' has exceeded maximum limit '{}' in RmiBridge! Not creating RMI object.", value, INVALID_RMI);
return INVALID_RMI;
} }
return value; return value;
} }
/**
* Automatically registers an object with the next available ID to allow the remote end of the RmiBridge connections to access it using the returned ID.
*
* @return the RMI object ID, if the registration failed (null object or TOO MANY objects), it will be {@link RmiBridge#INVALID_RMI} (Integer.MAX_VALUE).
*/
public
int register(Object object) {
if (object == null) {
return INVALID_RMI;
}
// this will return INVALID_RMI if there are too many in the ObjectSpace
int nextObjectId = nextObjectId();
if (nextObjectId != INVALID_RMI) {
// specifically avoid calling register(int, Object) method to skip non-necessary checks + exceptions
objectMap.put(nextObjectId, object);
if (logger.isTraceEnabled()) {
logger.trace("Object <proxy #{}> registered with ObjectSpace with .toString() = '{}'", nextObjectId, object);
}
}
return nextObjectId;
}
/** /**
* Registers an object to allow the remote end of the RmiBridge connections to access it using the specified ID. * Registers an object to allow the remote end of the RmiBridge connections to access it using the specified ID.
* *
* @param objectID * @param objectID Must not be <0 or {@link RmiBridge#INVALID_RMI} (Integer.MAX_VALUE).
* Must not be Integer.MAX_VALUE.
*/ */
@SuppressWarnings("AutoBoxing")
public public
void register(int objectID, Object object) { void register(int objectID, Object object) {
if (objectID == Integer.MAX_VALUE) { if (objectID < 0) {
throw new IllegalArgumentException("objectID cannot be Integer.MAX_VALUE."); throw new IllegalArgumentException("objectID cannot be " + INVALID_RMI);
}
if (objectID >= INVALID_RMI) {
throw new IllegalArgumentException("objectID cannot be " + INVALID_RMI);
} }
if (object == null) { if (object == null) {
throw new IllegalArgumentException("object cannot be null."); throw new IllegalArgumentException("object cannot be null.");
} }
WriteLock writeLock = RmiBridge.this.objectLock.writeLock(); objectMap.put(objectID, object);
writeLock.lock();
this.idToObject.put(objectID, object); if (logger.isTraceEnabled()) {
this.objectToID.put(object, objectID); logger.trace("Object <proxy #{}> registered with ObjectSpace with .toString() = '{}'", objectID, object);
writeLock.unlock();
Logger logger2 = this.logger;
if (logger2.isTraceEnabled()) {
logger2.trace("Object <proxy #{}> registered with ObjectSpace with .toString() = '{}'", objectID, object);
} }
} }
@ -340,19 +368,10 @@ class RmiBridge {
@SuppressWarnings("AutoBoxing") @SuppressWarnings("AutoBoxing")
public public
void remove(int objectID) { void remove(int objectID) {
WriteLock writeLock = RmiBridge.this.objectLock.writeLock(); Object object = objectMap.remove(objectID);
writeLock.lock();
Object object = this.idToObject.remove(objectID); if (logger.isTraceEnabled()) {
if (object != null) { logger.trace("Object <proxy #{}> removed from ObjectSpace with .toString() = '{}'", objectID, object);
this.objectToID.remove(object, 0);
}
writeLock.unlock();
Logger logger2 = this.logger;
if (logger2.isTraceEnabled()) {
logger2.trace("Object <proxy #{}> removed from ObjectSpace with .toString() = '{}'", objectID, object);
} }
} }
@ -362,23 +381,14 @@ class RmiBridge {
@SuppressWarnings("AutoBoxing") @SuppressWarnings("AutoBoxing")
public public
void remove(Object object) { void remove(Object object) {
WriteLock writeLock = RmiBridge.this.objectLock.writeLock(); int objectID = objectMap.inverse()
writeLock.lock(); .remove(object);
if (!this.idToObject.containsValue(object, true)) { if (objectID == INVALID_MAP_ID) {
writeLock.unlock(); logger.error("Object {} could not be found in the ObjectSpace.", object);
return;
} }
else if (logger.isTraceEnabled()) {
int objectID = this.idToObject.findKey(object, true, -1); logger.trace("Object {} (ID: {}) removed from ObjectSpace.", object, objectID);
this.idToObject.remove(objectID);
this.objectToID.remove(object, 0);
writeLock.unlock();
Logger logger2 = this.logger;
if (logger2.isTraceEnabled()) {
logger2.trace("Object {} removed from ObjectSpace: {}", objectID, object);
} }
} }
@ -387,28 +397,26 @@ class RmiBridge {
*/ */
public public
Object getRegisteredObject(final int objectID) { Object getRegisteredObject(final int objectID) {
ReadLock readLock = this.objectLock.readLock(); if (objectID < 0 || objectID >= INVALID_RMI) {
readLock.lock(); return null;
}
// Find an object with the objectID. // Find an object with the objectID.
Object object = this.idToObject.get(objectID); return objectMap.get(objectID);
readLock.unlock();
return object;
} }
/** /**
* Returns the ID registered for the specified object, or Integer.MAX_VALUE if not found. * Returns the ID registered for the specified object, or INVALID_RMI if not found.
*/ */
public public
<T> int getRegisteredId(final T object) { <T> int getRegisteredId(final T object) {
// Find an ID with the object. // Find an ID with the object.
ReadLock readLock = this.objectLock.readLock(); int i = objectMap.inverse()
.get(object);
if (i == INVALID_MAP_ID) {
return INVALID_RMI;
}
readLock.lock(); return i;
int id = this.objectToID.get(object, Integer.MAX_VALUE);
readLock.unlock();
return id;
} }
} }

View File

@ -20,6 +20,8 @@ import java.lang.reflect.Proxy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.slf4j.Logger;
import com.esotericsoftware.kryo.KryoException; import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.Serializer; import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.util.IdentityMap; import com.esotericsoftware.kryo.util.IdentityMap;
@ -39,14 +41,9 @@ import dorkbox.network.serialization.CryptoSerializationManager;
*/ */
public public
class RmiObjectLocalHandler extends RmiObjectHandler { class RmiObjectLocalHandler extends RmiObjectHandler {
private static final boolean ENABLE_PROXY_OBJECTS = ConnectionImpl.ENABLE_PROXY_OBJECTS; private static final boolean ENABLE_PROXY_OBJECTS = RmiBridge.ENABLE_PROXY_OBJECTS;
private static final Field[] NO_REMOTE_FIELDS = new Field[0]; private static final Field[] NO_REMOTE_FIELDS = new Field[0];
// private static final AtomicReferenceFieldUpdater<RmiObjectLocalHandler, IdentityMap> rmiFieldsREF = AtomicReferenceFieldUpdater.newUpdater(
// RmiObjectLocalHandler.class,
// IdentityMap.class,
// "fieldCache");
private static final AtomicReferenceFieldUpdater<RmiObjectLocalHandler, IdentityMap> implToProxyREF = AtomicReferenceFieldUpdater.newUpdater( private static final AtomicReferenceFieldUpdater<RmiObjectLocalHandler, IdentityMap> implToProxyREF = AtomicReferenceFieldUpdater.newUpdater(
RmiObjectLocalHandler.class, RmiObjectLocalHandler.class,
IdentityMap.class, IdentityMap.class,
@ -57,13 +54,14 @@ class RmiObjectLocalHandler extends RmiObjectHandler {
IdentityMap.class, IdentityMap.class,
"objectHasRemoteObjects"); "objectHasRemoteObjects");
// private volatile IdentityMap<Class<?>, Field[]> fieldCache = new IdentityMap<Class<?>, Field[]>();
private volatile IdentityMap<Object, Object> implToProxy = new IdentityMap<Object, Object>(); private volatile IdentityMap<Object, Object> implToProxy = new IdentityMap<Object, Object>();
private volatile IdentityMap<Object, Field[]> objectHasRemoteObjects = new IdentityMap<Object, Field[]>(); private volatile IdentityMap<Object, Field[]> objectHasRemoteObjects = new IdentityMap<Object, Field[]>();
private final Logger logger;
public public
RmiObjectLocalHandler() { RmiObjectLocalHandler(final Logger logger) {
this.logger = logger;
} }
@Override @Override
@ -176,17 +174,27 @@ class RmiObjectLocalHandler extends RmiObjectHandler {
// if we are the response, we want to create a proxy object instead of just passing the ACTUAL object. // if we are the response, we want to create a proxy object instead of just passing the ACTUAL object.
// On the "network" (RemoteObjectSerializer.java) stack, this process is automatic -- and here we have to mimic this behavior. // On the "network" (RemoteObjectSerializer.java) stack, this process is automatic -- and here we have to mimic this behavior.
// has to be 'registration.remoteObject' because we use it later on
if (ENABLE_PROXY_OBJECTS && registration.remoteObject != null) {
// override the implementation object with the proxy. This is required because RMI must be the same between "network" and "local"
// connections -- even if this "slows down" the speed/performance of what "local" connections offer.
RemoteObject proxyObject = connection.getProxyObject(registration.rmiId, interfaceClass);
// have to save A and B so we can correctly switch as necessary // if PROXY objects are enabled, we replace the IMPLEMENTATION with a proxy object, so that the network logic == local logic.
//noinspection SynchronizeOnNonFinalField if (ENABLE_PROXY_OBJECTS) {
synchronized (implToProxy) { RemoteObject proxyObject = null;
// i know what I'm doing. This must be synchronized.
implToProxy.put(registration.remoteObject, proxyObject); if (registration.rmiId == RmiBridge.INVALID_RMI) {
logger.error("RMI ID '{}' is invalid. Unable to create RMI object.", registration.rmiId);
}
else {
// override the implementation object with the proxy. This is required because RMI must be the same between "network" and "local"
// connections -- even if this "slows down" the speed/performance of what "local" connections offer.
proxyObject = connection.getProxyObject(registration.rmiId, interfaceClass);
if (proxyObject != null) {
// have to save A and B so we can correctly switch as necessary
//noinspection SynchronizeOnNonFinalField
synchronized (implToProxy) {
// i know what I'm doing. This must be synchronized.
implToProxy.put(registration.remoteObject, proxyObject);
}
}
} }
connection.runRmiCallback(interfaceClass, callbackId, proxyObject); connection.runRmiCallback(interfaceClass, callbackId, proxyObject);
@ -197,6 +205,7 @@ class RmiObjectLocalHandler extends RmiObjectHandler {
} }
} }
@SuppressWarnings("unchecked")
@Override @Override
public public
Object normalMessages(final ConnectionImpl connection, final Object message) { Object normalMessages(final ConnectionImpl connection, final Object message) {
@ -208,7 +217,6 @@ class RmiObjectLocalHandler extends RmiObjectHandler {
// maybe this object is supposed to switch to a proxy object?? (note: we cannot send proxy objects over local/network connections) // maybe this object is supposed to switch to a proxy object?? (note: we cannot send proxy objects over local/network connections)
@SuppressWarnings("unchecked")
IdentityMap<Object, Object> implToProxy = implToProxyREF.get(this); IdentityMap<Object, Object> implToProxy = implToProxyREF.get(this);
IdentityMap<Object, Field[]> objectHasRemoteObjects = remoteObjectREF.get(this); IdentityMap<Object, Field[]> objectHasRemoteObjects = remoteObjectREF.get(this);
@ -324,78 +332,4 @@ class RmiObjectLocalHandler extends RmiObjectHandler {
return message; return message;
} }
// private
// LocalRmiClassEncoder replaceFieldObjects(final ConnectionImpl connection, final Object object, final Class<?> implClass) {
// Field[] rmiFields = fieldCache.get(implClass);
// int length = rmiFields.length;
//
// Object rmiObject = null;
// int[] rmiFieldIds = new int[length];
// for (int i = 0; i < length; i++) {
// Field field = rmiFields[i];
//
// // if it's an RMI object we want to write out a proxy object in the field instead of the actual object
// try {
// rmiObject = field.get(object);
// } catch (IllegalAccessException e) {
// e.printStackTrace();
// }
//
// if (rmiObject == null) {
// rmiFieldIds[i] = 0; // 0 means it was null
// }
//
// final Map<Object, Integer> localWeakCache = objectThreadLocals.get();
//
// Integer id = localWeakCache.get(rmiObject);
// if (id == null) {
// // duplicates are fine, as they represent the same object (as specified by the ID) on the remote side.
// int registeredId = connection.getRegisteredId(rmiObject);
// rmiFieldIds[i] = registeredId;
// localWeakCache.put(rmiObject, registeredId);
// }
// else {
// rmiFieldIds[i] = id;
// }
// }
//
// LocalRmiClassEncoder localRmiClassEncoder = new LocalRmiClassEncoder();
// localRmiClassEncoder.rmiObject = object;
// localRmiClassEncoder.rmiFieldIds = rmiFieldIds;
//
// return localRmiClassEncoder;
// }
// Field[] getFields(final Class<?> clazz) {
// // duplicates are OK, because they will contain the same information, so we DO NOT care about single writers
//
// //noinspection unchecked
// final IdentityMap<Class<?>, Field[]> identityMap = rmiFieldsREF.get(this);
//
//
// Field[] rmiFields = identityMap.get(clazz);
// if (rmiFields != null) {
// return rmiFields;
// }
//
// final ArrayList<Field> fields = new ArrayList<Field>();
//
// for (Field field : clazz.getDeclaredFields()) {
// if (field.getAnnotation(Rmi.class) != null) {
// fields.add(field);
// }
// }
//
//
// rmiFields = new Field[fields.size()];
// fields.toArray(rmiFields);
//
// // save in cache
// fieldCache.put(clazz, rmiFields);
// rmiFieldsREF.lazySet(this, fieldCache);
//
// return rmiFields;
// }
} }

View File

@ -15,14 +15,19 @@
*/ */
package dorkbox.network.rmi; package dorkbox.network.rmi;
import org.slf4j.Logger;
import dorkbox.network.connection.ConnectionImpl; import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.Listener; import dorkbox.network.connection.Listener;
public public
class RmiObjectNetworkHandler extends RmiObjectHandler { class RmiObjectNetworkHandler extends RmiObjectHandler {
private final Logger logger;
public public
RmiObjectNetworkHandler() { RmiObjectNetworkHandler(final Logger logger) {
this.logger = logger;
} }
@Override @Override
@ -66,6 +71,10 @@ class RmiObjectNetworkHandler extends RmiObjectHandler {
} }
} }
else { else {
if (registration.rmiId == RmiBridge.INVALID_RMI) {
logger.error("RMI ID '{}' is invalid. Unable to create RMI object.", registration.rmiId);
}
// this is the response. // this is the response.
// THIS IS ON THE LOCAL CONNECTION SIDE, which is the side that called 'getRemoteObject()' This can be Server or Client. // THIS IS ON THE LOCAL CONNECTION SIDE, which is the side that called 'getRemoteObject()' This can be Server or Client.
connection.runRmiCallback(interfaceClass, callbackId, registration.remoteObject); connection.runRmiCallback(interfaceClass, callbackId, registration.remoteObject);

View File

@ -361,7 +361,7 @@ class RmiGlobalTest extends BaseTest {
class TestCowImpl extends ConnectionAware implements TestCow { class TestCowImpl extends ConnectionAware implements TestCow {
public long value = System.currentTimeMillis(); public long value = System.currentTimeMillis();
public int moos; public int moos;
private final int id = 1; private final int id = 0; // the RMI id should be == to this for each direction.
public public
TestCowImpl() { TestCowImpl() {