Fix for ONOS-4315
- Additional log on error - Allow count=0 using CountDownCompleter - test case to detect the issue (@Ignored by default right now) - other bug fixes found along the way Based on patch by Madan@China Change-Id: I7d6cb8c214052859900ef7ee0337a7e1c8a9d295
Showing
3 changed files
with
60 additions
and
6 deletions
| ... | @@ -18,6 +18,7 @@ package org.onosproject.store.primitives.resources.impl; | ... | @@ -18,6 +18,7 @@ package org.onosproject.store.primitives.resources.impl; |
| 18 | import static org.onosproject.store.service.MapEvent.Type.INSERT; | 18 | import static org.onosproject.store.service.MapEvent.Type.INSERT; |
| 19 | import static org.onosproject.store.service.MapEvent.Type.REMOVE; | 19 | import static org.onosproject.store.service.MapEvent.Type.REMOVE; |
| 20 | import static org.onosproject.store.service.MapEvent.Type.UPDATE; | 20 | import static org.onosproject.store.service.MapEvent.Type.UPDATE; |
| 21 | +import static org.slf4j.LoggerFactory.getLogger; | ||
| 21 | import io.atomix.copycat.server.session.ServerSession; | 22 | import io.atomix.copycat.server.session.ServerSession; |
| 22 | import io.atomix.copycat.server.Commit; | 23 | import io.atomix.copycat.server.Commit; |
| 23 | import io.atomix.copycat.server.Snapshottable; | 24 | import io.atomix.copycat.server.Snapshottable; |
| ... | @@ -60,7 +61,9 @@ import org.onosproject.store.primitives.resources.impl.AtomixConsistentMapComman | ... | @@ -60,7 +61,9 @@ import org.onosproject.store.primitives.resources.impl.AtomixConsistentMapComman |
| 60 | import org.onosproject.store.service.MapEvent; | 61 | import org.onosproject.store.service.MapEvent; |
| 61 | import org.onosproject.store.service.MapTransaction; | 62 | import org.onosproject.store.service.MapTransaction; |
| 62 | import org.onosproject.store.service.Versioned; | 63 | import org.onosproject.store.service.Versioned; |
| 64 | +import org.slf4j.Logger; | ||
| 63 | 65 | ||
| 66 | +import com.google.common.base.Throwables; | ||
| 64 | import com.google.common.collect.Lists; | 67 | import com.google.common.collect.Lists; |
| 65 | import com.google.common.collect.Maps; | 68 | import com.google.common.collect.Maps; |
| 66 | import com.google.common.collect.Sets; | 69 | import com.google.common.collect.Sets; |
| ... | @@ -72,6 +75,7 @@ import static com.google.common.base.Preconditions.checkState; | ... | @@ -72,6 +75,7 @@ import static com.google.common.base.Preconditions.checkState; |
| 72 | */ | 75 | */ |
| 73 | public class AtomixConsistentMapState extends ResourceStateMachine implements SessionListener, Snapshottable { | 76 | public class AtomixConsistentMapState extends ResourceStateMachine implements SessionListener, Snapshottable { |
| 74 | 77 | ||
| 78 | + private final Logger log = getLogger(getClass()); | ||
| 75 | private final Map<Long, Commit<? extends AtomixConsistentMapCommands.Listen>> listeners = new HashMap<>(); | 79 | private final Map<Long, Commit<? extends AtomixConsistentMapCommands.Listen>> listeners = new HashMap<>(); |
| 76 | private final Map<String, MapEntryValue> mapEntries = new HashMap<>(); | 80 | private final Map<String, MapEntryValue> mapEntries = new HashMap<>(); |
| 77 | private final Set<String> preparedKeys = Sets.newHashSet(); | 81 | private final Set<String> preparedKeys = Sets.newHashSet(); |
| ... | @@ -384,7 +388,7 @@ public class AtomixConsistentMapState extends ResourceStateMachine implements Se | ... | @@ -384,7 +388,7 @@ public class AtomixConsistentMapState extends ResourceStateMachine implements Se |
| 384 | } | 388 | } |
| 385 | MapEntryValue existingValue = mapEntries.get(key); | 389 | MapEntryValue existingValue = mapEntries.get(key); |
| 386 | if (existingValue == null) { | 390 | if (existingValue == null) { |
| 387 | - if (update.currentValue() != null) { | 391 | + if (update.type() != MapUpdate.Type.PUT_IF_ABSENT) { |
| 388 | return PrepareResult.OPTIMISTIC_LOCK_FAILURE; | 392 | return PrepareResult.OPTIMISTIC_LOCK_FAILURE; |
| 389 | } | 393 | } |
| 390 | } else { | 394 | } else { |
| ... | @@ -399,6 +403,9 @@ public class AtomixConsistentMapState extends ResourceStateMachine implements Se | ... | @@ -399,6 +403,9 @@ public class AtomixConsistentMapState extends ResourceStateMachine implements Se |
| 399 | transaction.updates().forEach(u -> preparedKeys.add(u.key())); | 403 | transaction.updates().forEach(u -> preparedKeys.add(u.key())); |
| 400 | ok = true; | 404 | ok = true; |
| 401 | return PrepareResult.OK; | 405 | return PrepareResult.OK; |
| 406 | + } catch (Exception e) { | ||
| 407 | + log.warn("Failure applying {}", commit, e); | ||
| 408 | + throw Throwables.propagate(e); | ||
| 402 | } finally { | 409 | } finally { |
| 403 | if (!ok) { | 410 | if (!ok) { |
| 404 | commit.close(); | 411 | commit.close(); |
| ... | @@ -416,6 +423,9 @@ public class AtomixConsistentMapState extends ResourceStateMachine implements Se | ... | @@ -416,6 +423,9 @@ public class AtomixConsistentMapState extends ResourceStateMachine implements Se |
| 416 | TransactionId transactionId = commit.operation().transactionId(); | 423 | TransactionId transactionId = commit.operation().transactionId(); |
| 417 | try { | 424 | try { |
| 418 | return commitInternal(transactionId); | 425 | return commitInternal(transactionId); |
| 426 | + } catch (Exception e) { | ||
| 427 | + log.warn("Failure applying {}", commit, e); | ||
| 428 | + throw Throwables.propagate(e); | ||
| 419 | } finally { | 429 | } finally { |
| 420 | commit.close(); | 430 | commit.close(); |
| 421 | } | 431 | } |
| ... | @@ -438,12 +448,11 @@ public class AtomixConsistentMapState extends ResourceStateMachine implements Se | ... | @@ -438,12 +448,11 @@ public class AtomixConsistentMapState extends ResourceStateMachine implements Se |
| 438 | List<MapEvent<String, byte[]>> eventsToPublish = Lists.newArrayList(); | 448 | List<MapEvent<String, byte[]>> eventsToPublish = Lists.newArrayList(); |
| 439 | for (MapUpdate<String, byte[]> update : transaction.updates()) { | 449 | for (MapUpdate<String, byte[]> update : transaction.updates()) { |
| 440 | String key = update.key(); | 450 | String key = update.key(); |
| 451 | + checkState(preparedKeys.remove(key), "key is not prepared"); | ||
| 441 | MapEntryValue previousValue = mapEntries.remove(key); | 452 | MapEntryValue previousValue = mapEntries.remove(key); |
| 442 | MapEntryValue newValue = null; | 453 | MapEntryValue newValue = null; |
| 443 | - checkState(preparedKeys.remove(key), "key is not prepared"); | ||
| 444 | if (update.type() != MapUpdate.Type.REMOVE_IF_VERSION_MATCH) { | 454 | if (update.type() != MapUpdate.Type.REMOVE_IF_VERSION_MATCH) { |
| 445 | - newValue = new TransactionalCommit(key, | 455 | + newValue = new TransactionalCommit(key, versionCounter.incrementAndGet(), completer); |
| 446 | - versionCounter.incrementAndGet(), completer); | ||
| 447 | } | 456 | } |
| 448 | eventsToPublish.add(new MapEvent<>("", key, toVersioned(newValue), toVersioned(previousValue))); | 457 | eventsToPublish.add(new MapEvent<>("", key, toVersioned(newValue), toVersioned(previousValue))); |
| 449 | if (newValue != null) { | 458 | if (newValue != null) { | ... | ... |
| ... | @@ -16,6 +16,8 @@ | ... | @@ -16,6 +16,8 @@ |
| 16 | package org.onosproject.store.primitives.resources.impl; | 16 | package org.onosproject.store.primitives.resources.impl; |
| 17 | 17 | ||
| 18 | import io.atomix.resource.ResourceType; | 18 | import io.atomix.resource.ResourceType; |
| 19 | + | ||
| 20 | +import static org.hamcrest.Matchers.*; | ||
| 19 | import static org.junit.Assert.*; | 21 | import static org.junit.Assert.*; |
| 20 | 22 | ||
| 21 | import java.util.Arrays; | 23 | import java.util.Arrays; |
| ... | @@ -348,6 +350,7 @@ public class AtomixConsistentMapTest extends AtomixTestBase { | ... | @@ -348,6 +350,7 @@ public class AtomixConsistentMapTest extends AtomixTestBase { |
| 348 | 350 | ||
| 349 | map.addListener(listener).join(); | 351 | map.addListener(listener).join(); |
| 350 | 352 | ||
| 353 | + // PUT_IF_ABSENT | ||
| 351 | MapUpdate<String, byte[]> update1 = | 354 | MapUpdate<String, byte[]> update1 = |
| 352 | MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT) | 355 | MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT) |
| 353 | .withKey("foo") | 356 | .withKey("foo") |
| ... | @@ -359,6 +362,7 @@ public class AtomixConsistentMapTest extends AtomixTestBase { | ... | @@ -359,6 +362,7 @@ public class AtomixConsistentMapTest extends AtomixTestBase { |
| 359 | map.prepare(tx).thenAccept(result -> { | 362 | map.prepare(tx).thenAccept(result -> { |
| 360 | assertEquals(true, result); | 363 | assertEquals(true, result); |
| 361 | }).join(); | 364 | }).join(); |
| 365 | + // verify changes in Tx is not visible yet until commit | ||
| 362 | assertFalse(listener.eventReceived()); | 366 | assertFalse(listener.eventReceived()); |
| 363 | 367 | ||
| 364 | map.size().thenAccept(result -> { | 368 | map.size().thenAccept(result -> { |
| ... | @@ -371,7 +375,7 @@ public class AtomixConsistentMapTest extends AtomixTestBase { | ... | @@ -371,7 +375,7 @@ public class AtomixConsistentMapTest extends AtomixTestBase { |
| 371 | 375 | ||
| 372 | try { | 376 | try { |
| 373 | map.put("foo", value2).join(); | 377 | map.put("foo", value2).join(); |
| 374 | - assertTrue(false); | 378 | + fail("update to map entry in open tx should fail with Exception"); |
| 375 | } catch (CompletionException e) { | 379 | } catch (CompletionException e) { |
| 376 | assertEquals(ConcurrentModificationException.class, e.getCause().getClass()); | 380 | assertEquals(ConcurrentModificationException.class, e.getCause().getClass()); |
| 377 | } | 381 | } |
| ... | @@ -384,6 +388,7 @@ public class AtomixConsistentMapTest extends AtomixTestBase { | ... | @@ -384,6 +388,7 @@ public class AtomixConsistentMapTest extends AtomixTestBase { |
| 384 | assertEquals(MapEvent.Type.INSERT, event.type()); | 388 | assertEquals(MapEvent.Type.INSERT, event.type()); |
| 385 | assertTrue(Arrays.equals(value1, event.newValue().value())); | 389 | assertTrue(Arrays.equals(value1, event.newValue().value())); |
| 386 | 390 | ||
| 391 | + // map should be update-able after commit | ||
| 387 | map.put("foo", value2).thenAccept(result -> { | 392 | map.put("foo", value2).thenAccept(result -> { |
| 388 | assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1)); | 393 | assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1)); |
| 389 | }).join(); | 394 | }).join(); |
| ... | @@ -391,6 +396,43 @@ public class AtomixConsistentMapTest extends AtomixTestBase { | ... | @@ -391,6 +396,43 @@ public class AtomixConsistentMapTest extends AtomixTestBase { |
| 391 | assertNotNull(event); | 396 | assertNotNull(event); |
| 392 | assertEquals(MapEvent.Type.UPDATE, event.type()); | 397 | assertEquals(MapEvent.Type.UPDATE, event.type()); |
| 393 | assertTrue(Arrays.equals(value2, event.newValue().value())); | 398 | assertTrue(Arrays.equals(value2, event.newValue().value())); |
| 399 | + | ||
| 400 | + | ||
| 401 | + // REMOVE_IF_VERSION_MATCH | ||
| 402 | + byte[] currFoo = map.get("foo").get().value(); | ||
| 403 | + long currFooVersion = map.get("foo").get().version(); | ||
| 404 | + MapUpdate<String, byte[]> remove1 = | ||
| 405 | + MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.REMOVE_IF_VERSION_MATCH) | ||
| 406 | + .withKey("foo") | ||
| 407 | + .withCurrentVersion(currFooVersion) | ||
| 408 | + .build(); | ||
| 409 | + | ||
| 410 | + tx = new MapTransaction<>(TransactionId.from("tx2"), Arrays.asList(remove1)); | ||
| 411 | + | ||
| 412 | + map.prepare(tx).thenAccept(result -> { | ||
| 413 | + assertTrue("prepare should succeed", result); | ||
| 414 | + }).join(); | ||
| 415 | + // verify changes in Tx is not visible yet until commit | ||
| 416 | + assertFalse(listener.eventReceived()); | ||
| 417 | + | ||
| 418 | + map.size().thenAccept(size -> { | ||
| 419 | + assertThat(size, is(1)); | ||
| 420 | + }).join(); | ||
| 421 | + | ||
| 422 | + map.get("foo").thenAccept(result -> { | ||
| 423 | + assertThat(result.value(), is(currFoo)); | ||
| 424 | + }).join(); | ||
| 425 | + | ||
| 426 | + map.commit(tx.transactionId()).join(); | ||
| 427 | + event = listener.event(); | ||
| 428 | + assertNotNull(event); | ||
| 429 | + assertEquals(MapEvent.Type.REMOVE, event.type()); | ||
| 430 | + assertArrayEquals(currFoo, event.oldValue().value()); | ||
| 431 | + | ||
| 432 | + map.size().thenAccept(size -> { | ||
| 433 | + assertThat(size, is(0)); | ||
| 434 | + }).join(); | ||
| 435 | + | ||
| 394 | } | 436 | } |
| 395 | 437 | ||
| 396 | protected void transactionRollbackTests(int clusterSize) throws Throwable { | 438 | protected void transactionRollbackTests(int clusterSize) throws Throwable { | ... | ... |
| ... | @@ -45,10 +45,13 @@ public final class CountDownCompleter<T> { | ... | @@ -45,10 +45,13 @@ public final class CountDownCompleter<T> { |
| 45 | * @param onCompleteCallback callback to invoke when completer is completed | 45 | * @param onCompleteCallback callback to invoke when completer is completed |
| 46 | */ | 46 | */ |
| 47 | public CountDownCompleter(T object, long count, Consumer<T> onCompleteCallback) { | 47 | public CountDownCompleter(T object, long count, Consumer<T> onCompleteCallback) { |
| 48 | - checkState(count > 0, "count must be positive"); | 48 | + checkState(count >= 0, "count must be non-negative"); |
| 49 | this.counter = new AtomicLong(count); | 49 | this.counter = new AtomicLong(count); |
| 50 | this.object = checkNotNull(object); | 50 | this.object = checkNotNull(object); |
| 51 | this.onCompleteCallback = checkNotNull(onCompleteCallback); | 51 | this.onCompleteCallback = checkNotNull(onCompleteCallback); |
| 52 | + if (count == 0) { | ||
| 53 | + onCompleteCallback.accept(object); | ||
| 54 | + } | ||
| 52 | } | 55 | } |
| 53 | 56 | ||
| 54 | /** | 57 | /** | ... | ... |
-
Please register or login to post a comment