appengineでMemcacheを使ったLockを実装した

id:kazunori_279さんのMemcacheでスピンロックを実装してTask Queue処理結果を集約してみるテスト - スティルハウスの書庫が便利な仕組みなので実装してみた。実はこれを実装するのに前エントリのMemcacheCounterを使っているのでそれもエントリした。

MemcacheLockの使い方

手動でロックする場合
        MemcacheLock lockObj = new MemcacheLock("Counter.countUp");
        lockObj.lock();
        try {
            Counter c = new Dao().from(Counter.class).getFirstResult();
            c.setCount(c.getCount() + 1);
            makePersistentInTx(c);
        } finally {
            lockObj.unLock();
        }
自動でロックする場合

こちらは「ロック取得に失敗しても、何回かはリトライしたい」を実装してあります。
10回ロックを取得して失敗した場合はExceptionをthrowしてロックを解放します。

        new MemcacheLock("Counter.countUp").runSynchronized(new Runnable() {
            public void run() {
                Counter c = new Dao().from(Counter.class).getFirstResult();
                c.setCount(c.getCount() + 1);
                makePersistentInTx(c);
            }
        });

MemcacheLock.class

import java.util.logging.Logger;

import org.slim3.util.ThrowableUtil;

/**
 * Memcacheを使用してロックを実現する
 */
public class MemcacheLock {

    @SuppressWarnings("unused")
    private static final Logger logger = Logger.getLogger(MemcacheLock.class.getName());
    private static final String KEY_PREFIX = "MemcacheLock.";

    @SuppressWarnings("unused")
    private static final long UNLOCKED = 0;
    private static final long LOCKED = 1;

    private MemcacheCounter counter = null;

    private boolean isLocked = false;

    public MemcacheLock(String key) {
        this.counter = MemcacheCounter.getCounter(KEY_PREFIX + key);
    }

    public boolean lock() {
        if (isLocked) {
            return true;
        }
        if (counter.countUp() == LOCKED) {
            isLocked = true;
            return true;
        }
        counter.countDown();
        return false;
    }

    public void unLock() {
        if (isLocked) {
            counter.countDown();
            isLocked = false;
        }
    }

    public void runSynchronized(Runnable runner) {
        for (int i = 0; i < 10; i++) {
            if (!lock()) {
                if (i == 9) {
                    // リトライをあきらめて例外
                    throw new RuntimeException("MemcacheLock.runSynchronized");
                }
                sleep();
                continue;
            }
            try {
                runner.run();
                return;
            } catch (Throwable t) {
                ThrowableUtil.wrapAndThrow(t);
            } finally {
                this.unLock();
            }
        }
    }

    private void sleep() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException t) {
            ThrowableUtil.wrapAndThrow(t);
        }
    }
}

お願い

ソース読んでバグぽいところがあったら教えてください。多数のリクエストを実行してカウントが取れることはテストしたのですが、こういう処理はバグが見えにくくて。。。