package eu.fbk.knowledgestore.runtime; import java.util.concurrent.Semaphore; import javax.annotation.Nullable; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; public final class Synchronizer { public static final int WX = -1; public static final int CX = -2; private final int maxConcurrentTx; private final int maxWriteTx; // can assume special values 0, WX or CX private final Semaphore mainSemaphore; @Nullable private final Semaphore writeSemaphore; @Nullable private final Semaphore commitSemaphore; private Synchronizer(final int maxConcurrentTx, final int maxWriteTx) { Preconditions.checkArgument(maxConcurrentTx > 0); Preconditions.checkArgument(maxWriteTx >= 0 && maxWriteTx <= maxConcurrentTx || maxWriteTx == WX || maxWriteTx == CX); this.maxConcurrentTx = maxConcurrentTx; this.maxWriteTx = maxWriteTx; this.mainSemaphore = new Semaphore(maxConcurrentTx, true); this.writeSemaphore = maxWriteTx != 0 ? new Semaphore(Math.max(maxWriteTx, 1), true) : null; this.commitSemaphore = maxWriteTx == CX ? new Semaphore(1, true) : null; } public static Synchronizer create(final String spec) { final int index = spec.indexOf(':'); final String first = (index <= 0 ? spec : spec.substring(0, index)).trim(); final String second = index <= 0 ? null : spec.substring(index + 1).trim().toUpperCase(); try { final int maxConcurrentTx = Integer.parseInt(first); final int maxWriteTx = second == null ? 0 : second.equals("WX") ? WX : // second.equals("CX") ? CX : Integer.parseInt(second); return new Synchronizer(maxConcurrentTx, maxWriteTx); } catch (final Throwable ex) { throw new IllegalArgumentException( "Illegal synchronizer specification '" + spec + "'", ex); } } public static Synchronizer create(final int maxConcurrentTx, final int maxWriteTx) { return new Synchronizer(maxConcurrentTx, maxWriteTx); } public void beginExclusive() { try { this.mainSemaphore.acquire(this.maxConcurrentTx); } catch (final Throwable ex) { Throwables.propagate(ex); } } public void endExclusive() { this.mainSemaphore.release(this.maxConcurrentTx); } public void beginTransaction(final boolean readOnly) { boolean writeAcquired = false; try { if (readOnly) { this.mainSemaphore.acquire(); } else if (this.writeSemaphore == null) { throw new IllegalStateException("Write transactions have been disabled"); } else { this.writeSemaphore.acquire(); writeAcquired = true; this.mainSemaphore.acquire(this.maxWriteTx == WX ? this.maxConcurrentTx : 1); } } catch (final Throwable ex) { if (writeAcquired) { this.writeSemaphore.release(); } Throwables.propagate(ex); } } public void endTransaction(final boolean readOnly) { if (readOnly) { this.mainSemaphore.release(1); } else { this.mainSemaphore.release(this.maxWriteTx == WX ? this.maxConcurrentTx : 1); this.writeSemaphore.release(); } } public void beginCommit() { if (this.maxWriteTx == CX) { boolean commitAcquired = false; try { this.commitSemaphore.acquire(); commitAcquired = true; this.mainSemaphore.acquire(this.maxConcurrentTx - 1); } catch (final Throwable ex) { if (commitAcquired) { this.commitSemaphore.release(); } Throwables.propagate(ex); } } } public void endCommit() { if (this.maxWriteTx == CX) { this.mainSemaphore.release(this.maxConcurrentTx - 1); this.commitSemaphore.release(); } } @Override public String toString() { return this.maxConcurrentTx + ":" + (this.maxWriteTx == WX ? "WX" : this.maxWriteTx == CX ? "CX" : this.maxWriteTx); } }