/** * The cause of a ForeignException can be an exception that was generated on a local in process * thread, or a thread from a 'remote' separate process. * * If the cause is a ProxyThrowable, we know it came from deserialization which usually means * it came from not only another thread, but also from a remote thread. * * @return true if went through deserialization, false if locally generated */ public boolean isRemote() { return getCause() instanceof ProxyThrowable; }
@Override public synchronized void rethrowException() throws ForeignException { if (exception != null) { // This gets the stack where this is caused, (instead of where it was deserialized). // This is much more useful for debugging throw new ForeignException(exception.getSource(), exception.getCause()); } }
/** * Sends an exception to all listeners. * @param message human readable message passed to the listener * @param e {@link ForeignException} containing the cause. Can be null. */ private void dispatch(ForeignException e) { // update all the listeners with the passed error for (ForeignExceptionListener l: listeners) { l.receive(e); } }
@Test public void testSingleDispatcherWithTimer() { ForeignExceptionListener listener1 = Mockito.mock(ForeignExceptionListener.class); ForeignExceptionListener listener2 = Mockito.mock(ForeignExceptionListener.class); ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(); // add the listeners monitor.addListener(listener1); monitor.addListener(listener2); TimeoutExceptionInjector timer = new TimeoutExceptionInjector(monitor, 1000); timer.start(); timer.trigger(); assertTrue("Monitor didn't get timeout", monitor.hasException()); // verify that that we propagated the error Mockito.verify(listener1).receive(Mockito.any()); Mockito.verify(listener2).receive(Mockito.any()); }
/** * Test that a manually triggered exception with data fires with the data in receiveError. */ @Test public void testTimerPassesOnErrorInfo() { final long time = 1000000; ForeignExceptionListener listener = Mockito.mock(ForeignExceptionListener.class); TimeoutExceptionInjector timer = new TimeoutExceptionInjector(listener, time); timer.start(); timer.trigger(); Mockito.verify(listener).receive(Mockito.any()); }
@Override public void run() { // ensure we don't run this task multiple times synchronized (this) { // quick exit if we already marked the task complete if (TimeoutExceptionInjector.this.complete) return; // mark the task is run, to avoid repeats TimeoutExceptionInjector.this.complete = true; } long end = EnvironmentEdgeManager.currentTime(); TimeoutException tee = new TimeoutException( "Timeout caused Foreign Exception", start, end, maxTime); String source = "timer-" + timer; listener.receive(new ForeignException(source, tee)); } };
/** * Method to cancel the Subprocedure by injecting an exception from and external source. * @param cause */ public void cancel(String msg, Throwable cause) { LOG.error(msg, cause); complete = true; if (cause instanceof ForeignException) { monitor.receive((ForeignException) cause); } else { monitor.receive(new ForeignException(getMemberName(), cause)); } }
@Override public synchronized void receive(ForeignException e) { // if we already have an exception, then ignore it if (exception != null) return; LOG.debug(name + " accepting received exception" , e); // mark that we got the error if (e != null) { exception = e; } else { exception = new ForeignException(name, ""); } // notify all the listeners dispatch(e); }
@Override public void rethrowExceptionIfFailed() throws ForeignException { monitor.rethrowException(); }
/** * A callback that handles incoming ForeignExceptions. */ @Override public void receive(ForeignException e) { monitor.receive(e); }
/** * Create a procedure. * * Users should generally not directly create instances of this. They are created them * implicitly via {@link ProcedureCoordinator#createProcedure(ForeignExceptionDispatcher, * String, byte[], List)}} * * @param coord coordinator to call back to for general errors (e.g. * {@link ProcedureCoordinator#rpcConnectionFailure(String, IOException)}). * @param wakeFreq frequency to check for errors while waiting * @param timeout amount of time to allow the procedure to run before cancelling * @param procName name of the procedure instance * @param args argument data associated with the procedure instance * @param expectedMembers names of the expected members */ public Procedure(ProcedureCoordinator coord, long wakeFreq, long timeout, String procName, byte[] args, List<String> expectedMembers) { this(coord, new ForeignExceptionDispatcher(), wakeFreq, timeout, procName, args, expectedMembers); }
@Override public boolean hasException() { return monitor.hasException(); }
@Override public ForeignException getExceptionIfFailed() { return monitor.getException(); }
ProxyThrowable(String msg, StackTraceElement[] trace) { super(msg); this.setStackTrace(trace); } }
/** * Test that the dispatcher can receive an error via the timer mechanism. */ @Test public void testAttemptTimer() { ForeignExceptionListener listener1 = Mockito.mock(ForeignExceptionListener.class); ForeignExceptionListener listener2 = Mockito.mock(ForeignExceptionListener.class); ForeignExceptionDispatcher orchestrator = new ForeignExceptionDispatcher(); // add the listeners orchestrator.addListener(listener1); orchestrator.addListener(listener2); // now create a timer and check for that error TimeoutExceptionInjector timer = new TimeoutExceptionInjector(orchestrator, 1000); timer.start(); timer.trigger(); // make sure that we got the timer error Mockito.verify(listener1, Mockito.times(1)).receive(Mockito.any()); Mockito.verify(listener2, Mockito.times(1)).receive(Mockito.any()); } }
/** * Test that a manually triggered timer fires an exception. */ @Test public void testTimerTrigger() { final long time = 10000000; // pick a value that is very far in the future ForeignExceptionListener listener = Mockito.mock(ForeignExceptionListener.class); TimeoutExceptionInjector timer = new TimeoutExceptionInjector(listener, time); timer.start(); timer.trigger(); Mockito.verify(listener, Mockito.times(1)).receive(Mockito.any()); }
@Override public void rethrowException() throws ForeignException { monitor.rethrowException(); }
@Override public ForeignException getException() { return monitor.getException(); }
private void rethrowException() throws ForeignException { monitor.rethrowException(); }
/** * Check if the entire procedure has globally completed, or has been aborted. * @throws ForeignException */ public boolean isCompleted() throws ForeignException { // Rethrow exception if any monitor.rethrowException(); return (completedLatch.getCount() == 0); }