@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); }
/** * Verify that we get back similar stack trace information before an after serialization. */ @Test public void testSimpleException() throws IOException { String data = "some bytes"; ForeignException in = new ForeignException("SRC", new IllegalArgumentException(data)); // check that we get the data back out ForeignException e = ForeignException.deserialize(ForeignException.serialize(srcName, in)); assertNotNull(e); // now check that we get the right stack trace StackTraceElement elem = new StackTraceElement(this.getClass().toString(), "method", "file", 1); in.setStackTrace(new StackTraceElement[] { elem }); e = ForeignException.deserialize(ForeignException.serialize(srcName, in)); assertNotNull(e); assertEquals("Stack trace got corrupted", elem, e.getCause().getStackTrace()[0]); assertEquals("Got an unexpectedly long stack trace", 1, e.getCause().getStackTrace().length); }
@Override public String toString() { String className = getCause().getClass().getName() ; return className + " via " + getSource() + ":" + getLocalizedMessage(); }
@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()); } }
@Override public void receive(ForeignException ee) { // if this is a notification from a remote source, just log if (ee.isRemote()) { LOG.debug("Was remote foreign exception, not redispatching error", ee); return; } // if this is a local KeeperException, don't attempt to notify other members if (ee.getCause() instanceof KeeperException) { LOG.debug("Was KeeperException, not redispatching error", ee); return; } // if it is other local error, then send it to the coordinator try { rpcs.sendMemberAborted(Subprocedure.this, ee); } catch (IOException e) { // this will fail all the running procedures, since the connection is down LOG.error("Can't reach controller, not propagating error", e); } } });
LOG.error(msg); ee = new ForeignException(getMemberName(), new IllegalArgumentException(msg)); } else { data = Arrays.copyOfRange(data, ProtobufUtil.lengthOfPBMagic(), data.length); ee = ForeignException.deserialize(data); + " but we can't read the information. Killing the procedure."); ee = new ForeignException(getMemberName(), e);
/** * Compare that a generic exception's stack trace has the same stack trace elements after * serialization and deserialization */ @Test public void testRemoteFromLocal() throws IOException { String errorMsg = "some message"; Exception generic = new Exception(errorMsg); generic.printStackTrace(); assertTrue(generic.getMessage().contains(errorMsg)); ForeignException e = ForeignException.deserialize(ForeignException.serialize(srcName, generic)); assertArrayEquals("Local stack trace got corrupted", generic.getStackTrace(), e.getCause().getStackTrace()); e.printStackTrace(); // should have ForeignException and source node in it. assertTrue(e.getCause().getCause() == null); // verify that original error message is present in Foreign exception message assertTrue(e.getCause().getMessage().contains(errorMsg)); }
/** * This is the abort message being sent by the coordinator to member * * TODO this code isn't actually used but can be used to issue a cancellation from the * coordinator. */ @Override final public void sendAbortToMembers(Procedure proc, ForeignException ee) { String procName = proc.getName(); LOG.debug("Aborting procedure '" + procName + "' in zk"); String procAbortNode = zkProc.getAbortZNode(procName); try { LOG.debug("Creating abort znode:" + procAbortNode); String source = (ee.getSource() == null) ? coordName : ee.getSource(); byte[] errorInfo = ProtobufUtil.prependPBMagic(ForeignException.serialize(source, ee)); // first create the znode for the procedure ZKUtil.createAndFailSilent(zkProc.getWatcher(), procAbortNode, errorInfo); LOG.debug("Finished creating abort node:" + procAbortNode); } catch (KeeperException e) { // possible that we get this error for the procedure if we already reset the zk state, but in // that case we should still get an error for that procedure anyways zkProc.logZKTree(zkProc.baseZNode); coordinator.rpcConnectionFailure("Failed to post zk node:" + procAbortNode + " to abort procedure '" + procName + "'", new IOException(e)); } }
/** * 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; }
/** * Takes a series of bytes and tries to generate an ForeignException instance for it. * @param bytes * @return the ForeignExcpetion instance * @throws InvalidProtocolBufferException if there was deserialization problem this is thrown. * @throws org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException */ public static ForeignException deserialize(byte[] bytes) throws IOException { // figure out the data we need to pass ForeignExceptionMessage eem = ForeignExceptionMessage.parseFrom(bytes); GenericExceptionMessage gem = eem.getGenericException(); StackTraceElement [] trace = ForeignException.toStackTrace(gem.getTraceList()); ProxyThrowable dfe = new ProxyThrowable(gem.getMessage(), trace); ForeignException e = new ForeignException(eem.getSource(), dfe); return e; }
/** * Converts a ForeignException to an array of bytes. * @param source the name of the external exception source * @param t the "local" external exception (local) * @return protobuf serialized version of ForeignException */ public static byte[] serialize(String source, Throwable t) { GenericExceptionMessage.Builder gemBuilder = GenericExceptionMessage.newBuilder(); gemBuilder.setClassName(t.getClass().getName()); if (t.getMessage() != null) { gemBuilder.setMessage(t.getMessage()); } // set the stack trace, if there is one List<StackTraceElementMessage> stack = ForeignException.toStackTraceElementMessages(t.getStackTrace()); if (stack != null) { gemBuilder.addAllTrace(stack); } GenericExceptionMessage payload = gemBuilder.build(); ForeignExceptionMessage.Builder exception = ForeignExceptionMessage.newBuilder(); exception.setGenericException(payload).setSource(source); ForeignExceptionMessage eem = exception.build(); return eem.toByteArray(); }
@Override public void receive(ForeignException ee) { // if this is a notification from a remote source, just log if (ee.isRemote()) { LOG.debug("Was remote foreign exception, not redispatching error", ee); return; } // if it is local, then send it to the coordinator try { rpcs.sendMemberAborted(Subprocedure.this, ee); } catch (IOException e) { // this will fail all the running procedures, since the connection is down LOG.error("Can't reach controller, not propagating error", e); } } });
+ " but we can't read the information. Killing the procedure."); ee = new ForeignException(coordName, "Data in abort node is illegally formatted. ignoring content."); } else { ee = ForeignException.deserialize(data); + " but we can't read the information. Killing the procedure."); ee = new ForeignException(coordName, e); } catch (KeeperException e) { coordinator.rpcConnectionFailure("Failed to get data for abort node:" + abortNode
@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()); } }
/** * Compare that a generic exception's stack trace has the same stack trace elements after * serialization and deserialization */ @Test public void testRemoteFromLocal() throws IOException { String errorMsg = "some message"; Exception generic = new Exception(errorMsg); generic.printStackTrace(); assertTrue(generic.getMessage().contains(errorMsg)); ForeignException e = ForeignException.deserialize(ForeignException.serialize(srcName, generic)); assertArrayEquals("Local stack trace got corrupted", generic.getStackTrace(), e.getCause().getStackTrace()); e.printStackTrace(); // should have ForeignException and source node in it. assertTrue(e.getCause().getCause() == null); // verify that original error message is present in Foreign exception message assertTrue(e.getCause().getMessage().contains(errorMsg)); }
/** * This should be called by the member and should write a serialized root cause exception as * to the abort znode. */ @Override public void sendMemberAborted(Subprocedure sub, ForeignException ee) { if (sub == null) { LOG.error("Failed due to null subprocedure", ee); return; } String procName = sub.getName(); LOG.debug("Aborting procedure (" + procName + ") in zk"); String procAbortZNode = zkController.getAbortZNode(procName); try { String source = (ee.getSource() == null) ? memberName: ee.getSource(); byte[] errorInfo = ProtobufUtil.prependPBMagic(ForeignException.serialize(source, ee)); ZKUtil.createAndFailSilent(zkController.getWatcher(), procAbortZNode, errorInfo); LOG.debug("Finished creating abort znode:" + procAbortZNode); } catch (KeeperException e) { // possible that we get this error for the procedure if we already reset the zk state, but in // that case we should still get an error for that procedure anyways zkController.logZKTree(zkController.getBaseZnode()); member.controllerConnectionFailure("Failed to post zk node:" + procAbortZNode + " to abort procedure", e, procName); } }
/** * Tests that a dispatcher only dispatches only the first exception, and does not propagate * subsequent exceptions. */ @Test public void testErrorPropagation() { ForeignExceptionListener listener1 = Mockito.mock(ForeignExceptionListener.class); ForeignExceptionListener listener2 = Mockito.mock(ForeignExceptionListener.class); ForeignExceptionDispatcher dispatcher = new ForeignExceptionDispatcher(); // add the listeners dispatcher.addListener(listener1); dispatcher.addListener(listener2); // create an artificial error dispatcher.receive(EXTEXN); // make sure the listeners got the error Mockito.verify(listener1, Mockito.times(1)).receive(EXTEXN); Mockito.verify(listener2, Mockito.times(1)).receive(EXTEXN); // make sure that we get an exception try { dispatcher.rethrowException(); fail("Monitor should have thrown an exception after getting error."); } catch (ForeignException ex) { assertTrue("Got an unexpected exception:" + ex, ex.getCause() == EXTEXN.getCause()); LOG.debug("Got the testing exception!"); } // push another error, which should be not be passed to listeners dispatcher.receive(EXTEXN2); Mockito.verify(listener1, Mockito.never()).receive(EXTEXN2); Mockito.verify(listener2, Mockito.never()).receive(EXTEXN2); }
/** * Takes a series of bytes and tries to generate an ForeignException instance for it. * @param bytes * @return the ForeignExcpetion instance * @throws InvalidProtocolBufferException if there was deserialization problem this is thrown. */ public static ForeignException deserialize(byte[] bytes) throws InvalidProtocolBufferException { // figure out the data we need to pass ForeignExceptionMessage eem = ForeignExceptionMessage.parseFrom(bytes); GenericExceptionMessage gem = eem.getGenericException(); StackTraceElement [] trace = ForeignException.toStackTrace(gem.getTraceList()); ProxyThrowable dfe = new ProxyThrowable(gem.getMessage(), trace); ForeignException e = new ForeignException(eem.getSource(), dfe); return e; }
@Override public void receive(ForeignException ee) { // if this is a notification from a remote source, just log if (ee.isRemote()) { LOG.debug("Was remote foreign exception, not redispatching error", ee); return; } // if this is a local KeeperException, don't attempt to notify other members if (ee.getCause() instanceof KeeperException) { LOG.debug("Was KeeperException, not redispatching error", ee); return; } // if it is other local error, then send it to the coordinator try { rpcs.sendMemberAborted(Subprocedure.this, ee); } catch (IOException e) { // this will fail all the running procedures, since the connection is down LOG.error("Can't reach controller, not propagating error", e); } } });
/** * Converts a ForeignException to an array of bytes. * @param source the name of the external exception source * @param t the "local" external exception (local) * @return protobuf serialized version of ForeignException */ public static byte[] serialize(String source, Throwable t) { GenericExceptionMessage.Builder gemBuilder = GenericExceptionMessage.newBuilder(); gemBuilder.setClassName(t.getClass().getName()); if (t.getMessage() != null) { gemBuilder.setMessage(t.getMessage()); } // set the stack trace, if there is one List<StackTraceElementMessage> stack = ForeignException.toStackTraceElementMessages(t.getStackTrace()); if (stack != null) { gemBuilder.addAllTrace(stack); } GenericExceptionMessage payload = gemBuilder.build(); ForeignExceptionMessage.Builder exception = ForeignExceptionMessage.newBuilder(); exception.setGenericException(payload).setSource(source); ForeignExceptionMessage eem = exception.build(); return eem.toByteArray(); }