use of com.ibm.etcd.api.KeyValue in project etcd-java by IBM.
the class RangeCacheTest method testBasics.
@Test
public void testBasics() throws Exception {
KvClient kvc = client.getKvClient();
kvc.delete(bs("tmp/")).asPrefix().sync();
try (RangeCache rc = new RangeCache(client, bs("tmp/"), false)) {
PutResult pr = rc.put(bs("tmp/a"), bs("val1"), 0L);
assertTrue(pr.succ());
assertEquals(bs("val1"), pr.kv().getValue());
rc.start().get(1L, TimeUnit.SECONDS);
assertTrue(rc.delete(bs("tmp/a")));
assertFalse(rc.delete(bs("tmp/c")));
assertTrue(rc.put(bs("tmp/a"), bs("val1"), 0L).succ());
assertEquals(bs("val1"), rc.get(bs("tmp/a")).getValue());
assertEquals(bs("val1"), rc.getRemote(bs("tmp/a")).getValue());
assertEquals(1, Iterators.size(rc.iterator()));
Iterator<KeyValue> it = rc.iterator(), sit = rc.strongIterator();
assertTrue(Iterators.elementsEqual(it, sit));
KvClient directKv = directClient.getKvClient();
directKv.put(bs("tmp/d"), bs("val2")).sync();
Thread.sleep(80L);
assertEquals(bs("val2"), rc.get(bs("tmp/d")).getValue());
directKv.put(bs("tmp/d"), bs("valX")).sync();
Thread.sleep(80L);
assertEquals(bs("valX"), rc.get(bs("tmp/d")).getValue());
directKv.delete(bs("tmp/d")).sync();
Thread.sleep(80L);
assertNull(rc.get(bs("tmp/d")));
}
}
use of com.ibm.etcd.api.KeyValue in project etcd-java by IBM.
the class RangeCache method fullRefreshCache.
// internal method - should not be called while watch is active
protected ListenableFuture<Boolean> fullRefreshCache() {
ListenableFuture<List<RangeResponse>> rrfut;
long seenUpTo = seenUpToRev;
boolean firstTime = (seenUpTo == 0L);
if (firstTime || entries.size() <= 20) {
// TODO *maybe* chunking (for large caches)
ListenableFuture<RangeResponse> rrf = kvClient.get(fromKey).rangeEnd(toKey).backoffRetry(() -> !closed).timeout(300_000L).async();
rrfut = Futures.transform(rrf, (Function<RangeResponse, List<RangeResponse>>) rr -> Collections.singletonList(rr));
} else {
// in case the local cache is large, reduce data transfer by requesting
// just keys, and full key+value only for those modified since seenUpToRev
RangeRequest.Builder rangeReqBld = RangeRequest.newBuilder().setKey(fromKey).setRangeEnd(toKey);
RangeRequest newModsReq = rangeReqBld.setMinModRevision(seenUpTo + 1).build();
RangeRequest otherKeysReq = rangeReqBld.clearMinModRevision().setMaxModRevision(seenUpTo).setKeysOnly(true).build();
ListenableFuture<TxnResponse> trf = kvClient.batch().get(newModsReq).get(otherKeysReq).backoffRetry(() -> !closed).timeout(300_000L).async();
rrfut = Futures.transform(trf, (Function<TxnResponse, List<RangeResponse>>) tr -> tr.getResponsesList().stream().map(r -> r.getResponseRange()).collect(Collectors.toList()));
}
return Futures.transformAsync(rrfut, rrs -> {
if (closed)
throw new CancellationException();
Set<ByteString> snapshot = firstTime ? null : new HashSet<>();
RangeResponse toUpdate = rrs.get(0);
if (toUpdate.getKvsCount() > 0)
for (KeyValue kv : toUpdate.getKvsList()) {
if (!firstTime)
snapshot.add(kv.getKey());
offerUpdate(kv, true);
}
long snapshotRev = toUpdate.getHeader().getRevision();
if (firstTime)
notifyListeners(EventType.INITIALIZED, null, true);
else {
if (rrs.size() > 1)
for (KeyValue kv : rrs.get(1).getKvsList()) {
snapshot.add(kv.getKey());
}
// prune deleted entries
KeyValue.Builder kvBld = null;
for (ByteString key : entries.keySet()) if (!snapshot.contains(key)) {
if (kvBld == null)
kvBld = KeyValue.newBuilder().setVersion(0L).setModRevision(snapshotRev);
offerUpdate(kvBld.setKey(key).build(), true);
}
}
revisionUpdate(snapshotRev);
Watch newWatch = // .prevKv() //TODO TBD
kvClient.watch(fromKey).rangeEnd(toKey).progressNotify().startRevision(snapshotRev + 1).executor(listenerExecutor).start(new StreamObserver<WatchUpdate>() {
@Override
public void onNext(WatchUpdate update) {
List<Event> events = update.getEvents();
int eventCount = events != null ? events.size() : 0;
if (eventCount > 0)
for (Event event : events) {
KeyValue kv = event.getKv();
// event.getPrevKv(); //TBD
switch(event.getType()) {
case DELETE:
if (kv.getVersion() != 0L)
kv = KeyValue.newBuilder(kv).setVersion(0L).clearValue().build();
// fall-thru
case PUT:
offerUpdate(kv, true);
break;
case UNRECOGNIZED:
default:
logger.warn("Unrecognized event for key " + kv.getKey().toStringUtf8());
break;
}
}
revisionUpdate(eventCount == 0 ? update.getHeader().getRevision() - 1L : events.get(eventCount - 1).getKv().getModRevision());
}
@Override
public void onCompleted() {
// should only happen after external close()
if (!closed) {
if (!client.isClosed()) {
logger.error("Watch completed unexpectedly (not closed)");
}
close();
}
}
@Override
public void onError(Throwable t) {
logger.error("Watch failed with exception ", t);
if (t instanceof RevisionCompactedException)
synchronized (RangeCache.this) {
// fail if happens during start, otherwise refresh
if (!closed && startFuture != null && startFuture.isDone()) {
// will renew watch
startFuture = fullRefreshCache();
}
}
}
});
synchronized (this) {
if (closed)
throw new CancellationException();
return watch = newWatch;
}
}, listenerExecutor);
}
use of com.ibm.etcd.api.KeyValue in project etcd-java by IBM.
the class RangeCache method revisionUpdate.
// called only from listenerExecutor context
protected void revisionUpdate(long upToRev) {
if (seenUpToRev >= upToRev)
return;
seenUpToRev = upToRev;
// process deletion queue up to upToRec
if (deletionQueue.isEmpty())
return;
for (Iterator<KeyValue> it = deletionQueue.iterator(); it.hasNext(); ) {
KeyValue kv = it.next();
if (kv.getModRevision() > upToRev)
return;
it.remove();
entries.remove(kv.getKey(), kv);
}
}
use of com.ibm.etcd.api.KeyValue in project etcd-java by IBM.
the class RangeCache method offerUpdate.
/**
* @param keyValue
* @param watchThread if being called from background watch context
* @return the provided value, <i>or a newer one</i>
*/
protected KeyValue offerUpdate(final KeyValue keyValue, boolean watchThread) {
final long modRevision = keyValue.getModRevision();
if (modRevision <= seenUpToRev)
return kvOrNullIfDeleted(keyValue);
final ByteString key = keyValue.getKey();
final boolean isDeleted = isDeleted(keyValue);
// a possible (but unlikely) race with deletions
if (watchThread && !isDeleted) {
// optimized non-deletion path
KeyValue newKv = entries.merge(key, keyValue, (k, v) -> (modRevision > v.getModRevision() ? keyValue : v));
if (newKv == keyValue)
notifyListeners(EventType.UPDATED, keyValue, true);
return kvOrNullIfDeleted(newKv);
}
KeyValue existKv = entries.get(key);
while (true) {
if (existKv != null) {
long existModRevision = existKv.getModRevision();
if (existModRevision >= modRevision)
return kvOrNullIfDeleted(existKv);
KeyValue newKv = entries.computeIfPresent(key, (k, v) -> (existModRevision == v.getModRevision() ? keyValue : v));
if (newKv != keyValue) {
existKv = newKv;
// update failed
continue;
}
// update succeeded
if (isDeleted) {
deletionQueue.add(keyValue);
if (!isDeleted(existKv)) {
// previous value
notifyListeners(EventType.DELETED, existKv, watchThread);
}
return null;
} else {
// added or updated
notifyListeners(EventType.UPDATED, keyValue, false);
return keyValue;
}
}
// here existKv == null
if (modRevision <= seenUpToRev)
return null;
if ((existKv = entries.putIfAbsent(key, keyValue)) == null) {
// update succeeded
if (isDeleted) {
deletionQueue.add(keyValue);
return null;
} else {
notifyListeners(EventType.UPDATED, keyValue, false);
return keyValue;
}
}
}
}
use of com.ibm.etcd.api.KeyValue in project etcd-java by IBM.
the class RangeCache method put.
/**
* Multi-purpose put or delete, returns updated or existing KeyValue
*
* @param key
* @param value new value to put, or null to delete
* @param lease leaseId to associate value with if successful, or 0L for no lease
* @param conditions conditions which must all match to proceed, null or empty for unconditional
* @return PutResult
*/
public PutResult put(ByteString key, ByteString value, long lease, CompareOrBuilder... conditions) {
ListenableFuture<TxnResponse> tf = doPut(key, value, true, lease, conditions);
// TODO -- async option
TxnResponse tr = waitFor(tf, TIMEOUT_MS);
if (tr.getSucceeded()) {
if (value != null) {
KeyValue putValue = tr.getResponses(1).getResponseRange().getKvs(0);
offerUpdate(putValue, false);
return new PutResult(true, putValue);
} else {
offerDelete(key, tr.getHeader().getRevision());
return new PutResult(true, null);
}
} else {
// assert conditions != null && conditions.length > 0;
RangeResponse rr = tr.getResponses(0).getResponseRange();
KeyValue exist = rr.getKvsCount() > 0 ? offerUpdate(rr.getKvs(0), false) : offerDelete(key, tr.getHeader().getRevision());
return new PutResult(false, exist);
}
}
Aggregations