This is an automated email from the ASF dual-hosted git repository. ctubbsii pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/accumulo.git
commit 5d1a26463c26b9f3ae0452f4ea34d674901f01eb Author: Christopher Tubbs <ctubb...@apache.org> AuthorDate: Wed May 16 18:34:23 2018 -0400 Update Thrift to latest (0.11.0) Remove workarounds for bugs fixed in 0.11.0: * Remove workaround for spammy debug print statement (THRIFT-4062) * Remove workaround for unused imports in enums (THRIFT-4073) * Remove RpcWrapper (more details below) *** RpcWrapper was added in ACCUMULO-2950 to catch server-side RuntimeExceptions and notify the client with a TApplicationException, as in Thrift 0.9.0 (See THRIFT-1805). It is no longer needed since a flag was added to the generator to optionally handle RuntimeExceptions this way, which we now set in our generate-thrift.sh. --- .../accumulo/core/rpc/TServiceClientWrapper.java | 57 ---------------- core/src/main/scripts/generate-thrift.sh | 8 +-- pom.xml | 2 +- .../main/java/org/apache/accumulo/proxy/Proxy.java | 4 +- .../org/apache/accumulo/server/rpc/RpcWrapper.java | 76 ---------------------- .../accumulo/server/util/TServerUtilsTest.java | 4 +- .../apache/accumulo/gc/SimpleGarbageCollector.java | 4 +- .../java/org/apache/accumulo/master/Master.java | 6 +- .../org/apache/accumulo/tserver/TabletServer.java | 6 +- .../test/rpc/SimpleThriftServiceRunner.java | 13 ++-- .../apache/accumulo/test/rpc/ThriftBehaviorIT.java | 5 +- 11 files changed, 21 insertions(+), 164 deletions(-) diff --git a/core/src/main/java/org/apache/accumulo/core/rpc/TServiceClientWrapper.java b/core/src/main/java/org/apache/accumulo/core/rpc/TServiceClientWrapper.java deleted file mode 100644 index 14027e7..0000000 --- a/core/src/main/java/org/apache/accumulo/core/rpc/TServiceClientWrapper.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.accumulo.core.rpc; - -import org.apache.thrift.TApplicationException; -import org.apache.thrift.TBase; -import org.apache.thrift.TException; -import org.apache.thrift.TServiceClient; -import org.apache.thrift.protocol.TMessage; -import org.apache.thrift.protocol.TMessageType; -import org.apache.thrift.protocol.TProtocol; - -// Wrapper for THRIFT-4062 workaround; shouldn't be needed in newer versions -// Also update generate-thrift.sh to stop using this -public abstract class TServiceClientWrapper extends TServiceClient { - - public TServiceClientWrapper(TProtocol iprot, TProtocol oprot) { - super(iprot, oprot); - } - - public TServiceClientWrapper(TProtocol prot) { - super(prot); - } - - @Override - protected void receiveBase(TBase<?,?> result, String methodName) throws TException { - TMessage msg = iprot_.readMessageBegin(); - if (msg.type == TMessageType.EXCEPTION) { - TApplicationException x = new TApplicationException(); - x.read(iprot_); - iprot_.readMessageEnd(); - throw x; - } - if (msg.seqid != seqid_) { - throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, - String.format("%s failed: out of sequence response: expected %d but got %d", methodName, - seqid_, msg.seqid)); - } - result.read(iprot_); - iprot_.readMessageEnd(); - } - -} diff --git a/core/src/main/scripts/generate-thrift.sh b/core/src/main/scripts/generate-thrift.sh index d189323..7326944 100755 --- a/core/src/main/scripts/generate-thrift.sh +++ b/core/src/main/scripts/generate-thrift.sh @@ -26,7 +26,7 @@ # INCLUDED_MODULES should be an array that includes other Maven modules with src/main/thrift directories # Use INCLUDED_MODULES=(-) in calling scripts that require no other modules # ======================================================================================================================== -[[ -z $REQUIRED_THRIFT_VERSION ]] && REQUIRED_THRIFT_VERSION='0.10.0' +[[ -z $REQUIRED_THRIFT_VERSION ]] && REQUIRED_THRIFT_VERSION='0.11.0' [[ -z $INCLUDED_MODULES ]] && INCLUDED_MODULES=(../server/tracer) [[ -z $BASE_OUTPUT_PACKAGE ]] && BASE_OUTPUT_PACKAGE='org.apache.accumulo.core' [[ -z $PACKAGES_TO_GENERATE ]] && PACKAGES_TO_GENERATE=(gc master tabletserver security client.impl data replication trace) @@ -65,7 +65,7 @@ THRIFT_ARGS="${THRIFT_ARGS} -o $BUILD_DIR" mkdir -p $BUILD_DIR rm -rf $BUILD_DIR/gen-java for f in src/main/thrift/*.thrift; do - thrift ${THRIFT_ARGS} --gen java:generated_annotations=undated "$f" || fail unable to generate java thrift classes + thrift ${THRIFT_ARGS} --gen java:generated_annotations=undated,handle_runtime_exceptions "$f" || fail unable to generate java thrift classes thrift ${THRIFT_ARGS} --gen py "$f" || fail unable to generate python thrift classes thrift ${THRIFT_ARGS} --gen rb "$f" || fail unable to generate ruby thrift classes thrift ${THRIFT_ARGS} --gen cpp "$f" || fail unable to generate cpp thrift classes @@ -73,10 +73,6 @@ done # For all generated thrift code, get rid of all warnings and add the LICENSE header -# workaround for THRIFT-4062; should be fixed in newer thrift versions -find $BUILD_DIR/gen-java -name '*.java' -exec sed -i -e 's/\(org[.]apache[.]\)thrift\([.]TServiceClient\) /\1accumulo.core.rpc\2Wrapper /' {} + -# upstream stopped doing import statements for classes, but overlooked enums; delete unused imports -find $BUILD_DIR/gen-java -name '*.java' -exec grep -Zl '^public enum ' {} + | xargs -0 sed -i -e '/^import .*$/d' # add dummy method to suppress "unnecessary suppress warnings" for classes which don't have any unused variables # this only affects classes, enums aren't affected find $BUILD_DIR/gen-java -name '*.java' -exec grep -Zl '^public class ' {} + | xargs -0 sed -i -e 's/^[}]$/ private static void unusedMethod() {}\ diff --git a/pom.xml b/pom.xml index f17ab5f..cde58c7 100644 --- a/pom.xml +++ b/pom.xml @@ -150,7 +150,7 @@ <surefire.failIfNoSpecifiedTests>false</surefire.failIfNoSpecifiedTests> <surefire.groups /> <!-- Thrift version --> - <thrift.version>0.10.0</thrift.version> + <thrift.version>0.11.0</thrift.version> <unitTestMemSize>-Xmx1G</unitTestMemSize> <!-- ZooKeeper version --> <zookeeper.version>3.4.10</zookeeper.version> diff --git a/proxy/src/main/java/org/apache/accumulo/proxy/Proxy.java b/proxy/src/main/java/org/apache/accumulo/proxy/Proxy.java index 6c206c1..de817f9 100644 --- a/proxy/src/main/java/org/apache/accumulo/proxy/Proxy.java +++ b/proxy/src/main/java/org/apache/accumulo/proxy/Proxy.java @@ -30,11 +30,11 @@ import org.apache.accumulo.core.conf.ClientProperty; import org.apache.accumulo.core.conf.ConfigurationTypeHelper; import org.apache.accumulo.core.conf.Property; import org.apache.accumulo.core.rpc.SslConnectionParams; +import org.apache.accumulo.core.trace.wrappers.TraceWrap; import org.apache.accumulo.core.util.HostAndPort; import org.apache.accumulo.minicluster.MiniAccumuloCluster; import org.apache.accumulo.proxy.thrift.AccumuloProxy; import org.apache.accumulo.server.metrics.MetricsFactory; -import org.apache.accumulo.server.rpc.RpcWrapper; import org.apache.accumulo.server.rpc.SaslServerConnectionParams; import org.apache.accumulo.server.rpc.ServerAddress; import org.apache.accumulo.server.rpc.TServerUtils; @@ -213,7 +213,7 @@ public class Proxy implements KeywordExecutable { ProxyServer impl = new ProxyServer(props); // Wrap the implementation -- translate some exceptions - AccumuloProxy.Iface wrappedImpl = RpcWrapper.service(impl); + AccumuloProxy.Iface wrappedImpl = TraceWrap.service(impl); // Create the processor from the implementation TProcessor processor = new AccumuloProxy.Processor<>(wrappedImpl); diff --git a/server/base/src/main/java/org/apache/accumulo/server/rpc/RpcWrapper.java b/server/base/src/main/java/org/apache/accumulo/server/rpc/RpcWrapper.java deleted file mode 100644 index 3e9d8d5..0000000 --- a/server/base/src/main/java/org/apache/accumulo/server/rpc/RpcWrapper.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.accumulo.server.rpc; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -import org.apache.accumulo.core.trace.wrappers.RpcServerInvocationHandler; -import org.apache.accumulo.core.trace.wrappers.TraceWrap; -import org.apache.thrift.TApplicationException; -import org.apache.thrift.TException; - -/** - * This class accommodates the changes in THRIFT-1805, which appeared in Thrift 0.9.1 and restricts - * client-side notification of server-side errors to {@link TException} only, by wrapping - * {@link RuntimeException} and {@link Error} as {@link TException}, so it doesn't just close the - * connection and look like a network issue, but informs the client that a - * {@link TApplicationException} had occurred, as it did in Thrift 0.9.0. This performs similar - * functions as {@link TraceWrap}, but with the additional action of translating exceptions. See - * also ACCUMULO-1691 and ACCUMULO-2950. - * - * ACCUMULO-4065 found that the above exception-wrapping is not appropriate for Thrift's - * implementation of oneway methods. Oneway methods are defined as a method which the client does - * not wait for it to return. Normally, this is acceptable as these methods are void. Therefore, if - * another client reuses the connection to send a new RPC, there is no "extra" data sitting on the - * InputStream from the Socket (that the server sent). However, the implementation of a oneway - * method <em>does</em> send a response to the client when the implementation throws a - * {@link TException}. This message showing up on the client's InputStream causes future use of the - * Thrift Connection to become unusable. As long as the Thrift implementation sends a message back - * when oneway methods throw a {@link TException}, we much make sure that we don't re-wrap-and-throw - * any exceptions as {@link TException}s. - * - * @since 1.6.1 - */ -public class RpcWrapper { - - public static <I> I service(final I instance) { - InvocationHandler handler = getInvocationHandler(instance); - - @SuppressWarnings("unchecked") - I proxiedInstance = (I) Proxy.newProxyInstance(instance.getClass().getClassLoader(), - instance.getClass().getInterfaces(), handler); - return proxiedInstance; - } - - protected static <T> RpcServerInvocationHandler<T> getInvocationHandler(final T instance) { - return new RpcServerInvocationHandler<T>(instance) { - - @Override - public Object invoke(Object obj, Method method, Object[] args) throws Throwable { - // e.g. ThriftClientHandler.flush(TInfo, TCredentials, ...) - try { - return super.invoke(obj, method, args); - } catch (RuntimeException e) { - // thrift will log the exception in ProcessFunction - throw new TException(e); - } - } - }; - } -} diff --git a/server/base/src/test/java/org/apache/accumulo/server/util/TServerUtilsTest.java b/server/base/src/test/java/org/apache/accumulo/server/util/TServerUtilsTest.java index b9e26e5..a418f8b 100644 --- a/server/base/src/test/java/org/apache/accumulo/server/util/TServerUtilsTest.java +++ b/server/base/src/test/java/org/apache/accumulo/server/util/TServerUtilsTest.java @@ -44,10 +44,10 @@ import org.apache.accumulo.core.conf.AccumuloConfiguration; import org.apache.accumulo.core.conf.ConfigurationCopy; import org.apache.accumulo.core.conf.DefaultConfiguration; import org.apache.accumulo.core.conf.Property; +import org.apache.accumulo.core.trace.wrappers.TraceWrap; import org.apache.accumulo.server.AccumuloServerContext; import org.apache.accumulo.server.client.ClientServiceHandler; import org.apache.accumulo.server.conf.ServerConfigurationFactory; -import org.apache.accumulo.server.rpc.RpcWrapper; import org.apache.accumulo.server.rpc.ServerAddress; import org.apache.accumulo.server.rpc.TServerUtils; import org.apache.thrift.server.TServer; @@ -342,7 +342,7 @@ public class TServerUtilsTest { private ServerAddress startServer() throws Exception { AccumuloServerContext ctx = new AccumuloServerContext(instance, factory); ClientServiceHandler clientHandler = new ClientServiceHandler(ctx, null, null); - Iface rpcProxy = RpcWrapper.service(clientHandler); + Iface rpcProxy = TraceWrap.service(clientHandler); Processor<Iface> processor = new Processor<>(rpcProxy); // "localhost" explicitly to make sure we can always bind to that interface (avoids DNS // misconfiguration) diff --git a/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java b/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java index ef605bc..7ac9b59 100644 --- a/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java +++ b/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java @@ -72,6 +72,7 @@ import org.apache.accumulo.core.trace.ProbabilitySampler; import org.apache.accumulo.core.trace.Span; import org.apache.accumulo.core.trace.Trace; import org.apache.accumulo.core.trace.thrift.TInfo; +import org.apache.accumulo.core.trace.wrappers.TraceWrap; import org.apache.accumulo.core.util.HostAndPort; import org.apache.accumulo.core.util.NamingThreadFactory; import org.apache.accumulo.core.util.Pair; @@ -94,7 +95,6 @@ import org.apache.accumulo.server.fs.VolumeManagerImpl; import org.apache.accumulo.server.fs.VolumeUtil; import org.apache.accumulo.server.metrics.MetricsSystemHelper; import org.apache.accumulo.server.replication.proto.Replication.Status; -import org.apache.accumulo.server.rpc.RpcWrapper; import org.apache.accumulo.server.rpc.ServerAddress; import org.apache.accumulo.server.rpc.TCredentialsUpdatingWrapper; import org.apache.accumulo.server.rpc.TServerUtils; @@ -735,7 +735,7 @@ public class SimpleGarbageCollector extends AccumuloServerContext implements Ifa } private HostAndPort startStatsService() throws UnknownHostException { - Iface rpcProxy = RpcWrapper.service(this); + Iface rpcProxy = TraceWrap.service(this); final Processor<Iface> processor; if (ThriftServerType.SASL == getThriftServerType()) { Iface tcProxy = TCredentialsUpdatingWrapper.service(rpcProxy, getClass(), getConfiguration()); diff --git a/server/master/src/main/java/org/apache/accumulo/master/Master.java b/server/master/src/main/java/org/apache/accumulo/master/Master.java index d4fe20a..3f34f16 100644 --- a/server/master/src/main/java/org/apache/accumulo/master/Master.java +++ b/server/master/src/main/java/org/apache/accumulo/master/Master.java @@ -80,6 +80,7 @@ import org.apache.accumulo.core.security.TablePermission; import org.apache.accumulo.core.tabletserver.thrift.TUnloadTabletGoal; import org.apache.accumulo.core.trace.DistributedTrace; import org.apache.accumulo.core.trace.thrift.TInfo; +import org.apache.accumulo.core.trace.wrappers.TraceWrap; import org.apache.accumulo.core.util.Daemon; import org.apache.accumulo.core.util.Pair; import org.apache.accumulo.core.zookeeper.ZooUtil; @@ -130,7 +131,6 @@ import org.apache.accumulo.server.metrics.Metrics; import org.apache.accumulo.server.metrics.MetricsSystemHelper; import org.apache.accumulo.server.replication.ZooKeeperInitialization; import org.apache.accumulo.server.rpc.HighlyAvailableServiceWrapper; -import org.apache.accumulo.server.rpc.RpcWrapper; import org.apache.accumulo.server.rpc.ServerAddress; import org.apache.accumulo.server.rpc.TCredentialsUpdatingWrapper; import org.apache.accumulo.server.rpc.TServerUtils; @@ -1215,7 +1215,7 @@ public class Master extends AccumuloServerContext clientHandler = new MasterClientServiceHandler(this); // Ensure that calls before the master gets the lock fail Iface haProxy = HighlyAvailableServiceWrapper.service(clientHandler, this); - Iface rpcProxy = RpcWrapper.service(haProxy); + Iface rpcProxy = TraceWrap.service(haProxy); final Processor<Iface> processor; if (ThriftServerType.SASL == getThriftServerType()) { Iface tcredsProxy = TCredentialsUpdatingWrapper.service(rpcProxy, clientHandler.getClass(), @@ -1236,7 +1236,7 @@ public class Master extends AccumuloServerContext this); // @formatter:off ReplicationCoordinator.Processor<ReplicationCoordinator.Iface> replicationCoordinatorProcessor = - new ReplicationCoordinator.Processor<>(RpcWrapper.service(haReplicationProxy)); + new ReplicationCoordinator.Processor<>(TraceWrap.service(haReplicationProxy)); // @formatter:on ServerAddress replAddress = TServerUtils.startServer(this, hostname, Property.MASTER_REPLICATION_COORDINATOR_PORT, replicationCoordinatorProcessor, diff --git a/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java b/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java index d4e39b8..5291ac0 100644 --- a/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java +++ b/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java @@ -150,6 +150,7 @@ import org.apache.accumulo.core.trace.DistributedTrace; import org.apache.accumulo.core.trace.Span; import org.apache.accumulo.core.trace.Trace; import org.apache.accumulo.core.trace.thrift.TInfo; +import org.apache.accumulo.core.trace.wrappers.TraceWrap; import org.apache.accumulo.core.util.ByteBufferUtil; import org.apache.accumulo.core.util.CachedConfiguration; import org.apache.accumulo.core.util.ColumnFQ; @@ -202,7 +203,6 @@ import org.apache.accumulo.server.metrics.MetricsSystemHelper; import org.apache.accumulo.server.problems.ProblemReport; import org.apache.accumulo.server.problems.ProblemReports; import org.apache.accumulo.server.replication.ZooKeeperInitialization; -import org.apache.accumulo.server.rpc.RpcWrapper; import org.apache.accumulo.server.rpc.ServerAddress; import org.apache.accumulo.server.rpc.TCredentialsUpdatingWrapper; import org.apache.accumulo.server.rpc.TServerUtils; @@ -2629,7 +2629,7 @@ public class TabletServer extends AccumuloServerContext implements Runnable { private HostAndPort startTabletClientService() throws UnknownHostException { // start listening for client connection last clientHandler = new ThriftClientHandler(); - Iface rpcProxy = RpcWrapper.service(clientHandler); + Iface rpcProxy = TraceWrap.service(clientHandler); final Processor<Iface> processor; if (ThriftServerType.SASL == getThriftServerType()) { Iface tcredProxy = TCredentialsUpdatingWrapper.service(rpcProxy, ThriftClientHandler.class, @@ -2646,7 +2646,7 @@ public class TabletServer extends AccumuloServerContext implements Runnable { private HostAndPort startReplicationService() throws UnknownHostException { final ReplicationServicerHandler handler = new ReplicationServicerHandler(this); - ReplicationServicer.Iface rpcProxy = RpcWrapper.service(handler); + ReplicationServicer.Iface rpcProxy = TraceWrap.service(handler); ReplicationServicer.Iface repl = TCredentialsUpdatingWrapper.service(rpcProxy, handler.getClass(), getConfiguration()); // @formatter:off diff --git a/test/src/main/java/org/apache/accumulo/test/rpc/SimpleThriftServiceRunner.java b/test/src/main/java/org/apache/accumulo/test/rpc/SimpleThriftServiceRunner.java index 87bf62b..5f65716 100644 --- a/test/src/main/java/org/apache/accumulo/test/rpc/SimpleThriftServiceRunner.java +++ b/test/src/main/java/org/apache/accumulo/test/rpc/SimpleThriftServiceRunner.java @@ -16,7 +16,7 @@ */ package org.apache.accumulo.test.rpc; -import org.apache.accumulo.server.rpc.RpcWrapper; +import org.apache.accumulo.core.trace.wrappers.TraceWrap; import org.apache.accumulo.test.rpc.thrift.SimpleThriftService; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.server.TServer; @@ -34,9 +34,9 @@ public class SimpleThriftServiceRunner { private final Thread serviceThread; private final TServer server; - public SimpleThriftServiceRunner(String threadName, boolean useWrapper) { + public SimpleThriftServiceRunner(String threadName) { this.mocket = new Mocket(); - this.server = createServer(useWrapper); + this.server = createServer(); this.serviceThread = new Thread(() -> server.serve(), threadName); } @@ -52,12 +52,9 @@ public class SimpleThriftServiceRunner { return new SimpleThriftService.Client(new TBinaryProtocol(mocket.getClientTransport())); } - private TServer createServer(boolean useWrapper) { + private TServer createServer() { TServer.Args args = new TServer.Args(mocket.getServerTransport()); - SimpleThriftService.Iface actualHandler = handler; - if (useWrapper) { - actualHandler = RpcWrapper.<SimpleThriftService.Iface> service(handler); - } + SimpleThriftService.Iface actualHandler = TraceWrap.service(handler); args.processor(new SimpleThriftService.Processor<>(actualHandler)); args.protocolFactory(new TBinaryProtocol.Factory()); return new TSimpleServer(args); diff --git a/test/src/main/java/org/apache/accumulo/test/rpc/ThriftBehaviorIT.java b/test/src/main/java/org/apache/accumulo/test/rpc/ThriftBehaviorIT.java index 52c4277..7414003 100644 --- a/test/src/main/java/org/apache/accumulo/test/rpc/ThriftBehaviorIT.java +++ b/test/src/main/java/org/apache/accumulo/test/rpc/ThriftBehaviorIT.java @@ -60,9 +60,6 @@ public class ThriftBehaviorIT { private static final String KITTY_MSG = "🐈 Kitty! 🐈"; - // can delete wrapper when tests pass without using it (assuming tests are good enough) - private static final boolean USE_RPC_WRAPPER = true; - private static final boolean SUPPRESS_SPAMMY_LOGGERS = true; @Before @@ -77,7 +74,7 @@ public class ThriftBehaviorIT { }); String threadName = ThriftBehaviorIT.class.getSimpleName() + "." + testName.getMethodName(); - serviceRunner = new SimpleThriftServiceRunner(threadName, USE_RPC_WRAPPER); + serviceRunner = new SimpleThriftServiceRunner(threadName); serviceRunner.startService(); client = serviceRunner.client(); handler = serviceRunner.handler(); -- To stop receiving notification emails like this one, please contact ctubb...@apache.org.