/** {@inheritDoc} */ public <V, T extends Throwable> V call(Callable<V, T> callable) throws IOException, T, InterruptedException { UserRequest<V, T> request = null; try { request = new UserRequest<V, T>(this, callable); UserResponse<V, T> r = request.call(this); return r.retrieve(this, UserRequest.getClassLoader(callable)); // re-wrap the exception so that we can capture the stack trace of the caller. } catch (ClassNotFoundException e) { IOException x = new IOException("Remote call on " + name + " failed"); x.initCause(e); throw x; } catch (Error e) { IOException x = new IOException("Remote call on " + name + " failed"); x.initCause(e); throw x; } finally { // since this is synchronous operation, when the round trip is over // we assume all the exported objects are out of scope. // (that is, the operation shouldn't spawn a new thread or altter // global state in the remote system. if (request != null) request.releaseExports(); } }
/** * Preloads jar files on the remote side. * * <p>This is a performance improvement method that can be safely ignored if your goal is just to * make things working. * * <p>Normally, classes are transferred over the network one at a time, on-demand. This design is * mainly driven by how Java classloading works — we can't predict what classes will be * necessarily upfront very easily. * * <p>Classes are loaded only once, so for long-running {@link Channel}, this is normally an * acceptable overhead. But sometimes, for example when a channel is short-lived, or when you know * that you'll need a majority of classes in certain jar files, then it is more efficient to send * a whole jar file over the network upfront and thereby avoiding individual class transfer over * the network. * * <p>That is what this method does. It ensures that a series of jar files are copied to the * remote side (AKA "preloading.") Classloading will consult the preloaded jars before performing * network transfer of class files. * * <p><strong>Beware</strong> that this method is not useful in all configurations. If a {@link * RemoteClassLoader} has another {@link RemoteClassLoader} as a {@linkplain ClassLoader#getParent * parent}, which would be typical, then preloading a JAR in it will not reduce network * round-trips: each class load still has to call {@link ClassLoader#loadClass(String, boolean) * loadClass} on the parent, which will wind up checking the remote side just to get a negative * answer. * * @param classLoaderRef This parameter is used to identify the remote classloader that will * prefetch the specified jar files. That is, prefetching will ensure that prefetched jars * will kick in when this {@link Callable} object is actually executed remote side. * <p>{@link RemoteClassLoader}s are created wisely, one per local {@link ClassLoader}, so * this parameter doesn't have to be exactly the same {@link Callable} to be executed later * — it just has to be of the same class. * @param classesInJar {@link Class} objects that identify jar files to be preloaded. Jar files * that contain the specified classes will be preloaded into the remote peer. You just need to * specify one class per one jar. * @return true if the preloading actually happened. false if all the jars are already preloaded. * This method is implemented in such a way that unnecessary jar file transfer will be * avoided, and the return value will tell you if this optimization kicked in. Under normal * circumstances your program shouldn't depend on this return value. It's just a hint. * @throws IOException if the preloading fails. */ public boolean preloadJar(Callable<?, ?> classLoaderRef, Class... classesInJar) throws IOException, InterruptedException { return preloadJar(UserRequest.getClassLoader(classLoaderRef), classesInJar); }