Committed by
Gerrit Code Review
Added RetryingFunction for simplified retry support.
Moved retry logic out of primitives (AtomicCounter) to the caller site. Change-Id: I319d61f153f98d421baf32a1b5cd69d20dc63427
Showing
11 changed files
with
221 additions
and
176 deletions
... | @@ -15,8 +15,6 @@ | ... | @@ -15,8 +15,6 @@ |
15 | */ | 15 | */ |
16 | package org.onosproject.store.service; | 16 | package org.onosproject.store.service; |
17 | 17 | ||
18 | -import java.util.concurrent.ScheduledExecutorService; | ||
19 | - | ||
20 | /** | 18 | /** |
21 | * Builder for AtomicCounter. | 19 | * Builder for AtomicCounter. |
22 | */ | 20 | */ |
... | @@ -50,15 +48,6 @@ public interface AtomicCounterBuilder { | ... | @@ -50,15 +48,6 @@ public interface AtomicCounterBuilder { |
50 | AtomicCounterBuilder withPartitionsDisabled(); | 48 | AtomicCounterBuilder withPartitionsDisabled(); |
51 | 49 | ||
52 | /** | 50 | /** |
53 | - * Enables retries when counter operations fail. | ||
54 | - * <p> | ||
55 | - * Note: Use with caution. By default retries are disabled. | ||
56 | - * </p> | ||
57 | - * @return this AtomicCounterBuilder | ||
58 | - */ | ||
59 | - AtomicCounterBuilder withRetryOnFailure(); | ||
60 | - | ||
61 | - /** | ||
62 | * Instantiates Metering service to gather usage and performance metrics. | 51 | * Instantiates Metering service to gather usage and performance metrics. |
63 | * By default, usage data will be stored. | 52 | * By default, usage data will be stored. |
64 | * | 53 | * |
... | @@ -67,16 +56,6 @@ public interface AtomicCounterBuilder { | ... | @@ -67,16 +56,6 @@ public interface AtomicCounterBuilder { |
67 | AtomicCounterBuilder withMeteringDisabled(); | 56 | AtomicCounterBuilder withMeteringDisabled(); |
68 | 57 | ||
69 | /** | 58 | /** |
70 | - * Sets the executor service to use for retrying failed operations. | ||
71 | - * <p> | ||
72 | - * Note: Must be set when retries are enabled | ||
73 | - * </p> | ||
74 | - * @param executor executor service | ||
75 | - * @return this AtomicCounterBuilder | ||
76 | - */ | ||
77 | - AtomicCounterBuilder withRetryExecutor(ScheduledExecutorService executor); | ||
78 | - | ||
79 | - /** | ||
80 | * Builds a AtomicCounter based on the configuration options | 59 | * Builds a AtomicCounter based on the configuration options |
81 | * supplied to this builder. | 60 | * supplied to this builder. |
82 | * | 61 | * | ... | ... |
... | @@ -15,7 +15,6 @@ | ... | @@ -15,7 +15,6 @@ |
15 | */ | 15 | */ |
16 | package org.onosproject.store.service; | 16 | package org.onosproject.store.service; |
17 | 17 | ||
18 | -import java.util.concurrent.ScheduledExecutorService; | ||
19 | import java.util.concurrent.atomic.AtomicLong; | 18 | import java.util.concurrent.atomic.AtomicLong; |
20 | 19 | ||
21 | /** | 20 | /** |
... | @@ -69,21 +68,11 @@ public final class TestAtomicCounter implements AtomicCounter { | ... | @@ -69,21 +68,11 @@ public final class TestAtomicCounter implements AtomicCounter { |
69 | } | 68 | } |
70 | 69 | ||
71 | @Override | 70 | @Override |
72 | - public AtomicCounterBuilder withRetryOnFailure() { | ||
73 | - return this; | ||
74 | - } | ||
75 | - | ||
76 | - @Override | ||
77 | public AtomicCounterBuilder withMeteringDisabled() { | 71 | public AtomicCounterBuilder withMeteringDisabled() { |
78 | return this; | 72 | return this; |
79 | } | 73 | } |
80 | 74 | ||
81 | @Override | 75 | @Override |
82 | - public AtomicCounterBuilder withRetryExecutor(ScheduledExecutorService executor) { | ||
83 | - return this; | ||
84 | - } | ||
85 | - | ||
86 | - @Override | ||
87 | public AsyncAtomicCounter buildAsyncCounter() { | 76 | public AsyncAtomicCounter buildAsyncCounter() { |
88 | throw new UnsupportedOperationException("Async Counter is not supported"); | 77 | throw new UnsupportedOperationException("Async Counter is not supported"); |
89 | } | 78 | } | ... | ... |
... | @@ -16,15 +16,9 @@ | ... | @@ -16,15 +16,9 @@ |
16 | package org.onosproject.store.consistent.impl; | 16 | package org.onosproject.store.consistent.impl; |
17 | 17 | ||
18 | import org.onosproject.store.service.AsyncAtomicCounter; | 18 | import org.onosproject.store.service.AsyncAtomicCounter; |
19 | -import org.slf4j.Logger; | ||
20 | 19 | ||
21 | import java.util.concurrent.CompletableFuture; | 20 | import java.util.concurrent.CompletableFuture; |
22 | -import java.util.concurrent.ScheduledExecutorService; | ||
23 | -import java.util.concurrent.TimeUnit; | ||
24 | -import java.util.function.BiFunction; | ||
25 | - | ||
26 | import static com.google.common.base.Preconditions.checkNotNull; | 21 | import static com.google.common.base.Preconditions.checkNotNull; |
27 | -import static org.slf4j.LoggerFactory.getLogger; | ||
28 | 22 | ||
29 | /** | 23 | /** |
30 | * Default implementation for a distributed AsyncAtomicCounter backed by | 24 | * Default implementation for a distributed AsyncAtomicCounter backed by |
... | @@ -36,11 +30,6 @@ public class DefaultAsyncAtomicCounter implements AsyncAtomicCounter { | ... | @@ -36,11 +30,6 @@ public class DefaultAsyncAtomicCounter implements AsyncAtomicCounter { |
36 | 30 | ||
37 | private final String name; | 31 | private final String name; |
38 | private final Database database; | 32 | private final Database database; |
39 | - private final boolean retryOnFailure; | ||
40 | - private final ScheduledExecutorService retryExecutor; | ||
41 | - // TODO: configure delay via builder | ||
42 | - private static final int DELAY_BETWEEN_RETRY_SEC = 1; | ||
43 | - private final Logger log = getLogger(getClass()); | ||
44 | private final MeteringAgent monitor; | 33 | private final MeteringAgent monitor; |
45 | 34 | ||
46 | private static final String PRIMITIVE_NAME = "atomicCounter"; | 35 | private static final String PRIMITIVE_NAME = "atomicCounter"; |
... | @@ -52,13 +41,9 @@ public class DefaultAsyncAtomicCounter implements AsyncAtomicCounter { | ... | @@ -52,13 +41,9 @@ public class DefaultAsyncAtomicCounter implements AsyncAtomicCounter { |
52 | 41 | ||
53 | public DefaultAsyncAtomicCounter(String name, | 42 | public DefaultAsyncAtomicCounter(String name, |
54 | Database database, | 43 | Database database, |
55 | - boolean retryOnException, | 44 | + boolean meteringEnabled) { |
56 | - boolean meteringEnabled, | ||
57 | - ScheduledExecutorService retryExecutor) { | ||
58 | this.name = checkNotNull(name); | 45 | this.name = checkNotNull(name); |
59 | this.database = checkNotNull(database); | 46 | this.database = checkNotNull(database); |
60 | - this.retryOnFailure = retryOnException; | ||
61 | - this.retryExecutor = retryExecutor; | ||
62 | this.monitor = new MeteringAgent(PRIMITIVE_NAME, name, meteringEnabled); | 47 | this.monitor = new MeteringAgent(PRIMITIVE_NAME, name, meteringEnabled); |
63 | } | 48 | } |
64 | 49 | ||
... | @@ -86,77 +71,14 @@ public class DefaultAsyncAtomicCounter implements AsyncAtomicCounter { | ... | @@ -86,77 +71,14 @@ public class DefaultAsyncAtomicCounter implements AsyncAtomicCounter { |
86 | @Override | 71 | @Override |
87 | public CompletableFuture<Long> getAndAdd(long delta) { | 72 | public CompletableFuture<Long> getAndAdd(long delta) { |
88 | final MeteringAgent.Context timer = monitor.startTimer(GET_AND_ADD); | 73 | final MeteringAgent.Context timer = monitor.startTimer(GET_AND_ADD); |
89 | - CompletableFuture<Long> result = database.counterGetAndAdd(name, delta); | 74 | + return database.counterGetAndAdd(name, delta) |
90 | - if (!retryOnFailure) { | 75 | + .whenComplete((r, e) -> timer.stop()); |
91 | - return result | ||
92 | - .whenComplete((r, e) -> timer.stop()); | ||
93 | - } | ||
94 | - | ||
95 | - CompletableFuture<Long> future = new CompletableFuture<>(); | ||
96 | - return result.whenComplete((r, e) -> { | ||
97 | - timer.stop(); | ||
98 | - // TODO : Account for retries | ||
99 | - if (e != null) { | ||
100 | - log.warn("getAndAdd failed due to {}. Will retry", e.getMessage()); | ||
101 | - retryExecutor.schedule(new RetryTask(database::counterGetAndAdd, delta, future), | ||
102 | - DELAY_BETWEEN_RETRY_SEC, | ||
103 | - TimeUnit.SECONDS); | ||
104 | - } else { | ||
105 | - future.complete(r); | ||
106 | - } | ||
107 | - }).thenCompose(v -> future); | ||
108 | } | 76 | } |
109 | 77 | ||
110 | @Override | 78 | @Override |
111 | public CompletableFuture<Long> addAndGet(long delta) { | 79 | public CompletableFuture<Long> addAndGet(long delta) { |
112 | final MeteringAgent.Context timer = monitor.startTimer(ADD_AND_GET); | 80 | final MeteringAgent.Context timer = monitor.startTimer(ADD_AND_GET); |
113 | - CompletableFuture<Long> result = database.counterAddAndGet(name, delta); | 81 | + return database.counterAddAndGet(name, delta) |
114 | - if (!retryOnFailure) { | 82 | + .whenComplete((r, e) -> timer.stop()); |
115 | - return result | ||
116 | - .whenComplete((r, e) -> timer.stop()); | ||
117 | - } | ||
118 | - | ||
119 | - CompletableFuture<Long> future = new CompletableFuture<>(); | ||
120 | - return result.whenComplete((r, e) -> { | ||
121 | - timer.stop(); | ||
122 | - // TODO : Account for retries | ||
123 | - if (e != null) { | ||
124 | - log.warn("addAndGet failed due to {}. Will retry", e.getMessage()); | ||
125 | - retryExecutor.schedule(new RetryTask(database::counterAddAndGet, delta, future), | ||
126 | - DELAY_BETWEEN_RETRY_SEC, | ||
127 | - TimeUnit.SECONDS); | ||
128 | - } else { | ||
129 | - future.complete(r); | ||
130 | - } | ||
131 | - }).thenCompose(v -> future); | ||
132 | - } | ||
133 | - | ||
134 | - private class RetryTask implements Runnable { | ||
135 | - | ||
136 | - private final BiFunction<String, Long, CompletableFuture<Long>> function; | ||
137 | - private final Long delta; | ||
138 | - private final CompletableFuture<Long> result; | ||
139 | - | ||
140 | - public RetryTask(BiFunction<String, Long, CompletableFuture<Long>> function, | ||
141 | - Long delta, | ||
142 | - CompletableFuture<Long> result) { | ||
143 | - this.function = function; | ||
144 | - this.delta = delta; | ||
145 | - this.result = result; | ||
146 | - } | ||
147 | - | ||
148 | - @Override | ||
149 | - public void run() { | ||
150 | - function.apply(name, delta).whenComplete((r, e) -> { | ||
151 | - if (e == null) { | ||
152 | - result.complete(r); | ||
153 | - } else { | ||
154 | - log.warn("{} retry failed due to {}. Will try again...", function, e.getMessage()); | ||
155 | - // TODO: Exponential backoff | ||
156 | - // TODO: limit retries | ||
157 | - retryExecutor.schedule(this, DELAY_BETWEEN_RETRY_SEC, TimeUnit.SECONDS); | ||
158 | - } | ||
159 | - }); | ||
160 | - } | ||
161 | } | 83 | } |
162 | } | 84 | } |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -21,7 +21,6 @@ import org.onosproject.store.service.StorageException; | ... | @@ -21,7 +21,6 @@ import org.onosproject.store.service.StorageException; |
21 | 21 | ||
22 | import java.util.concurrent.CompletableFuture; | 22 | import java.util.concurrent.CompletableFuture; |
23 | import java.util.concurrent.ExecutionException; | 23 | import java.util.concurrent.ExecutionException; |
24 | -import java.util.concurrent.ScheduledExecutorService; | ||
25 | import java.util.concurrent.TimeUnit; | 24 | import java.util.concurrent.TimeUnit; |
26 | import java.util.concurrent.TimeoutException; | 25 | import java.util.concurrent.TimeoutException; |
27 | 26 | ||
... | @@ -39,10 +38,8 @@ public class DefaultAtomicCounter implements AtomicCounter { | ... | @@ -39,10 +38,8 @@ public class DefaultAtomicCounter implements AtomicCounter { |
39 | 38 | ||
40 | public DefaultAtomicCounter(String name, | 39 | public DefaultAtomicCounter(String name, |
41 | Database database, | 40 | Database database, |
42 | - boolean retryOnException, | 41 | + boolean meteringEnabled) { |
43 | - boolean meteringEnabled, | 42 | + asyncCounter = new DefaultAsyncAtomicCounter(name, database, meteringEnabled); |
44 | - ScheduledExecutorService retryExecutor) { | ||
45 | - asyncCounter = new DefaultAsyncAtomicCounter(name, database, retryOnException, meteringEnabled, retryExecutor); | ||
46 | } | 43 | } |
47 | 44 | ||
48 | @Override | 45 | @Override | ... | ... |
core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicCounterBuilder.java
... | @@ -19,9 +19,8 @@ import org.onosproject.store.service.AsyncAtomicCounter; | ... | @@ -19,9 +19,8 @@ import org.onosproject.store.service.AsyncAtomicCounter; |
19 | import org.onosproject.store.service.AtomicCounter; | 19 | import org.onosproject.store.service.AtomicCounter; |
20 | import org.onosproject.store.service.AtomicCounterBuilder; | 20 | import org.onosproject.store.service.AtomicCounterBuilder; |
21 | 21 | ||
22 | -import java.util.concurrent.ScheduledExecutorService; | ||
23 | - | ||
24 | import static com.google.common.base.Preconditions.checkArgument; | 22 | import static com.google.common.base.Preconditions.checkArgument; |
23 | +import static com.google.common.base.Preconditions.checkState; | ||
25 | 24 | ||
26 | /** | 25 | /** |
27 | * Default implementation of AtomicCounterBuilder. | 26 | * Default implementation of AtomicCounterBuilder. |
... | @@ -32,9 +31,7 @@ public class DefaultAtomicCounterBuilder implements AtomicCounterBuilder { | ... | @@ -32,9 +31,7 @@ public class DefaultAtomicCounterBuilder implements AtomicCounterBuilder { |
32 | private boolean partitionsEnabled = true; | 31 | private boolean partitionsEnabled = true; |
33 | private final Database partitionedDatabase; | 32 | private final Database partitionedDatabase; |
34 | private final Database inMemoryDatabase; | 33 | private final Database inMemoryDatabase; |
35 | - private boolean retryOnFailure = false; | ||
36 | private boolean metering = true; | 34 | private boolean metering = true; |
37 | - private ScheduledExecutorService retryExecutor = null; | ||
38 | 35 | ||
39 | public DefaultAtomicCounterBuilder(Database inMemoryDatabase, Database partitionedDatabase) { | 36 | public DefaultAtomicCounterBuilder(Database inMemoryDatabase, Database partitionedDatabase) { |
40 | this.inMemoryDatabase = inMemoryDatabase; | 37 | this.inMemoryDatabase = inMemoryDatabase; |
... | @@ -58,20 +55,14 @@ public class DefaultAtomicCounterBuilder implements AtomicCounterBuilder { | ... | @@ -58,20 +55,14 @@ public class DefaultAtomicCounterBuilder implements AtomicCounterBuilder { |
58 | public AtomicCounter build() { | 55 | public AtomicCounter build() { |
59 | validateInputs(); | 56 | validateInputs(); |
60 | Database database = partitionsEnabled ? partitionedDatabase : inMemoryDatabase; | 57 | Database database = partitionsEnabled ? partitionedDatabase : inMemoryDatabase; |
61 | - return new DefaultAtomicCounter(name, database, retryOnFailure, metering, retryExecutor); | 58 | + return new DefaultAtomicCounter(name, database, metering); |
62 | } | 59 | } |
63 | 60 | ||
64 | @Override | 61 | @Override |
65 | public AsyncAtomicCounter buildAsyncCounter() { | 62 | public AsyncAtomicCounter buildAsyncCounter() { |
66 | validateInputs(); | 63 | validateInputs(); |
67 | Database database = partitionsEnabled ? partitionedDatabase : inMemoryDatabase; | 64 | Database database = partitionsEnabled ? partitionedDatabase : inMemoryDatabase; |
68 | - return new DefaultAsyncAtomicCounter(name, database, retryOnFailure, metering, retryExecutor); | 65 | + return new DefaultAsyncAtomicCounter(name, database, metering); |
69 | - } | ||
70 | - | ||
71 | - @Override | ||
72 | - public AtomicCounterBuilder withRetryOnFailure() { | ||
73 | - retryOnFailure = true; | ||
74 | - return this; | ||
75 | } | 66 | } |
76 | 67 | ||
77 | @Override | 68 | @Override |
... | @@ -80,17 +71,7 @@ public class DefaultAtomicCounterBuilder implements AtomicCounterBuilder { | ... | @@ -80,17 +71,7 @@ public class DefaultAtomicCounterBuilder implements AtomicCounterBuilder { |
80 | return this; | 71 | return this; |
81 | } | 72 | } |
82 | 73 | ||
83 | - @Override | ||
84 | - public AtomicCounterBuilder withRetryExecutor(ScheduledExecutorService executor) { | ||
85 | - this.retryExecutor = executor; | ||
86 | - return this; | ||
87 | - } | ||
88 | - | ||
89 | private void validateInputs() { | 74 | private void validateInputs() { |
90 | - if (retryOnFailure) { | 75 | + checkState(name != null, "name must be specified"); |
91 | - if (retryExecutor == null) { | ||
92 | - throw new IllegalArgumentException("RetryExecutor must be specified when retries are enabled"); | ||
93 | - } | ||
94 | - } | ||
95 | } | 76 | } |
96 | } | 77 | } | ... | ... |
... | @@ -15,12 +15,10 @@ | ... | @@ -15,12 +15,10 @@ |
15 | */ | 15 | */ |
16 | package org.onosproject.store.core.impl; | 16 | package org.onosproject.store.core.impl; |
17 | 17 | ||
18 | -import static org.onlab.util.Tools.groupedThreads; | ||
19 | import static org.slf4j.LoggerFactory.getLogger; | 18 | import static org.slf4j.LoggerFactory.getLogger; |
20 | 19 | ||
21 | import java.util.Map; | 20 | import java.util.Map; |
22 | import java.util.Set; | 21 | import java.util.Set; |
23 | -import java.util.concurrent.Executors; | ||
24 | import java.util.concurrent.ScheduledExecutorService; | 22 | import java.util.concurrent.ScheduledExecutorService; |
25 | 23 | ||
26 | import org.apache.felix.scr.annotations.Activate; | 24 | import org.apache.felix.scr.annotations.Activate; |
... | @@ -30,20 +28,21 @@ import org.apache.felix.scr.annotations.Reference; | ... | @@ -30,20 +28,21 @@ import org.apache.felix.scr.annotations.Reference; |
30 | import org.apache.felix.scr.annotations.ReferenceCardinality; | 28 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
31 | import org.apache.felix.scr.annotations.Service; | 29 | import org.apache.felix.scr.annotations.Service; |
32 | import org.onlab.util.KryoNamespace; | 30 | import org.onlab.util.KryoNamespace; |
31 | +import org.onlab.util.Tools; | ||
33 | import org.onosproject.core.ApplicationId; | 32 | import org.onosproject.core.ApplicationId; |
34 | import org.onosproject.core.ApplicationIdStore; | 33 | import org.onosproject.core.ApplicationIdStore; |
35 | import org.onosproject.core.DefaultApplicationId; | 34 | import org.onosproject.core.DefaultApplicationId; |
36 | import org.onosproject.store.serializers.KryoNamespaces; | 35 | import org.onosproject.store.serializers.KryoNamespaces; |
37 | -import org.onosproject.store.service.AsyncAtomicCounter; | 36 | +import org.onosproject.store.service.AtomicCounter; |
38 | import org.onosproject.store.service.ConsistentMap; | 37 | import org.onosproject.store.service.ConsistentMap; |
39 | import org.onosproject.store.service.Serializer; | 38 | import org.onosproject.store.service.Serializer; |
39 | +import org.onosproject.store.service.StorageException; | ||
40 | import org.onosproject.store.service.StorageService; | 40 | import org.onosproject.store.service.StorageService; |
41 | import org.onosproject.store.service.Versioned; | 41 | import org.onosproject.store.service.Versioned; |
42 | import org.slf4j.Logger; | 42 | import org.slf4j.Logger; |
43 | 43 | ||
44 | import com.google.common.collect.ImmutableSet; | 44 | import com.google.common.collect.ImmutableSet; |
45 | import com.google.common.collect.Maps; | 45 | import com.google.common.collect.Maps; |
46 | -import com.google.common.util.concurrent.Futures; | ||
47 | 46 | ||
48 | /** | 47 | /** |
49 | * ApplicationIdStore implementation on top of {@code AtomicCounter} | 48 | * ApplicationIdStore implementation on top of {@code AtomicCounter} |
... | @@ -58,7 +57,7 @@ public class ConsistentApplicationIdStore implements ApplicationIdStore { | ... | @@ -58,7 +57,7 @@ public class ConsistentApplicationIdStore implements ApplicationIdStore { |
58 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 57 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
59 | protected StorageService storageService; | 58 | protected StorageService storageService; |
60 | 59 | ||
61 | - private AsyncAtomicCounter appIdCounter; | 60 | + private AtomicCounter appIdCounter; |
62 | private ConsistentMap<String, ApplicationId> registeredIds; | 61 | private ConsistentMap<String, ApplicationId> registeredIds; |
63 | private Map<String, ApplicationId> nameToAppIdCache = Maps.newConcurrentMap(); | 62 | private Map<String, ApplicationId> nameToAppIdCache = Maps.newConcurrentMap(); |
64 | private Map<Short, ApplicationId> idToAppIdCache = Maps.newConcurrentMap(); | 63 | private Map<Short, ApplicationId> idToAppIdCache = Maps.newConcurrentMap(); |
... | @@ -71,13 +70,10 @@ public class ConsistentApplicationIdStore implements ApplicationIdStore { | ... | @@ -71,13 +70,10 @@ public class ConsistentApplicationIdStore implements ApplicationIdStore { |
71 | 70 | ||
72 | @Activate | 71 | @Activate |
73 | public void activate() { | 72 | public void activate() { |
74 | - executor = Executors.newSingleThreadScheduledExecutor(groupedThreads("onos/store/appId", "retry-handler")); | ||
75 | appIdCounter = storageService.atomicCounterBuilder() | 73 | appIdCounter = storageService.atomicCounterBuilder() |
76 | .withName("onos-app-id-counter") | 74 | .withName("onos-app-id-counter") |
77 | .withPartitionsDisabled() | 75 | .withPartitionsDisabled() |
78 | - .withRetryOnFailure() | 76 | + .build(); |
79 | - .withRetryExecutor(executor) | ||
80 | - .buildAsyncCounter(); | ||
81 | 77 | ||
82 | registeredIds = storageService.<String, ApplicationId>consistentMapBuilder() | 78 | registeredIds = storageService.<String, ApplicationId>consistentMapBuilder() |
83 | .withName("onos-app-ids") | 79 | .withName("onos-app-ids") |
... | @@ -128,7 +124,9 @@ public class ConsistentApplicationIdStore implements ApplicationIdStore { | ... | @@ -128,7 +124,9 @@ public class ConsistentApplicationIdStore implements ApplicationIdStore { |
128 | ApplicationId appId = nameToAppIdCache.computeIfAbsent(name, key -> { | 124 | ApplicationId appId = nameToAppIdCache.computeIfAbsent(name, key -> { |
129 | Versioned<ApplicationId> existingAppId = registeredIds.get(name); | 125 | Versioned<ApplicationId> existingAppId = registeredIds.get(name); |
130 | if (existingAppId == null) { | 126 | if (existingAppId == null) { |
131 | - int id = Futures.getUnchecked(appIdCounter.incrementAndGet()).intValue(); | 127 | + int id = Tools.retryable(appIdCounter::incrementAndGet, StorageException.class, 1, 2000) |
128 | + .get() | ||
129 | + .intValue(); | ||
132 | DefaultApplicationId newAppId = new DefaultApplicationId(id, name); | 130 | DefaultApplicationId newAppId = new DefaultApplicationId(id, name); |
133 | existingAppId = registeredIds.putIfAbsent(name, newAppId); | 131 | existingAppId = registeredIds.putIfAbsent(name, newAppId); |
134 | if (existingAppId != null) { | 132 | if (existingAppId != null) { | ... | ... |
1 | package org.onosproject.store.core.impl; | 1 | package org.onosproject.store.core.impl; |
2 | 2 | ||
3 | import com.google.common.collect.Maps; | 3 | import com.google.common.collect.Maps; |
4 | + | ||
4 | import org.apache.felix.scr.annotations.Activate; | 5 | import org.apache.felix.scr.annotations.Activate; |
5 | import org.apache.felix.scr.annotations.Component; | 6 | import org.apache.felix.scr.annotations.Component; |
6 | import org.apache.felix.scr.annotations.Deactivate; | 7 | import org.apache.felix.scr.annotations.Deactivate; |
7 | import org.apache.felix.scr.annotations.Reference; | 8 | import org.apache.felix.scr.annotations.Reference; |
8 | import org.apache.felix.scr.annotations.ReferenceCardinality; | 9 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
9 | import org.apache.felix.scr.annotations.Service; | 10 | import org.apache.felix.scr.annotations.Service; |
11 | +import org.onlab.util.Tools; | ||
10 | import org.onosproject.core.IdBlock; | 12 | import org.onosproject.core.IdBlock; |
11 | import org.onosproject.core.IdBlockStore; | 13 | import org.onosproject.core.IdBlockStore; |
12 | import org.onosproject.store.service.AtomicCounter; | 14 | import org.onosproject.store.service.AtomicCounter; |
... | @@ -16,7 +18,6 @@ import org.slf4j.Logger; | ... | @@ -16,7 +18,6 @@ import org.slf4j.Logger; |
16 | 18 | ||
17 | import java.util.Map; | 19 | import java.util.Map; |
18 | 20 | ||
19 | -import static org.onlab.util.Tools.randomDelay; | ||
20 | import static org.slf4j.LoggerFactory.getLogger; | 21 | import static org.slf4j.LoggerFactory.getLogger; |
21 | 22 | ||
22 | /** | 23 | /** |
... | @@ -54,19 +55,10 @@ public class ConsistentIdBlockStore implements IdBlockStore { | ... | @@ -54,19 +55,10 @@ public class ConsistentIdBlockStore implements IdBlockStore { |
54 | name -> storageService.atomicCounterBuilder() | 55 | name -> storageService.atomicCounterBuilder() |
55 | .withName(name) | 56 | .withName(name) |
56 | .build()); | 57 | .build()); |
57 | - Throwable exc = null; | 58 | + Long blockBase = Tools.retryable(counter::getAndAdd, |
58 | - for (int i = 0; i < MAX_TRIES; i++) { | 59 | + StorageException.class, |
59 | - try { | 60 | + MAX_TRIES, |
60 | - Long blockBase = counter.getAndAdd(DEFAULT_BLOCK_SIZE); | 61 | + RETRY_DELAY_MS).apply(DEFAULT_BLOCK_SIZE); |
61 | - return new IdBlock(blockBase, DEFAULT_BLOCK_SIZE); | 62 | + return new IdBlock(blockBase, DEFAULT_BLOCK_SIZE); |
62 | - } catch (StorageException e) { | ||
63 | - log.warn("Unable to allocate ID block due to {}; retrying...", | ||
64 | - e.getMessage()); | ||
65 | - exc = e; | ||
66 | - randomDelay(RETRY_DELAY_MS); // FIXME: This is a deliberate hack; fix in Drake | ||
67 | - } | ||
68 | - } | ||
69 | - throw new IllegalStateException("Unable to allocate ID block", exc); | ||
70 | } | 63 | } |
71 | - | ||
72 | } | 64 | } | ... | ... |
... | @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.node.ShortNode; | ... | @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.node.ShortNode; |
26 | import com.fasterxml.jackson.databind.node.TextNode; | 26 | import com.fasterxml.jackson.databind.node.TextNode; |
27 | import com.google.common.collect.ImmutableSet; | 27 | import com.google.common.collect.ImmutableSet; |
28 | import com.google.common.collect.Maps; | 28 | import com.google.common.collect.Maps; |
29 | + | ||
29 | import org.apache.felix.scr.annotations.Activate; | 30 | import org.apache.felix.scr.annotations.Activate; |
30 | import org.apache.felix.scr.annotations.Component; | 31 | import org.apache.felix.scr.annotations.Component; |
31 | import org.apache.felix.scr.annotations.Deactivate; | 32 | import org.apache.felix.scr.annotations.Deactivate; |
... | @@ -165,14 +166,9 @@ public class DistributedNetworkConfigStore | ... | @@ -165,14 +166,9 @@ public class DistributedNetworkConfigStore |
165 | 166 | ||
166 | @Override | 167 | @Override |
167 | public <S, T extends Config<S>> T getConfig(S subject, Class<T> configClass) { | 168 | public <S, T extends Config<S>> T getConfig(S subject, Class<T> configClass) { |
168 | - // FIXME: There has to be a better way to absorb the timeout exceptions! | 169 | + // TODO: need to identify and address the root cause for timeouts. |
169 | - Versioned<ObjectNode> json = null; | 170 | + Versioned<ObjectNode> json = Tools.retryable(configs::get, ConsistentMapException.class, 1, MAX_BACKOFF) |
170 | - try { | 171 | + .apply(key(subject, configClass)); |
171 | - json = configs.get(key(subject, configClass)); | ||
172 | - } catch (ConsistentMapException e) { | ||
173 | - Tools.randomDelay(MAX_BACKOFF); | ||
174 | - json = configs.get(key(subject, configClass)); | ||
175 | - } | ||
176 | return json != null ? createConfig(subject, configClass, json.value()) : null; | 172 | return json != null ? createConfig(subject, configClass, json.value()) : null; |
177 | } | 173 | } |
178 | 174 | ... | ... |
1 | +/* | ||
2 | + * Copyright 2015 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.onlab.util; | ||
17 | + | ||
18 | +import java.util.function.Function; | ||
19 | + | ||
20 | +import com.google.common.base.Throwables; | ||
21 | + | ||
22 | +/** | ||
23 | + * Function that retries execution on failure. | ||
24 | + * | ||
25 | + * @param <U> input type | ||
26 | + * @param <V> output type | ||
27 | + */ | ||
28 | +public class RetryingFunction<U, V> implements Function<U, V> { | ||
29 | + | ||
30 | + private final Function<U, V> baseFunction; | ||
31 | + private final Class<? extends Throwable> exceptionClass; | ||
32 | + private final int maxRetries; | ||
33 | + private final int maxDelayBetweenRetries; | ||
34 | + | ||
35 | + public RetryingFunction(Function<U, V> baseFunction, | ||
36 | + Class<? extends Throwable> exceptionClass, | ||
37 | + int maxRetries, | ||
38 | + int maxDelayBetweenRetries) { | ||
39 | + this.baseFunction = baseFunction; | ||
40 | + this.exceptionClass = exceptionClass; | ||
41 | + this.maxRetries = maxRetries; | ||
42 | + this.maxDelayBetweenRetries = maxDelayBetweenRetries; | ||
43 | + } | ||
44 | + | ||
45 | + @Override | ||
46 | + public V apply(U input) { | ||
47 | + int retryAttempts = 0; | ||
48 | + while (true) { | ||
49 | + try { | ||
50 | + return baseFunction.apply(input); | ||
51 | + } catch (Throwable t) { | ||
52 | + if (!exceptionClass.isAssignableFrom(t.getClass()) || retryAttempts == maxRetries) { | ||
53 | + Throwables.propagate(t); | ||
54 | + } | ||
55 | + Tools.randomDelay(maxDelayBetweenRetries); | ||
56 | + retryAttempts++; | ||
57 | + } | ||
58 | + } | ||
59 | + } | ||
60 | +} |
... | @@ -46,6 +46,8 @@ import java.util.concurrent.Future; | ... | @@ -46,6 +46,8 @@ import java.util.concurrent.Future; |
46 | import java.util.concurrent.ThreadFactory; | 46 | import java.util.concurrent.ThreadFactory; |
47 | import java.util.concurrent.TimeUnit; | 47 | import java.util.concurrent.TimeUnit; |
48 | import java.util.concurrent.TimeoutException; | 48 | import java.util.concurrent.TimeoutException; |
49 | +import java.util.function.Function; | ||
50 | +import java.util.function.Supplier; | ||
49 | import java.util.stream.Stream; | 51 | import java.util.stream.Stream; |
50 | import java.util.stream.StreamSupport; | 52 | import java.util.stream.StreamSupport; |
51 | 53 | ||
... | @@ -227,6 +229,41 @@ public abstract class Tools { | ... | @@ -227,6 +229,41 @@ public abstract class Tools { |
227 | } | 229 | } |
228 | 230 | ||
229 | /** | 231 | /** |
232 | + * Returns a function that retries execution on failure. | ||
233 | + * @param base base function | ||
234 | + * @param exceptionClass type of exception for which to retry | ||
235 | + * @param maxRetries max number of retries before giving up | ||
236 | + * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from | ||
237 | + * the interval (0, maxDelayBetweenRetries] | ||
238 | + * @return function | ||
239 | + */ | ||
240 | + public static <U, V> Function<U, V> retryable(Function<U, V> base, | ||
241 | + Class<? extends Throwable> exceptionClass, | ||
242 | + int maxRetries, | ||
243 | + int maxDelayBetweenRetries) { | ||
244 | + return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries); | ||
245 | + } | ||
246 | + | ||
247 | + /** | ||
248 | + * Returns a Supplier that retries execution on failure. | ||
249 | + * @param base base supplier | ||
250 | + * @param exceptionClass type of exception for which to retry | ||
251 | + * @param maxRetries max number of retries before giving up | ||
252 | + * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from | ||
253 | + * the interval (0, maxDelayBetweenRetries] | ||
254 | + * @return supplier | ||
255 | + */ | ||
256 | + public static <V> Supplier<V> retryable(Supplier<V> base, | ||
257 | + Class<? extends Throwable> exceptionClass, | ||
258 | + int maxRetries, | ||
259 | + int maxDelayBetweenRetries) { | ||
260 | + return () -> new RetryingFunction<>(v -> base.get(), | ||
261 | + exceptionClass, | ||
262 | + maxRetries, | ||
263 | + maxDelayBetweenRetries).apply(null); | ||
264 | + } | ||
265 | + | ||
266 | + /** | ||
230 | * Suspends the current thread for a random number of millis between 0 and | 267 | * Suspends the current thread for a random number of millis between 0 and |
231 | * the indicated limit. | 268 | * the indicated limit. |
232 | * | 269 | * | ... | ... |
1 | +/* | ||
2 | + * Copyright 2015 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.onlab.util; | ||
17 | + | ||
18 | +import org.junit.After; | ||
19 | +import org.junit.Before; | ||
20 | +import org.junit.Test; | ||
21 | + | ||
22 | +/** | ||
23 | + * Unit tests for RetryingFunction. | ||
24 | + * | ||
25 | + */ | ||
26 | +public class RetryingFunctionTest { | ||
27 | + | ||
28 | + private int round; | ||
29 | + | ||
30 | + @Before | ||
31 | + public void setUp() { | ||
32 | + round = 1; | ||
33 | + } | ||
34 | + | ||
35 | + @After | ||
36 | + public void tearDown() { | ||
37 | + round = 0; | ||
38 | + } | ||
39 | + | ||
40 | + @Test(expected = RetryableException.class) | ||
41 | + public void testNoRetries() { | ||
42 | + new RetryingFunction<>(this::succeedAfterOneFailure, RetryableException.class, 0, 10).apply(null); | ||
43 | + } | ||
44 | + | ||
45 | + @Test | ||
46 | + public void testSuccessAfterOneRetry() { | ||
47 | + new RetryingFunction<>(this::succeedAfterOneFailure, RetryableException.class, 1, 10).apply(null); | ||
48 | + } | ||
49 | + | ||
50 | + @Test(expected = RetryableException.class) | ||
51 | + public void testFailureAfterOneRetry() { | ||
52 | + new RetryingFunction<>(this::succeedAfterTwoFailures, RetryableException.class, 1, 10).apply(null); | ||
53 | + } | ||
54 | + | ||
55 | + @Test | ||
56 | + public void testFailureAfterTwoRetries() { | ||
57 | + new RetryingFunction<>(this::succeedAfterTwoFailures, RetryableException.class, 2, 10).apply(null); | ||
58 | + } | ||
59 | + | ||
60 | + @Test(expected = NonRetryableException.class) | ||
61 | + public void testFailureWithNonRetryableFailure() { | ||
62 | + new RetryingFunction<>(this::failCompletely, RetryableException.class, 2, 10).apply(null); | ||
63 | + } | ||
64 | + | ||
65 | + private String succeedAfterOneFailure(String input) { | ||
66 | + if (round++ <= 1) { | ||
67 | + throw new RetryableException(); | ||
68 | + } else { | ||
69 | + return "pass"; | ||
70 | + } | ||
71 | + } | ||
72 | + | ||
73 | + private String succeedAfterTwoFailures(String input) { | ||
74 | + if (round++ <= 2) { | ||
75 | + throw new RetryableException(); | ||
76 | + } else { | ||
77 | + return "pass"; | ||
78 | + } | ||
79 | + } | ||
80 | + | ||
81 | + private String failCompletely(String input) { | ||
82 | + if (round++ <= 1) { | ||
83 | + throw new NonRetryableException(); | ||
84 | + } else { | ||
85 | + return "pass"; | ||
86 | + } | ||
87 | + } | ||
88 | + | ||
89 | + private class RetryableException extends RuntimeException { | ||
90 | + } | ||
91 | + | ||
92 | + private class NonRetryableException extends RuntimeException { | ||
93 | + } | ||
94 | +} |
-
Please register or login to post a comment