Package org.jenkinsci.plugins.workflow.cps

Source Code of org.jenkinsci.plugins.workflow.cps.CpsBodyExecution

package org.jenkinsci.plugins.workflow.cps;

import com.cloudbees.groovy.cps.Outcome;
import com.google.common.util.concurrent.FutureCallback;
import hudson.model.Result;
import jenkins.model.CauseOfInterruption;
import org.jenkinsci.plugins.workflow.steps.BodyExecution;
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException;
import org.jenkinsci.plugins.workflow.steps.StepExecution;

import javax.annotation.concurrent.GuardedBy;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* {@link BodyExecution} impl for CPS.
*
* This object is serializable while {@link BodyInvoker} isn't.
*
* When the body finishes execution, this object should be notified as {@link FutureCallback}.
*
* @author Kohsuke Kawaguchi
* @see BodyInvoker#bodyExecution
*/
class CpsBodyExecution extends BodyExecution implements FutureCallback {
    /**
     * Thread that's executing
     */
    @GuardedBy("this")
    private CpsThread thread;

    /**
     * Set to non-null if the body execution is stopped.
     */
    @GuardedBy("this")
    private FlowInterruptedException stopped;

    @GuardedBy("this")
    private List<FutureCallback<Object>> callbacks = new ArrayList<FutureCallback<Object>>();

    @GuardedBy("this")
    private Outcome outcome;

    @Override
    public synchronized Collection<StepExecution> getCurrentExecutions() {
        if (thread==null)   return Collections.emptyList();

        StepExecution s = thread.getStep();
        if (s!=null)        return Collections.singleton(s);
        else                return Collections.emptyList();
    }

    @Override
    public boolean cancel(final CauseOfInterruption... causes) {
        // 'stopped' and 'thread' are updated atomically
        final CpsThread t;
        synchronized (this) {
            if (isDone())  return false;   // already complete
            stopped = new FlowInterruptedException(Result.ABORTED, causes); // TODO: the fact that I'm hard-coding exception seems to indicate an abstraction leak. Come back and think about this.
            t = this.thread;
        }

        if (t!=null) {
            t.getExecution().runInCpsVmThread(new FutureCallback<CpsThreadGroup>() {
                @Override
                public void onSuccess(CpsThreadGroup g) {
                    StepExecution s = t.getStep()// this is the part that should run in CpsVmThread
                    if (s == null) {
                        // TODO: if it's not running inside a StepExecution, we need to set an interrupt flag
                        // and interrupt at an earliest convenience
                        return;
                    }

                    try {
                        s.stop(stopped);
                    } catch (Exception e) {
                        LOGGER.log(Level.WARNING, "Failed to stop " + s, e);
                    }
                }

                @Override
                public void onFailure(Throwable t) {
                    // couldn't cancel
                }
            });
        } else {
            // if it hasn't begun executing, we'll stop it when
            // it begins.
        }
        return true;
    }

    @Override
    public synchronized boolean isCancelled() {
        return stopped!=null && isDone();
    }

    @Override
    public synchronized Object get() throws InterruptedException, ExecutionException {
        while (outcome==null) {
            wait();
        }
        if (outcome.isSuccess())    return outcome.getNormal();
        else    throw new ExecutionException(outcome.getAbnormal());
    }

    @Override
    public synchronized Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        long endTime = System.currentTimeMillis() + unit.toMillis(timeout);
        long remaining;
        while (outcome==null && (remaining=endTime-System.currentTimeMillis()) > 0) {
            wait(remaining);
        }

        if (outcome==null)
            throw new TimeoutException();

        if (outcome.isSuccess())    return outcome.getNormal();
        else    throw new ExecutionException(outcome.getAbnormal());
    }

    /**
     * Start running the new thread unless the stop is requested, in which case the thread gets aborted right away.
     */
    @CpsVmThreadOnly
    /*package*/ synchronized void startExecution(CpsThread t) {
        // either get the new thread going normally, or abort from the beginning
        t.resume(new Outcome(null, stopped));

        assert this.thread==null;
        this.thread = t;

    }

    public void prependCallback(FutureCallback<Object> callback) {
        if (!(callback instanceof Serializable))
            throw new IllegalStateException("Callback must be persistable, but got "+callback.getClass());

        Outcome o;
        synchronized (this) {
            if (callbacks != null) {
                callbacks.add(0,callback);
                return;
            }
            o = outcome;
        }

        // if the computation has completed,
        fire(callback, o);
    }

    public void addCallback(FutureCallback<Object> callback) {
        if (!(callback instanceof Serializable))
            throw new IllegalStateException("Callback must be persistable, but got "+callback.getClass());

        Outcome o;
        synchronized (this) {
            if (callbacks != null) {
                callbacks.add(callback);
                return;
            }
            o = outcome;
        }

        // if the computation has completed,
        fire(callback, o);
    }

    private void fire(FutureCallback<Object> callback, Outcome o) {
        if (o.isSuccess())    callback.onSuccess(o.getNormal());
        else                  callback.onFailure(o.getAbnormal());
    }

    public synchronized boolean isDone() {
        return outcome!=null;
    }

    /**
     * Atomically commits the outcome and then grabs all the callbacks.
     */
    private synchronized List<FutureCallback<Object>> grabCallbacks(Outcome o) {
        if (this.outcome!=null)     return Collections.emptyList(); // already completed

        this.outcome = o;
        List<FutureCallback<Object>> r = callbacks;
        callbacks = null;
        return r;
    }

    @Override
    public void onSuccess(Object result) {
        for (FutureCallback<Object> c : grabCallbacks(new Outcome(result,null))) {
            c.onSuccess(result);
        }
    }

    @Override
    public void onFailure(Throwable t) {
        for (FutureCallback<Object> c : grabCallbacks(new Outcome(null,t))) {
            c.onFailure(t);
        }
    }


    private static final long serialVersionUID = 1L;

    private static final Logger LOGGER = Logger.getLogger(CpsBodyExecution.class.getName());
}
TOP

Related Classes of org.jenkinsci.plugins.workflow.cps.CpsBodyExecution

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.