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

swim.db.Database Maven / Gradle / Ivy

Go to download

Lock-free document store—optimized for high rate atomic state changes—that concurrently commits and compacts on-disk log-structured storage files without blocking parallel in-memory updates to associative B-tree maps, spatial Q-tree maps, sequential S-tree lists, and singleton U-tree values

There is a newer version: 4.3.15
Show newest version
// Copyright 2015-2020 SWIM.AI inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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 swim.db;

import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import swim.codec.Binary;
import swim.codec.Output;
import swim.codec.OutputBuffer;
import swim.codec.Utf8;
import swim.collections.FingerTrieSeq;
import swim.collections.HashTrieMap;
import swim.concurrent.Cont;
import swim.concurrent.Conts;
import swim.concurrent.Stage;
import swim.concurrent.Sync;
import swim.math.Z2Form;
import swim.structure.Item;
import swim.structure.Num;
import swim.structure.Record;
import swim.structure.Text;
import swim.structure.Value;
import swim.util.Builder;
import swim.util.Cursor;

public class Database {

  final Store store;
  volatile DatabaseDelegate delegate;
  volatile Germ germ;
  volatile int stem;
  volatile int post;
  volatile long version;
  volatile long diffSize;
  volatile long treeSize;
  final Trunk metaTrunk;
  final Trunk seedTrunk;
  volatile HashTrieMap>> trunks;
  volatile HashTrieMap> sprouts;
  volatile int status;

  Database(Store store, int stem, long version) {
    this.store = store;
    this.stem = stem;
    this.version = version;
    this.metaTrunk = new Trunk(this, Record.create(1).attr("meta"), null);
    this.metaTrunk.tree = new BTree(this.metaTrunk, 0, version, true, false);
    this.seedTrunk = new Trunk(this, Record.create(1).attr("seed"), null);
    this.seedTrunk.tree = new BTree(this.seedTrunk, 1, version, true, false);
    this.trunks = HashTrieMap.empty();
    this.sprouts = HashTrieMap.empty();

    final long time = System.currentTimeMillis();
    this.germ = new Germ(stem, version, time, time, this.seedTrunk.tree.rootRef().toValue());
  }

  Database(Store store, Germ germ) {
    this.store = store;
    this.germ = germ;
    this.stem = germ.stem();
    this.version = germ.version() + 1L;
    this.metaTrunk = new Trunk(this, Record.create(1).attr("meta"), null);
    this.metaTrunk.tree = new BTree(this.metaTrunk, 0, version, true, false);
    this.seedTrunk = new Trunk(this, Record.create(1).attr("seed"), null);
    this.seedTrunk.tree = new BTree(this.seedTrunk, germ.seed(), true, false);
    this.trunks = HashTrieMap.empty();
    this.sprouts = HashTrieMap.empty();
  }

  Database(Store store) {
    this(store, 10, 1L);
  }

  public Store store() {
    return this.store;
  }

  public StoreSettings settings() {
    return this.store.settings();
  }

  public Stage stage() {
    return this.store.stage();
  }

  public DatabaseDelegate databaseDelegate() {
    return this.delegate;
  }

  public void setDatabaseDelegate(DatabaseDelegate delegate) {
    this.delegate = delegate;
  }

  public Germ germ() {
    return this.germ;
  }

  public int stem() {
    return this.stem;
  }

  public int post() {
    return this.post;
  }

  public long version() {
    return this.version;
  }

  public long diffSize() {
    return this.diffSize;
  }

  public long treeSize() {
    return this.treeSize;
  }

  public long treeCount() {
    return this.seedTrunk.tree.span();
  }

  public int trunkCount() {
    return this.trunks.size();
  }

  public void openAsync(Cont cont) {
    try {
      do {
        final int oldStatus = this.status;
        if ((oldStatus & (OPENING | OPENED | FAILED)) == 0) {
          final int newStatus = oldStatus | OPENING;
          if (STATUS.compareAndSet(this, oldStatus, newStatus)) {
            try {
              this.store.databaseWillOpen(this);
              this.seedTrunk.tree.loadAsync(new DatabaseOpen(this, cont));
            } catch (Throwable cause) {
              STATUS.set(this, FAILED);
              synchronized (this) {
                notifyAll();
              }
              throw cause;
            }
            break;
          }
        } else {
          if ((oldStatus & OPENING) != 0) {
            synchronized (this) {
              ForkJoinPool.managedBlock(new DatabaseAwait(this));
            }
          }
          if ((this.status & OPENED) != 0) {
            cont.bind(this);
          } else {
            cont.trap(new StoreException("failed to open database"));
          }
          break;
        }
      } while (true);
    } catch (InterruptedException cause) {
      cont.trap(cause);
    } catch (Throwable cause) {
      if (Conts.isNonFatal(cause)) {
        cont.trap(cause);
      } else {
        throw cause;
      }
    }
  }

  public Database open() throws InterruptedException {
    final Sync syncDatabase = new Sync();
    openAsync(syncDatabase);
    return syncDatabase.await(settings().databaseOpenTimeout);
  }

  public void closeAsync(Cont cont) {
    try {
      commitAsync(Commit.closed().andThen(new DatabaseClose(this, cont)));
    } catch (Throwable cause) {
      if (Conts.isNonFatal(cause)) {
        cont.trap(cause);
      } else {
        throw cause;
      }
    }
  }

  public void close() throws InterruptedException {
    final Sync syncDatabase = new Sync();
    closeAsync(syncDatabase);
    syncDatabase.await(settings().databaseCloseTimeout);
  }

  @SuppressWarnings("unchecked")
  public  Trunk openTrunk(Value name, TreeType treeType, boolean isResident, boolean isTransient) {
    Trunk newTrunk = null;
    WeakReference> newTrunkRef = null;
    boolean created = false;
    do {
      final HashTrieMap>> oldTrunks = this.trunks;
      final WeakReference> oldTrunkRef = oldTrunks.get(name);
      final Trunk oldTrunk = oldTrunkRef != null ? oldTrunkRef.get() : null;
      if (oldTrunk == null) {
        if (newTrunk == null) {
          final Seed seed = Seed.fromValue(this.seedTrunk.tree.get(name));
          newTrunk = new Trunk(this, name, null);
          if (seed != null) {
            newTrunk.tree = (T) seed.treeType().treeFromSeed(newTrunk, seed, isResident, isTransient);
          } else if (treeType != null) {
            final int stem = STEM.getAndIncrement(this);
            newTrunk.tree = (T) treeType.emptyTree(newTrunk, stem, this.version, isResident, isTransient);
            created = true;
          } else {
            return null;
          }
          newTrunkRef = new WeakReference>((Trunk) newTrunk);
        }
        final HashTrieMap>> newTrunks = oldTrunks.updated(name, newTrunkRef);
        if (TRUNKS.compareAndSet(this, oldTrunks, newTrunks)) {
          if (created) {
            databaseDidCreateTrunk(newTrunk);
          }
          databaseDidOpenTrunk(newTrunk);
          return newTrunk;
        }
      } else {
        return (Trunk) oldTrunk;
      }
    } while (true);
  }

  public Trunk openBTreeTrunk(Value name, boolean isResident, boolean isTransient) {
    return openTrunk(name, TreeType.BTREE, isResident, isTransient);
  }

  public BTreeMap openBTreeMap(Value name, boolean isResident, boolean isTransient) {
    return new BTreeMap(openBTreeTrunk(name, isResident, isTransient));
  }

  public BTreeMap openBTreeMap(Value name) {
    return new BTreeMap(openBTreeTrunk(name, false, false));
  }

  public BTreeMap openBTreeMap(String name) {
    return new BTreeMap(openBTreeTrunk(Text.from(name), false, false));
  }

  public Trunk openQTreeTrunk(Value name, boolean isResident, boolean isTransient) {
    return openTrunk(name, TreeType.QTREE, isResident, isTransient);
  }

  public  QTreeMap openQTreeMap(Value name, Z2Form shapeForm, boolean isResident, boolean isTransient) {
    return new QTreeMap(openQTreeTrunk(name, isResident, isTransient), shapeForm);
  }

