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

io.servicetalk.concurrent.internal.DelayedSubscription Maven / Gradle / Ivy

/*
 * Copyright © 2018 Apple Inc. and the ServiceTalk project authors
 *
 * 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.servicetalk.concurrent.internal;

import io.servicetalk.concurrent.PublisherSource.Subscription;

import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import javax.annotation.Nullable;

import static io.servicetalk.concurrent.internal.FlowControlUtils.addWithOverflowProtection;
import static java.lang.Long.MIN_VALUE;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.atomic.AtomicLongFieldUpdater.newUpdater;

/**
 * A {@link Subscription} which serves as a placeholder until the "real" {@link Subscription} is available.
 */
public final class DelayedSubscription implements Subscription {
    private static final long SUBSCRIPTION_SETTING = MIN_VALUE;
    private static final long SUBSCRIPTION_SET = MIN_VALUE + 1;
    private static final long SUBSCRIPTION_CANCEL_PENDING = MIN_VALUE + 2;
    private static final long GREATEST_CONTROL_VALUE = SUBSCRIPTION_CANCEL_PENDING;
    private static final AtomicLongFieldUpdater requestedUpdater =
            newUpdater(DelayedSubscription.class, "requested");

    @Nullable
    private Subscription subscription;
    private volatile long requested;

    /**
     * Set the delayed {@link Subscription}. This method can only be called a single time and
     * subsequent calls will result in {@link #cancel()} being called on {@code delayedSubscription}.
     *
     * @param delayedSubscription The delayed {@link Subscription}.
     */
    public void delayedSubscription(Subscription delayedSubscription) {
        requireNonNull(delayedSubscription);
        for (;;) {
            final long prevRequested = requested;
            if (prevRequested <= GREATEST_CONTROL_VALUE) {
                delayedSubscription.cancel();
                break;
            } else if (requestedUpdater.compareAndSet(this, prevRequested, SUBSCRIPTION_SETTING)) {
                if (prevRequested != 0) {
                    delayedSubscription.request(prevRequested);
                }
                // Set the subscription before CAS to make it visible to the thread interacting with the Subscription.
                // The Subscription thread won't use the state unless the CAS to SUBSCRIPTION_SET is successful, so
                // there will be no concurrency introduced by this operation.
                subscription = delayedSubscription;
                if (requestedUpdater.compareAndSet(this, SUBSCRIPTION_SETTING, SUBSCRIPTION_SET)) {
                    break;
                }
            }
        }
    }

    @Override
    public void request(long n) {
        for (;;) {
            final long prevRequested = requested;
            if (prevRequested == SUBSCRIPTION_SET) {
                assert subscription != null;
                subscription.request(n);
                break;
            } else if (prevRequested == SUBSCRIPTION_SETTING) {
                if (requestedUpdater.compareAndSet(this, SUBSCRIPTION_SETTING, addRequestN(0, n))) {
                    break;
                }
            } else if (prevRequested < 0 ||
                    requestedUpdater.compareAndSet(this, prevRequested, addRequestN(prevRequested, n))) {
                // prevRequested < 0 covers the following cases:
                //  SUBSCRIPTION_CANCEL_PENDING - delayedSubscription(..) is responsible for propagating cancel()
                //  prior invalid request(n) - this value should be preserved
                break;
            }
        }
    }

    @Override
    public void cancel() {
        for (;;) {
            final long prevRequested = requested;
            if (prevRequested == SUBSCRIPTION_SET) {
                assert subscription != null;
                subscription.cancel();
                break;
            } else if (prevRequested == SUBSCRIPTION_SETTING) {
                if (requestedUpdater.compareAndSet(this, SUBSCRIPTION_SETTING, SUBSCRIPTION_CANCEL_PENDING)) {
                    break;
                }
            } else if (prevRequested < 0 ||
                    requestedUpdater.compareAndSet(this, prevRequested, SUBSCRIPTION_CANCEL_PENDING)) {
                // prevRequested < 0 covers the following cases:
                //  SUBSCRIPTION_CANCEL_PENDING - delayedSubscription(..) is responsible for propagating cancel()
                //  prior invalid request(n) - this value should be preserved
                break;
            }
        }
    }

    private static long addRequestN(long prevRequested, long n) {
        return n <= GREATEST_CONTROL_VALUE || n == 0 ? -1 :
                n < 0 ? n : addWithOverflowProtection(prevRequested, n);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy