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

kong.unirest.core.java.MultipartSubscription Maven / Gradle / Ivy

There is a newer version: 4.4.5
Show newest version
/**
 * The MIT License
 *
 * Copyright for portions of unirest-java are held by Kong Inc (c) 2013.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package kong.unirest.core.java;

import kong.unirest.core.ProgressMonitor;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Flow;

import static java.nio.charset.StandardCharsets.UTF_8;

class MultipartSubscription implements Flow.Subscription {

    // An executor that executes the runnable in the calling thread.
    static final Executor SYNC_EXECUTOR = Runnable::run;
    private static final int RUN = 0x1; // run signaller task
    private static final int KEEP_ALIVE = 0x2; // keep running signaller task
    private static final int CANCELLED = 0x4; // subscription is cancelled
    private static final int SUBSCRIBED = 0x8; // onSubscribe called

    /*
     * Implementation is loosely modeled after SubmissionPublisher$BufferedSubscription, mainly
     * regarding how execution is controlled by CASes on a state field that manipulate bits
     * representing execution states.
     */
    private static final VarHandle STATE;
    private static final VarHandle PENDING_ERROR;
    private static final VarHandle DEMAND;
    private static final VarHandle PART_SUBSCRIBER;

    static {
        try {
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            PART_SUBSCRIBER = lookup.findVarHandle(MultipartSubscription.class, "partSubscriber", Flow.Subscriber.class);
            STATE = lookup.findVarHandle(MultipartSubscription.class, "state", int.class);
            DEMAND = lookup.findVarHandle(MultipartSubscription.class, "demand", long.class);
            PENDING_ERROR = lookup.findVarHandle(MultipartSubscription.class, "pendingError", Throwable.class);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    // A tombstone to protect against race conditions that would otherwise occur if a
    // thread tries to abort() while another tries to nextPartHeaders(), which might lead
    // to a newly subscribed part being missed by abort().
    private static final Flow.Subscriber CANCELLED_SUBSCRIBER =
            new Flow.Subscriber<>() {
                @Override public void onSubscribe(Flow.Subscription subscription) {}
                @Override public void onNext(ByteBuffer item) {}
                @Override public void onError(Throwable throwable) {}
                @Override public void onComplete() {}
            };

    private final String boundary;
    private final List parts;
    private int partIndex;
    private boolean complete;
    private final ProgressMonitor monitor;
    private final Flow.Subscriber downstream;
    private final Executor executor;


    private volatile Flow.Subscriber partSubscriber;
    private volatile int state;
    private volatile long demand;
    private volatile Throwable pendingError;

    MultipartSubscription(String boundary, List parts, ProgressMonitor monitor, Flow.Subscriber downstream) {
        this.monitor = monitor;
        this.downstream = downstream;
        this.executor = SYNC_EXECUTOR;
        this.boundary = boundary;
        this.parts = parts;
    }

    @Override
    public final void request(long n) {
        if (n > 0 && getAndAddDemand(this, DEMAND, n) == 0) {
            signal();
        } else if (n <= 0) {
            signalError(new IllegalArgumentException("non-positive subscription request"));
        }
    }

    @Override
    public final void cancel() {
        if ((getAndBitwiseOrState(CANCELLED) & CANCELLED) == 0) {
            abort(true);
        }
    }

    /** Adds given count to demand not exceeding {@code Long.MAX_VALUE}. */
    private long getAndAddDemand(Object owner, VarHandle demand, long n) {
        while (true) {
            long currentDemand = (long) demand.getVolatile(owner);
            long addedDemand = currentDemand + n;
            if (addedDemand < 0) { // overflow
                addedDemand = Long.MAX_VALUE;
            }
            if (demand.compareAndSet(owner, currentDemand, addedDemand)) {
                return currentDemand;
            }
        }
    }

    /** Subtracts given count from demand. Caller ensures result won't be negative. */
    private long subtractAndGetDemand(Object owner, VarHandle demand, long n) {
        return (long) demand.getAndAdd(owner, -n) - n;
    }



    /** Schedules a signaller task. {@code force} tells whether to schedule in case of no demand */
    public final void signal(boolean force) {
        if (force || demand > 0) {
            signal();
        }
    }

    public final void signalError(Throwable error) {
        propagateError(error);
        signal();
    }
    
    /** Returns {@code true} if cancelled. {@code false} result is immediately outdated. */
    private final boolean isCancelled() {
        return (state & CANCELLED) != 0;
    }

    /**
     * Returns {@code true} if the subscriber is to be completed exceptionally. {@code false} result
     * is immediately outdated. Can be used by implementation to halt producing items in case the
     * subscription was asynchronously signalled with an error.
     */
    private final boolean hasPendingErrors() {
        return pendingError != null;
    }


    /**
     * Calls downstream's {@code onError} after cancelling this subscription. {@code flowInterrupted}
     * tells whether the error interrupted the normal flow of signals.
     */
    private final void cancelOnError(Flow.Subscriber downstream, Throwable error, boolean flowInterrupted) {
        if ((getAndBitwiseOrState(CANCELLED) & CANCELLED) == 0) {
            try {
                downstream.onError(error);
            } finally {
                abort(flowInterrupted);
            }
        }
    }

    /** Calls downstream's {@code onComplete} after cancelling this subscription. */
    private final void cancelOnComplete(Flow.Subscriber downstream) {
        if ((getAndBitwiseOrState(CANCELLED) & CANCELLED) == 0) {
            try {
                downstream.onComplete();
            } finally {
                abort(false);
            }
        }
    }

    /** Submits given item to the downstream, returning {@code false} and cancelling on failure. */
    private final boolean submitOnNext(Flow.Subscriber downstream, ByteBuffer item) {
        if (!(isCancelled() || hasPendingErrors())) {
            try {
                downstream.onNext(item);
                return true;
            } catch (Throwable t) {
                Throwable error = propagateError(t);
                pendingError = null;
                cancelOnError(downstream, error, true);
            }
        }
        return false;
    }

    private void signal() {
        boolean casSucceeded = false;
        for (int s; !casSucceeded && ((s = state) & CANCELLED) == 0; ) {
            int setBit = (s & RUN) != 0 ? KEEP_ALIVE : RUN; // try to keep alive or run & execute
            casSucceeded = STATE.compareAndSet(this, s, s | setBit);
            if (casSucceeded && setBit == RUN) {
                try {
                    executor.execute(this::run);
                } catch (RuntimeException | Error e) {
                    // this is a problem because we cannot call any of onXXXX methods here
                    // as that would ruin the execution context guarantee. SubmissionPublisher's
                    // behaviour here is followed (cancel & rethrow).
                    cancel();
                    throw e;
                }
            }
        }
    }

    private void run() {
        int s;
        Flow.Subscriber d = downstream;
        subscribeOnDrain(d);
        for (long x = 0L, r = demand; ((s = state) & CANCELLED) == 0; ) {
            long emitted;
            Throwable error = pendingError;
            if (error != null) {
                pendingError = null;
                cancelOnError(d, error, false);
            } else if ((emitted = emit(d, r - x)) > 0L) {
                x += emitted;
                r = demand; // get fresh demand
                if (x == r) { // 'x' needs to be flushed
                    r = subtractAndGetDemand(this, DEMAND, x);
                    x = 0L;
                }
            } else if (r == (r = demand)) { // check that emit() actually failed on a fresh demand
                // un keep-alive or kill task if a dead-end is reached, which is possible if:
                // - there is no active emission (x <= 0)
                // - there is no active demand (r <= 0 after flushing x)
                // - cancelled (checked by the loop condition)
                boolean exhausted = x <= 0L;
                if (!exhausted) {
                    r = subtractAndGetDemand(this, DEMAND, x);
                    x = 0L;
                    exhausted = r <= 0L;
                }
                int unsetBit = (s & KEEP_ALIVE) != 0 ? KEEP_ALIVE : RUN;
                if (exhausted && STATE.compareAndSet(this, s, s & ~unsetBit) && unsetBit == RUN) {
                    break;
                }
            }
        }
    }

    private void subscribeOnDrain(Flow.Subscriber downstream) {
        if ((state & (SUBSCRIBED | CANCELLED)) == 0
                && (getAndBitwiseOrState(SUBSCRIBED) & (SUBSCRIBED | CANCELLED)) == 0) {
            try {
                downstream.onSubscribe(this);
            } catch (Throwable t) {
                Throwable e = propagateError(t);
                pendingError = null;
                cancelOnError(downstream, e, true);
            }
        }
    }

    /** Sets pending error or adds new one as suppressed in case of multiple error sources. */
    private Throwable propagateError(Throwable error) {
        while(true) {
            Throwable currentError = pendingError;
            if (currentError != null) {
                currentError.addSuppressed(error); // addSuppressed is thread-safe
                return currentError;
            }
            if (PENDING_ERROR.compareAndSet(this, null, error)) {
                return error;
            }
        }
    }

    private int getAndBitwiseOrState(int bits) {
        return (int) STATE.getAndBitwiseOr(this, bits);
    }

    /**
     * Main method for item emission. At most {@code e} items are emitted to the downstream using
     * {submitOnNext(Flow.Subscriber, Object)} as long as it returns {@code true}. The actual number
     * of emitted items is returned, may be {@code 0} in case of cancellation. If the underlying
     * source is finished, the subscriber is completed with {@link #cancelOnComplete(Flow.Subscriber)}.
     */
    private long emit(Flow.Subscriber downstream, long emit) {
        long submitted = 0L;
        while (true) {
            ByteBuffer batch;
            if (complete) {
                cancelOnComplete(downstream);
                return 0;
            } else if (submitted >= emit || (batch = pollNext()) == null) { // exhausted demand or batches
                return submitted;
            } else if (submitOnNext(downstream, batch)) {
                submitted++;
            } else {
                return 0;
            }
        }
    }

    /**
     * Called when the subscription is cancelled. {@code flowInterrupted} specifies whether
     * cancellation was due to ending the normal flow of signals (signal|signalError) or due to flow
     * interruption by downstream (e.g. calling {@code cancel()} or throwing from {@code onNext}).
     */
    private void abort(boolean flowInterrupted) {
        Flow.Subscriber previous =
                (Flow.Subscriber) PART_SUBSCRIBER.getAndSet(this, CANCELLED_SUBSCRIBER);
        if (previous instanceof PartSubscriber) {
            ((PartSubscriber) previous).abortUpstream(flowInterrupted);
        }
    }

    private ByteBuffer pollNext() {

        Flow.Subscriber subscriber = partSubscriber;
        if (subscriber instanceof PartSubscriber) { // not cancelled & not null
            ByteBuffer next = ((PartSubscriber) subscriber).pollNext();
            if (next != PartSubscriber.END_OF_PART) {
                return next;
            }
        }
        return subscriber != CANCELLED_SUBSCRIBER ? nextPartHeaders() : null;
    }

    private ByteBuffer nextPartHeaders() {
        StringBuilder heading = new StringBuilder();
        BoundaryAppender.get(partIndex, parts.size()).append(heading, boundary);
        if (partIndex < parts.size()) {
            Part part = parts.get(partIndex++);
            if (!subscribeToPart(part)) {
                return null;
            }
            MultipartBodyPublisher.appendPartHeaders(heading, part);
            heading.append("\r\n");
        } else {
            partSubscriber = CANCELLED_SUBSCRIBER; // race against abort() here is OK
            complete = true;
        }
        return UTF_8.encode(CharBuffer.wrap(heading));
    }

    private boolean subscribeToPart(Part part) {
        PartSubscriber subscriber = new PartSubscriber(this, part, monitor);
        Flow.Subscriber current = partSubscriber;
        if (current != CANCELLED_SUBSCRIBER && PART_SUBSCRIBER.compareAndSet(this, current, subscriber)) {
            part.bodyPublisher().subscribe(subscriber);
            return true;
        }
        return false;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy