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

io.github.resilience4j.reactor.ResilienceBaseSubscriber Maven / Gradle / Ivy

Go to download

Resilience4j is a lightweight, easy-to-use fault tolerance library designed for Java8 and functional programming

There is a newer version: 2.2.0
Show newest version
/*
 * Copyright 2018 Julien Hoarau
 *
 * 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 io.github.resilience4j.reactor;

import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.Disposable;
import reactor.core.Exceptions;
import reactor.core.publisher.Operators;
import reactor.core.publisher.SignalType;
import reactor.util.context.Context;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

/**
 * Heavily inspired by {@link reactor.core.publisher.BaseSubscriber}
 *
 * @param 
 */
public abstract class ResilienceBaseSubscriber implements CoreSubscriber, Subscription,
        Disposable {

    protected final CoreSubscriber actual;

    private volatile Subscription subscription;

    private static final AtomicReferenceFieldUpdater S =
            AtomicReferenceFieldUpdater.newUpdater(ResilienceBaseSubscriber.class, Subscription.class, "subscription");

    private final AtomicReference permitted = new AtomicReference<>(Permit.PENDING);

    protected ResilienceBaseSubscriber(CoreSubscriber actual) {
        this.actual = actual;
    }

    /**
     * Return current {@link Subscription}
     * @return current {@link Subscription}
     */
    protected Subscription upstream() {
        return subscription;
    }

    @Override
    public boolean isDisposed() {
        return subscription == Operators.cancelledSubscription();
    }

    /**
     * {@link Disposable#dispose() Dispose} the {@link Subscription} by
     * {@link Subscription#cancel() cancelling} it.
     */
    @Override
    public void dispose() {
        cancel();
    }

    @Override
    public Context currentContext() {
        return actual.currentContext();
    }

    protected boolean notCancelled() {
        return !this.isDisposed();
    }

    /**
     * Hook for further processing of onSubscribe's Subscription.
     *
     * @param subscription the subscription to optionally process
     */
    protected void hookOnSubscribe(Subscription subscription){
        // NO-OP
    }

    /**
     * Hook for processing of onNext values. You can call {@link #request(long)} here
     * to further request data from the source {@link org.reactivestreams.Publisher} if
     * the {@link #hookOnSubscribe(Subscription) initial request} wasn't unbounded.
     * 

Defaults to doing nothing. * * @param value the emitted value to process */ protected void hookOnNext(T value){ // NO-OP } /** * Optional hook for completion processing. Defaults to doing nothing. */ protected void hookOnComplete() { // NO-OP } /** * Optional hook for error processing. Default is to call * {@link Exceptions#errorCallbackNotImplemented(Throwable)}. * * @param throwable the error to process */ protected void hookOnError(Throwable throwable) { throw Exceptions.errorCallbackNotImplemented(throwable); } /** * Optional hook executed when the subscription is cancelled by calling this * Subscriber's {@link #cancel()} method. Defaults to doing nothing. */ protected void hookOnCancel() { //NO-OP } /** * Optional hook executed after any of the termination events (onError, onComplete, * cancel). The hook is executed in addition to and after {@link #hookOnError(Throwable)}, * {@link #hookOnComplete()} and {@link #hookOnCancel()} hooks, even if these callbacks * fail. Defaults to doing nothing. A failure of the callback will be caught by * {@link Operators#onErrorDropped(Throwable, reactor.util.context.Context)}. * * @param type the type of termination event that triggered the hook * ({@link SignalType#ON_ERROR}, {@link SignalType#ON_COMPLETE} or * {@link SignalType#CANCEL}) */ protected void hookFinally(SignalType type) { //NO-OP } /** * Optional hook executed when permit call is acquired. */ protected void hookOnPermitAcquired() { //NO-OP } /** * @return true if call is permitted, false otherwise */ protected abstract boolean isCallPermitted(); protected boolean acquireCallPermit() { boolean callPermitted = false; if (permitted.compareAndSet(Permit.PENDING, Permit.ACQUIRED)) { callPermitted = isCallPermitted(); if (!callPermitted) { permitted.set(Permit.REJECTED); } else { hookOnPermitAcquired(); } } return callPermitted; } protected boolean wasCallPermitted() { return permitted.get() == Permit.ACQUIRED; } protected abstract Throwable getThrowable(); @Override public final void onSubscribe(Subscription s) { if (Operators.setOnce(S, this, s)) { try { hookOnSubscribe(s); if (acquireCallPermit()) { actual.onSubscribe(this); } else { cancel(); actual.onSubscribe(this); actual.onError(getThrowable()); } } catch (Throwable throwable) { onError(Operators.onOperatorError(s, throwable, currentContext())); } } } @Override public final void onNext(T value) { Objects.requireNonNull(value, "onNext"); try { hookOnNext(value); } catch (Throwable throwable) { onError(Operators.onOperatorError(subscription, throwable, value, currentContext())); } } @Override public final void onError(Throwable t) { Objects.requireNonNull(t, "onError"); if (S.getAndSet(this, Operators.cancelledSubscription()) == Operators .cancelledSubscription()) { // already cancelled concurrently... if (permitted.get() == Permit.REJECTED) { // Ignore if the call was rejected return; } // signal on error dropped otherwise Operators.onErrorDropped(t, currentContext()); return; } try { hookOnError(t); } catch (Throwable e) { e = Exceptions.addSuppressed(e, t); Operators.onErrorDropped(e, currentContext()); } finally { safeHookFinally(SignalType.ON_ERROR); } } @Override public final void onComplete() { if (S.getAndSet(this, Operators.cancelledSubscription()) != Operators .cancelledSubscription()) { //we're sure it has not been concurrently cancelled try { hookOnComplete(); } catch (Throwable throwable) { //onError itself will short-circuit due to the CancelledSubscription being push above hookOnError(Operators.onOperatorError(throwable, currentContext())); } finally { safeHookFinally(SignalType.ON_COMPLETE); } } } @Override public final void request(long n) { if (Operators.validate(n)) { Subscription s = this.subscription; if (s != null) { s.request(n); } } } /** * {@link #request(long) Request} an unbounded amount. */ public final void requestUnbounded() { request(Long.MAX_VALUE); } @Override public final void cancel() { if (Operators.terminate(S, this)) { try { hookOnCancel(); } catch (Throwable throwable) { hookOnError(Operators.onOperatorError(subscription, throwable, currentContext())); } finally { safeHookFinally(SignalType.CANCEL); } } } private void safeHookFinally(SignalType type) { try { hookFinally(type); } catch (Throwable finallyFailure) { Operators.onErrorDropped(finallyFailure, currentContext()); } } @Override public String toString() { return getClass().getSimpleName(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy