/*
 * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.controller.cluster.access.client;

import com.google.common.annotations.Beta;
import com.google.common.base.Verify;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.StampedLock;

/**
 * A lock implementation which allows users to perform optimistic reads and validate them in a fashion similar
 * to {@link StampedLock}. In case a read is contented with a write, the read side will throw
 * an {@link InversibleLockException}, which the caller can catch and use to wait for the write to resolve.
 *
 * @author Robert Varga
 */
@Beta
public final class InversibleLock {
    private static final AtomicReferenceFieldUpdater<InversibleLock, CountDownLatch> LATCH_UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(InversibleLock.class, CountDownLatch.class, "latch");

    private final StampedLock lock = new StampedLock();
    private volatile CountDownLatch latch;

    /**
     * Return a stamp for read validation.
     *
     * @return A stamp, which can be used with {@link #validate(long)}.
     * @throws InversibleLockException if this lock is currently write-locked
     */
    public long optimisticRead() {
        while (true) {
            final long stamp = lock.tryOptimisticRead();
            if (stamp != 0) {
                return stamp;
            }

            // Write-locked. Read the corresponding latch and if present report an exception, which will propagate
            // and force release of locks.
            final CountDownLatch local = latch;
            if (local != null) {
                throw new InversibleLockException(latch);
            }

            // No latch present: retry optimistic lock
        }
    }

    public boolean validate(final long stamp) {
        return lock.validate(stamp);
    }

    public long writeLock() {
        final CountDownLatch local = new CountDownLatch(1);
        final boolean taken = LATCH_UPDATER.compareAndSet(this, null, local);
        Verify.verify(taken);

        return lock.writeLock();
    }

    public void unlockWrite(final long stamp) {
        final CountDownLatch local = LATCH_UPDATER.getAndSet(this, null);
        Verify.verifyNotNull(local);
        lock.unlockWrite(stamp);
        local.countDown();
    }

}

/*
 * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.controller.cluster.access.client;

import com.google.common.annotations.Beta;
import com.google.common.base.Verify;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.StampedLock;

/**
 * A lock implementation which allows users to perform optimistic reads and validate them in a fashion similar
 * to {@link StampedLock}. In case a read is contented with a write, the read side will throw
 * an {@link InversibleLockException}, which the caller can catch and use to wait for the write to resolve.
 *
 * @author Robert Varga
 */
@Beta
public final class InversibleLock {
    private static final AtomicReferenceFieldUpdater<InversibleLock, CountDownLatch> LATCH_UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(InversibleLock.class, CountDownLatch.class, "latch");

    private final StampedLock lock = new StampedLock();
    private volatile CountDownLatch latch;

    /**
     * Return a stamp for read validation.
     *
     * @return A stamp, which can be used with {@link #validate(long)}.
     * @throws InversibleLockException if this lock is currently write-locked
     */
    public long optimisticRead() {
        while (true) {
            final long stamp = lock.tryOptimisticRead();
            if (stamp != 0) {
                return stamp;
            }

            // Write-locked. Read the corresponding latch and if present report an exception, which will propagate
            // and force release of locks.
            final CountDownLatch local = latch;
            if (local != null) {
                throw new InversibleLockException(local);
            }

            // No latch present: retry optimistic lock
        }
    }

    public boolean validate(final long stamp) {
        return lock.validate(stamp);
    }

    public long writeLock() {
        final CountDownLatch local = new CountDownLatch(1);
        final boolean taken = LATCH_UPDATER.compareAndSet(this, null, local);
        Verify.verify(taken);

        return lock.writeLock();
    }

    public void unlockWrite(final long stamp) {
        final CountDownLatch local = LATCH_UPDATER.getAndSet(this, null);
        Verify.verifyNotNull(local);
        lock.unlockWrite(stamp);
        local.countDown();
    }

}

fix InversibleLock race