package org.araqne.logstorage; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.araqne.logstorage.TableLock.Purpose; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TableLockImpl { public static Logger logger = LoggerFactory.getLogger(TableLockImpl.class); static final int EXCLUSIVE = 65535; Semaphore sem = new Semaphore(EXCLUSIVE, true); public static UUID READ_LOCK_UUID = UUID.fromString("ffffffff-ffff-ffff-ffff-ffffffffffff"); private static AtomicLong nextGuid = new AtomicLong(0); private static class PurposeImpl implements Purpose { String name; UUID uuid; int count; public PurposeImpl(String purpose) { name = purpose; uuid = UUID.randomUUID(); count = 1; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((uuid == null) ? 0 : uuid.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; PurposeImpl other = (PurposeImpl) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (uuid == null) { if (other.uuid != null) return false; } else if (!uuid.equals(other.uuid)) return false; return true; } @Override public String getName() { return name; } @Override public UUID getUUID() { return uuid; } @Override public String toString() { return String.format("%s:%d", name, count); } }; String owner; Map<String, PurposeImpl> purposes; private int tid; @Override public String toString() { return String.format("TableLockImpl [owner=%s, purposes=%s]", owner, purposes); } public TableLockImpl(int tableId) { this.tid = tableId; owner = null; purposes = new HashMap<String, PurposeImpl>(); } public int availableShared() { return sem.availablePermits(); } public class ReadLock implements TableLock { long ownerTid = -1; @Override public UUID lock() { sem.acquireUninterruptibly(); ownerTid = Thread.currentThread().getId(); return READ_LOCK_UUID; } @Override public UUID lockInterruptibly() throws InterruptedException { sem.acquire(); ownerTid = Thread.currentThread().getId(); return READ_LOCK_UUID; } @Override public UUID tryLock() { boolean locked = sem.tryAcquire(); if (locked) { ownerTid = Thread.currentThread().getId(); return READ_LOCK_UUID; } else { return null; } } @Override public UUID tryLock(long time, TimeUnit unit) throws InterruptedException { boolean locked = sem.tryAcquire(time, unit); if (locked) { ownerTid = Thread.currentThread().getId(); return READ_LOCK_UUID; } else { return null; } } @Override public void unlock() { if (ownerTid == -1) return; else if (ownerTid != Thread.currentThread().getId()) throw new IllegalThreadStateException("unlocking from the thread which doesn't own the lock"); ownerTid = -1; sem.release(); } @Override public int getTableId() { return tid; } @Override public String getLockOwner() { return TableLockImpl.this.getOwner(); } @Override public Collection<Purpose> getPurposes() { return TableLockImpl.this.getPurposes(); } } public TableLock readLock() { return new ReadLock(); } public class WriteLock implements TableLock { final public String acquierer; final public String purpose; public WriteLock(String owner, String purpose) { this.acquierer = owner; this.purpose = purpose; } @Override public UUID lock() { assert acquierer != null; if (checkReentrant()) { return TableLockImpl.this.getUuid(purpose); } sem.acquireUninterruptibly(EXCLUSIVE); onLockAcquired(); return TableLockImpl.this.getUuid(purpose); } private void onLockAcquired() { if (!acquierer.equals(TableLockImpl.this.owner)) { TableLockImpl.this.owner = acquierer; TableLockImpl.this.purposes.clear(); } TableLockImpl.this.acquirePurpose(purpose); } @Override public UUID lockInterruptibly() throws InterruptedException { if (checkReentrant()) { return TableLockImpl.this.getUuid(purpose); } sem.acquire(EXCLUSIVE); onLockAcquired(); return TableLockImpl.this.getUuid(purpose); } @Override public UUID tryLock() { if (checkReentrant()) { return TableLockImpl.this.getUuid(purpose); } boolean locked = sem.tryAcquire(EXCLUSIVE); if (locked) { onLockAcquired(); return TableLockImpl.this.getUuid(purpose); } else { return null; } } @Override public UUID tryLock(long time, TimeUnit unit) throws InterruptedException { if (checkReentrant()) return TableLockImpl.this.getUuid(purpose); boolean locked = sem.tryAcquire(EXCLUSIVE, time, unit); if (locked) { onLockAcquired(); return TableLockImpl.this.getUuid(purpose); } else { return null; } } private boolean checkReentrant() { synchronized (TableLockImpl.this) { if (acquierer.equals(TableLockImpl.this.owner)) { onLockAcquired(); return true; } return false; } } @Override public void unlock() { if (TableLockImpl.this.owner == null) { return; } else { if (onLockReleased()) sem.release(EXCLUSIVE); } } private boolean onLockReleased() { synchronized (TableLockImpl.this) { if (!acquierer.equals(TableLockImpl.this.owner)) { throw new IllegalMonitorStateException(owner + " cannot unlock this lock now: " + TableLockImpl.this.owner); } releasePurpose(purpose); if (TableLockImpl.this.purposes.size() == 0) { TableLockImpl.this.owner = null; return true; } return false; } } @Override public int getTableId() { return tid; } @Override public String getLockOwner() { return TableLockImpl.this.getOwner(); } @Override public Collection<Purpose> getPurposes() { return TableLockImpl.this.getPurposes(); } } public TableLock writeLock(String owner, String purpose) { if (owner == null) throw new IllegalArgumentException("owner argument cannot be null"); return new WriteLock(owner, purpose); } // being called with mutex public void acquirePurpose(String purpose) { if (purposes.containsKey(purpose)) { purposes.get(purpose).count++; } else { purposes.put(purpose, new PurposeImpl(purpose)); } } // being called with mutex public void releasePurpose(String purpose) { if (purposes.containsKey(purpose)) { PurposeImpl p = purposes.get(purpose); p.count--; if (p.count == 0) purposes.remove(purpose); } else { // ignore // throw new IllegalStateException("doesn't contain the purpose [" + purpose + "]"); } } public UUID getUuid(String purpose) { PurposeImpl p = purposes.get(purpose); if (p != null) return p.uuid; else return null; } public String getOwner() { return owner; } public int getReentrantCount() { return purposes.size(); } public Collection<Purpose> getPurposes() { ArrayList<Purpose> purposeList = new ArrayList<Purpose>(); for (Entry<String, PurposeImpl> p: purposes.entrySet()) { purposeList.add(p.getValue()); } return purposeList; } }