  public  QTreeMap openQTreeMap(Value name, Z2Form shapeForm) {
    return new QTreeMap(openQTreeTrunk(name, false, false), shapeForm);
  }

  public  QTreeMap openQTreeMap(String name, Z2Form shapeForm) {
    return new QTreeMap(openQTreeTrunk(Text.from(name), false, false), shapeForm);
  }

  public Trunk openSTreeTrunk(Value name, boolean isResident, boolean isTransient) {
    return openTrunk(name, TreeType.STREE, isResident, isTransient);
  }

  public STreeList openSTreeList(Value name, boolean isResident, boolean isTransient) {
    return new STreeList(openSTreeTrunk(name, isResident, isTransient));
  }

  public STreeList openSTreeList(Value name) {
    return new STreeList(openSTreeTrunk(name, false, false));
  }

  public STreeList openSTreeList(String name) {
    return new STreeList(openSTreeTrunk(Text.from(name), false, false));
  }

  private Trunk openUTreeTrunk(Value name, boolean isResident, boolean isTransient) {
    return openTrunk(name, TreeType.UTREE, isResident, isTransient);
  }

  public UTreeValue openUTreeValue(Value name) {
    return new UTreeValue(openUTreeTrunk(name, false, false));
  }

  public UTreeValue openUTreeValue(String name) {
    return new UTreeValue(openUTreeTrunk(Text.from(name), false, false));
  }

  public void closeTrunk(Value name) {
    do {
      final HashTrieMap>> oldTrunks = this.trunks;
      final HashTrieMap>> newTrunks = oldTrunks.removed(name);
      if (oldTrunks != newTrunks) {
        if (TRUNKS.compareAndSet(this, oldTrunks, newTrunks)) {
          final WeakReference> oldTrunkRef = oldTrunks.get(name);
          final Trunk oldTrunk = oldTrunkRef.get();
          if (oldTrunk != null) {
            databaseDidCloseTrunk(oldTrunk);
          }
          break;
        }
      } else {
        break;
      }
    } while (true);
  }

  public void removeTree(Value name) {
    closeTrunk(name);
    do {
      final HashTrieMap> oldSprouts = this.sprouts;
      final HashTrieMap> newSprouts = oldSprouts.removed(name);
      if (oldSprouts != newSprouts) {
        if (SPROUTS.compareAndSet(this, oldSprouts, newSprouts)) {
          break;
        }
      } else {
        break;
      }
    } while (true);
    do {
      final long newVersion = this.version;
      final int newPost = this.post;
      final BTree oldSeedTree = this.seedTrunk.tree;
      final BTree newSeedTree = oldSeedTree.removed(name, newVersion, newPost);
      if (oldSeedTree != newSeedTree) {
        if (Trunk.TREE.compareAndSet(this.seedTrunk, oldSeedTree, newSeedTree)) {
          final Value seedValue = oldSeedTree.get(name);
          final Value sizeValue = seedValue.get("root").head().toValue().get("area");
          final long treeSize = sizeValue.longValue(0L);
          TREE_SIZE.addAndGet(this, -treeSize);
          break;
        }
      } else {
        break;
      }
    } while (true);
  }

  public void commitAsync(Commit commit) {
    try {
      if (this.diffSize > 0L) {
        this.store.commitAsync(commit);
      } else {
        commit.bind(null);
      }
    } catch (Throwable cause) {
      if (Conts.isNonFatal(cause)) {
        commit.trap(cause);
      } else {
        throw cause;
      }
    }
  }

  public Chunk commit(Commit commit) throws InterruptedException {
    if (this.diffSize > 0L) {
      final Sync syncChunk = new Sync();
      commitAsync(commit.andThen(syncChunk));
      return syncChunk.await(settings().databaseCommitTimeout);
    } else {
      return null;
    }
  }

  public void compactAsync(Compact compact) {
    try {
      this.store.compactAsync(compact);
    } catch (Throwable cause) {
      if (Conts.isNonFatal(cause)) {
        compact.trap(cause);
      } else {
        throw cause;
      }
    }
  }

  public Store compact(Compact compact) throws InterruptedException {
    final Sync syncStore = new Sync();
    compactAsync(compact.andThen(syncStore));
    return syncStore.await(settings().databaseCompactTimeout);
  }

  public void evacuateAsync(int post, Cont cont) {
    try {
      stage().execute(new DatabaseEvacuate(this, this.post, cont));
    } catch (Throwable cause) {
      if (Conts.isNonFatal(cause)) {
        cont.trap(cause);
      } else {
        throw cause;
      }
    }
  }

  public void evacuate(int post) {
    boolean quiescent;
    do {
      quiescent = true;

      // Evacuate data trees
      final Cursor> seedCursor = this.seedTrunk.tree.cursor();
      while (seedCursor.hasNext()) {
        final Value name = seedCursor.next().getKey();
        do {
          final long version = this.version;
          final Trunk trunk = openTrunk(name, null, false, false);
          final Tree oldTree = trunk.tree;
          final Tree newTree = oldTree.evacuated(post, version);
          if (oldTree != newTree) {
            final int newPost = newTree.post();
            if (trunk.updateTree(oldTree, newTree, version)) {
              if (newPost != 0 && newPost < post) {
                quiescent = false;
                break;
              }
            }
          } else {
            break;
          }
        } while (true);
      }

      // Evacuate seed tree
      do {
        final long version = this.version;
        final BTree oldSeedTree = this.seedTrunk.tree;
        final BTree newSeedTree = oldSeedTree.evacuated(post, version);
        if (oldSeedTree != newSeedTree) {
          final int newPost = newSeedTree.post();
          if (Trunk.TREE.compareAndSet(this.seedTrunk, oldSeedTree, newSeedTree)) {
            if (newPost != 0 && newPost < post) {
              quiescent = false;
              break;
            }
          }
        } else {
          break;
        }
      } while (true);

      // Evacuate meta tree
      do {
        final long version = this.version;
        final BTree oldMetaTree = this.metaTrunk.tree;
        final BTree newMetaTree = oldMetaTree.evacuated(post, version);
        if (oldMetaTree != newMetaTree) {
          final int newPost = newMetaTree.post();
          if (Trunk.TREE.compareAndSet(this.metaTrunk, oldMetaTree, newMetaTree)) {
            if (newPost != 0 && newPost < post) {
              quiescent = false;
              break;
            }
          }
        } else {
          break;
        }
      } while (true);
    } while (!quiescent);
  }

  public void shiftZone() {
    this.store.shiftZone();
  }

