/** * Explicitly passes the given ThreadConfinedProxy to a new thread. If the proxy passed in is not a ThreadConfinedProxy, but is a * different type of proxy that also uses a {@linkplain DelegatingInvocationHandler}, this method will recursively apply to the * delegate. This means that this method can handle arbitrarily nested DelegatingInvocationHandlers, including nested * ThreadConfinedProxy objects. * */ public static void changeThread(Object proxy, Thread oldThread, Thread newThread) { Validate.notNull(proxy, "Proxy argument must not be null"); if (Proxy.isProxyClass(proxy.getClass())) { InvocationHandler handler = Proxy.getInvocationHandler(proxy); changeHandlerThread(handler, oldThread, newThread); } else if (proxy instanceof Delegator) { changeThread(((Delegator) proxy).getDelegate(), oldThread, newThread); } }
/** * Creates a new ThreadConfinedProxy with the given Strictness (ASSERT_AND_LOG or VALIDATE), initially assigned to the current thread. */ public static <T> T newProxyInstance(Class<T> interfaceClass, T delegate, Strictness strictness) { return newProxyInstance(interfaceClass, delegate, strictness, Thread.currentThread()); }
/** * Creates a new ThreadConfinedProxy with the given Strictness (ASSERT_AND_LOG or VALIDATE), initially assigned to the given thread. */ @SuppressWarnings("unchecked") public static <T> T newProxyInstance(Class<T> interfaceClass, T delegate, Strictness strictness, Thread current) { return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] {interfaceClass}, new ThreadConfinedProxy(delegate, strictness, current.getName(), current.getId())); }
@Test public void testChildThreadCanDelegateBackToMainThread() throws InterruptedException { final AtomicReference<List<String>> inputReference = new AtomicReference<List<String>>(null); final AtomicInteger outputReference = new AtomicInteger(0); final Thread mainThread = Thread.currentThread(); Thread childThread = new Thread(() -> { outputReference.compareAndSet(0, 1); List<String> subjectInChildThread = inputReference.get(); ThreadConfinedProxy.changeThread(subjectInChildThread, mainThread, Thread.currentThread()); subjectInChildThread.add(testString); if (Iterables.getOnlyElement(subjectInChildThread).equals(testString)) { outputReference.compareAndSet(1, 2); } ThreadConfinedProxy.changeThread(subjectInChildThread, Thread.currentThread(), mainThread); }); @SuppressWarnings("unchecked") List<String> subject = ThreadConfinedProxy.newProxyInstance(List.class, new ArrayList<String>(), ThreadConfinedProxy.Strictness.VALIDATE); inputReference.set(subject); childThread.start(); childThread.join(10000); assertEquals(2, outputReference.get()); // We got delegated back, so we can use subject again assertEquals(testString, Iterables.getOnlyElement(subject)); }
private static void changeHandlerThread(InvocationHandler handler, Thread oldThread, Thread newThread) { if (handler instanceof ThreadConfinedProxy) { ((ThreadConfinedProxy) handler).changeThread(oldThread, newThread); } if (handler instanceof DelegatingInvocationHandler) { changeThread(((DelegatingInvocationHandler) handler).getDelegate(), oldThread, newThread); } }
private synchronized void checkThreadChange(Thread oldThread, Thread newThread) { if (oldThread.getId() != threadId) { String message = String.format( "Thread confinement violation: tried to change threads from thread %s (ID %s) to thread %s (ID %s), but we expected thread %s (ID %s)", oldThread.getId(), oldThread.getName(), newThread.getId(), newThread.getName(), threadName, threadId); fail(message); } }
@Override protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable { checkThread(method); try { return method.invoke(delegate, args); } catch (InvocationTargetException e) { throw e.getCause(); } catch (IllegalAccessException e) { throw Throwables.propagate(e); } }
private synchronized void changeThread(Thread oldThread, Thread newThread) { checkThreadChange(oldThread, newThread); threadId = newThread.getId(); threadName = newThread.getName(); }
final @Nullable Connection connection) throws PalantirSqlException { Future<T> future = executorService.submit(ThreadNamingCallable.wrapWithThreadName( ThreadConfinedProxy.threadLendingCallable(connection, () -> { if(Thread.currentThread().isInterrupted()) {
outputReference.compareAndSet(0, 1); List<String> subjectInChildThread = inputReference.get(); ThreadConfinedProxy.changeThread(subjectInChildThread, mainThread, Thread.currentThread()); subjectInChildThread.add(testString); if (Iterables.getOnlyElement(subjectInChildThread).equals(testString)) { outputReference.compareAndSet(1, 2); ThreadConfinedProxy.changeThread(subjectInChildThread, Thread.currentThread(), mainThread); }); subject = ThreadConfinedProxy.newProxyInstance(List.class, subject, ThreadConfinedProxy.Strictness.VALIDATE); subject = TimingProxy.newProxyInstance(List.class, subject, LoggingOperationTimer.create(log)); subject = ThreadConfinedProxy.newProxyInstance(List.class, subject, ThreadConfinedProxy.Strictness.VALIDATE); subject = new DelegatingArrayListString(subject);
/** * Wraps a callable in a new callable that assigns ownership to the thread running the callable, then passes ownership back to * the thread that called this method. */ public static <T> Callable<T> threadLendingCallable(final Object proxy, final Callable<T> callable) { if (proxy == null) { return callable; } final Thread parent = Thread.currentThread(); return () -> { Thread child = Thread.currentThread(); changeThread(proxy, parent, child); try { return callable.call(); } finally { changeThread(proxy, child, parent); } }; }
private synchronized void checkThread(Method method) { Thread current = Thread.currentThread(); if (threadId != current.getId()) { String message = String.format( "Thread confinement violation: method %s#%s was called from thread %s (ID %s) instead of thread %s (ID %s)", method.getDeclaringClass().getCanonicalName(), method.getName(), current.getName(), current.getId(), threadName, threadId); fail(message); } }
@Override protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable { checkThread(method); try { return method.invoke(delegate, args); } catch (InvocationTargetException e) { throw e.getCause(); } catch (IllegalAccessException e) { throw Throwables.propagate(e); } }
private synchronized void changeThread(Thread oldThread, Thread newThread) { checkThreadChange(oldThread, newThread); threadId = newThread.getId(); threadName = newThread.getName(); }
final @Nullable Connection connection) throws PalantirSqlException { Future<T> future = executorService.submit(ThreadNamingCallable.wrapWithThreadName( ThreadConfinedProxy.threadLendingCallable(connection, () -> { if(Thread.currentThread().isInterrupted()) {
outputReference.compareAndSet(0, 1); List<String> subjectInChildThread = inputReference.get(); ThreadConfinedProxy.changeThread(subjectInChildThread, mainThread, Thread.currentThread()); subjectInChildThread.add(testString); if (Iterables.getOnlyElement(subjectInChildThread).equals(testString)) { List<String> subject = ThreadConfinedProxy.newProxyInstance(List.class, new ArrayList<String>(), ThreadConfinedProxy.Strictness.VALIDATE); inputReference.set(subject); outputReference.compareAndSet(3, 4); List<String> subjectInChildThread = inputReference.get(); ThreadConfinedProxy.changeThread(subjectInChildThread, mainThread, Thread.currentThread()); outputReference.compareAndSet(4, 5); }); ThreadConfinedProxy.changeThread(subject, mainThread, otherThread); fail(); } catch (Exception e) {
@Test public void testCurrentThreadCanCreateAndUseSubject() { @SuppressWarnings("unchecked") List<String> subject = ThreadConfinedProxy.newProxyInstance(List.class, new ArrayList<String>(), ThreadConfinedProxy.Strictness.VALIDATE); subject.add(testString); assertEquals(testString, Iterables.getOnlyElement(subject)); }
/** * Explicitly passes the given ThreadConfinedProxy to a new thread. If the proxy passed in is not a ThreadConfinedProxy, but is a * different type of proxy that also uses a {@linkplain DelegatingInvocationHandler}, this method will recursively apply to the * delegate. This means that this method can handle arbitrarily nested DelegatingInvocationHandlers, including nested * ThreadConfinedProxy objects. * */ public static void changeThread(Object proxy, Thread oldThread, Thread newThread) { Validate.notNull(proxy, "Proxy argument must not be null"); if (Proxy.isProxyClass(proxy.getClass())) { InvocationHandler handler = Proxy.getInvocationHandler(proxy); changeHandlerThread(handler, oldThread, newThread); } else if (proxy instanceof Delegator) { changeThread(((Delegator) proxy).getDelegate(), oldThread, newThread); } }
private static void changeHandlerThread(InvocationHandler handler, Thread oldThread, Thread newThread) { if (handler instanceof ThreadConfinedProxy) { ((ThreadConfinedProxy) handler).changeThread(oldThread, newThread); } if (handler instanceof DelegatingInvocationHandler) { changeThread(((DelegatingInvocationHandler) handler).getDelegate(), oldThread, newThread); } }
private synchronized void checkThreadChange(Thread oldThread, Thread newThread) { if (oldThread.getId() != threadId) { String message = String.format( "Thread confinement violation: tried to change threads from thread %s (ID %s) to thread %s (ID %s), but we expected thread %s (ID %s)", oldThread.getId(), oldThread.getName(), newThread.getId(), newThread.getName(), threadName, threadId); fail(message); } }