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

nstream.persist.kv.state.MapChangesets Maven / Gradle / Ivy

There is a newer version: 4.12.20
Show newest version
// Copyright 2015-2024 Nstream, 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 nstream.persist.kv.state;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.UnaryOperator;
import nstream.persist.api.PersistenceException;
import swim.collections.HashTrieMap;
import swim.structure.Value;

/**
 * Keeps track of changes to all map states in the {@link StoreState}.
 */
public class MapChangesets {

  @SuppressWarnings("unchecked")
  private static final AtomicReferenceFieldUpdater>> CHANGESETS =
      AtomicReferenceFieldUpdater.newUpdater(MapChangesets.class, (Class>>) (Class) HashTrieMap.class, "changesets");
  private volatile HashTrieMap> changesets;

  public MapChangesets() {
    this.changesets = HashTrieMap.empty();
  }

  private static long withChangeset(AtomicReference changeset, UnaryOperator op) {
    MapChangeset oldChangeset, newChangeset;
    do {
      oldChangeset = changeset.get();
      //If the change-set is null, it has been consumed so we return Long.MIN_VALUE to indicate they outer loop should retry.
      if (oldChangeset == null) {
        return Long.MIN_VALUE;
      }
      newChangeset = op.apply(oldChangeset);
    } while (!changeset.compareAndSet(oldChangeset, newChangeset));
    return newChangeset.getCommitSize() - oldChangeset.getCommitSize();
  }

  private static long updateChangeset(AtomicReference changeset, long epoch, Value key, long valueSize) {
    return withChangeset(changeset, (oldChangeSet) -> oldChangeSet.added(epoch, key, valueSize));
  }

  private static long clearChangeset(AtomicReference changeset, long epoch) {
    return withChangeset(changeset, (oldChangeSet) -> oldChangeSet.cleared(epoch));
  }

  private static HashMap toMap(HashTrieMap> oldChangesets) throws PersistenceException {
    if (oldChangesets == null) {
      throw new PersistenceException("Already closed.");
    }
    final var consumed = new HashMap(oldChangesets.size());
    for (Map.Entry> entry : oldChangesets.entrySet()) {
      consumed.put(entry.getKey(), entry.getValue().getAndSet(null));
    }
    return consumed;
  }

  /**
   * Consume all change-sets in the map.
   *
   * @return Map associating database IDs with change-sets.
   */
  public HashMap consume() throws PersistenceException {
    final var oldChangesets = CHANGESETS.getAndSet(this, HashTrieMap.empty());
    return toMap(oldChangesets);
  }

  /**
   * Consume all changesets and do not accept any more.
   *
   * @return Map associating database IDs with change-sets.
   */
  public HashMap close() throws PersistenceException {
    final var oldChangesets = CHANGESETS.getAndSet(this, null);
    return toMap(oldChangesets);
  }

  /**
   * Add an update to a changeset in the map.
   *
   * @param id        The database ID of the lane.
   * @param epoch     The monotonic epoch (for the lane).
   * @param key       The key of the entry that was updated.
   * @param valueSize The size value that was updated in bytes ({@code 0} for a removal).
   * @return The change in the total size of all changes in the map.
   */
  public long update(long id, long epoch, Value key, long valueSize) throws PersistenceException {
    long delta;
    boolean done;
    do {
      final var oldChangesets = CHANGESETS.get(this);
      if (oldChangesets == null) {
        throw new PersistenceException("Already closed.");
      }
      final var changeset = oldChangesets.get(id);
      if (changeset == null) {
        final var newChangeset = new MapChangeset(epoch).added(epoch, key, valueSize);
        final var newChangesets = oldChangesets.updated(id, new AtomicReference<>(newChangeset));
        done = CHANGESETS.compareAndSet(this, oldChangesets, newChangesets);
        delta = newChangeset.getCommitSize();
      } else {
        //This will return Long.MIN_VALUE if the changeset has been removed since we read it which means we need to retry.
        delta = updateChangeset(changeset, epoch, key, valueSize);
        done = delta != Long.MIN_VALUE;
      }
    } while (!done);
    return delta;
  }

  /**
   * Clear the change-set for a lane.
   *
   * @param id    The database ID of the lane.
   * @param epoch The monotonic epoch (for the lane).
   * @return The change in the total size of all changes in the map.
   */
  public long clear(long id, long epoch) throws PersistenceException {
    long delta;
    boolean done;
    do {
      final var oldChangesets = CHANGESETS.get(this);
      if (oldChangesets == null) {
        throw new PersistenceException("Already closed.");
      }
      final var changeset = oldChangesets.get(id);
      if (changeset == null) {
        final var newChangeset = new MapChangeset(epoch).cleared(epoch);
        final var newChangesets = oldChangesets.updated(id, new AtomicReference<>(newChangeset));
        done = CHANGESETS.compareAndSet(this, oldChangesets, newChangesets);
        delta = newChangeset.getCommitSize();
      } else {
        //This will return Long.MIN_VALUE if the changeset has been removed since we read it which means we need to retry.
        delta = clearChangeset(changeset, epoch);
        done = delta != Long.MIN_VALUE;
      }
    } while (!done);
    return delta;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy