All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.eureka2.client.channel.consumer.RetryableChannelConsumer Maven / Gradle / Ivy

/*
 * Copyright 2014 Netflix, Inc.
 *
 * 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 com.netflix.eureka2.client.channel.consumer;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import com.netflix.eureka2.service.ServiceChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Scheduler;
import rx.Scheduler.Worker;
import rx.Subscriber;
import rx.functions.Action0;

/**
 * Channel consumer that reconnects underlying channel in case of failures. It provides
 * a set of template methods/steps for the reconnect algorithm:
 * 
    *
  • reestablish - create a new channel, parallel to the broken one
  • *
  • repopulate - re-establish the desired state from the new channel, prior to swapping it with the broken one
  • *
  • release - release all the resources allocated for the broken channel; this step is performed after the channel swap
  • *
* After execution of these steps, the original channel is closed. * * @author Tomasz Bak */ public abstract class RetryableChannelConsumer { private static final Logger logger = LoggerFactory.getLogger(RetryableChannelConsumer.class); public static final int MAX_EXP_BACK_OFF_MULTIPLIER = 10; public class StateWithChannel { private final C channel; private final S state; public StateWithChannel(C channel, S state) { this.channel = channel; this.state = state; } public C getChannel() { return channel; } public S getState() { return state; } } // Channel descriptive name to be used in the log file - that should come from channel API private final String name = getClass().getSimpleName(); private final long retryInitialDelayMs; private final long maxRetryDelayMs; private final Worker worker; private final AtomicReference currentStateWithChannel = new AtomicReference<>(); private long lastConnectTime; private long retryDelay; protected RetryableChannelConsumer(long retryInitialDelayMs, Scheduler scheduler) { this.retryInitialDelayMs = retryInitialDelayMs; this.maxRetryDelayMs = retryInitialDelayMs * MAX_EXP_BACK_OFF_MULTIPLIER; this.worker = scheduler.createWorker(); this.retryDelay = retryInitialDelayMs; } public StateWithChannel getStateWithChannel() { return currentStateWithChannel.get(); } public void shutdownRetryableConsumer() { worker.unsubscribe(); if (currentStateWithChannel.get() != null && currentStateWithChannel.get().channel != null) { currentStateWithChannel.get().channel.close(); } } protected abstract StateWithChannel reestablish(); protected abstract Observable repopulate(StateWithChannel newState); protected abstract void release(StateWithChannel oldState); protected Worker getWorker() { return worker; } protected void initializeRetryableConsumer() { currentStateWithChannel.set(reestablish()); subscribeToChannelLifecycle(); } private void retry() { logger.info("Reconnecting channel {}", name); final StateWithChannel newStateWithChannel = reestablish(); lastConnectTime = worker.now(); repopulate(newStateWithChannel).subscribe(new Subscriber() { @Override public void onCompleted() { StateWithChannel oldState = currentStateWithChannel.getAndSet(newStateWithChannel); subscribeToChannelLifecycle(); release(oldState); logger.info("Channel {} successfully reconnected and the state has been restored", name); } @Override public void onError(Throwable e) { logger.error("Failed to reconnect channel " + name, e); scheduleRetry(); } @Override public void onNext(Void aVoid) { // No-op } }); } private void scheduleRetry() { worker.schedule(retryAction, retryDelay, TimeUnit.MILLISECONDS); bumpUpRetryDelay(); } private void bumpUpRetryDelay() { retryDelay = Math.min(maxRetryDelayMs, retryDelay * 2); } private void subscribeToChannelLifecycle() { Observable lifecycleObservable = getStateWithChannel().getChannel().asLifecycleObservable(); lifecycleObservable.subscribe(new Subscriber() { @Override public void onCompleted() { logger.info("Channel closed gracefully and must be reconnected"); long closedAfter = worker.now() - lastConnectTime; if (closedAfter >= maxRetryDelayMs) { retryDelay = retryInitialDelayMs; } scheduleRetry(); } @Override public void onError(Throwable e) { logger.info("Channel failure; scheduling the reconnection in " + retryDelay + "ms", e); scheduleRetry(); } @Override public void onNext(Void aVoid) { // No-op } }); } private final Action0 retryAction = new Action0() { @Override public void call() { retry(); } }; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy