package org.multiverse.stms.gamma;
import org.multiverse.api.BackoffPolicy;
import org.multiverse.api.DefaultBackoffPolicy;
import org.multiverse.api.IsolationLevel;
import org.multiverse.api.LockMode;
import org.multiverse.api.PropagationLevel;
import org.multiverse.api.TraceLevel;
import org.multiverse.api.lifecycle.TxnListener;
import java.util.LinkedList;
import java.util.List;
import static java.lang.String.format;
/**
* Contains the default configuration for all transactions created by the GammaStm. With the TxnFactoryBuilder,
* this behavior can be overridden.
* <p/>
* Once the GammaStm has been created, changes on this structure are ignored because the content of this configuration
* is copied.
*
* @author Peter Veentjer.
*/
@SuppressWarnings({"ClassWithTooManyFields"})
public final class GammaStmConfig {
/**
* Contains the permanent TxnListeners that should always be executed. Null references are not allowed.
*/
public List<TxnListener> permanentListeners = new LinkedList<TxnListener>();
/**
* The default propagation level for all transactions executed by the Stm.
*/
public PropagationLevel propagationLevel = PropagationLevel.Requires;
/**
* The default isolation level for all transactions executed by the GammaStm.
*/
public IsolationLevel isolationLevel = IsolationLevel.Snapshot;
/**
* The default isolation level for all reads. Putting this to a level higher than LockMode.None has the same
* effect as putting the isolationLevel to IsolationLevel.Serialized although the former approach will be
* pessimistic and the later be optimistic.
*/
public LockMode readLockMode = LockMode.None;
/**
* The default isolation level for all writes. For a write also a read is acquired, so the highest one wins.
* <p/>
* Putting it the LockMode.Write gives the same behavior as you have with Oracle. Reads are still allowed (even
* though a WriteLock is allowed) only other transactions are not able to acquire a lock on it (whatever the lock
* should be).
*/
public LockMode writeLockMode = LockMode.None;
/**
* The default behavior if blocking transactions are allowed.
*/
public boolean blockingAllowed = true;
/**
* The default behavior for blocking transactions if they are allowed to be interrupted.
*/
public boolean interruptible = false;
/**
* The default timeout for a transaction if it blocks. A Long.MAX_VALUE indicates that there is no timeout.
*/
public long timeoutNs = Long.MAX_VALUE;
/**
* The default readonly behavior. Setting this to true would be quite useless.
*/
public boolean readonly = false;
/**
* The default number of spins a transaction is allowed for a read/write/commit if something is locked.
*/
public int spinCount = 64;
/**
* The default behavior for writing 'dirty' changes for an update transaction. If it is set to true, a change needs to
* be made. If there is no change, it will not be written (and essentially be seen as a read).
*/
public boolean dirtyCheck = true;
/**
* The minimal size for the internal array for a variable length transaction. A variable length transaction internally uses
* an array to store its content and when the transaction grows, the array will grow accordingly.
*/
public int minimalVariableLengthTransactionSize = 4;
/**
* If reads should be tracked by the transaction (this is something else than (semi)visible reads). The advantage of readtracking
* is there is less overhead once it has been read, but the disadvantage is that the transaction needs to track more changes.
*/
public boolean trackReads = true;
/**
* The default number of retries a transaction is allowed to do if a transaction fails for a read/write conflict. The GammaStm also
* uses a speculative configuration mechanism that can consume some, so setting it to a very low value in combination with
* speculativeConfigEnabled is not recommended.
*/
public int maxRetries = 1000;
/**
* The GammaStm makes use of a speculative mechanism to select the optimal transaction settings/implementation for executing a transactional closure.
* An TxnExecutor will start cheap and grow to use more expensive transaction implementations (see the Lean/Fat transactions) and
* use the automatic retry mechanism of the STM to rescue itself from a situation where the speculation was incorrect. Setting it to false
* reduces the number of unwanted conflicts (once the TxnExecutor has learned it will not make the same mistakes again, so you only need
* to pay the price of speculation in the beginning) at the cost of overhead.
*/
public boolean speculativeConfigEnabled = true;
/**
* The maximum size size of a fixed length transaction. A fixed length transaction is very cheap compared to a variable length, but
* the big problem of the fixed length is that it needs to do a full transaction scan to see if the desired data is there. So there is
* a point where the full scan gets more expensive (O(N) vs O(log n)) expensive.
* <p/>
* Atm there has not been put much research in finding the optimal size and it could differ from machine to machine.
* <p/>
* It also is important that the fixed length transactions are able to put a frequently read ref in a hot spot, making the overhead
* of searching lower.
*/
public int maxFixedLengthTransactionSize = 20;
/**
* If a transaction fails for a read/write conflict it should not hammer the system by trying again and running in the same conflict
* The default backoff policy helps to back threads of by sleeping/yielding.
*/
public BackoffPolicy backoffPolicy = DefaultBackoffPolicy.MAX_100_MS;
/**
* With the trace level you have control if you get output of transactions executing. It helps with debugging. If the
* org.multiverse.MultiverseConstants.___TracingEnabled is not set to true, this value is ignored and the whole profiling
* stuff is removed by the JIT since it effectively has become dead code.
*/
public TraceLevel traceLevel = TraceLevel.None;
/**
* If control flow errors should be reused. Normally exception reuse would be a very very very bad thing to do. But if they are
* used to regulate control flow, they can be thrown thousands of times a second and this puts a lot of pressure on the gc. The most
* expensive part is building the StackTrace.
* <p/>
* For more info about the control flow errors see the subclasses of the {@link org.multiverse.api.exceptions.ControlFlowError} like
* the {@link org.multiverse.api.exceptions.ReadWriteConflict}, {@link org.multiverse.api.exceptions.RetryError} and the
* {@link org.multiverse.api.exceptions.SpeculativeConfigurationError}.
*/
public boolean controlFlowErrorsReused = true;
/**
* Should only be used internally to select fat instead of lean transactions. Normally the speculative configuration takes care of this
* but for testing purposes you want to control it manually.
*/
public boolean isFat = false;
/**
* The maximum size of a transaction that is allowed to do a full conflict scan instead of arrive/depart operations. Arrive/depart
* is more expensive since it increased the pressure on refs that a full conflict scan for short transactions, but at a certain length
* of the transaction only needing to do a full conflict scan when the global conflict counter increases, becomes cheaper.
*/
public int maximumPoorMansConflictScanLength = 20;
/**
* The number of times a transactional object is only read before becoming readbiased. The advantage of a readBiased transactional object
* is that you don't need to arrive/depart (richmans conflict scan), but the disadvantage is that it could cause transactions to do
* full conflict scans even though they are not required.
*/
public int readBiasedThreshold = 128;
/**
* Checks if the configuration is valid.
*
* @throws IllegalStateException if the configuration isn't valid.
*/
public void validate() {
if (timeoutNs < 0) {
throw new IllegalStateException(
"[GammaStmConfig] timeoutNs can't be smaller than 0, " +
"timeoutNs was " + timeoutNs);
}
if (readBiasedThreshold < 0) {
throw new IllegalStateException(
"[GammaStmConfig] readBiasedThreshold can't be smaller than 0, " +
"readBiasedThreshold was " + readBiasedThreshold);
}
if (readBiasedThreshold > 1023) {
throw new IllegalStateException(
"[GammaStmConfig] readBiasedThreshold can't be larger than 1023, " +
"readBiasedThreshold was " + readBiasedThreshold);
}
if (maximumPoorMansConflictScanLength < 0) {
throw new IllegalStateException(
"[GammaStmConfig] maximumFullConflictScanSize can't be smaller than 0, " +
"maximumFullConflictScanSize was " + maxFixedLengthTransactionSize);
}
if (readLockMode == null) {
throw new IllegalStateException(
"[GammaStmConfig] readLockMode can't be null");
}
if (writeLockMode == null) {
throw new IllegalStateException(
"[GammaStmConfig] writeLockMode can't be null");
}
if (writeLockMode.asInt() < readLockMode.asInt()) {
throw new IllegalStateException(
format("[GammaStmConfig] writeLockMode [%s] can't be lower than readLockMode [%s]",
writeLockMode, readLockMode));
}
if (isolationLevel == null) {
throw new IllegalStateException("[GammaStmConfig] isolationLevel can't be null");
}
if (isolationLevel.doesAllowWriteSkew() && !trackReads) {
throw new IllegalStateException(
format("[GammaStmConfig] isolation level '%s' can't be combined with readtracking" +
"is false since it is needed to prevent the writeskew problem", isolationLevel));
}
if (blockingAllowed && !trackReads) {
throw new IllegalStateException(
"[GammaStmConfig] blockingAllowed can't be true if trackReads is false");
}
if (spinCount < 0) {
throw new IllegalStateException(
"[GammaStmConfig] spinCount can't be smaller than 0, but was " + spinCount);
}
if (minimalVariableLengthTransactionSize < 1) {
throw new IllegalStateException(
"[GammaStmConfig] minimalVariableLengthTransactionSize can't be smaller than 1, but was "
+ minimalVariableLengthTransactionSize);
}
if (maxRetries < 0) {
throw new IllegalStateException(
"[GammaStmConfig] maxRetries can't be smaller than 0, but was " + maxRetries);
}
if (maxFixedLengthTransactionSize < 2) {
throw new IllegalStateException(
"[GammaStmConfig] maxFixedLengthTransactionSize can't be smaller than 2, but was "
+ maxFixedLengthTransactionSize);
}
if (backoffPolicy == null) {
throw new IllegalStateException("[GammaStmConfig] backoffPolicy can't be null");
}
if (traceLevel == null) {
throw new IllegalStateException("[GammaStmConfig] traceLevel can't be null");
}
if (propagationLevel == null) {
throw new IllegalStateException("[GammaStmConfig] propagationLevel can't be null");
}
if (permanentListeners == null) {
throw new IllegalStateException("[GammaStmConfig] permanentListeners can't be null");
}
for (int k = 0; k < permanentListeners.size(); k++) {
TxnListener listener = permanentListeners.get(k);
if (listener == null) {
throw new IllegalStateException(
format("[GammaStmConfig] permanentListener at index %s can't be null", k));
}
}
}
}