/* * Copyright 2016-present Open Networking Laboratory * * Licensed 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.onosproject.store.primitives.impl; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static org.onlab.util.Tools.groupedThreads; import static org.onlab.util.Tools.maxPriority; import static org.slf4j.LoggerFactory.getLogger; import java.net.ConnectException; import java.nio.channels.ClosedChannelException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Predicate; import org.onlab.util.Tools; import org.onosproject.store.service.StorageException; import org.slf4j.Logger; import com.google.common.base.Throwables; import io.atomix.catalyst.transport.TransportException; import io.atomix.copycat.Query; import io.atomix.copycat.client.CopycatClient; import io.atomix.copycat.error.QueryException; import io.atomix.copycat.error.UnknownSessionException; import io.atomix.copycat.session.ClosedSessionException; /** * Custom {@code CopycatClient} for injecting additional logic that runs before/after operation submission. */ public class OnosCopycatClient extends DelegatingCopycatClient { private final int maxRetries; private final long delayBetweenRetriesMillis; private final ScheduledExecutorService executor; private final Logger log = getLogger(getClass()); private final Predicate<Throwable> retryableCheck = e -> e instanceof ConnectException || e instanceof TimeoutException || e instanceof TransportException || e instanceof ClosedChannelException || e instanceof QueryException || e instanceof UnknownSessionException || e instanceof ClosedSessionException || e instanceof StorageException.Unavailable; OnosCopycatClient(CopycatClient client, int maxRetries, long delayBetweenRetriesMillis) { super(client); this.maxRetries = maxRetries; this.delayBetweenRetriesMillis = delayBetweenRetriesMillis; this.executor = newSingleThreadScheduledExecutor(maxPriority(groupedThreads("OnosCopycat", "client", log))); } @Override public CompletableFuture<Void> close() { executor.shutdown(); return super.close(); } @Override public <T> CompletableFuture<T> submit(Query<T> query) { if (state() == State.CLOSED) { return Tools.exceptionalFuture(new StorageException.Unavailable()); } CompletableFuture<T> future = new CompletableFuture<>(); executor.execute(() -> submit(query, 1, future)); return future; } private <T> void submit(Query<T> query, int attemptIndex, CompletableFuture<T> future) { client.submit(query).whenComplete((r, e) -> { if (e != null) { if (attemptIndex < maxRetries + 1 && retryableCheck.test(Throwables.getRootCause(e))) { log.debug("Retry attempt ({} of {}). Failure due to {}", attemptIndex, maxRetries, Throwables.getRootCause(e).getClass()); executor.schedule(() -> submit(query, attemptIndex + 1, future), delayBetweenRetriesMillis, TimeUnit.MILLISECONDS); } else { future.completeExceptionally(e); } } else { future.complete(r); } }); } }