net.sf.saxon.expr.instruct.Bindery Maven / Gradle / Ivy
Show all versions of Saxon-HE Show documentation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.expr.instruct;
import net.sf.saxon.expr.PackageData;
import net.sf.saxon.om.GroundedValue;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.ObjectValue;
/**
* The Bindery class holds information about variables and their values. It is
* used only for global variables: local variables are held in the XPathContext object.
*
* Variables are identified by a {@link GlobalVariable} object. Values will always be of class {@link Sequence}.
*
* The Bindery is a run-time object, but slot numbers within the bindery are allocated to global
* variables at compile time. Because XSLT packages can be separately compiled, each package needs
* to have its own bindery.
*
* From Saxon 9.7 there is one Bindery for the global variables in each separately-compiled package.
* The Bindery is no longer used to hold supplied values of global parameters, but it does hold their
* values after initialization, treating them as normal global variables. Management of dependencies
* among global variables, and checking for dynamic circularities, has been moved to the {@link net.sf.saxon.Controller}.
*/
public final class Bindery {
private GroundedValue[] globals; // values of global variables and parameters
private long[] busy; // set to current thread id while variable is being evaluated
public Bindery(PackageData pack) {
allocateGlobals(pack.getGlobalSlotManager());
}
/**
* Define how many slots are needed for global variables
*
* @param map the SlotManager that keeps track of slot allocation for global variables.
*/
private void allocateGlobals(SlotManager map) {
int n = map.getNumberOfVariables() + 1;
globals = new GroundedValue[n];
busy = new long[n];
for (int i = 0; i < n; i++) {
globals[i] = null;
busy[i] = -1L;
}
}
/**
* Provide a value for a global variable
*
* @param binding identifies the variable
* @param value the value of the variable
*/
public void setGlobalVariable(GlobalVariable binding, GroundedValue value) {
globals[binding.getBinderySlotNumber()] = value;
}
/**
* Set/Unset a flag to indicate that a particular global variable is currently being
* evaluated. Note that this code is not synchronized, so there is no absolute guarantee that
* two threads will not both evaluate the same global variable; however, apart from wasted time,
* it is harmless if they do.
*
* @param binding the global variable in question
* @return true if evaluation of the variable should proceed; false if it is found that the variable has now been
* evaluated in another thread.
* @throws net.sf.saxon.trans.XPathException
* If an attempt is made to set the flag when it is already set, this means
* the definition of the variable is circular.
*/
public boolean setExecuting(GlobalVariable binding)
throws XPathException {
long thisThread = Thread.currentThread().getId();
int slot = binding.getBinderySlotNumber();
long busyThread = busy[slot];
if (busyThread != -1L) {
if (busyThread == thisThread) {
// The global variable is being evaluated in this thread. This shouldn't happen, because
// we have already tested for circularities. If it does happen, however, we fail cleanly.
throw new XPathException.Circularity("Circular definition of variable "
+ binding.getVariableQName().getDisplayName());
} else {
// The global variable is being evaluated in another thread. Give it a chance to finish.
// It could be a circularity, or just an accident of timing. Note that in the latter case,
// we will actually re-evaluate the variable; this normally does no harm, though there is a small
// risk it could lead to problems with the identity of a node changing.
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(20 * i);
} catch (InterruptedException e) {
// no action
}
if (busy[slot] == -1L) {
// evaluation has finished in another thread
return false;
}
}
// We've waited long enough; there could be a deadlock if we wait any longer.
// Continue with the evaluation; whichever thread completes the evaluation first will
// save the value.
return true;
}
}
busy[slot] = thisThread;
return true;
}
/**
* Indicate that a global variable is not currently being evaluated
*
* @param binding the global variable
*/
public void setNotExecuting(GlobalVariable binding) {
int slot = binding.getBinderySlotNumber();
busy[slot] = -1L;
}
/**
* Save the value of a global variable, and mark evaluation as complete.
*
* @param binding the global variable in question
* @param value the value that this thread has obtained by evaluating the variable
* @return the value actually given to the variable. Exceptionally this will be different from the supplied
* value if another thread has evaluated the same global variable concurrently. The returned value should be
* used in preference, to ensure that all threads agree on the value. They could be different if for example
* the variable is initialized using the collection() function.
*/
public synchronized GroundedValue saveGlobalVariableValue(
GlobalVariable binding, GroundedValue value) {
int slot = binding.getBinderySlotNumber();
if (globals[slot] != null) {
// another thread has already evaluated the value
return globals[slot];
} else {
busy[slot] = -1L;
globals[slot] = value;
return value;
}
}
/**
* Get the value of a global variable
*
* @param binding the Binding that establishes the unique instance of the variable
* @return the Value of the variable if defined, null otherwise.
*/
public GroundedValue getGlobalVariableValue(GlobalVariable binding) {
return globals[binding.getBinderySlotNumber()];
}
/**
* Get the value of a global variable whose slot number is known
*
* @param slot the slot number of the required variable
* @return the Value of the variable if defined, null otherwise.
*/
public GroundedValue getGlobalVariable(int slot) {
return globals[slot];
}
/**
* Get all the global variables, as an array. This is provided for use by debuggers.
* The meaning of the result can be determined by use of data held in the corresponding PackageData.
*
* @return the array of global variables.
*/
public GroundedValue[] getGlobalVariables() {
return globals;
}
/**
* A value that can be saved in a global variable to indicate that evaluation failed, and that
* subsequent attempts at evaluation should also fail
*/
public static class FailureValue extends ObjectValue {
public FailureValue(XPathException err) {
super(err);
}
}
}