You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by es...@apache.org on 2018/11/30 00:58:12 UTC
[geode] branch feature/GEODE-6108 updated: GEODE-6108: Handle
client putIfAbsent returns value due to retry
This is an automated email from the ASF dual-hosted git repository.
eshu11 pushed a commit to branch feature/GEODE-6108
in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/feature/GEODE-6108 by this push:
new eb57bfd GEODE-6108: Handle client putIfAbsent returns value due to retry
eb57bfd is described below
commit eb57bfd45e51dd3ac150594e9aa4763764d8bd2c
Author: eshu <es...@pivotal.io>
AuthorDate: Thu Nov 29 16:52:33 2018 -0800
GEODE-6108: Handle client putIfAbsent returns value due to retry
* Check putIfAbsent return value from server if it could caused by retry.
* Make sure client putIfAbsent operation message.isRetry is set if it retries after failed when using singleHop
---
.../apache/geode/cache/client/internal/PutOp.java | 7 +-
.../geode/internal/cache/EntryEventImpl.java | 10 ++
.../apache/geode/internal/cache/LocalRegion.java | 27 +++-
.../geode/internal/cache/LocalRegionTest.java | 143 +++++++++++++++++++++
4 files changed, 182 insertions(+), 5 deletions(-)
diff --git a/geode-core/src/main/java/org/apache/geode/cache/client/internal/PutOp.java b/geode-core/src/main/java/org/apache/geode/cache/client/internal/PutOp.java
index de56cce..580aba9 100644
--- a/geode-core/src/main/java/org/apache/geode/cache/client/internal/PutOp.java
+++ b/geode-core/src/main/java/org/apache/geode/cache/client/internal/PutOp.java
@@ -82,11 +82,16 @@ public class PutOp {
if (e instanceof ServerOperationException) {
throw e; // fixed 44656
}
+ op.getMessage().setIsRetry();
cms.removeBucketServerLocation(server);
}
}
}
- return pool.execute(op);
+ Object result = pool.execute(op);
+ if (op.getMessage().isRetry()) {
+ event.setRetried(true);
+ }
+ return result;
}
public static Object execute(ExecutablePool pool, String regionName, Object key, Object value,
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/EntryEventImpl.java b/geode-core/src/main/java/org/apache/geode/internal/cache/EntryEventImpl.java
index a6db3db..ff27adf 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/EntryEventImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/EntryEventImpl.java
@@ -181,6 +181,8 @@ public class EntryEventImpl implements InternalEntryEvent, InternalCacheEvent,
private transient boolean isPendingSecondaryExpireDestroy = false;
+ private transient boolean hasRetried = false;
+
public static final Object SUSPECT_TOKEN = new Object();
public EntryEventImpl() {
@@ -610,6 +612,14 @@ public class EntryEventImpl implements InternalEntryEvent, InternalCacheEvent,
return this.isEvicted;
}
+ public boolean hasRetried() {
+ return hasRetried;
+ }
+
+ public void setRetried(boolean retried) {
+ hasRetried = retried;
+ }
+
public boolean isPendingSecondaryExpireDestroy() {
return this.isPendingSecondaryExpireDestroy;
}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/LocalRegion.java b/geode-core/src/main/java/org/apache/geode/internal/cache/LocalRegion.java
index 90089f4..488cd58 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/LocalRegion.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/LocalRegion.java
@@ -3041,10 +3041,7 @@ public class LocalRegion extends AbstractRegion implements LoaderHelperFactory,
event.setConcurrentMapOldValue(result);
}
if (op == Operation.PUT_IF_ABSENT) {
- if (result != null) {
- // customers don't see this exception
- throw new EntryNotFoundException("entry existed for putIfAbsent");
- }
+ checkPutIfAbsentResult(event, value, result);
} else if (op == Operation.REPLACE) {
if (requireOldValue && result == null) {
throw new EntryNotFoundException("entry not found for replace");
@@ -3060,6 +3057,28 @@ public class LocalRegion extends AbstractRegion implements LoaderHelperFactory,
}
}
+ void checkPutIfAbsentResult(EntryEventImpl event, Object value, Object result) {
+ if (result != null) {
+ // we may see a non null result possibly due to retry
+ if (event.hasRetried() && putIfAbsentResultHasSameValue(value, result)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("retried putIfAbsent and result is the value to be put,"
+ + " treat as a successful putIfAbsent");
+ }
+ } else {
+ // customers don't see this exception
+ throw new EntryNotFoundException("entry existed for putIfAbsent");
+ }
+ }
+ }
+
+ boolean putIfAbsentResultHasSameValue(Object value, Object result) {
+ if (Token.isInvalid(result)) {
+ return value == null;
+ }
+ return result.equals(value);
+ }
+
/**
* Destroy an entry on the server given its event.
*
diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/LocalRegionTest.java b/geode-core/src/test/java/org/apache/geode/internal/cache/LocalRegionTest.java
new file mode 100644
index 0000000..771d360
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/cache/LocalRegionTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.geode.internal.cache;
+
+import static org.assertj.core.api.Java6Assertions.assertThat;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.CancelCriterion;
+import org.apache.geode.cache.EntryNotFoundException;
+import org.apache.geode.cache.Operation;
+import org.apache.geode.cache.client.internal.ServerRegionProxy;
+
+public class LocalRegionTest {
+ private LocalRegion region;
+ private EntryEventImpl event;
+ private ServerRegionProxy serverRegionProxy;
+ private Operation operation;
+ private CancelCriterion cancelCriterion;
+
+ private final Object key = new Object();
+ private final String value = "value";
+
+ @Before
+ public void setup() {
+ region = mock(LocalRegion.class);
+ event = mock(EntryEventImpl.class);
+ serverRegionProxy = mock(ServerRegionProxy.class);
+ cancelCriterion = mock(CancelCriterion.class);
+
+ when(region.getServerProxy()).thenReturn(serverRegionProxy);
+ when(event.isFromServer()).thenReturn(false);
+ when(event.getKey()).thenReturn(key);
+ when(event.getRawNewValue()).thenReturn(value);
+ when(region.getCancelCriterion()).thenReturn(cancelCriterion);
+ }
+
+ @Test
+ public void serverPutWillCheckPutIfAbsentResult() {
+ Object result = new Object();
+ operation = Operation.PUT_IF_ABSENT;
+ when(event.getOperation()).thenReturn(operation);
+ when(event.isCreate()).thenReturn(true);
+ when(serverRegionProxy.put(key, value, null, event, operation, true, null, null, true))
+ .thenReturn(result);
+ doCallRealMethod().when(region).serverPut(event, true, null);
+
+ region.serverPut(event, true, null);
+
+ verify(region).checkPutIfAbsentResult(event, value, result);
+ }
+
+ @Test
+ public void checkPutIfAbsentResultSucceedsIfResultIsNull() {
+ Object result = null;
+ doCallRealMethod().when(region).checkPutIfAbsentResult(event, value, result);
+
+ region.checkPutIfAbsentResult(event, value, result);
+
+ verify(event, never()).hasRetried();
+ }
+
+ @Test(expected = EntryNotFoundException.class)
+ public void checkPutIfAbsentResultThrowsIfResultNotNullAndEventHasNotRetried() {
+ Object result = new Object();
+ when(event.hasRetried()).thenReturn(false);
+ doCallRealMethod().when(region).checkPutIfAbsentResult(event, value, result);
+
+ region.checkPutIfAbsentResult(event, value, result);
+ }
+
+ @Test(expected = EntryNotFoundException.class)
+ public void checkPutIfAbsentResultThrowsIfEventHasRetriedButResultNotHaveSameValue() {
+ Object result = new Object();
+ when(event.hasRetried()).thenReturn(true);
+ when(region.putIfAbsentResultHasSameValue(value, result)).thenReturn(false);
+ doCallRealMethod().when(region).checkPutIfAbsentResult(event, value, result);
+
+ region.checkPutIfAbsentResult(event, value, result);
+ }
+
+ @Test
+ public void checkPutIfAbsentResultSucceedsIfEventHasRetriedAndResultHasSameValue() {
+ Object result = new Object();
+ when(event.hasRetried()).thenReturn(true);
+ when(region.putIfAbsentResultHasSameValue(value, result)).thenReturn(true);
+ doCallRealMethod().when(region).checkPutIfAbsentResult(event, value, result);
+
+ region.checkPutIfAbsentResult(event, value, result);
+
+ verify(event).hasRetried();
+ verify(region).putIfAbsentResultHasSameValue(value, result);
+ }
+
+ @Test
+ public void putIfAbsentResultHasSameValueReturnTrueIfResultIsInvalidTokenAndValueToBePutIsNull() {
+ when(region.putIfAbsentResultHasSameValue(null, Token.INVALID)).thenCallRealMethod();
+
+ assertThat(region.putIfAbsentResultHasSameValue(null, Token.INVALID)).isTrue();
+ }
+
+ @Test
+ public void putIfAbsentResultHasSameValueReturnFalseIfResultIsInvalidTokenAndValueToBePutIsNotNull() {
+ when(region.putIfAbsentResultHasSameValue(value, Token.INVALID)).thenCallRealMethod();
+
+ assertThat(region.putIfAbsentResultHasSameValue(value, Token.INVALID)).isFalse();
+ }
+
+ @Test
+ public void putIfAbsentResultHasSameValueReturnTrueIfResultHasSameValue() {
+ Object result = "value";
+ when(region.putIfAbsentResultHasSameValue(value, result)).thenCallRealMethod();
+
+ assertThat(region.putIfAbsentResultHasSameValue(value, result)).isTrue();
+ }
+
+ @Test
+ public void putIfAbsentResultHasSameValueReturnFalseIfResultDoesNotHaveSameValue() {
+ Object result = "differentValue";
+ when(region.putIfAbsentResultHasSameValue(value, result)).thenCallRealMethod();
+
+ assertThat(region.putIfAbsentResultHasSameValue(value, result)).isFalse();
+ }
+
+}