/* * Copyright (C) 2012, 2016 higherfrequencytrading.com * Copyright (C) 2016 Roman Leventov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.openhft.chronicle.hash.impl.stage.entry; import net.openhft.chronicle.hash.impl.VanillaChronicleHashHolder; import net.openhft.chronicle.hash.impl.stage.hash.CheckOnEachPublicOperation; import net.openhft.chronicle.hash.locks.InterProcessDeadLockException; import net.openhft.chronicle.hash.locks.InterProcessLock; import net.openhft.sg.StageRef; import net.openhft.sg.Staged; import org.jetbrains.annotations.NotNull; import java.util.concurrent.TimeUnit; import static net.openhft.chronicle.hash.impl.LocalLockState.UPDATE_LOCKED; import static net.openhft.chronicle.hash.impl.LocalLockState.WRITE_LOCKED; @Staged public class WriteLock implements InterProcessLock { @StageRef VanillaChronicleHashHolder<?> hh; @StageRef CheckOnEachPublicOperation checkOnEachPublicOperation; @StageRef SegmentStages s; @StageRef HashEntryStages<?> entry; @Override public boolean isHeldByCurrentThread() { checkOnEachPublicOperation.checkOnEachLockOperation(); return s.localLockState.write; } @Override public void lock() { checkOnEachPublicOperation.checkOnEachLockOperation(); switch (s.localLockState) { case UNLOCKED: s.checkIterationContextNotLockedInThisThread(); if (s.writeZero()) { if (!s.updateZero()) { s.segmentHeader.upgradeUpdateToWriteLock(s.segmentHeaderAddress); } else { if (!s.readZero()) throw forbiddenWriteLockWhenOuterContextReadLocked(); try { s.segmentHeader.writeLock(s.segmentHeaderAddress); } catch (InterProcessDeadLockException e) { throw s.debugContextsAndLocks(e); } } } s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return; case READ_LOCKED: throw forbiddenUpgrade(); case UPDATE_LOCKED: if (s.writeZero()) { assert !s.updateZero(); try { s.segmentHeader.upgradeUpdateToWriteLock(s.segmentHeaderAddress); } catch (InterProcessDeadLockException e) { throw s.debugContextsAndLocks(e); } } s.decrementUpdate(); s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); case WRITE_LOCKED: // do nothing } } /** * Non-static because after compilation it becomes inner class which forbids static methods */ @NotNull private IllegalMonitorStateException forbiddenUpgrade() { return new IllegalMonitorStateException( hh.h().toIdentityString() + ": Cannot upgrade from read to write lock"); } /** * Non-static because after compilation it becomes inner class which forbids static methods */ @NotNull private IllegalStateException forbiddenWriteLockWhenOuterContextReadLocked() { return new IllegalStateException(hh.h().toIdentityString() + ": Cannot acquire write lock, because outer context holds read lock. " + "In this case you should acquire update lock in the outer context up front"); } @Override public void lockInterruptibly() throws InterruptedException { checkOnEachPublicOperation.checkOnEachLockOperation(); if (Thread.interrupted()) throw new InterruptedException(); switch (s.localLockState) { case UNLOCKED: s.checkIterationContextNotLockedInThisThread(); if (s.writeZero()) { if (!s.updateZero()) { s.segmentHeader.upgradeUpdateToWriteLockInterruptibly( s.segmentHeaderAddress); } else { if (!s.readZero()) throw forbiddenWriteLockWhenOuterContextReadLocked(); try { s.segmentHeader.writeLockInterruptibly(s.segmentHeaderAddress); } catch (InterProcessDeadLockException e) { throw s.debugContextsAndLocks(e); } } } s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return; case READ_LOCKED: throw forbiddenUpgrade(); case UPDATE_LOCKED: if (s.writeZero()) { assert !s.updateZero(); try { s.segmentHeader.upgradeUpdateToWriteLockInterruptibly( s.segmentHeaderAddress); } catch (InterProcessDeadLockException e) { throw s.debugContextsAndLocks(e); } } s.decrementUpdate(); s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); case WRITE_LOCKED: // do nothing } } @Override public boolean tryLock() { checkOnEachPublicOperation.checkOnEachLockOperation(); switch (s.localLockState) { case UNLOCKED: s.checkIterationContextNotLockedInThisThread(); if (s.writeZero()) { if (!s.updateZero()) { if (s.segmentHeader.tryUpgradeUpdateToWriteLock(s.segmentHeaderAddress)) { s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return true; } else { return false; } } else { if (!s.readZero()) throw forbiddenWriteLockWhenOuterContextReadLocked(); if (s.segmentHeader.tryWriteLock(s.segmentHeaderAddress)) { s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return true; } else { return false; } } } else { s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return true; } case READ_LOCKED: throw forbiddenUpgrade(); case UPDATE_LOCKED: if (s.writeZero()) { assert !s.updateZero(); if (s.segmentHeader.tryUpgradeUpdateToWriteLock(s.segmentHeaderAddress)) { s.decrementUpdate(); s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return true; } else { return false; } } else { s.decrementUpdate(); s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return true; } case WRITE_LOCKED: return true; default: throw new IllegalStateException(hh.h().toIdentityString() + ": unexpected localLockState=" + s.localLockState); } } @Override public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException { checkOnEachPublicOperation.checkOnEachLockOperation(); if (Thread.interrupted()) throw new InterruptedException(); switch (s.localLockState) { case UNLOCKED: s.checkIterationContextNotLockedInThisThread(); if (s.writeZero()) { if (!s.updateZero()) { if (s.segmentHeader.tryUpgradeUpdateToWriteLock( s.segmentHeaderAddress, time, unit)) { s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return true; } else { return false; } } else { if (!s.readZero()) throw forbiddenWriteLockWhenOuterContextReadLocked(); if (s.segmentHeader.tryWriteLock(s.segmentHeaderAddress, time, unit)) { s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return true; } else { return false; } } } else { s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return true; } case READ_LOCKED: throw forbiddenUpgrade(); case UPDATE_LOCKED: if (s.writeZero()) { assert !s.updateZero(); if (s.segmentHeader.tryUpgradeUpdateToWriteLock( s.segmentHeaderAddress, time, unit)) { s.decrementUpdate(); s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return true; } else { return false; } } else { s.decrementUpdate(); s.incrementWrite(); s.setLocalLockState(WRITE_LOCKED); return true; } case WRITE_LOCKED: return true; default: throw new IllegalStateException(hh.h().toIdentityString() + ": unexpected localLockState=" + s.localLockState); } } @Override public void unlock() { checkOnEachPublicOperation.checkOnEachLockOperation(); switch (s.localLockState) { case UNLOCKED: case READ_LOCKED: case UPDATE_LOCKED: return; case WRITE_LOCKED: entry.closeDelayedUpdateChecksum(); if (s.decrementWrite() == 0) s.segmentHeader.downgradeWriteToUpdateLock(s.segmentHeaderAddress); s.incrementUpdate(); s.setLocalLockState(UPDATE_LOCKED); } } }