package org.infinispan.persistence.sifs; import java.util.concurrent.ConcurrentMap; import org.infinispan.commons.util.CollectionFactory; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * Table holding the entry positions in log before these are persisted to the index. * * @author Radim Vansa <rvansa@redhat.com> */ public class TemporaryTable { private static Log log = LogFactory.getLog(TemporaryTable.class); private static boolean trace = log.isTraceEnabled(); private ConcurrentMap<Object, Entry> table; public TemporaryTable(int capacity) { table = CollectionFactory.makeConcurrentMap(capacity); } public void set(Object key, int file, int offset) { for (; ; ) { Entry entry = table.putIfAbsent(key, new Entry(file, offset, false)); if (entry != null) { synchronized (entry) { if (entry.isRemoved()) { continue; } else if (entry.isLocked()) { try { if (trace) { log.tracef("Waiting for lock on %s", key); } entry.wait(); continue; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException("Unexpected interruption!", e); } } entry.update(file, offset); return; } } else { return; } } } public LockedEntry replaceOrLock(Object key, int file, int offset, int prevFile, int prevOffset) { for (;;) { Entry lockedEntry = new Entry(-1, -1, true); Entry entry = table.putIfAbsent(key, lockedEntry); if (entry != null) { synchronized (entry) { if (entry.isRemoved()) { continue; } if (entry.isLocked()) { throw new IllegalStateException("Unexpected double locking"); } if (entry.getFile() == prevFile && entry.getOffset() == prevOffset) { entry.update(file, offset); } return null; } } else { return lockedEntry; } } } public void updateAndUnlock(LockedEntry lockedEntry, int file, int offset) { Entry entry = (Entry) lockedEntry; synchronized (entry) { entry.file = file; entry.offset = offset; entry.locked = false; entry.notifyAll(); } } public void removeAndUnlock(LockedEntry lockedEntry, Object key) { Entry entry = (Entry) lockedEntry; synchronized (entry) { table.remove(key); entry.setRemoved(true); entry.notifyAll(); } } public EntryPosition get(Object key) { Entry entry = table.get(key); if (entry == null) { return null; } synchronized (entry) { // when the entry is locked, it means that it was not in the table before // and it's protected against writes, but its value is not up-to-date if (entry.isLocked()) { return null; } return new EntryPosition(entry.getFile(), entry.getOffset()); } } public void clear() { table.clear(); } public void removeConditionally(Object key, int file, int offset) { Entry tempEntry = table.get(key); if (tempEntry != null) { synchronized (tempEntry) { if (tempEntry.isLocked()) { return; } if (tempEntry.getFile() == file && tempEntry.getOffset() == offset) { table.remove(key, tempEntry); tempEntry.setRemoved(true); } } } } private static class Entry extends LockedEntry { private int file; private int offset; private boolean locked; private boolean removed = false; Entry(int file, int offset, boolean locked) { this.file = file; this.offset = offset; this.locked = locked; } public int getFile() { return file; } public int getOffset() { return offset; } public void update(int currentFile, int currentOffset) { this.file = currentFile; this.offset = currentOffset; } public boolean isRemoved() { return removed; } public void setRemoved(boolean removed) { this.removed = removed; } public boolean isLocked() { return locked; } } public abstract static class LockedEntry { private LockedEntry() {} } }