Madan Jampani
Committed by Gerrit Code Review

ONOS-4218: Fixes for resource store transaction failures

Change-Id: Ie48bb04d7daf6ed7b63c33a3c3c2703496179aa6
...@@ -21,6 +21,8 @@ import static com.google.common.base.Preconditions.checkState; ...@@ -21,6 +21,8 @@ import static com.google.common.base.Preconditions.checkState;
21 21
22 import java.util.function.Function; 22 import java.util.function.Function;
23 23
24 +import org.onlab.util.ByteArraySizeHashPrinter;
25 +
24 import com.google.common.base.MoreObjects; 26 import com.google.common.base.MoreObjects;
25 27
26 /** 28 /**
...@@ -153,7 +155,7 @@ public final class MapUpdate<K, V> { ...@@ -153,7 +155,7 @@ public final class MapUpdate<K, V> {
153 .add("mapName", mapName) 155 .add("mapName", mapName)
154 .add("type", type) 156 .add("type", type)
155 .add("key", key) 157 .add("key", key)
156 - .add("value", value) 158 + .add("value", value instanceof byte[] ? new ByteArraySizeHashPrinter((byte[]) value) : value)
157 .add("currentValue", currentValue) 159 .add("currentValue", currentValue)
158 .add("currentVersion", currentVersion) 160 .add("currentVersion", currentVersion)
159 .toString(); 161 .toString();
......
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.store.service;
17 +
18 +/**
19 + * Completion status of transaction.
20 + */
21 +public enum CommitStatus {
22 + /**
23 + * Indicates a successfully completed transaction with all the updates committed.
24 + */
25 + SUCCESS,
26 +
27 + /**
28 + * Indicates a aborted transaction i.e. no updates were committed.
29 + */
30 + FAILURE
31 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
16 16
17 package org.onosproject.store.service; 17 package org.onosproject.store.service;
18 18
19 +import java.util.concurrent.CompletableFuture;
20 +
19 import org.onosproject.store.primitives.TransactionId; 21 import org.onosproject.store.primitives.TransactionId;
20 22
21 /** 23 /**
...@@ -63,9 +65,9 @@ public interface TransactionContext extends DistributedPrimitive { ...@@ -63,9 +65,9 @@ public interface TransactionContext extends DistributedPrimitive {
63 * Commits a transaction that was previously started thereby making its changes permanent 65 * Commits a transaction that was previously started thereby making its changes permanent
64 * and externally visible. 66 * and externally visible.
65 * 67 *
66 - * @return true if this transaction succeeded, otherwise false. 68 + * @return A future that will be completed when the operation completes
67 */ 69 */
68 - boolean commit(); 70 + CompletableFuture<CommitStatus> commit();
69 71
70 /** 72 /**
71 * Aborts any changes made in this transaction context and discarding all locally cached updates. 73 * Aborts any changes made in this transaction context and discarding all locally cached updates.
......
...@@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableList; ...@@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.ImmutableSet; 20 import com.google.common.collect.ImmutableSet;
21 import com.google.common.collect.Maps; 21 import com.google.common.collect.Maps;
22 import com.google.common.collect.Sets; 22 import com.google.common.collect.Sets;
23 +
23 import org.apache.felix.scr.annotations.Activate; 24 import org.apache.felix.scr.annotations.Activate;
24 import org.apache.felix.scr.annotations.Component; 25 import org.apache.felix.scr.annotations.Component;
25 import org.apache.felix.scr.annotations.Reference; 26 import org.apache.felix.scr.annotations.Reference;
...@@ -41,6 +42,7 @@ import org.onosproject.net.resource.ResourceStoreDelegate; ...@@ -41,6 +42,7 @@ import org.onosproject.net.resource.ResourceStoreDelegate;
41 import org.onosproject.net.resource.Resources; 42 import org.onosproject.net.resource.Resources;
42 import org.onosproject.store.AbstractStore; 43 import org.onosproject.store.AbstractStore;
43 import org.onosproject.store.serializers.KryoNamespaces; 44 import org.onosproject.store.serializers.KryoNamespaces;
45 +import org.onosproject.store.service.CommitStatus;
44 import org.onosproject.store.service.ConsistentMap; 46 import org.onosproject.store.service.ConsistentMap;
45 import org.onosproject.store.service.ConsistentMapException; 47 import org.onosproject.store.service.ConsistentMapException;
46 import org.onosproject.store.service.Serializer; 48 import org.onosproject.store.service.Serializer;
...@@ -178,18 +180,18 @@ public class ConsistentResourceStore extends AbstractStore<ResourceEvent, Resour ...@@ -178,18 +180,18 @@ public class ConsistentResourceStore extends AbstractStore<ResourceEvent, Resour
178 } 180 }
179 } 181 }
180 182
181 - boolean success = tx.commit(); 183 + return tx.commit().whenComplete((status, error) -> {
182 - if (success) { 184 + if (status == CommitStatus.SUCCESS) {
183 - log.trace("Transaction commit succeeded on registration: resources={}", resources); 185 + log.trace("Transaction commit succeeded on registration: resources={}", resources);
184 - List<ResourceEvent> events = resources.stream() 186 + List<ResourceEvent> events = resources.stream()
185 - .filter(x -> x.parent().isPresent()) 187 + .filter(x -> x.parent().isPresent())
186 - .map(x -> new ResourceEvent(RESOURCE_ADDED, x)) 188 + .map(x -> new ResourceEvent(RESOURCE_ADDED, x))
187 - .collect(Collectors.toList()); 189 + .collect(Collectors.toList());
188 - notifyDelegate(events); 190 + notifyDelegate(events);
189 - } else { 191 + } else {
190 - log.debug("Transaction commit failed on registration: resources={}", resources); 192 + log.warn("Transaction commit failed on registration", error);
191 - } 193 + }
192 - return success; 194 + }).join() == CommitStatus.SUCCESS;
193 } 195 }
194 196
195 @Override 197 @Override
...@@ -252,17 +254,17 @@ public class ConsistentResourceStore extends AbstractStore<ResourceEvent, Resour ...@@ -252,17 +254,17 @@ public class ConsistentResourceStore extends AbstractStore<ResourceEvent, Resour
252 } 254 }
253 } 255 }
254 256
255 - boolean success = tx.commit(); 257 + return tx.commit().whenComplete((status, error) -> {
256 - if (success) { 258 + if (status == CommitStatus.SUCCESS) {
257 - List<ResourceEvent> events = resources.stream() 259 + List<ResourceEvent> events = resources.stream()
258 - .filter(x -> x.parent().isPresent()) 260 + .filter(x -> x.parent().isPresent())
259 - .map(x -> new ResourceEvent(RESOURCE_REMOVED, x)) 261 + .map(x -> new ResourceEvent(RESOURCE_REMOVED, x))
260 - .collect(Collectors.toList()); 262 + .collect(Collectors.toList());
261 - notifyDelegate(events); 263 + notifyDelegate(events);
262 - } else { 264 + } else {
263 - log.warn("Failed to unregister {}: Commit failed.", ids); 265 + log.warn("Failed to unregister {}: Commit failed.", ids, error);
264 - } 266 + }
265 - return success; 267 + }).join() == CommitStatus.SUCCESS;
266 } 268 }
267 269
268 @Override 270 @Override
...@@ -308,7 +310,7 @@ public class ConsistentResourceStore extends AbstractStore<ResourceEvent, Resour ...@@ -308,7 +310,7 @@ public class ConsistentResourceStore extends AbstractStore<ResourceEvent, Resour
308 } 310 }
309 } 311 }
310 312
311 - return tx.commit(); 313 + return tx.commit().join() == CommitStatus.SUCCESS;
312 } 314 }
313 315
314 @Override 316 @Override
...@@ -348,7 +350,7 @@ public class ConsistentResourceStore extends AbstractStore<ResourceEvent, Resour ...@@ -348,7 +350,7 @@ public class ConsistentResourceStore extends AbstractStore<ResourceEvent, Resour
348 } 350 }
349 } 351 }
350 352
351 - return tx.commit(); 353 + return tx.commit().join() == CommitStatus.SUCCESS;
352 } 354 }
353 355
354 // computational complexity: O(1) if the resource is discrete type. 356 // computational complexity: O(1) if the resource is discrete type.
......
...@@ -27,6 +27,7 @@ import static com.google.common.base.Preconditions.*; ...@@ -27,6 +27,7 @@ import static com.google.common.base.Preconditions.*;
27 import org.onosproject.store.primitives.MapUpdate; 27 import org.onosproject.store.primitives.MapUpdate;
28 import org.onosproject.store.primitives.TransactionId; 28 import org.onosproject.store.primitives.TransactionId;
29 import org.onosproject.store.primitives.resources.impl.CommitResult; 29 import org.onosproject.store.primitives.resources.impl.CommitResult;
30 +import org.onosproject.store.service.CommitStatus;
30 import org.onosproject.store.service.ConsistentMapBuilder; 31 import org.onosproject.store.service.ConsistentMapBuilder;
31 import org.onosproject.store.service.Serializer; 32 import org.onosproject.store.service.Serializer;
32 import org.onosproject.store.service.TransactionContext; 33 import org.onosproject.store.service.TransactionContext;
...@@ -96,22 +97,23 @@ public class DefaultTransactionContext implements TransactionContext { ...@@ -96,22 +97,23 @@ public class DefaultTransactionContext implements TransactionContext {
96 97
97 @SuppressWarnings("unchecked") 98 @SuppressWarnings("unchecked")
98 @Override 99 @Override
99 - public boolean commit() { 100 + public CompletableFuture<CommitStatus> commit() {
100 // TODO: rework commit implementation to be more intuitive 101 // TODO: rework commit implementation to be more intuitive
101 checkState(isOpen, TX_NOT_OPEN_ERROR); 102 checkState(isOpen, TX_NOT_OPEN_ERROR);
102 - CommitResult result = null; 103 + CommitStatus status;
103 try { 104 try {
104 List<MapUpdate<String, byte[]>> updates = Lists.newLinkedList(); 105 List<MapUpdate<String, byte[]>> updates = Lists.newLinkedList();
105 txMaps.values().forEach(m -> updates.addAll(m.toMapUpdates())); 106 txMaps.values().forEach(m -> updates.addAll(m.toMapUpdates()));
106 Transaction transaction = new Transaction(transactionId, updates); 107 Transaction transaction = new Transaction(transactionId, updates);
107 - result = Futures.getUnchecked(transactionCommitter.apply(transaction)); 108 + status = Futures.getUnchecked(transactionCommitter.apply(transaction)) == CommitResult.OK
108 - return result == CommitResult.OK; 109 + ? CommitStatus.SUCCESS : CommitStatus.FAILURE;
109 } catch (Exception e) { 110 } catch (Exception e) {
110 abort(); 111 abort();
111 - return false; 112 + status = CommitStatus.FAILURE;
112 } finally { 113 } finally {
113 isOpen = false; 114 isOpen = false;
114 } 115 }
116 + return CompletableFuture.completedFuture(status);
115 } 117 }
116 118
117 @Override 119 @Override
......
...@@ -16,13 +16,16 @@ ...@@ -16,13 +16,16 @@
16 package org.onosproject.store.primitives.impl; 16 package org.onosproject.store.primitives.impl;
17 17
18 import java.util.Set; 18 import java.util.Set;
19 +import java.util.concurrent.CompletableFuture;
19 import java.util.concurrent.atomic.AtomicBoolean; 20 import java.util.concurrent.atomic.AtomicBoolean;
20 21
21 import org.onosproject.store.primitives.DistributedPrimitiveCreator; 22 import org.onosproject.store.primitives.DistributedPrimitiveCreator;
22 import org.onosproject.store.primitives.TransactionId; 23 import org.onosproject.store.primitives.TransactionId;
24 +import org.onosproject.store.service.CommitStatus;
23 import org.onosproject.store.service.Serializer; 25 import org.onosproject.store.service.Serializer;
24 import org.onosproject.store.service.TransactionContext; 26 import org.onosproject.store.service.TransactionContext;
25 import org.onosproject.store.service.TransactionalMap; 27 import org.onosproject.store.service.TransactionalMap;
28 +import org.onosproject.utils.MeteringAgent;
26 29
27 import com.google.common.collect.Sets; 30 import com.google.common.collect.Sets;
28 31
...@@ -36,6 +39,7 @@ public class NewDefaultTransactionContext implements TransactionContext { ...@@ -36,6 +39,7 @@ public class NewDefaultTransactionContext implements TransactionContext {
36 private final TransactionId transactionId; 39 private final TransactionId transactionId;
37 private final TransactionCoordinator transactionCoordinator; 40 private final TransactionCoordinator transactionCoordinator;
38 private final Set<TransactionParticipant> txParticipants = Sets.newConcurrentHashSet(); 41 private final Set<TransactionParticipant> txParticipants = Sets.newConcurrentHashSet();
42 + private final MeteringAgent monitor;
39 43
40 public NewDefaultTransactionContext(TransactionId transactionId, 44 public NewDefaultTransactionContext(TransactionId transactionId,
41 DistributedPrimitiveCreator creator, 45 DistributedPrimitiveCreator creator,
...@@ -43,6 +47,7 @@ public class NewDefaultTransactionContext implements TransactionContext { ...@@ -43,6 +47,7 @@ public class NewDefaultTransactionContext implements TransactionContext {
43 this.transactionId = transactionId; 47 this.transactionId = transactionId;
44 this.creator = creator; 48 this.creator = creator;
45 this.transactionCoordinator = transactionCoordinator; 49 this.transactionCoordinator = transactionCoordinator;
50 + this.monitor = new MeteringAgent("transactionContext", "*", true);
46 } 51 }
47 52
48 @Override 53 @Override
...@@ -68,9 +73,10 @@ public class NewDefaultTransactionContext implements TransactionContext { ...@@ -68,9 +73,10 @@ public class NewDefaultTransactionContext implements TransactionContext {
68 } 73 }
69 74
70 @Override 75 @Override
71 - public boolean commit() { 76 + public CompletableFuture<CommitStatus> commit() {
72 - transactionCoordinator.commit(transactionId, txParticipants).getNow(null); 77 + final MeteringAgent.Context timer = monitor.startTimer("commit");
73 - return true; 78 + return transactionCoordinator.commit(transactionId, txParticipants)
79 + .whenComplete((r, e) -> timer.stop(e));
74 } 80 }
75 81
76 @Override 82 @Override
......
...@@ -22,6 +22,7 @@ import java.util.stream.Collectors; ...@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
22 import org.onlab.util.Tools; 22 import org.onlab.util.Tools;
23 import org.onosproject.store.primitives.TransactionId; 23 import org.onosproject.store.primitives.TransactionId;
24 import org.onosproject.store.service.AsyncConsistentMap; 24 import org.onosproject.store.service.AsyncConsistentMap;
25 +import org.onosproject.store.service.CommitStatus;
25 26
26 /** 27 /**
27 * Coordinator for a two-phase commit protocol. 28 * Coordinator for a two-phase commit protocol.
...@@ -37,45 +38,47 @@ public class TransactionCoordinator { ...@@ -37,45 +38,47 @@ public class TransactionCoordinator {
37 /** 38 /**
38 * Commits a transaction. 39 * Commits a transaction.
39 * 40 *
40 - * @param transactionId transaction 41 + * @param transactionId transaction identifier
41 * @param transactionParticipants set of transaction participants 42 * @param transactionParticipants set of transaction participants
42 * @return future for commit result 43 * @return future for commit result
43 */ 44 */
44 - CompletableFuture<Void> commit(TransactionId transactionId, Set<TransactionParticipant> transactionParticipants) { 45 + CompletableFuture<CommitStatus> commit(TransactionId transactionId,
46 + Set<TransactionParticipant> transactionParticipants) {
45 if (!transactionParticipants.stream().anyMatch(t -> t.hasPendingUpdates())) { 47 if (!transactionParticipants.stream().anyMatch(t -> t.hasPendingUpdates())) {
46 - return CompletableFuture.completedFuture(null); 48 + return CompletableFuture.completedFuture(CommitStatus.SUCCESS);
47 } 49 }
48 50
49 - return transactions.put(transactionId, Transaction.State.PREPARING) 51 + CompletableFuture<CommitStatus> status = transactions.put(transactionId, Transaction.State.PREPARING)
50 .thenCompose(v -> this.doPrepare(transactionParticipants)) 52 .thenCompose(v -> this.doPrepare(transactionParticipants))
51 .thenCompose(result -> result 53 .thenCompose(result -> result
52 ? transactions.put(transactionId, Transaction.State.COMMITTING) 54 ? transactions.put(transactionId, Transaction.State.COMMITTING)
53 .thenCompose(v -> doCommit(transactionParticipants)) 55 .thenCompose(v -> doCommit(transactionParticipants))
54 - .thenApply(v -> null) 56 + .thenApply(v -> CommitStatus.SUCCESS)
55 : transactions.put(transactionId, Transaction.State.ROLLINGBACK) 57 : transactions.put(transactionId, Transaction.State.ROLLINGBACK)
56 .thenCompose(v -> doRollback(transactionParticipants)) 58 .thenCompose(v -> doRollback(transactionParticipants))
57 - .thenApply(v -> null)) 59 + .thenApply(v -> CommitStatus.FAILURE));
58 - .thenCompose(v -> transactions.remove(transactionId)) 60 + return status.thenCompose(v -> transactions.remove(transactionId).thenApply(u -> v));
59 - .thenApply(v -> null);
60 } 61 }
61 62
62 private CompletableFuture<Boolean> doPrepare(Set<TransactionParticipant> transactionParticipants) { 63 private CompletableFuture<Boolean> doPrepare(Set<TransactionParticipant> transactionParticipants) {
63 - return Tools.allOf(transactionParticipants 64 + return Tools.allOf(transactionParticipants.stream()
64 - .stream() 65 + .filter(TransactionParticipant::hasPendingUpdates)
65 - .map(TransactionParticipant::prepare) 66 + .map(TransactionParticipant::prepare)
66 - .collect(Collectors.toList())) 67 + .collect(Collectors.toList()))
67 .thenApply(list -> list.stream().reduce(Boolean::logicalAnd).orElse(true)); 68 .thenApply(list -> list.stream().reduce(Boolean::logicalAnd).orElse(true));
68 } 69 }
69 70
70 private CompletableFuture<Void> doCommit(Set<TransactionParticipant> transactionParticipants) { 71 private CompletableFuture<Void> doCommit(Set<TransactionParticipant> transactionParticipants) {
71 return CompletableFuture.allOf(transactionParticipants.stream() 72 return CompletableFuture.allOf(transactionParticipants.stream()
72 - .map(p -> p.commit()) 73 + .filter(TransactionParticipant::hasPendingUpdates)
74 + .map(TransactionParticipant::commit)
73 .toArray(CompletableFuture[]::new)); 75 .toArray(CompletableFuture[]::new));
74 } 76 }
75 77
76 private CompletableFuture<Void> doRollback(Set<TransactionParticipant> transactionParticipants) { 78 private CompletableFuture<Void> doRollback(Set<TransactionParticipant> transactionParticipants) {
77 return CompletableFuture.allOf(transactionParticipants.stream() 79 return CompletableFuture.allOf(transactionParticipants.stream()
78 - .map(p -> p.rollback()) 80 + .filter(TransactionParticipant::hasPendingUpdates)
81 + .map(TransactionParticipant::rollback)
79 .toArray(CompletableFuture[]::new)); 82 .toArray(CompletableFuture[]::new));
80 } 83 }
81 } 84 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -87,13 +87,7 @@ public class AtomixConsistentMap extends AbstractResource<AtomixConsistentMap> ...@@ -87,13 +87,7 @@ public class AtomixConsistentMap extends AbstractResource<AtomixConsistentMap>
87 } 87 }
88 88
89 private void handleEvent(List<MapEvent<String, byte[]>> events) { 89 private void handleEvent(List<MapEvent<String, byte[]>> events) {
90 - events.forEach(event -> mapEventListeners.forEach(listener -> { 90 + events.forEach(event -> mapEventListeners.forEach(listener -> listener.event(event)));
91 - try {
92 - listener.event(event);
93 - } catch (Exception e) {
94 - log.warn("Error processing map event", e);
95 - }
96 - }));
97 } 91 }
98 92
99 @Override 93 @Override
......