/* $HeadURL$ * $Id$ * * Copyright (c) 2009-2010 DuraSpace * http://duraspace.org * * In collaboration with Topaz Inc. * http://www.topazproject.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.akubraproject.qsc; import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; import java.util.Set; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import javax.transaction.Synchronization; import javax.transaction.Transaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.akubraproject.BlobStoreConnection; import org.akubraproject.impl.StreamManager; /** * Utility class that tracks the open streams of a <code>BlobStore</code> in order to provide a * <code>setQuiescent</code> implementation with the correct blocking behavior as well as to * ensure that streams that belong to a <code>BlobStoreConnection</code> are closed when the * connection is closed. * * @author Chris Wilper * @author Ronald Tschalär */ class QuiescingStreamManager extends StreamManager { private static final Logger log = LoggerFactory.getLogger(QuiescingStreamManager.class); /** Exclusive lock on the quiescent state. */ private final ReentrantLock stateLock = new ReentrantLock(true); /** Used to await and signal when quiescent state changes to false. */ private final Condition becameUnquiescent = stateLock.newCondition(); /** The current quiescent state. */ private boolean quiescent; /** the list of open, transactional connections */ private final Set<QuiescingBlobStoreConnection> txnCons = new HashSet<QuiescingBlobStoreConnection>(); /** the list of open, non-transactional connections */ private final Set<QuiescingBlobStoreConnection> rawCons = new HashSet<QuiescingBlobStoreConnection>(); void register(final QuiescingBlobStoreConnection con, Transaction tx) throws IOException { Set<QuiescingBlobStoreConnection> cons; if (tx != null) { try { tx.registerSynchronization(new Synchronization() { public void beforeCompletion() { } public void afterCompletion(int status) { unregister(con, true); } }); } catch (Exception e) { throw new IOException("Error registering txn synchronization", e); } cons = txnCons; } else { cons = rawCons; } synchronized (cons) { cons.add(con); } } void unregister(QuiescingBlobStoreConnection con, boolean transactional) { Set<QuiescingBlobStoreConnection> cons = transactional ? txnCons : rawCons; synchronized (cons) { if (cons.remove(con)) cons.notifyAll(); } } /** * Acquires the state lock in an unquiescent state. * <p> * This causes the calling thread to block until the unquiescent state is * reached. When obtained, the caller is responsible for releasing the state * lock as soon as possible. * * @see #unlockState */ public void lockUnquiesced() throws IOException { boolean ok = false; try { stateLock.lockInterruptibly(); while (quiescent) { log.info("lockUnquiesced: Waiting...", new Throwable()); becameUnquiescent.await(); log.info("lockUnquiesced: Wait is over."); } log.debug("Aquired the unquiescent lock"); ok = true; } catch (InterruptedException ie) { throw new IOException("lockUnquiesced: interrupted", ie); } finally { if (!ok && stateLock.isHeldByCurrentThread()) stateLock.unlock(); } } /** * Releases the lock previously obtained via {@link #lockUnquiesced}. */ public void unlockState() { stateLock.unlock(); log.debug("Released the unquiescent lock"); } /** * Sets the quiescent state. * * <p>Note that setting to the current state has no effect. * * @param quiescent whether to go into the quiescent (true) or non-quiescent (false) state. * @return true if successful, false if the thread was interrupted while blocking. * @see org.akubraproject.qsc.QuiescingBlobStore#setQuiescent */ public boolean setQuiescent(boolean quiescent) throws IOException { try { stateLock.lockInterruptibly(); if (quiescent && !this.quiescent) { synchronized (openOutputStreams) { while (!openOutputStreams.isEmpty()) { log.info("setQuiescent: Waiting for " + openOutputStreams.size() + " output streams to close..."); openOutputStreams.wait(); // wake up when next one is closed } } synchronized (txnCons) { int cnt; while ((cnt = countWriteTransactions()) > 0) { log.info("setQuiescent: Waiting for " + cnt + " write transactions to close..."); txnCons.wait(); // wake up when next one is completed } } synchronized (rawCons) { for (QuiescingBlobStoreConnection con : rawCons) { if (con.hasModifications()) con.sync(); } } log.info("setQuiescent: No open output streams or active write transactions. " + "Entering quiescent state."); } if (!quiescent && this.quiescent) { log.info("setQuiescent: Exiting quiescent state."); becameUnquiescent.signalAll(); } this.quiescent = quiescent; return true; } catch (InterruptedException ie) { if (quiescent) log.warn("Interrupted while waiting to enter quiescent state", ie); else log.warn("Interrupted while waiting to exit quiescent state", ie); return false; } finally { if (stateLock.isHeldByCurrentThread()) stateLock.unlock(); } } /* Runs with both txnCons monitor held as well as stateLock held */ private int countWriteTransactions() { int cnt = 0; for (QuiescingBlobStoreConnection con : txnCons) { if (con.hasModifications()) cnt++; } return cnt; } @Override public OutputStream manageOutputStream(BlobStoreConnection con, OutputStream stream) throws IOException { lockIOE(); try { return super.manageOutputStream(con, stream); } finally { stateLock.unlock(); } } private void lockIOE() throws IOException { try { stateLock.lockInterruptibly(); } catch (InterruptedException ie) { throw new IOException("Wait for state-lock interrupted", ie); } } // are we in the quiescent state? (for testability) boolean isQuiescent() { stateLock.lock(); try { return quiescent; } finally { stateLock.unlock(); } } }