Skip to content

Commit 486d07e

Browse files
authored
Fixed bug where alters that rely on state are retried but shouldn't (#356)
* Fixed bug where alters that rely on state are retried but shouldn't
1 parent 2e15dcb commit 486d07e

File tree

4 files changed

+69
-28
lines changed

4 files changed

+69
-28
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11

2+
## [4.1.3] - 2025-11-26
3+
### Fixed
4+
- Fixed bug where alter statements are retried resulting incorrect behaviour due to them not being idempotent.
5+
26
## [4.1.2] - 2025-11-03
37
### Changed
48
- `hive-exec` dependency to use shaded jar to avoid Kryo conflicts.

waggle-dance-core/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,22 @@
202202
<dependency>
203203
<groupId>org.apache.hadoop</groupId>
204204
<artifactId>hadoop-common</artifactId>
205+
<exclusions>
206+
<exclusion>
207+
<groupId>org.slf4j</groupId>
208+
<artifactId>slf4j-reload4j</artifactId>
209+
</exclusion>
210+
</exclusions>
205211
</dependency>
206212
<dependency>
207213
<groupId>org.apache.hadoop</groupId>
208214
<artifactId>hadoop-mapreduce-client-core</artifactId>
215+
<exclusions>
216+
<exclusion>
217+
<groupId>org.slf4j</groupId>
218+
<artifactId>slf4j-reload4j</artifactId>
219+
</exclusion>
220+
</exclusions>
209221
</dependency>
210222

211223
<dependency>

waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/DefaultMetaStoreClientFactory.java

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import com.hotels.bdp.waggledance.client.compatibility.HiveCompatibleThriftHiveMetastoreIfaceFactory;
3434
import com.hotels.hcommon.hive.metastore.exception.MetastoreUnavailableException;
3535

36-
3736
public class DefaultMetaStoreClientFactory implements MetaStoreClientFactory {
3837

3938
static final Class<?>[] INTERFACES = new Class<?>[] { CloseableThriftHiveMetastoreIface.class };
@@ -48,9 +47,9 @@ private static class ReconnectingMetastoreClientInvocationHandler implements Inv
4847
private HiveUgiArgs cachedUgi = null;
4948

5049
private ReconnectingMetastoreClientInvocationHandler(
51-
String name,
52-
int maxRetries,
53-
AbstractThriftMetastoreClientManager base) {
50+
String name,
51+
int maxRetries,
52+
AbstractThriftMetastoreClientManager base) {
5453
this.name = name;
5554
this.maxRetries = maxRetries;
5655
this.base = base;
@@ -81,8 +80,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
8180
cachedUgi = new HiveUgiArgs(user, groups);
8281
if (base.isOpen()) {
8382
log
84-
.info("calling #set_ugi (on already open client) for user '{}', on metastore {}", cachedUgi.getUser(),
85-
name);
83+
.info("calling #set_ugi (on already open client) for user '{}', on metastore {}", cachedUgi.getUser(),
84+
name);
8685
return doRealCall(method, args, attempt);
8786
} else {
8887
// delay call until we get the next non set_ugi call, this helps doing unnecessary calls to Federated
@@ -107,21 +106,25 @@ private Object doRealCall(Method method, Object[] args, int attempt) throws Ille
107106
base.reconnect(cachedUgi);
108107
continue;
109108
}
110-
throw new MetastoreUnavailableException("Client " + name + " is not available", realException);
109+
log.debug("Client " + name + " is not available");
110+
throw realException;
111111
}
112112
throw realException;
113113
}
114114
} while (++attempt <= maxRetries);
115115
throw new RuntimeException("Unreachable code");
116116
}
117117

118+
/**
119+
* Decides whether a method should be retried. Only 'get' methods are retried. Alters/creates methods are not
120+
* retried as there are cases where this is not idempotent. TODO Potentially in the future we should remove the
121+
* whole retry mechanic and just leave that up to the caller/client.
122+
*/
118123
private boolean shouldRetry(Method method) {
119-
switch (method.getName()) {
120-
case "shutdown":
121-
return false;
122-
default:
123-
return true;
124+
if (method.getName().startsWith("get")) {
125+
return true;
124126
}
127+
return false;
125128
}
126129

127130
private void reconnectIfDisconnected() {
@@ -143,10 +146,10 @@ private void reconnectIfDisconnected() {
143146
*/
144147
@Override
145148
public CloseableThriftHiveMetastoreIface newInstance(
146-
HiveConf hiveConf,
147-
String name,
148-
int reconnectionRetries,
149-
int connectionTimeout) {
149+
HiveConf hiveConf,
150+
String name,
151+
int reconnectionRetries,
152+
int connectionTimeout) {
150153
boolean useSasl = hiveConf.getBoolVar(ConfVars.METASTORE_USE_THRIFT_SASL);
151154
HiveCompatibleThriftHiveMetastoreIfaceFactory factory = new HiveCompatibleThriftHiveMetastoreIfaceFactory();
152155
AbstractThriftMetastoreClientManager base = null;
@@ -160,13 +163,13 @@ public CloseableThriftHiveMetastoreIface newInstance(
160163

161164
@VisibleForTesting
162165
CloseableThriftHiveMetastoreIface newInstance(
163-
String name,
164-
int reconnectionRetries,
165-
AbstractThriftMetastoreClientManager base) {
166+
String name,
167+
int reconnectionRetries,
168+
AbstractThriftMetastoreClientManager base) {
166169
ReconnectingMetastoreClientInvocationHandler reconnectingHandler = new ReconnectingMetastoreClientInvocationHandler(
167170
name, reconnectionRetries, base);
168-
return (CloseableThriftHiveMetastoreIface) Proxy.newProxyInstance(getClass().getClassLoader(),
169-
INTERFACES, reconnectingHandler);
171+
return (CloseableThriftHiveMetastoreIface) Proxy
172+
.newProxyInstance(getClass().getClassLoader(), INTERFACES, reconnectingHandler);
170173
}
171174

172-
}
175+
}

waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/client/DefaultMetaStoreClientFactoryTest.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@
1515
*/
1616
package com.hotels.bdp.waggledance.client;
1717

18+
import static com.hotels.bdp.waggledance.client.HiveUgiArgsStub.TEST_ARGS;
1819
import static org.hamcrest.CoreMatchers.is;
1920
import static org.hamcrest.MatcherAssert.assertThat;
21+
import static org.junit.Assert.fail;
2022
import static org.mockito.Mockito.doThrow;
2123
import static org.mockito.Mockito.never;
2224
import static org.mockito.Mockito.verify;
2325
import static org.mockito.Mockito.when;
2426

25-
import static com.hotels.bdp.waggledance.client.HiveUgiArgsStub.TEST_ARGS;
26-
2727
import java.util.List;
2828

29+
import org.apache.hadoop.hive.metastore.api.EnvironmentContext;
30+
import org.apache.hadoop.hive.metastore.api.MetaException;
31+
import org.apache.hadoop.hive.metastore.api.Table;
2932
import org.apache.hadoop.hive.metastore.api.ThriftHiveMetastore.Iface;
3033
import org.apache.thrift.TException;
3134
import org.apache.thrift.transport.TTransportException;
@@ -36,8 +39,6 @@
3639

3740
import com.google.common.collect.Lists;
3841

39-
import com.hotels.hcommon.hive.metastore.exception.MetastoreUnavailableException;
40-
4142
@RunWith(MockitoJUnitRunner.class)
4243
public class DefaultMetaStoreClientFactoryTest {
4344

@@ -103,6 +104,27 @@ public void defaultMethodCallThrowsTransportExceptionRetries() throws TException
103104
verify(base).reconnect(null);
104105
}
105106

107+
@Test
108+
public void manipulatingDataMethodCallThrowsTransportExceptionNoRetries() throws TException {
109+
when(base.getClient()).thenReturn(client);
110+
EnvironmentContext envContext = new EnvironmentContext();
111+
Table table = new Table();
112+
String dbName = "db";
113+
String tableName = "table";
114+
doThrow(new TTransportException("No connection"), new MetaException("Server Error"))
115+
.when(client)
116+
.alter_table_with_environment_context(dbName, tableName, table, envContext);
117+
118+
CloseableThriftHiveMetastoreIface iface = factory.newInstance("name", RECONNECTION_RETRIES, base);
119+
120+
try {
121+
iface.alter_table_with_environment_context(dbName, tableName, table, envContext);
122+
fail("Expected TTransportException");
123+
} catch (TTransportException e) {
124+
assertThat(e.getMessage(), is("No connection"));
125+
}
126+
}
127+
106128
@Test
107129
public void set_ugi_before_call() throws Exception {
108130
when(base.getClient()).thenReturn(client);
@@ -141,7 +163,7 @@ public void set_ugi_CalledWhenOpen() throws Exception {
141163
assertThat(setUgiResult, is(Lists.newArrayList("users!")));
142164
}
143165

144-
@Test(expected = MetastoreUnavailableException.class)
166+
@Test(expected = TTransportException.class)
145167
public void shutdownThrowsTransportExceptionNoRetry() throws TException {
146168
when(base.getClient()).thenReturn(client);
147169
doThrow(new TTransportException()).when(client).shutdown();
@@ -151,7 +173,7 @@ public void shutdownThrowsTransportExceptionNoRetry() throws TException {
151173
iface.shutdown();
152174
}
153175

154-
@Test(expected = MetastoreUnavailableException.class)
176+
@Test(expected = TTransportException.class)
155177
public void defaultMethodCallThrowsTransportExceptionNoRetriesLeft() throws TException {
156178
when(base.getClient()).thenReturn(client);
157179
when(client.getName()).thenThrow(new TTransportException());

0 commit comments

Comments
 (0)