@Override
public void purge(Executor executor, PurgeListener purgeListener) {
try {
semaphore.acquire();
} catch (InterruptedException e) {
throw new PersistenceException("Cannot acquire semaphore: CacheStore is likely stopped.", e);
}
try {
if (stopped) {
throw new PersistenceException("LevelDB is stopped");
}
// Drain queue and update expiry tree
List<ExpiryEntry> entries = new ArrayList<ExpiryEntry>();
expiryEntryQueue.drainTo(entries);
for (ExpiryEntry entry : entries) {
final byte[] expiryBytes = marshall(entry.expiry);
final byte[] keyBytes = marshall(entry.key);
final byte[] existingBytes = expiredDb.get(expiryBytes);
if (existingBytes != null) {
// in the case of collision make the key a List ...
final Object existing = unmarshall(existingBytes);
if (existing instanceof List) {
((List<Object>) existing).add(entry.key);
expiredDb.put(expiryBytes, marshall(existing));
} else {
List<Object> al = new ArrayList<Object>(2);
al.add(existing);
al.add(entry.key);
expiredDb.put(expiryBytes, marshall(al));
}
} else {
expiredDb.put(expiryBytes, keyBytes);
}
}
List<Long> times = new ArrayList<Long>();
List<Object> keys = new ArrayList<Object>();
DBIterator it = expiredDb.iterator(new ReadOptions().fillCache(false));
long now = ctx.getTimeService().wallClockTime();
try {
for (it.seekToFirst(); it.hasNext();) {
Map.Entry<byte[], byte[]> entry = it.next();
Long time = (Long) unmarshall(entry.getKey());
if (time > now)
break;
times.add(time);
Object key = unmarshall(entry.getValue());
if (key instanceof List)
keys.addAll((List<?>) key);
else
keys.add(key);
}
for (Long time : times) {
expiredDb.delete(marshall(time));
}
if (!keys.isEmpty())
log.debugf("purge (up to) %d entries", keys.size());
int count = 0;
for (Object key : keys) {
byte[] keyBytes = marshall(key);
byte[] b = db.get(keyBytes);
if (b == null)
continue;
MarshalledEntry me = (MarshalledEntry) ctx.getMarshaller().objectFromByteBuffer(b);
// TODO race condition: the entry could be updated between the get and delete!
if (me.getMetadata() != null && me.getMetadata().isExpired(now)) {
// somewhat inefficient to FIND then REMOVE...
db.delete(keyBytes);
purgeListener.entryPurged(key);
count++;
}
}
if (count != 0)
log.debugf("purged %d entries", count);
} catch (Exception e) {
throw new PersistenceException(e);
} finally {
try {
it.close();
} catch (IOException e) {
log.warnUnableToCloseDbIterator(e);
}
}
} catch (PersistenceException e) {
throw e;
} catch (Exception e) {
throw new PersistenceException(e);
} finally {
semaphore.release();
}
}