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

org.jsimpledb.kv.util.KeyWatchTracker Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package org.jsimpledb.kv.util;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.ListenableFuture;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;

import org.jsimpledb.kv.KeyRange;
import org.jsimpledb.kv.mvcc.Mutations;
import org.jsimpledb.util.ByteUtil;

/**
 * Utility class used to track key watches.
 *
 * 

* For space efficiency, this class does not track original values. Therefore, spurious notifications can result. * *

* Instances are thread safe. * * @see org.jsimpledb.kv.KVTransaction#watchKey */ public class KeyWatchTracker { private final TreeMap keyInfos = new TreeMap<>(ByteUtil.COMPARATOR); /** * Register a new watch. * *

* If the returned {@link java.util.concurrent.Future} is {@link java.util.concurrent.Future#cancel cancel()}'ed, * the watch is automatically unregistered. * * @param key the key to watch * @return a {@link ListenableFuture} that returns {@code key} when the value associated with {@code key} is modified * @throws IllegalArgumentException if {@code key} is null */ public synchronized ListenableFuture register(byte[] key) { Preconditions.checkArgument(key != null, "null key"); KeyInfo keyInfo = this.keyInfos.get(key); if (keyInfo == null) { key = key.clone(); // avoid external mutation of key contents keyInfo = new KeyInfo(key); this.keyInfos.put(key, keyInfo); } return keyInfo.add(); } /** * Count the number of keys being watched. * *

* Note that the same key can be watched more than once, so this only counts keys being watched, not total watches. * * @return number of keys being watched */ public synchronized int getNumKeysWatched() { return this.keyInfos.size(); } /** * Trigger all watches associated with the given key. * * @param key the key that has been modified * @return true if any watches were triggered, otherwise false * @throws IllegalArgumentException if {@code key} is null */ public boolean trigger(byte[] key) { Preconditions.checkArgument(key != null, "null key"); final KeyInfo keyInfo; synchronized (this) { keyInfo = KeyWatchTracker.this.keyInfos.remove(key); } if (keyInfo != null) { keyInfo.succeed(); return true; } return false; } /** * Trigger all watches associated with the given keys. * * @param keys keys that have been modified * @return true if any watches were triggered, otherwise false * @throws IllegalArgumentException if {@code keys} is null */ public boolean trigger(Iterable keys) { Preconditions.checkArgument(keys != null, "null keys"); final ArrayList triggerList = new ArrayList<>(); synchronized (this) { for (byte[] key : keys) { final KeyInfo keyInfo = KeyWatchTracker.this.keyInfos.remove(key); if (keyInfo != null) triggerList.add(keyInfo); } } for (KeyInfo keyInfo : triggerList) keyInfo.succeed(); return !triggerList.isEmpty(); } /** * Trigger all watches associated with keys in the given range. * * @param range range of keys that have been modified * @return true if any watches were triggered, otherwise false * @throws IllegalArgumentException if {@code range} is null */ public boolean trigger(KeyRange range) { Preconditions.checkArgument(range != null, "null range"); final ArrayList triggerList = new ArrayList<>(); synchronized (this) { final NavigableMap subMap = range.getMax() != null ? this.keyInfos.subMap(range.getMin(), true, range.getMax(), false) : this.keyInfos.tailMap(range.getMin(), true); triggerList.addAll(subMap.values()); subMap.clear(); } for (KeyInfo keyInfo : triggerList) keyInfo.succeed(); return !triggerList.isEmpty(); } /** * Trigger all watches associated with the given mutations. * * @param mutations mutations * @return true if any watches were triggered, otherwise false * @throws IllegalArgumentException if {@code mutations} is null */ public boolean trigger(Mutations mutations) { Preconditions.checkArgument(mutations != null, "null mutations"); boolean result = false; for (KeyRange range : mutations.getRemoveRanges()) result |= this.trigger(range); final EntryKeyFunction keyFunction = new EntryKeyFunction(); result |= this.trigger(this.applyEntryKeyFunction(mutations.getPutPairs(), keyFunction)); result |= this.trigger(this.applyEntryKeyFunction(mutations.getAdjustPairs(), keyFunction)); return result; } // This method exists solely to bind the generic type parameters private > Iterable applyEntryKeyFunction(Iterable i, EntryKeyFunction keyFunction) { return Iterables.transform(i, keyFunction); } /** * Trigger all watches. */ public synchronized void triggerAll() { for (KeyInfo keyInfo : this.keyInfos.values()) keyInfo.succeed(); this.keyInfos.clear(); } /** * Discard all outstanding key watches and fail them with the given exception. * * @param e failing exception */ public synchronized void failAll(Exception e) { for (KeyInfo keyInfo : this.keyInfos.values()) keyInfo.fail(e); this.keyInfos.clear(); } // KeyInfo private class KeyInfo { private final byte[] key; private final HashSet futures = new HashSet<>(1); KeyInfo(byte[] key) { assert key != null; this.key = key; } KeyFuture add() { assert Thread.holdsLock(KeyWatchTracker.this); final KeyFuture future = new KeyFuture(this); this.futures.add(future); return future; } void cancel(KeyFuture future) { synchronized (KeyWatchTracker.this) { if (this.futures.remove(future) && this.futures.isEmpty()) KeyWatchTracker.this.keyInfos.remove(this.key); } } void succeed() { for (KeyFuture future : this.futures) future.set(null); } void fail(Exception e) { for (KeyFuture future : this.futures) future.setException(e); } } // KeyFuture private static class KeyFuture extends AbstractFuture { private final KeyInfo keyInfo; KeyFuture(KeyInfo keyInfo) { assert keyInfo != null; this.keyInfo = keyInfo; } @Override protected boolean set(Void value) { return super.set(value); } @Override protected boolean setException(Throwable t) { return super.setException(t); } @Override public boolean cancel(boolean mayInterruptIfRunning) { this.keyInfo.cancel(this); return super.cancel(mayInterruptIfRunning); } } // EntryKeyFunction private static class EntryKeyFunction implements Function, byte[]> { @Override public byte[] apply(Map.Entry entry) { return entry.getKey(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy