com.thimbleware.jmemcached.CacheImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmemcached-core Show documentation
Show all versions of jmemcached-core Show documentation
Core implementation of the cache daemon
The newest version!
/**
* Copyright 2008 ThimbleWare 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 com.thimbleware.jmemcached;
import com.thimbleware.jmemcached.storage.CacheStorage;
import com.thimbleware.jmemcached.util.BufferUtils;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.*;
/**
* Default implementation of the cache handler, supporting local memory cache elements.
*/
public final class CacheImpl extends AbstractCache implements Cache {
final CacheStorage storage;
final DelayQueue deleteQueue;
private final ScheduledExecutorService scavenger;
/**
* @inheritDoc
*/
public CacheImpl(CacheStorage storage) {
super();
this.storage = storage;
deleteQueue = new DelayQueue();
scavenger = Executors.newScheduledThreadPool(1);
scavenger.scheduleAtFixedRate(new Runnable(){
public void run() {
asyncEventPing();
}
}, 10, 2, TimeUnit.SECONDS);
}
/**
* @inheritDoc
*/
public DeleteResponse delete(Key key, int time) {
boolean removed = false;
// delayed remove
if (time != 0) {
// block the element and schedule a delete; replace its entry with a blocked element
LocalCacheElement placeHolder = new LocalCacheElement(key, 0, 0, 0L);
placeHolder.setData(ChannelBuffers.buffer(0));
placeHolder.block(Now() + (long)time);
storage.replace(key, placeHolder);
// this must go on a queue for processing later...
deleteQueue.add(new DelayedMCElement(placeHolder));
} else
removed = storage.remove(key) != null;
if (removed) return DeleteResponse.DELETED;
else return DeleteResponse.NOT_FOUND;
}
/**
* @inheritDoc
*/
public StoreResponse add(LocalCacheElement e) {
final long origCasUnique = e.getCasUnique();
e.setCasUnique(casCounter.getAndIncrement());
final boolean stored = storage.putIfAbsent(e.getKey(), e) == null;
// we should restore the former cas so that the object isn't left dirty
if (!stored) {
e.setCasUnique(origCasUnique);
}
return stored ? StoreResponse.STORED : StoreResponse.EXISTS;
}
/**
* @inheritDoc
*/
public StoreResponse replace(LocalCacheElement e) {
return storage.replace(e.getKey(), e) != null ? StoreResponse.STORED : StoreResponse.NOT_STORED;
}
/**
* @inheritDoc
*/
public StoreResponse append(LocalCacheElement element) {
LocalCacheElement old = storage.get(element.getKey());
if (old == null || isBlocked(old) || isExpired(old)) {
getMisses.incrementAndGet();
return StoreResponse.NOT_FOUND;
}
else {
return storage.replace(old.getKey(), old, old.append(element)) ? StoreResponse.STORED : StoreResponse.NOT_STORED;
}
}
/**
* @inheritDoc
*/
public StoreResponse prepend(LocalCacheElement element) {
LocalCacheElement old = storage.get(element.getKey());
if (old == null || isBlocked(old) || isExpired(old)) {
getMisses.incrementAndGet();
return StoreResponse.NOT_FOUND;
}
else {
return storage.replace(old.getKey(), old, old.prepend(element)) ? StoreResponse.STORED : StoreResponse.NOT_STORED;
}
}
/**
* @inheritDoc
*/
public StoreResponse set(LocalCacheElement e) {
setCmds.incrementAndGet();//update stats
e.setCasUnique(casCounter.getAndIncrement());
storage.put(e.getKey(), e);
return StoreResponse.STORED;
}
/**
* @inheritDoc
*/
public StoreResponse cas(Long cas_key, LocalCacheElement e) {
// have to get the element
LocalCacheElement element = storage.get(e.getKey());
if (element == null || isBlocked(element)) {
getMisses.incrementAndGet();
return StoreResponse.NOT_FOUND;
}
if (element.getCasUnique() == cas_key) {
// casUnique matches, now set the element
e.setCasUnique(casCounter.getAndIncrement());
if (storage.replace(e.getKey(), element, e)) return StoreResponse.STORED;
else {
getMisses.incrementAndGet();
return StoreResponse.NOT_FOUND;
}
} else {
// cas didn't match; someone else beat us to it
return StoreResponse.EXISTS;
}
}
/**
* @inheritDoc
*/
public Long get_add(Key key, long mod, long initial, long expire) {
LocalCacheElement old = storage.get(key);
if (old == null || isBlocked(old) || isExpired(old)) {
getMisses.incrementAndGet();
if(expire != 0xFFFFFFFFL){
LocalCacheElement newElement = new LocalCacheElement(key);
newElement.setExpire(expire);
newElement.setData(BufferUtils.ltoa(initial));
storage.put(key, newElement);
return initial;
} else {
return null;
}
} else {
LocalCacheElement.IncrDecrResult result = old.add(mod);
LocalCacheElement replacement = result.replace;
//replacement.setExpire(expire); // memcached doesn't do this
return storage.replace(old.getKey(), old, replacement) ? result.oldValue : null;
}
}
protected boolean isBlocked(CacheElement e) {
return e.isBlocked() && e.getBlockedUntil() > Now();
}
protected boolean isExpired(CacheElement e) {
if (e.getExpire() == 0) return false;
return e.getExpire() != 0 && e.getExpire() < Now();
}
/**
* @inheritDoc
*/
public LocalCacheElement[] get(Key ... keys) {
getCmds.incrementAndGet();//updates stats
LocalCacheElement[] elements = new LocalCacheElement[keys.length];
int x = 0;
int hits = 0;
int misses = 0;
for (Key key : keys) {
LocalCacheElement e = storage.get(key);
if (e == null || isExpired(e) || e.isBlocked()) {
misses++;
elements[x] = null;
} else {
hits++;
elements[x] = e;
}
x++;
}
getMisses.addAndGet(misses);
getHits.addAndGet(hits);
return elements;
}
public LocalCacheElement[] gat(long expire, Key[] keys) {
getCmds.incrementAndGet();//updates stats
LocalCacheElement[] elements = new LocalCacheElement[keys.length];
int x = 0;
int hits = 0;
int misses = 0;
for (Key key : keys) {
LocalCacheElement e = storage.get(key);
if (e == null || isExpired(e) || e.isBlocked()) {
misses++;
elements[x] = null;
} else {
hits++;
e.setExpire(expire);
storage.put(key, e);
elements[x] = e;
}
x++;
}
getMisses.addAndGet(misses);
getHits.addAndGet(hits);
return elements;
}
public StoreResponse touch(Key[] keys,
long expiry) {
StoreResponse response = StoreResponse.NOT_FOUND;
int x = 0;
for (Key key : keys) {
LocalCacheElement e = storage.get(key);
if (e == null || isExpired(e) || e.isBlocked()) {
} else {
e.setExpire(expiry);
storage.put(key, e);
response = StoreResponse.STORED;
}
x++;
}
return response;
}
/**
* @inheritDoc
*/
public boolean flush_all() {
return flush_all(0);
}
/**
* @inheritDoc
*/
public boolean flush_all(int expire) {
// TODO implement this, it isn't right... but how to handle efficiently? (don't want to linear scan entire cacheStorage)
storage.clear();
return true;
}
/**
* @inheritDoc
*/
public void close() throws IOException {
scavenger.shutdown();;
storage.close();
}
/**
* @inheritDoc
*/
@Override
protected Set keys() {
return storage.keySet();
}
/**
* @inheritDoc
*/
@Override
public long getCurrentItems() {
return storage.size();
}
/**
* @inheritDoc
*/
@Override
public long getLimitMaxBytes() {
return storage.getMemoryCapacity();
}
/**
* @inheritDoc
*/
@Override
public long getCurrentBytes() {
return storage.getMemoryUsed();
}
/**
* @inheritDoc
*/
@Override
public void asyncEventPing() {
DelayedMCElement toDelete = deleteQueue.poll();
if (toDelete != null) {
storage.remove(toDelete.element.getKey());
}
}
/**
* Delayed key blocks get processed occasionally.
*/
protected static class DelayedMCElement implements Delayed {
private CacheElement element;
public DelayedMCElement(CacheElement element) {
this.element = element;
}
public long getDelay(TimeUnit timeUnit) {
return timeUnit.convert(element.getBlockedUntil() - Now(), TimeUnit.MILLISECONDS);
}
public int compareTo(Delayed delayed) {
if (!(delayed instanceof CacheImpl.DelayedMCElement))
return -1;
else
return element.getKey().toString().compareTo(((DelayedMCElement) delayed).element.getKey().toString());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy