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