blob: 3f4bd2bf1ca35982b393585f7aee49c128a6aaae [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2020 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* E.D.Willink - Initial API and implementation
*******************************************************************************/
package org.eclipse.qvtd.runtime.internal.evaluation;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.pivot.ids.TypeId;
import org.eclipse.qvtd.runtime.evaluation.AbstractConnection;
import org.eclipse.qvtd.runtime.evaluation.AbstractTransformer;
import org.eclipse.qvtd.runtime.evaluation.Connection;
import org.eclipse.qvtd.runtime.evaluation.Connection.Incremental;
import org.eclipse.qvtd.runtime.evaluation.ExecutionVisitor;
import org.eclipse.qvtd.runtime.evaluation.Interval;
import org.eclipse.qvtd.runtime.evaluation.Invocation;
import org.eclipse.qvtd.runtime.evaluation.InvocationFailedException;
import org.eclipse.qvtd.runtime.evaluation.InvocationManager;
import org.eclipse.qvtd.runtime.evaluation.ModeFactory;
import org.eclipse.qvtd.runtime.evaluation.SlotState;
import org.eclipse.qvtd.runtime.evaluation.SlotState.Speculating;
import com.google.common.collect.Iterables;
/**
* AbstractIntervalInternal provides the shared implementation of the intrusive blocked/waiting linked list functionality.
*/
public abstract class AbstractIntervalInternal implements Interval
{
protected final boolean debugBlocks = AbstractTransformer.BLOCKS.isActive();
protected final boolean debugInvocations = AbstractTransformer.INVOCATIONS.isActive();
protected final @NonNull InvocationManager invocationManager;
protected final int intervalIndex;
/**
* Head of a singly linked list element of connections awaiting propagation, null when empty.
*/
private @Nullable AbstractConnection headConnection = null;
/**
* Tail of a singly linked list element of connections awaiting propagation, null when empty.
*/
private @Nullable AbstractConnection tailConnection = null;
/**
* Head of doubly linked list of blocked invocations.
*/
private @NonNull List<@NonNull Connection> connections = new ArrayList<>();
/**
* Head of doubly linked list of speculatable invocations.
*/
private @Nullable AbstractInvocationInternal speculatableInvocations = null;
/**
* Head of doubly linked list of blocked invocations.
*/
private @Nullable AbstractInvocationInternal blockedInvocations = null;
/**
* Head of doubly linked list of unblocked invocations waiting for an execution attempt.
*/
private @Nullable AbstractInvocationInternal waitingInvocations = null;
public AbstractIntervalInternal(@NonNull InvocationManager invocationManager, int intervalIndex) {
this.invocationManager = invocationManager;
this.intervalIndex = intervalIndex;
invocationManager.setInterval(this);
}
@Override
public <R> R accept(@NonNull ExecutionVisitor<R> visitor) {
return visitor.visitInterval(this);
}
@Override
public @NonNull Connection createConnection(@NonNull String name, @NonNull TypeId typeId, boolean isStrict, @NonNull ModeFactory modeFactory) {
Connection connection = modeFactory.createConnection(this, name, typeId, isStrict);
connections.add(connection);
return connection;
}
@Deprecated /* @deprecated provide isIncremental argument */
@Override
public @NonNull Connection createConnection(@NonNull String name, @NonNull TypeId typeId, boolean isStrict) {
return createConnection(name, typeId, isStrict, ModeFactory.NON_INCREMENTAL);
}
@Deprecated /* @deprecated provide isIncremental argument */
@Override
public Connection.@NonNull Incremental createIncrementalConnection(@NonNull String name, @NonNull TypeId typeId, boolean isStrict) {
return (Incremental) createConnection(name, typeId, isStrict, ModeFactory.INCREMENTAL);
}
public synchronized void destroy(@NonNull Invocation invocation) {
assert invocation.getInterval() == this;
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println("destroy " + invocation);
}
AbstractInvocationInternal castInvocation = (AbstractInvocationInternal) invocation;
assert speculatableInvocations != castInvocation;
assert blockedInvocations != castInvocation;
if (waitingInvocations == castInvocation) {
waitingInvocations = castInvocation.next;
if (waitingInvocations == castInvocation) {
waitingInvocations = null;
}
}
AbstractInvocationInternal prevInvocation = castInvocation.prev;
if (prevInvocation != castInvocation) {
AbstractInvocationInternal nextInvocation = castInvocation.next;
prevInvocation.next = nextInvocation;
nextInvocation.prev = prevInvocation;
castInvocation.next = castInvocation;
castInvocation.prev = castInvocation;
}
}
@Override
public void diagnoseWorkLists(@NonNull StringBuilder s) {
for (AbstractInvocationInternal blocked = blockedInvocations; blocked != null; ) {
SlotState blockedBy = blocked.blockedBy;
s.append("\n" + intervalIndex + ": " + blocked + "\n\tblocked by " + blockedBy);
if (blockedBy != null) {
blockedBy.debugUnblock();
}
blocked = blocked.next;
if (blocked == blockedInvocations) {
break;
}
}
for (AbstractInvocationInternal speculatable = speculatableInvocations; speculatable != null; ) {
SlotState blockedBy = speculatable.blockedBy;
s.append("\n" + intervalIndex + ": " + speculatable + "\n\tspeculating " + blockedBy);
if (blockedBy != null) {
// blockedBy.debugUnblock();
}
speculatable = speculatable.next;
if (speculatable == speculatableInvocations) {
break;
}
}
for (AbstractInvocationInternal waiting = waitingInvocations; waiting != null; ) {
SlotState blockedBy = waiting.blockedBy;
s.append("\n" + intervalIndex + ": " + waiting + "\n\twaiting for " + blockedBy);
if (blockedBy != null) {
blockedBy.debugUnblock();
}
waiting = waiting.next;
if (waiting == waitingInvocations) {
break;
}
}
for (AbstractConnection connection = headConnection; connection != null; connection = connection.getNextConnection()) {
s.append("\n" + intervalIndex + ": connection: " + connection);
}
}
@Override
public boolean flush() {
do {
while (headConnection != null) {
AbstractConnection nextConnection2;
synchronized(this) {
nextConnection2 = headConnection;
if (nextConnection2 != null) {
headConnection = nextConnection2.getNextConnection();
if (headConnection == null) {
tailConnection = null;
}
nextConnection2.resetQueued();
}
}
if (nextConnection2 != null) {
nextConnection2.propagate();
}
}
while (waitingInvocations != null) { // Inner loop does the normal work
AbstractInvocationInternal invocation = null;
synchronized (this) {
AbstractInvocationInternal waitingInvocations2 = waitingInvocations;
if (waitingInvocations2 != null) {
invocation = waitingInvocations2;
waitingInvocations = waitingInvocations2.next;
if (waitingInvocations == invocation) {
waitingInvocations = null;
}
invocation.remove();
}
}
if (invocation != null) {
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println("invoke " + invocation);
}
try {
boolean success = invocation.execute();
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println((success ? "done " : "fail-clean ") + invocation);
}
}
catch (InvocationFailedException e) {
queueAsBlockedOrSpeculatable(invocation, e);
}
catch (Exception e) {
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println(("fail-dirty ") + invocation);
}
throw e;
}
}
}
if ((headConnection == null) && (speculatableInvocations != null)) {
AbstractInvocationInternal speculatableInvocation = null;
synchronized (this) {
AbstractInvocationInternal speculatableInvocations2 = speculatableInvocations;
if (speculatableInvocations2 != null) {
speculatableInvocation = speculatableInvocations2;
speculatableInvocations = speculatableInvocations2.next;
if (speculatableInvocations == speculatableInvocation) {
speculatableInvocations = null;
}
speculatableInvocation.remove();
}
}
if (speculatableInvocation != null) {
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println("speculate " + speculatableInvocation);
}
Speculating blockedBy = (Speculating) speculatableInvocation.blockedBy;
assert blockedBy != null;
if (speculate(blockedBy)) {
assert waitingInvocations != null;
// break; // To execute the now-waiting invocation
}
// ?? set speculation failed
}
}
} while ((headConnection != null) || (waitingInvocations != null) || (speculatableInvocations != null));
AbstractInvocationInternal blockedInvocation = blockedInvocations;
if (blockedInvocation == null) {
return true;
}
do {
if (debugBlocks) {
AbstractTransformer.BLOCKS.println("still blocked " + blockedInvocation + " by " + blockedInvocation.blockedBy);
}
blockedInvocation = blockedInvocation.next;
}
while ((blockedInvocation != blockedInvocations) && (blockedInvocation != speculatableInvocations));
return false;
}
@Override
public @NonNull Iterable<@NonNull Connection> getConnections() {
return connections;
}
@Override
public int getIndex() {
return intervalIndex;
}
@Override
public @NonNull InvocationManager getInvocationManager() {
return invocationManager;
}
@Override
public @NonNull String getName() {
return String.valueOf(intervalIndex);
}
@Override
public boolean isFlushed() {
if (tailConnection != null) {
return false;
}
// assert each connection fully propagatred
if (blockedInvocations != null) {
return false;
}
if (speculatableInvocations != null) {
return false;
}
if (waitingInvocations != null) {
return false;
}
return true;
}
public void queue() {
invocationManager.setWorkToDoAt(intervalIndex);
}
@Override
public synchronized void queue(@NonNull Connection connection) {
AbstractConnection connection2 = (AbstractConnection)connection;
AbstractConnection tailConnection2 = tailConnection;
if (tailConnection2 == null) { // Empty list
assert headConnection == null;
assert connection2.getNextConnection() == null;
headConnection = connection2;
}
else { // New element
assert headConnection != null;
if (connection2.getNextConnection() != null) {
return;
}
tailConnection2.setNextConnection(connection2);
}
tailConnection = connection2;
queue();
}
@Override
public synchronized void queue(@NonNull Invocation invocation) {
assert invocation.getInterval() == this;
if (debugBlocks) {
AbstractTransformer.BLOCKS.println("queue " + invocation);
}
AbstractInvocationInternal castInvocation = (AbstractInvocationInternal) invocation;
assert castInvocation.blockedBy == null;
assert speculatableInvocations != castInvocation;
assert blockedInvocations != castInvocation;
AbstractInvocationInternal waitingInvocations2 = waitingInvocations;
if (waitingInvocations2 == null) {
waitingInvocations = castInvocation;
}
else {
castInvocation.insertAfter(waitingInvocations2.prev);
}
queue();
}
private synchronized void queueAsBlockedOrSpeculatable(@NonNull Invocation invocation, @NonNull InvocationFailedException e) {
SlotState slotState = e.slotState;
AbstractInvocationInternal castInvocation = (AbstractInvocationInternal) invocation;
assert castInvocation.blockedBy == null;
assert castInvocation.next == castInvocation;
assert castInvocation.prev == castInvocation;
assert speculatableInvocations != castInvocation;
assert blockedInvocations != castInvocation;
assert waitingInvocations != castInvocation;
castInvocation.blockedBy = slotState;
if (e.isSpeculation) {
AbstractInvocationInternal speculatableInvocations2 = speculatableInvocations;
if (speculatableInvocations2 == null) {
speculatableInvocations = castInvocation;
}
else {
castInvocation.insertAfter(speculatableInvocations2.prev);
}
slotState.block(invocation);
if (debugInvocations) {
AbstractTransformer.BLOCKS.println("block-speculate " + invocation + " for " + slotState);
}
}
else {
AbstractInvocationInternal blockedInvocations2 = blockedInvocations;
if (blockedInvocations2 == null) {
blockedInvocations = castInvocation;
}
else {
castInvocation.insertAfter(blockedInvocations2.prev);
}
slotState.block(invocation);
if (debugInvocations) {
AbstractTransformer.BLOCKS.println("block-not-ready " + invocation + " for " + slotState);
}
}
}
/* private synchronized void speculate(@NonNull Invocation invocation, SlotState.@NonNull Speculating slotState) {
AbstractInvocationInternal castInvocation = (AbstractInvocationInternal) invocation;
assert castInvocation.blockedBy == null;
assert castInvocation.next == castInvocation;
assert castInvocation.prev == castInvocation;
assert speculatableInvocations != castInvocation;
assert blockedInvocations != castInvocation;
assert waitingInvocations != castInvocation;
castInvocation.blockedBy = slotState;
AbstractInvocationInternal blockedInvocations2 = speculatableInvocations;
if (blockedInvocations2 == null) {
speculatableInvocations = castInvocation;
}
else {
castInvocation.insertAfter(blockedInvocations2.prev);
}
slotState.block(invocation);
if (debugInvocations) {
AbstractTransformer.BLOCKS.println("speculate " + invocation + " for " + slotState);
}
} */
/**
* Assess the speculation hypothesis that this speculatable can be satisfied.
* Return null, if it cannot. Return a Set of all the speculatables that can be jointly
* satisfied concurrently with this speculatable.
*
public boolean speculate(SlotState.@NonNull Speculating aSpeculatable) {
Set<SlotState.@NonNull Speculating> inputSpeculatablesClosure = new HashSet<>();
if (!speculate(aSpeculatable, inputSpeculatablesClosure)) {
aSpeculatable.setStatus(Boolean.FALSE);
// aSpeculatable.assigned(null, null, Boolean.FALSE, false);
return false;
}
for (SlotState.@NonNull Speculating speculatable : inputSpeculatablesClosure) {
speculatable.setStatus(Boolean.TRUE);
// speculatable.assigned(null, null, Boolean.TRUE, false);
}
return true;
}
private boolean speculate(SlotState.@NonNull Speculating aSpeculatable, @NonNull Set<SlotState.@NonNull Speculating> inputSpeculatablesClosure) {
Iterable<? extends @NonNull Speculating> inputs = aSpeculatable.getInputs();
if (Iterables.size(inputs) <= 0) {
return false;
}
if (inputSpeculatablesClosure.add(aSpeculatable)) {
for (SlotState.@NonNull Speculating inputSpeculatable : inputs) {
if (!speculate(inputSpeculatable, inputSpeculatablesClosure)) {
return false;
}
}
}
return true;
} */
/**
* Pursue the speculation hypothesis that aSpeculatable can be satisfied.
* Return true if it can after setting the input dependencies successful.
* Return false if it cannot.
*/
public boolean speculate(SlotState.@NonNull Speculating aSpeculatable) {
Set<SlotState.@NonNull Speculating> inputSpeculatablesClosure = new HashSet<>();
Boolean status = speculate(aSpeculatable, inputSpeculatablesClosure);
if (status != null) {
if (status == Boolean.FALSE) {
aSpeculatable.setSpeculated(false);
}
else {
aSpeculatable.setSpeculated(true);
// aSpeculatable.unblock2();
}
// aSpeculatable.assigned(null, null, Boolean.FALSE, false);
return status.booleanValue();
}
for (SlotState.@NonNull Speculating speculatable : inputSpeculatablesClosure) {
speculatable.setSpeculated(true);
// speculatable.setStatus(Boolean.TRUE);
// speculatable.assigned(null, null, Boolean.TRUE, false);
}
// aSpeculatable.setStatus(Boolean.TRUE); // Only assign one, since we must re-execute to do assigns
return true;
}
/**
* Gather the set of indeterminate speculatables that must all be true to successfully speculate aSpeculatable.
*
* Return true, if speculation is (already) successful; all transitive speculatables have true success. Never happens.
* Return false if speculation cannot succeed; a transitive speculatable's status is false.
* Return null if speculation is feasible; all transitive speculatiables are null/true.
*/
private @Nullable Boolean speculate(SlotState.@NonNull Speculating aSpeculatable, @NonNull Set<SlotState.@NonNull Speculating> inputSpeculatablesClosure) {
Boolean status = aSpeculatable.getSpeculationStatus();
if (status != null) { // Already determined?
return status;
}
if (!inputSpeculatablesClosure.add(aSpeculatable)) {
return null;
}
Iterable<@NonNull Speculating> inputs = aSpeculatable.getInputs();
if (Iterables.isEmpty(inputs)) {
return null;
}
boolean alreadySuccessful = true;
for (SlotState.@NonNull Speculating inputSpeculatable : inputs) {
Boolean inputStatus = speculate(inputSpeculatable, inputSpeculatablesClosure);
if (inputStatus != Boolean.TRUE) {
if (inputStatus == Boolean.FALSE) {
return Boolean.FALSE;
}
alreadySuccessful = false;
}
}
return alreadySuccessful ? Boolean.TRUE : null;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("<");
s.append(intervalIndex);
s.append("> ");
int i = 0;
for (AbstractConnection aConnection = headConnection; aConnection != null; aConnection = aConnection.getNextConnection()) {
i++;
if (++i >= 100) {
s.append(">=");
break;
}
}
s.append(i);
s.append(" connections, ");
int k = 0;
AbstractInvocationInternal waitingInvocation = waitingInvocations;
if (waitingInvocation != null) {
k++;
while ((waitingInvocation = waitingInvocation.next) != waitingInvocations) {
if (++k >= 100) {
s.append(">=");
break;
}
}
}
s.append(k);
s.append(" waiting, ");
int m = 0;
AbstractInvocationInternal unspeculatedInvocation = speculatableInvocations;
if (unspeculatedInvocation != null) {
m++;
while ((unspeculatedInvocation = unspeculatedInvocation.next) != speculatableInvocations) {
if (++m >= 100) {
s.append(">=");
break;
}
}
}
s.append(m);
s.append(" speculatable, ");
int j = 0;
AbstractInvocationInternal blockedInvocation = blockedInvocations;
if (blockedInvocation != null) {
j++;
while ((blockedInvocation = blockedInvocation.next) != blockedInvocations) {
if (++j >= 100) {
s.append(">=");
break;
}
}
}
s.append(j);
s.append(" blocked");
return s.toString();
}
@Override
public synchronized void unblock(@NonNull Invocation invocation) {
if (debugInvocations) {
AbstractTransformer.BLOCKS.println("unblock " + invocation);
}
AbstractInvocationInternal castInvocation = (AbstractInvocationInternal) invocation;
assert castInvocation.blockedBy != null;
castInvocation.blockedBy = null;
if (blockedInvocations == castInvocation) {
blockedInvocations = castInvocation.next;
if (blockedInvocations == castInvocation) {
blockedInvocations = null;
}
}
else if (speculatableInvocations == castInvocation) {
speculatableInvocations = castInvocation.next;
if (speculatableInvocations == castInvocation) {
speculatableInvocations = null;
}
}
castInvocation.remove();
AbstractInvocationInternal waitingInvocations2 = waitingInvocations;
if (waitingInvocations2 == null) {
waitingInvocations = castInvocation;
}
else {
castInvocation.insertAfter(waitingInvocations2.prev);
}
queue();
}
}