All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.objectfabric.TransactionManager Maven / Gradle / Ivy

The newest version!
/**
 * This file is part of ObjectFabric (http://objectfabric.org).
 *
 * ObjectFabric is licensed under the Apache License, Version 2.0, the terms
 * of which may be found at http://www.apache.org/licenses/LICENSE-2.0.html.
 * 
 * Copyright ObjectFabric Inc.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

package org.objectfabric;

import java.util.Arrays;

import org.objectfabric.Resource.Block;
import org.objectfabric.TObject.Transaction;
import org.objectfabric.TObject.Version;
import org.objectfabric.Workspace.Granularity;

final class TransactionManager {

    /*
     * TODO extend AtomicReferenceArray and move all CAS here with indexes far from each
     * other to avoid same cache line conflicts.
     */

    public static final int OBJECTS_VERSIONS_INDEX = 0;

    private static final int MAP_QUEUE_SIZE_THRESHOLD = 80;

    private static final int MAP_QUEUE_SIZE_MAXIMUM = 100;

    public static final TObject.Version[] OBJECTS_VERSIONS = new TObject.Version[0];

    private TransactionManager() {
        throw new IllegalStateException();
    }

    public static boolean commit(Transaction transaction) {
        if (Debug.ENABLED) {
            transaction.checkInvariants();
            transaction.assertUpdates();
        }

        Transaction parent = transaction.parent();
        boolean result;

        if (parent == null) {
            Workspace workspace = transaction.workspace();

            if (transaction.getWrites() != null) {
                if (Debug.ENABLED) {
                    Debug.assertion(!transaction.noWrites());
                    Helper.instance().checkVersionsHaveWrites(transaction.getWrites());
                }

                result = validateAndUpdateSnapshot(transaction);
            } else {
                Snapshot snapshot = transaction.getSnapshot();

                if (Debug.ENABLED)
                    workspace.releaseSnapshotDebug(snapshot, transaction, "TransactionManager.commit (No write)");

                workspace.releaseSnapshot(snapshot);
                transaction.reset();

                if (Debug.THREADS)
                    ThreadAssert.removePrivate(transaction);

                workspace.recycle(transaction);
                result = true;
            }

            if (Stats.ENABLED) {
                if (result)
                    Stats.Instance.Committed.incrementAndGet();
            }
        } else {
            parent.mergePrivate(transaction);

            if (Debug.THREADS)
                ThreadAssert.removePrivate(transaction);

            result = true;
        }

        return result;
    }

    private static boolean validateAndUpdateSnapshot(Transaction transaction) {
        final Workspace workspace = transaction.workspace();
        final Version[] reads = transaction.getReads();
        final Version[] writes = transaction.getWrites();
        final VersionMap map = transaction.getOrCreateVersionMap();

        Snapshot snapshot;
        Snapshot lastValidated = transaction.getSnapshot();
        int lastValidatedAddedWatchers = 1;
        List lastValidatedAddedWatchersList = null;

        if (Debug.ENABLED) {
            Debug.assertion(transaction.parent() == null);
            lastValidatedAddedWatchersList = new List();
            lastValidatedAddedWatchersList.add(transaction);
        }

        final Snapshot newSnapshot = new Snapshot();
        int retryCount = 0;

        for (;;) {
            int addedWatchers = 0;
            int temporaryWatchers = 0;
            List watchersList = null;
            List temporaryWatchersList = null;
            boolean preventsMerge;

            for (;;) {
                for (;;) {
                    snapshot = workspace.snapshot();

                    if (snapshot.last() == VersionMap.CLOSING)
                        throw new ClosedException();

                    if (Stats.ENABLED) {
                        for (;;) {
                            long max = Stats.Instance.MaxMapCount.get();

                            if (snapshot.getVersionMaps().length <= max)
                                break;

                            if (Stats.Instance.MaxMapCount.compareAndSet(max, snapshot.getVersionMaps().length))
                                break;
                        }
                    }

                    /*
                     * TODO also measure size of each map, do not allow merging of maps if
                     * over size of a commit using same stuff as for source splitting.
                     */

                    if (snapshot.getVersionMaps().length >= MAP_QUEUE_SIZE_MAXIMUM)
                        workspace.onOverloaded();
                    else {
                        if (snapshot.getVersionMaps().length >= MAP_QUEUE_SIZE_THRESHOLD)
                            workspace.onOverloading();

                        break;
                    }
                }

                if (Debug.ENABLED) {
                    watchersList = new List();
                    temporaryWatchersList = new List();
                }

                Extension[] sourceSplitters = null;
                Extension[] noMerge = null;

                if (snapshot.slowChanging() != null) {
                    sourceSplitters = snapshot.slowChanging().Splitters;

                    if (workspace.granularity() == Granularity.ALL)
                        noMerge = snapshot.slowChanging().Extensions;
                }

                preventsMerge = noMerge != null;

                if (sourceSplitters != null) {
                    if (snapshot.last().isRemote() != map.isRemote()) {
                        addedWatchers += sourceSplitters.length;

                        if (Debug.ENABLED)
                            for (Extension sourceSplitter : sourceSplitters)
                                watchersList.add(sourceSplitter);
                    }
                }

                if (noMerge != null) {
                    addedWatchers += noMerge.length;

                    if (Debug.ENABLED)
                        for (Extension extension : noMerge)
                            watchersList.add(extension);
                }

                /*
                 * If snapshot has changed since transaction's start or last validation,
                 * add at least one watcher to prevent last map from merging with future
                 * ones during validation.
                 */
                if (snapshot != lastValidated && addedWatchers == 0) {
                    addedWatchers++;
                    temporaryWatchers++;

                    if (Debug.ENABLED) {
                        watchersList.add(transaction);
                        temporaryWatchersList.add(transaction);
                    }
                }

                if (addedWatchers == 0 || snapshot.last().tryToAddWatchers(addedWatchers)) {
                    if (Debug.ENABLED)
                        for (int i = 0; i < watchersList.size(); i++)
                            Helper.instance().addWatcher(snapshot.last(), watchersList.get(i), snapshot, "validateAndUpdateSnapshot");

                    /*
                     * If we use transaction's snapshot, keep it until after commit
                     * instead of after validation only.
                     */
                    if (snapshot == transaction.getSnapshot()) {
                        addedWatchers++;
                        temporaryWatchers++;
                        lastValidatedAddedWatchers--;

                        if (Debug.ENABLED) {
                            watchersList.add(transaction);
                            temporaryWatchersList.add(transaction);

                            if (lastValidatedAddedWatchersList == null)
                                throw new AssertionError();

                            boolean r = lastValidatedAddedWatchersList.remove(transaction);
                            Debug.assertion(r);
                        }
                    }

                    break;
                }
            }

            Runnable delayedMerge = null;

            {
                boolean conflict = false;

                if (snapshot.last() != lastValidated.last()) {
                    if (reads != null) {
                        int start = Helper.getIndex(snapshot, lastValidated.last()) + 1;
                        int stop = snapshot.writes().length;

                        if (!Helper.validateCheckOnce(map, reads, snapshot, start, stop)) {
                            if (Debug.ENABLED)
                                Debug.assertion(!Helper.instance().AssertNoConflict);

                            conflict = true;
                        }
                    }
                }

                if (lastValidatedAddedWatchers != 0) {
                    if (Debug.ENABLED) {
                        if (lastValidatedAddedWatchersList == null)
                            throw new AssertionError();

                        for (int i = 0; i < lastValidatedAddedWatchersList.size(); i++)
                            Helper.instance().removeWatcher(lastValidated.last(), lastValidatedAddedWatchersList.get(i), snapshot, "Validation done");
                    }

                    delayedMerge = lastValidated.last().removeWatchers(workspace, lastValidatedAddedWatchers, true, lastValidated);
                }

                if (Debug.ENABLED) {
                    if (Helper.instance().ConflictAlways)
                        conflict = true;

                    if (Helper.instance().ConflictRandom && Platform.get().randomBoolean())
                        conflict = true;
                }

                if (conflict) {
                    if (delayedMerge != null)
                        delayedMerge.run();

                    dispose(transaction, addedWatchers, watchersList, snapshot);
                    return false;
                }
            }

            //

            newSnapshot.setVersionMaps(Helper.addVersionMap(snapshot.getVersionMaps(), map));

            if (reads != null && snapshot.getReads() == null) {
                Version[][] newReads = new Version[newSnapshot.getVersionMaps().length][];
                newReads[newReads.length - 1] = reads;
                newSnapshot.setReads(newReads);
            } else if (reads != null || snapshot.getReads() != null)
                newSnapshot.setReads(Helper.addVersions(snapshot.getReads(), reads));
            else
                newSnapshot.setReads(null);

            newSnapshot.writes(Helper.addVersions(snapshot.writes(), writes));
            newSnapshot.slowChanging(snapshot.slowChanging());

            if (preventsMerge) {
                transaction.setSnapshot(newSnapshot);
                transaction.setPublicSnapshotVersions(newSnapshot.writes());
                transaction.setWrites(null);

                if (Debug.ENABLED) {
                    Helper.instance().disableEqualsOrHashCheck();
                    Helper.instance().getAllowExistingReadsOrWrites().put(transaction, transaction);
                    Helper.instance().enableEqualsOrHashCheck();
                }

                transaction.addFlags(TransactionBase.FLAG_IGNORE_READS | TransactionBase.FLAG_NO_WRITES | TransactionBase.FLAG_COMMITTED);
                map.setTransaction(transaction);
            } else if (!map.isRemote())
                map.setTransaction(null);

            for (int i = writes.length - 1; i >= 0; i--)
                if (writes[i] != null)
                    writes[i].onPublishing(newSnapshot, snapshot.writes().length);

            if (Debug.THREADS) {
                ThreadAssert.share(map);

                if (preventsMerge)
                    ThreadAssert.share(transaction);
            }

            /*
             * Try to publish the new snapshot. TODO: do merge on same CAS, only if fails
             * use delayedMerge.
             */
            if (!workspace.casSnapshot(snapshot, newSnapshot)) {
                if (Debug.THREADS) {
                    if (preventsMerge) {
                        ThreadAssert.removeShared(transaction);
                        ThreadAssert.addPrivate(transaction);
                    }

                    ThreadAssert.removeShared(map);
                    ThreadAssert.addPrivate(map);
                }

                if (delayedMerge != null)
                    delayedMerge.run();

                if (Debug.ENABLED) {
                    retryCount++;

                    if (Stats.ENABLED) {
                        Stats.Instance.ValidationRetries.incrementAndGet();

                        for (;;) {
                            long max = Stats.Instance.ValidationRetriesMax.get();

                            if (retryCount <= max)
                                break;

                            if (Stats.Instance.ValidationRetriesMax.compareAndSet(max, retryCount))
                                break;
                        }
                    }

                    Helper.instance().setRetryCount(retryCount);
                }

                /*
                 * Store added watchers so that they are removed after next validation.
                 */
                lastValidated = snapshot;
                lastValidatedAddedWatchers = addedWatchers;

                if (Debug.ENABLED)
                    lastValidatedAddedWatchersList = watchersList;
            } else {
                /*
                 * Success!
                 */
                if (Debug.ENABLED) {
                    /*
                     * Assert all watched maps are still there.
                     */
                    for (int i = 0; i < snapshot.getVersionMaps().length; i++) {
                        VersionMap current = snapshot.getVersionMaps()[i];

                        if (!Arrays.asList(newSnapshot.getVersionMaps()).contains(current))
                            Debug.assertion(current.getWatchers() == 0);
                    }

                    if (Debug.THREADS)
                        for (int i = 0; i < newSnapshot.getVersionMaps().length; i++)
                            Debug.assertion(!ThreadAssert.isPrivate(newSnapshot.getVersionMaps()[i]));

                    Helper.instance().removeValidated(map);
                }

                if (delayedMerge != null)
                    delayedMerge.run();

                if (Debug.ENABLED) {
                    if (temporaryWatchersList == null)
                        throw new AssertionError();

                    for (int i = 0; i < temporaryWatchersList.size(); i++)
                        Helper.instance().removeWatcher(snapshot.last(), temporaryWatchersList.get(i), snapshot, "Success!");

                    Helper.instance().removeWatcher(snapshot.last(), workspace, snapshot, "No more last");
                }

                snapshot.last().removeWatchers(workspace, temporaryWatchers + 1, false, snapshot);

                if (snapshot.slowChanging() != null) {
                    Actor[] actors = snapshot.slowChanging().Actors;

                    if (actors != null)
                        for (int i = actors.length - 1; i >= 0; i--)
                            actors[i].requestRun();
                }

                if (!preventsMerge) {
                    transaction.reset();

                    if (Debug.THREADS)
                        ThreadAssert.removePrivate(transaction);

                    workspace.recycle(transaction);
                }

                return true;
            }
        }
    }

    // Merge with abort
    private static void dispose(Transaction transaction, int addedWatchers, List watchersList, Snapshot snapshot) {
        if (Debug.ENABLED)
            Debug.assertion(transaction.parent() == null);

        Workspace workspace = transaction.workspace();

        if (addedWatchers != 0) {
            snapshot.last().removeWatchers(workspace, addedWatchers, false, snapshot);

            if (Debug.ENABLED)
                for (int i = 0; i < watchersList.size(); i++)
                    Helper.instance().removeWatcher(snapshot.last(), watchersList.get(i), snapshot, "Validation failed");
        }

        if (Debug.ENABLED) {
            if (transaction.getVersionMap() != null)
                Helper.instance().removeWatcher(transaction.getVersionMap(), workspace, snapshot, "dispose uncommitted map");

            Helper.instance().removeValidated(transaction.getVersionMap());
        }

        if (Stats.ENABLED)
            Stats.Instance.Aborted.incrementAndGet();

        if (Debug.THREADS)
            if (transaction.getVersionMap() != null)
                ThreadAssert.removePrivate(transaction.getVersionMap());

        transaction.reset();

        if (Debug.THREADS)
            ThreadAssert.removePrivate(transaction);

        workspace.recycle(transaction);
    }

    /**
     * Commits versions without validation.
     */
    public static boolean load(Workspace workspace, Version[] versions, VersionMap expect, Resource resource, List acks) {
        if (Debug.ENABLED)
            Debug.assertion(versions.length > 0);

        final VersionMap map = new VersionMap();
        map.setRemote();
        final Snapshot newSnapshot = new Snapshot();
        int retryCount = 0;

        for (;;) {
            Snapshot snapshot;
            int lastAckWatchers;
            List lastAckWatchersList;

            Extension[] sourceSplitters = null;
            Extension[] noMerge = null;
            boolean preventsMerge;

            for (;;) {
                snapshot = workspace.snapshot();

                if (snapshot.last() != expect) {
                    if (Debug.THREADS)
                        ThreadAssert.removePrivate(map);

                    return false;
                }

                if (snapshot.slowChanging() != null) {
                    sourceSplitters = snapshot.slowChanging().Splitters;

                    if (workspace.granularity() == Granularity.ALL)
                        noMerge = snapshot.slowChanging().Extensions;
                }

                preventsMerge = noMerge != null;
                lastAckWatchers = 0;
                lastAckWatchersList = Debug.ENABLED ? new List() : null;

                if (sourceSplitters != null) {
                    if (!snapshot.last().isRemote()) {
                        lastAckWatchers += sourceSplitters.length;

                        if (Debug.ENABLED)
                            for (Extension sourceSplitter : sourceSplitters)
                                lastAckWatchersList.add(sourceSplitter);
                    }
                }

                if (noMerge != null) {
                    lastAckWatchers += noMerge.length;

                    if (Debug.ENABLED)
                        for (Extension extension : noMerge)
                            lastAckWatchersList.add(extension);
                }

                if (lastAckWatchers == 0 || snapshot.last().tryToAddWatchers(lastAckWatchers)) {
                    if (Debug.ENABLED)
                        for (int i = 0; i < lastAckWatchersList.size(); i++)
                            Helper.instance().addWatcher(snapshot.last(), lastAckWatchersList.get(i), snapshot, "load ackWatchersList");

                    break;
                }
            }

            if (Debug.ENABLED)
                Helper.instance().addWatcher(map, workspace, snapshot, "TransactionManager::load");

            //

            newSnapshot.setVersionMaps(Helper.addVersionMap(snapshot.getVersionMaps(), map));

            if (snapshot.getReads() != null)
                newSnapshot.setReads(Helper.addVersions(snapshot.getReads(), null));
            else
                newSnapshot.setReads(null);

            newSnapshot.writes(Helper.addVersions(snapshot.writes(), versions));
            newSnapshot.slowChanging(snapshot.slowChanging());

            Transaction transaction = null;

            if (preventsMerge) {
                transaction = workspace.getOrCreateTransaction();
                transaction.setSnapshot(newSnapshot);
                transaction.setPublicSnapshotVersions(newSnapshot.writes());

                if (Debug.ENABLED)
                    Helper.instance().getAllowExistingReadsOrWrites().put(transaction, transaction);

                transaction.addFlags(TransactionBase.FLAG_IGNORE_READS | TransactionBase.FLAG_NO_WRITES | TransactionBase.FLAG_COMMITTED);
                map.setTransaction(transaction);
                transaction.setVersionMap(map);
            }

            // TODO: find out if publishing is still needed
            for (int i = versions.length - 1; i >= 0; i--) {
                if (versions[i] != null) {
                    int todo_merge_methods;
                    versions[i].onDeserialized(newSnapshot);
                    versions[i].onPublishing(newSnapshot, snapshot.getVersionMaps().length);
                }
            }

            if (Debug.THREADS) {
                ThreadAssert.share(map);

                if (preventsMerge)
                    ThreadAssert.share(transaction);
            }

            if (!workspace.casSnapshot(snapshot, newSnapshot)) {
                if (Debug.THREADS) {
                    if (preventsMerge) {
                        ThreadAssert.removeShared(transaction);
                        ThreadAssert.addPrivate(transaction);
                    }

                    ThreadAssert.removeShared(map);
                    ThreadAssert.addPrivate(map);
                }

                if (Debug.ENABLED) {
                    retryCount++;

                    if (Stats.ENABLED) {
                        Stats.Instance.ValidationRetries.incrementAndGet();
                        Stats.max(Stats.Instance.ValidationRetriesMax, retryCount);
                    }

                    Helper.instance().setRetryCount(retryCount);
                    Helper.instance().removeWatcher(map, workspace, snapshot, "TransactionManager::load");

                    for (int i = 0; i < lastAckWatchersList.size(); i++)
                        Helper.instance().removeWatcher(snapshot.last(), lastAckWatchersList.get(i), snapshot, "TransactionManager::load ackWatchersList");
                }

                if (lastAckWatchers != 0)
                    snapshot.last().removeWatchers(workspace, lastAckWatchers, false, null);

                if (Debug.ENABLED)
                    Debug.assertion((transaction != null) == preventsMerge);

                if (transaction != null) {
                    transaction.reset();
                    workspace.recycle(transaction);
                    map.setTransaction(null);
                    transaction = null;
                }
            } else {
                resource.onLoad(newSnapshot, acks);

                if (Debug.ENABLED)
                    Helper.instance().removeWatcher(snapshot.last(), workspace, snapshot, "TransactionManager::load old");

                snapshot.last().removeWatchers(workspace, 1, false, null);

                //

                if (snapshot.slowChanging() != null) {
                    Actor[] actors = snapshot.slowChanging().Actors;

                    if (actors != null)
                        for (int i = actors.length - 1; i >= 0; i--)
                            actors[i].requestRun();
                }

                return true;
            }
        }
    }

    public static void abort(Transaction transaction) {
        if (Debug.ENABLED)
            transaction.checkInvariants();

        Workspace workspace = transaction.workspace();
        Transaction parent = transaction.parent();

        if (parent == null) {
            if (Debug.ENABLED) {
                if (transaction.getVersionMap() != null)
                    Helper.instance().removeWatcher(transaction.getVersionMap(), workspace, transaction.getSnapshot(), "TransactionManager::load abort");

                workspace.releaseSnapshotDebug(transaction.getSnapshot(), transaction, "TransactionManager::load abort");
            }

            workspace.releaseSnapshot(transaction.getSnapshot());
            transaction.reset();

            if (Debug.THREADS)
                ThreadAssert.removePrivate(transaction);

            workspace.recycle(transaction);

            if (Stats.ENABLED)
                Stats.Instance.Aborted.incrementAndGet();
        } else {
            if (Debug.ENABLED)
                if (transaction.getVersionMap() != null)
                    Helper.instance().removeWatcher(transaction.getVersionMap(), parent, transaction.getSnapshot(), "TransactionManager::load abort private");

            if (Debug.THREADS)
                ThreadAssert.removePrivate(transaction);
        }
    }
}