  public Chunk commitChunk(Commit commit, int zone, long base) {
    DIFF_SIZE.set(this, 0L);
    final long version = VERSION.getAndIncrement(this);
    final long time = System.currentTimeMillis();

    HashTrieMap> sprouts;
    do {
      sprouts = this.sprouts;
    } while (!SPROUTS.compareAndSet(this, sprouts, HashTrieMap.>empty()));
    if (sprouts.isEmpty()) {
      return null;
    }

    final Builder> commitBuilder = FingerTrieSeq.builder();
    long step = base;

    // Commit data pages
    final Iterator> trunks = sprouts.valueIterator();
    while (trunks.hasNext()) {
      final Trunk trunk = trunks.next();
      do {
        final Tree oldTree = trunk.tree;
        final Tree newTree = oldTree.committed(zone, step, version, time);
        if (oldTree != newTree) {
          if (Trunk.TREE.compareAndSet(trunk, oldTree, newTree)) {
            do {
              final BTree oldSeedTree = this.seedTrunk.tree;
              final BTree newSeedTree = oldSeedTree.updated(trunk.name, newTree.seed().toValue(), version, this.post);
              if (Trunk.TREE.compareAndSet(this.seedTrunk, oldSeedTree, newSeedTree)) {
                break;
              }
            } while (true);
            commitBuilder.add(newTree);
            TREE_SIZE.addAndGet(this, newTree.treeSize() - oldTree.treeSize());
            newTree.treeContext().treeDidCommit(newTree, oldTree);
            step += newTree.diffSize(version);
            break;
          }
        } else {
          break;
        }
      } while (true);
    }

    // Commit seed tree
    BTree seedTree;
    do {
      final BTree oldSeedTree = this.seedTrunk.tree;
      seedTree = oldSeedTree.committed(zone, step, version, time);
      if (Trunk.TREE.compareAndSet(this.seedTrunk, oldSeedTree, seedTree)) {
        step += seedTree.diffSize(version);
        break;
      }
    } while (true);

    // Commit meta tree
    BTree metaTree;
    do {
      final BTree oldMetaTree = this.metaTrunk.tree;
      metaTree = oldMetaTree.updated(Text.from("seed"), seedTree.rootRef().toValue(), version, this.post)
          .updated(Text.from("stem"), Num.from(this.stem), version, this.post)
          .updated(Text.from("time"), Num.from(time), version, this.post)
          .committed(zone, step, version, time);
      if (Trunk.TREE.compareAndSet(this.metaTrunk, oldMetaTree, metaTree)) {
        step += metaTree.diffSize(version);
        break;
      }
    } while (true);

    final FingerTrieSeq commits = commitBuilder.bind();
    final int size = (int) (step - base);
    final OutputBuffer output = Binary.outputBuffer(new byte[size]);
    final Output encoder = Utf8.encodedOutput(output);
    step = base;

    // Write data pages
    for (Tree tree : commits) {
      tree.writeDiff(encoder, version);
      step += tree.diffSize(version);
      assertStep(output, base, step);
    }

    // Write seed pages
    seedTree.writeDiff(encoder, version);
    step += seedTree.diffSize(version);
    assertStep(output, base, step);

    // Write meta pages
    metaTree.writeDiff(encoder, version);
    step += metaTree.diffSize(version);
    assertStep(output, base, step);

    if (output.index() != size) {
      throw new StoreException();
    }

    final Germ germ = new Germ(this.stem, version, this.germ.created(), time,
        seedTree.rootRef().toValue());
    this.germ = germ;
    return new Chunk(this, commit, zone, germ, commits, output.bind());
  }

  private static void assertStep(OutputBuffer output, long base, long step) {
    final int skew = output.index() - (int) (step - base);
    if (skew != 0) {
      final StoreException error = new StoreException("chunk offset skew: " + skew + "; base: " + base + "; step: " + step);
      if (skew < 0) {
        throw error;
      } else {
        error.printStackTrace();
      }
    }
  }

  public void uncommit(long version) {
    final Cursor> seedCursor = this.seedTrunk.tree.cursor();
    while (seedCursor.hasNext()) {
      final Value name = seedCursor.next().getKey();
      do {
        final long newVersion = this.version;
        final Trunk trunk = openTrunk(name, null, false, false);
        final Tree oldTree = trunk.tree;
        final Tree newTree = oldTree.uncommitted(version);
        if (oldTree != newTree) {
          if (trunk.updateTree(oldTree, newTree, newVersion)) {
            break;
          }
        } else {
          break;
        }
      } while (true);
    }
  }

  public Iterator trees() {
    return new DatabaseTreeIterator(this.seedTrunk.tree.cursor());
  }

  public Iterator leafs() {
    return new DatabaseLeafIterator(this, trees());
  }

  void databaseDidOpen() {
    long treeSize = 0L;
    final Cursor> seedCursor = this.seedTrunk.tree.cursor();
    while (seedCursor.hasNext()) {
      final Value seedValue = seedCursor.next().getValue();
      final Value sizeValue = seedValue.get("root").head().toValue().get("area");
      treeSize += sizeValue.longValue(0L);
    }
    TREE_SIZE.set(this, treeSize);
    this.store.databaseDidOpen(this);
  }

  void databaseDidClose() {
    this.store.databaseDidClose(this);
  }

  public void databaseDidCreateTrunk(Trunk trunk) {
    TREE_SIZE.addAndGet(this, trunk.tree.treeSize());
  }

  public void databaseDidOpenTrunk(Trunk trunk) {
    this.store.treeDidOpen(this, trunk.tree);
  }

  Commit databaseWillCommit(Commit commit) {
    return this.store.databaseWillCommit(this, commit);
  }

  void databaseDidCommit(Chunk chunk) {
    this.store.databaseDidCommit(this, chunk);
    final DatabaseDelegate delegate = this.delegate;
    if (delegate != null) {
      delegate.databaseDidCommit(this, chunk);
    }
  }

  void databaseCommitDidFail(Throwable error) {
    this.store.databaseCommitDidFail(this, error);
  }

  Compact databaseWillCompact(Compact compact) {
    return this.store.databaseWillCompact(this, compact);
  }

  void databaseDidCompact(Compact compact) {
    this.store.databaseDidCompact(this, compact);
    final DatabaseDelegate delegate = this.delegate;
    if (delegate != null) {
      delegate.databaseDidCompact(this, compact);
    }
  }

  void databaseCompactDidFail(Throwable error) {
    this.store.databaseCompactDidFail(this, error);
  }

  @SuppressWarnings("unchecked")
  public void databaseDidUpdateTrunk(Trunk trunk, Tree newTree, Tree oldTree, long newVersion) {
    if (!newTree.isTransient()) {
      do {
        final HashTrieMap> oldSprouts = this.sprouts;
        final HashTrieMap> newSprouts = oldSprouts.updated(trunk.name, (Trunk) trunk);
        if (oldSprouts != newSprouts) {
          if (SPROUTS.compareAndSet(this, oldSprouts, newSprouts)) {
            break;
          }
        } else {
          break;
        }
      } while (true);
      int deltaSize = newTree.diffSize(newVersion);
      if (!oldTree.isTransient()) {
        deltaSize -= oldTree.diffSize(newVersion);
      }
      DIFF_SIZE.addAndGet(this, deltaSize);
    }
    TREE_SIZE.addAndGet(this, newTree.treeSize() - oldTree.treeSize());
  }

  public void databaseDidCloseTrunk(Trunk trunk) {
    this.store.treeDidClose(this, trunk.tree);
  }

  static final int OPENING = 1 << 0;
  static final int OPENED = 1 << 1;
  static final int FAILED = 1 << 2;

  static final AtomicIntegerFieldUpdater STEM =
      AtomicIntegerFieldUpdater.newUpdater(Database.class, "stem");

  static final AtomicLongFieldUpdater VERSION =
      AtomicLongFieldUpdater.newUpdater(Database.class, "version");

  static final AtomicIntegerFieldUpdater POST =
      AtomicIntegerFieldUpdater.newUpdater(Database.class, "post");

  static final AtomicLongFieldUpdater DIFF_SIZE =
      AtomicLongFieldUpdater.newUpdater(Database.class, "diffSize");

  static final AtomicLongFieldUpdater TREE_SIZE =
      AtomicLongFieldUpdater.newUpdater(Database.class, "treeSize");

  static final AtomicIntegerFieldUpdater STATUS =
      AtomicIntegerFieldUpdater.newUpdater(Database.class, "status");

  @SuppressWarnings("unchecked")
  static final AtomicReferenceFieldUpdater>>> TRUNKS =
      AtomicReferenceFieldUpdater.newUpdater(Database.class, (Class>>>) (Class) HashTrieMap.class, "trunks");

  @SuppressWarnings("unchecked")
  static final AtomicReferenceFieldUpdater>> SPROUTS =
      AtomicReferenceFieldUpdater.newUpdater(Database.class, (Class>>) (Class) HashTrieMap.class, "sprouts");

}

final class DatabaseOpen implements Cont {

  final Database database;
  final Cont andThen;

  DatabaseOpen(Database database, Cont andThen) {
    this.database = database;
    this.andThen = andThen;
  }

  @Override
  public void bind(Tree tree) {
    try {
      this.database.databaseDidOpen();
      Database.STATUS.set(this.database, Database.OPENED);
      this.andThen.bind(this.database);
      synchronized (this.database) {
        this.database.notifyAll();
      }
    } catch (Throwable cause) {
      if (Conts.isNonFatal(cause)) {
        trap(cause);
      } else {
        throw cause;
      }
    }
  }

  @Override
  public void trap(Throwable error) {
    try {
      Database.STATUS.set(this.database, Database.FAILED);
      this.andThen.trap(error);
    } finally {
      synchronized (this.database) {
        this.database.notifyAll();
      }
    }
  }

}

final class DatabaseAwait implements ForkJoinPool.ManagedBlocker {

  final Database database;

  DatabaseAwait(Database database) {
    this.database = database;
  }

  @Override
  public boolean isReleasable() {
    return (this.database.status & Database.OPENING) == 0;
  }

  @Override
  public boolean block() throws InterruptedException {
    if ((this.database.status & Database.OPENING) != 0) {
      this.database.wait();
    }
    return (this.database.status & Database.OPENING) == 0;
  }

}

final class DatabaseClose implements Cont {

  final Database database;
  final Cont andThen;

  DatabaseClose(Database database, Cont andThen) {
    this.database = database;
    this.andThen = andThen;
  }

  @Override
  public void bind(Chunk chunk) {
    final Database database = this.database;
    try {
      database.databaseDidClose();
      database.stage().call(this.andThen).bind(database);
    } catch (Throwable cause) {
      if (Conts.isNonFatal(cause)) {
        trap(cause);
      } else {
        throw cause;
      }
    }
  }

  @Override
  public void trap(Throwable cause) {
    this.andThen.trap(cause);
  }

}

final class DatabaseEvacuate implements Runnable {

  final Database database;
  final int post;
  final Cont andThen;

  DatabaseEvacuate(Database database, int post, Cont andThen) {
    this.database = database;
    this.post = post;
    this.andThen = andThen;
  }

  @Override
  public void run() {
    try {
      this.database.evacuate(this.post);
      this.andThen.bind(this.database);
    } catch (Throwable cause) {
      if (Conts.isNonFatal(cause)) {
        this.andThen.trap(cause);
      } else {
        throw cause;
      }
    }
  }

}

final class DatabaseTreeIterator implements Iterator {

  final Cursor> seeds;

  DatabaseTreeIterator(Cursor> seeds) {
    this.seeds = seeds;
  }

  @Override
  public boolean hasNext() {
    return this.seeds.hasNext();
  }

  @Override
  public MetaTree next() {
    final Map.Entry slot = this.seeds.next();
    return MetaTree.fromValue(slot.getKey(), slot.getValue());
  }

  @Override
  public void remove() {
    throw new UnsupportedOperationException();
  }

}

final class DatabaseLeafIterator implements Iterator {

  final Database database;
  final Iterator trees;
  Trunk trunk;
  Cursor leafs;

  DatabaseLeafIterator(Database database, Iterator trees) {
    this.database = database;
    this.trees = trees;
  }

  @Override
  public boolean hasNext() {
    do {
      if (this.leafs != null) {
        if (this.leafs.hasNext()) {
          return true;
        } else {
          this.trunk = null;
          this.leafs = null;
        }
      }
      if (this.trees.hasNext()) {
        final MetaTree metaTree = this.trees.next();
        final Value name = metaTree.name;
        final TreeType type = metaTree.type;
        this.trunk = this.database.openTrunk(name, type, false, false);
        this.leafs = this.trunk.tree.cursor();
      } else {
        return false;
      }
    } while (true);
  }

  @Override
  public MetaLeaf next() {
    do {
      if (this.leafs != null) {
        if (this.leafs.hasNext()) {
          final Item leaf = (Item) this.leafs.next();
          final Value name = this.trunk.name;
          final TreeType type = this.trunk.tree.treeType();
          final Value key = leaf.key();
          final Value value = leaf.toValue();
          return new MetaLeaf(name, type, key, value);
        } else {
          this.trunk = null;
          this.leafs = null;
        }
      }
      if (this.trees.hasNext()) {
        final MetaTree metaTree = this.trees.next();
        final Value name = metaTree.name;
        final TreeType type = metaTree.type;
        this.trunk = this.database.openTrunk(name, type, false, false);
        this.leafs = this.trunk.tree.cursor();
      } else {
        throw new NoSuchElementException();
      }
    } while (true);
  }

  @Override
  public void remove() {
    throw new UnsupportedOperationException();
  }

}