final long fetchRecord(Version shared) {
        if (Debug.ENABLED)
            Debug.assertion(!_updatedVersions.contains(shared));

        long record = Record.NOT_STORED;
        Object union = shared.getUnion();

        if (union instanceof Descriptor) {
            Descriptor descriptor = (Descriptor) union;
            Session session = descriptor.getSession();
            long records = session.getRecords();

            if (records == Record.NOT_STORED) {
                records = _sessions.fetch(session.getSharedVersion_objectfabric().getUID());
                session.setRecords(records);
            }

            record = fetchRecord(records, descriptor.getId() & 0xff);
        } else {
            byte[] uid = shared.getUID();

            if (uid != null) {
                long records = _sessions.fetch(uid);
                record = fetchRecord(records, Session.UID_OBJECT_ID & 0xff);
            }
        }

        if (Debug.ENABLED)
            Debug.assertion(record != Record.UNKNOWN);

        return record;
    }
    @SuppressWarnings("unchecked")
    @Override
    protected void getAsync(UserTObject object, Object key, FutureWithCallback<Object> future) {
        if (object.getSharedVersion_objectfabric().getUnion() instanceof Record) {
            long record = getOrFetchRecord(object.getSharedVersion_objectfabric());

            if (Record.isStored(record)) {
                BTree tree = BTree.load(getRecordManager(), record, false);
                _writer.write(key);
                byte[] data = new byte[_writer.getOffset()];
                PlatformAdapter.arraycopy(_writer.getBuffer(), 0, data, 0, data.length);
                long id = tree.fetch(data);

                if (id != 0) {
                    data = getRecordManager().fetch(id);
                    Object value = _reader.read(data);

                    _reader.readVersions();

                    Version version = _reader.getOrCreateVersion(object);
                    TKeyedEntry entry = new TKeyedEntry(key, TKeyed.hash(key), value, false);

                    if (Debug.ENABLED)
                        Debug.assertion(value != TKeyedEntry.REMOVAL);

                    ((LazyMapVersion) version).putEntry(key, entry, true, true);
                    _reader.importVersions();
                    future.set(value);
                    return;
                }
            }
        }

        future.set(null);
    }
  protected final void onStarted() {
    if (!_state.compareAndSet(STARTING, IDLE)) {
      if (Debug.ENABLED) Debug.assertion(_state.get() == STARTING_SCHEDULED);

      _state.set(IDLE);
      requestRun();
    }
  }
    final void updateRecord(Version shared, Session session, byte[] uid, long currentRecord, long newRecord, int id) {
        if (Debug.ENABLED)
            Debug.assertion(newRecord != currentRecord);

        int index = _updatedVersions.add(shared);

        if (index == _updatedVersionsRecords.length)
            _updatedVersionsRecords = Utils.extend(_updatedVersionsRecords);

        _updatedVersionsRecords[index] = newRecord;

        // Sessions

        int sessionIndex = _updatedSessions.indexOf(session);
        long records = sessionIndex >= 0 ? _updatedSessionsRecords[sessionIndex] : session.getRecords();
        final long initialRecords = records;

        if (records == Record.NOT_STORED)
            records = _sessions.fetch(uid);

        if (records != Record.NOT_STORED) {
            byte[] data = getRecordManager().fetch(records);

            if (Debug.ENABLED) {
                Debug.assertion(data.length == Session.TOTAL_LENGTH * 8);
                Debug.assertion(Utils.readLong(data, id * 8) == currentRecord);
            }

            Utils.writeLong(data, id * 8, newRecord);
            getRecordManager().update(records, data, 0, data.length);
        } else {
            byte[] data = new byte[Session.TOTAL_LENGTH * 8];
            Utils.writeLong(data, id * 8, newRecord);
            records = getRecordManager().insert(data, 0, data.length);
            _sessions.put(uid, records);
        }

        if (records != initialRecords) {
            index = _updatedSessions.add(session);

            if (index == _updatedSessionsRecords.length)
                _updatedSessionsRecords = Utils.extend(_updatedSessionsRecords);

            _updatedSessionsRecords[index] = records;
        }
    }
  protected final boolean onRunStarting() {
    if (!_state.compareAndSet(SCHEDULED, RUNNING)) {
      if (Debug.ENABLED) Debug.assertion(_state.get() == DISPOSED);

      return false;
    }

    return true;
  }
    @Override
    protected void onVisitingVersions(Visitor visitor, Version shared) {
        super.onVisitingVersions(visitor, shared);

        if (Debug.ENABLED)
            Debug.assertion(_writer.getLimit() == _writer.getBuffer().length);

        _writer.reset();
    }
    final long getOrFetchRecord(Version shared) {
        long record = getRecord(shared);

        if (record == Record.UNKNOWN)
            record = fetchRecord(shared);

        if (Debug.ENABLED)
            Debug.assertion(record != Record.UNKNOWN);

        return record;
    }
    private final long fetchRecord(long records, int id) {
        if (records != Record.NOT_STORED) {
            byte[] session = getRecordManager().fetch(records);

            if (Debug.ENABLED)
                Debug.assertion(session.length == Session.TOTAL_LENGTH * 8);

            return Utils.readLong(session, id * 8);
        }

        return Record.NOT_STORED;
    }
  protected final void onRunEnded() {
    for (; ; ) {
      if (_state.get() == RUNNING) {
        if (_state.compareAndSet(RUNNING, IDLE)) break;
      } else {
        if (Debug.ENABLED) Debug.assertion(_state.get() == RUNNING_SCHEDULED);

        if (_state.compareAndSet(RUNNING_SCHEDULED, SCHEDULED)) {
          startRun();
          break;
        }
      }
    }
  }
  protected final void assertRunning() {
    if (!Debug.ENABLED) throw new RuntimeException();

    int state = _state.get();
    Debug.assertion(state == RUNNING || state == RUNNING_SCHEDULED);
  }
  protected final void assertScheduled() {
    if (!Debug.ENABLED) throw new RuntimeException();

    int state = _state.get();
    Debug.assertion(state == SCHEDULED || state == DISPOSED);
  }
  protected final void assertStarting() {
    if (!Debug.ENABLED) throw new RuntimeException();

    int state = _state.get();
    Debug.assertion(state == STARTING);
  }
        @Override
        protected void checkedRun() {
            if (Debug.THREADS) {
                ThreadAssert.resume(this, false);
                ThreadAssert.exchangeTake(this);
            }

            Exception ex;

            try {
                onRunStarting();
                runTasks();

                //

                while (_inserts.size() > 0)
                    _refs.add(writeObject(_inserts.poll()));

                //

                if (Debug.ENABLED)
                    Debug.assertion(!_writer.interrupted());

                for (;;) {
                    BinaryStore.this.run(_writer);

                    if (!_writer.interrupted())
                        break;

                    _writer.grow();
                }

                _writer.writeSnapshots();

                //

                Exception wrongStore = null;
                int toremove;

                if (_writer.getWrongStore() == null)
                    _jdbm.commit();
                else {
                    _jdbm.rollback();
                    wrongStore = new RuntimeException(Strings.WRONG_STORE + _writer.getWrongStore());
                }

                while (_processed.size() > 0) {
                    Transaction branch = _processed.pollPartOfClear();
                    byte interception = _processedInterceptions[_processed.size()];

                    if (wrongStore == null)
                        Interceptor.ack(branch, interception, true);
                    else
                        Interceptor.nack(branch, null, wrongStore);
                }

                if (wrongStore != null) {
                    if (Debug.ENABLED) {
                        // Assert old records have been restored on rollback
                        for (int i = 0; i < _updatedVersions.size(); i++) {
                            Version shared = _updatedVersions.get(i);
                            Debug.assertion(_jdbm.fetch(shared.getRecord()) != null);
                        }
                    }

                    _updatedVersions.clear();
                    _updatedSessions.clear();
                } else {
                    while (_updatedVersions.size() > 0) {
                        Version shared = _updatedVersions.pollPartOfClear();
                        long record = _updatedVersionsRecords[_updatedVersions.size()];
                        shared.setRecord(record);
                    }

                    while (_updatedSessions.size() > 0) {
                        Session session = _updatedSessions.pollPartOfClear();
                        long records = _updatedSessionsRecords[_updatedSessions.size()];
                        session.setRecords(records);
                    }
                }

                //

                while (_callbacks.size() > 0) {
                    AsyncCallback<byte[]> callback = _callbacks.poll();
                    byte[] ref = _refs.poll();

                    if (wrongStore == null)
                        callback.onSuccess(ref);
                    else
                        callback.onFailure(wrongStore);
                }

                //

                _writer.resetWrongStore();

                if (Debug.THREADS)
                    ThreadAssert.suspend(this);

                setFlushes();
                onRunEnded();
                return;
            } catch (Exception e) {
                ex = e;
            }

            while (_callbacks.size() > 0)
                _callbacks.poll().onFailure(ex);

            // Only in case of exception (can be closing)
            onException(ex);
        }