
com.uber.cadence.internal.sync.WorkflowThreadContext Maven / Gradle / Ivy
/*
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.uber.cadence.internal.sync;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.function.Consumer;
import java.util.function.Supplier;
class WorkflowThreadContext {
// Shared runner lock
private final Lock lock;
// Used to block await call
private final Condition yieldCondition;
// Used to block runUntilBlocked call
private final Condition runCondition;
// Used to block evaluateInCoroutineContext
private final Condition evaluationCondition;
private Status status = Status.CREATED;
private Consumer evaluationFunction;
private Throwable unhandledException;
private boolean inRunUntilBlocked;
private boolean remainedBlocked;
private String yieldReason;
private boolean destroyRequested;
WorkflowThreadContext(Lock lock) {
this.lock = lock;
this.yieldCondition = lock.newCondition();
this.runCondition = lock.newCondition();
this.evaluationCondition = lock.newCondition();
}
public void initialYield() {
Status status = getStatus();
if (status == Status.DONE) {
throw new DestroyWorkflowThreadError("done in initialYield");
}
if (status != Status.RUNNING) {
throw new IllegalStateException("not in RUNNING but in " + status + " state");
}
yield("created", () -> true);
}
public void yield(String reason, Supplier unblockFunction) {
if (unblockFunction == null) {
throw new IllegalArgumentException("null unblockFunction");
}
// Evaluates unblockFunction out of the lock to avoid deadlocks.
lock.lock();
try {
// TODO: Verify that calling unblockFunction under the lock is a sane thing to do.
while (!inRunUntilBlocked || !unblockFunction.get()) {
if (destroyRequested) {
throw new DestroyWorkflowThreadError();
}
status = Status.YIELDED;
runCondition.signal();
yieldCondition.await();
mayBeEvaluate(reason);
yieldReason = reason;
}
} catch (InterruptedException e) {
// Throwing Error in workflow code aborts decision without failing workflow.
throw new Error("Unexpected interrupt", e);
} finally {
setStatus(Status.RUNNING);
remainedBlocked = false;
yieldReason = null;
lock.unlock();
}
}
/**
* Execute evaluation function by the thread that owns this context if {@link
* #evaluateInCoroutineContext(Consumer)} was called.
*
* @param reason human readable reason for current thread blockage passed to await call.
*/
private void mayBeEvaluate(String reason) {
if (status == Status.EVALUATING) {
try {
evaluationFunction.accept(reason);
} catch (Exception e) {
evaluationFunction.accept(e.toString());
} finally {
status = Status.YIELDED;
evaluationCondition.signal();
}
}
}
/**
* Call function by the thread that owns this context and is currently blocked in a await. Used to
* get information about current state of the thread like current stack trace.
*
* @param function to evaluate. Consumes reason for yielding parameter.
*/
public void evaluateInCoroutineContext(Consumer function) {
lock.lock();
try {
if (function == null) {
throw new IllegalArgumentException("null function");
}
if (status != Status.YIELDED && status != Status.RUNNING) {
throw new IllegalStateException("Not in yielded status: " + status);
}
if (evaluationFunction != null) {
throw new IllegalStateException("Already evaluating");
}
if (inRunUntilBlocked) {
throw new IllegalStateException("Running runUntilBlocked");
}
evaluationFunction = function;
status = Status.EVALUATING;
yieldCondition.signal();
while (status == Status.EVALUATING) {
evaluationCondition.await();
}
} catch (InterruptedException e) {
throw new Error("Unexpected interrupt", e);
} finally {
evaluationFunction = null;
lock.unlock();
}
}
public Status getStatus() {
lock.lock();
try {
return status;
} finally {
lock.unlock();
}
}
public void setStatus(Status status) {
// Unblock runUntilBlocked if thread exited instead of yielding.
lock.lock();
try {
this.status = status;
if (isDone()) {
runCondition.signal();
}
} finally {
lock.unlock();
}
}
public boolean isDone() {
lock.lock();
try {
return status == Status.DONE;
} finally {
lock.unlock();
}
}
public Throwable getUnhandledException() {
lock.lock();
try {
return unhandledException;
} finally {
lock.unlock();
}
}
public void setUnhandledException(Throwable unhandledException) {
lock.lock();
try {
this.unhandledException = unhandledException;
} finally {
lock.unlock();
}
}
public String getYieldReason() {
return yieldReason;
}
/**
* @return true if thread made some progress. Which is await was unblocked and some code after it
* was executed.
*/
public boolean runUntilBlocked() {
lock.lock();
try {
if (status == Status.DONE) {
return false;
}
if (evaluationFunction != null) {
throw new IllegalStateException("Cannot runUntilBlocked while evaluating");
}
inRunUntilBlocked = true;
if (status != Status.CREATED) {
status = Status.RUNNING;
}
remainedBlocked = true;
yieldCondition.signal();
while (status == Status.RUNNING || status == Status.CREATED) {
runCondition.await();
if (evaluationFunction != null) {
throw new IllegalStateException("Cannot runUntilBlocked while evaluating");
}
}
return !remainedBlocked;
} catch (InterruptedException e) {
throw new Error("Unexpected interrupt", e);
} finally {
inRunUntilBlocked = false;
lock.unlock();
}
}
public boolean isDestroyRequested() {
lock.lock();
try {
return destroyRequested;
} finally {
lock.unlock();
}
}
public void destroy() {
lock.lock();
try {
destroyRequested = true;
if (status == Status.CREATED || status == Status.RUNNING || status == Status.DONE) {
status = Status.DONE;
return;
}
} finally {
lock.unlock();
}
evaluateInCoroutineContext(
(r) -> {
throw new DestroyWorkflowThreadError();
});
runUntilBlocked();
}
/** To be called only from a workflow thread. */
public void exit() {
lock.lock();
try {
destroyRequested = true;
} finally {
lock.unlock();
}
throw new DestroyWorkflowThreadError();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy