001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    package org.apache.hadoop.util;
019    
020    import java.nio.channels.AsynchronousCloseException;
021    import java.nio.channels.ClosedChannelException;
022    import java.util.concurrent.atomic.AtomicInteger;
023    
024    import com.google.common.base.Preconditions;
025    
026    /**
027     * A closeable object that maintains a reference count.
028     *
029     * Once the object is closed, attempting to take a new reference will throw
030     * ClosedChannelException.
031     */
032    public class CloseableReferenceCount {
033      /**
034       * Bit mask representing a closed domain socket.
035       */
036      private static final int STATUS_CLOSED_MASK = 1 << 30;
037    
038      /**
039       * The status bits.
040       *
041       * Bit 30: 0 = open, 1 = closed.
042       * Bits 29 to 0: the reference count.
043       */
044      private final AtomicInteger status = new AtomicInteger(0);
045    
046      public CloseableReferenceCount() { }
047    
048      /**
049       * Increment the reference count.
050       *
051       * @throws ClosedChannelException      If the status is closed.
052       */
053      public void reference() throws ClosedChannelException {
054        int curBits = status.incrementAndGet();
055        if ((curBits & STATUS_CLOSED_MASK) != 0) {
056          status.decrementAndGet();
057          throw new ClosedChannelException();
058        }
059      }
060    
061      /**
062       * Decrement the reference count.
063       *
064       * @return          True if the object is closed and has no outstanding
065       *                  references.
066       */
067      public boolean unreference() {
068        int newVal = status.decrementAndGet();
069        Preconditions.checkState(newVal != 0xffffffff,
070            "called unreference when the reference count was already at 0.");
071        return newVal == STATUS_CLOSED_MASK;
072      }
073    
074      /**
075       * Decrement the reference count, checking to make sure that the
076       * CloseableReferenceCount is not closed.
077       *
078       * @throws AsynchronousCloseException  If the status is closed.
079       */
080      public void unreferenceCheckClosed() throws ClosedChannelException {
081        int newVal = status.decrementAndGet();
082        if ((newVal & STATUS_CLOSED_MASK) != 0) {
083          throw new AsynchronousCloseException();
084        }
085      }
086    
087      /**
088       * Return true if the status is currently open.
089       *
090       * @return                 True if the status is currently open.
091       */
092      public boolean isOpen() {
093        return ((status.get() & STATUS_CLOSED_MASK) == 0);
094      }
095    
096      /**
097       * Mark the status as closed.
098       *
099       * Once the status is closed, it cannot be reopened.
100       *
101       * @return                         The current reference count.
102       * @throws ClosedChannelException  If someone else closes the object
103       *                                 before we do.
104       */
105      public int setClosed() throws ClosedChannelException {
106        while (true) {
107          int curBits = status.get();
108          if ((curBits & STATUS_CLOSED_MASK) != 0) {
109            throw new ClosedChannelException();
110          }
111          if (status.compareAndSet(curBits, curBits | STATUS_CLOSED_MASK)) {
112            return curBits & (~STATUS_CLOSED_MASK);
113          }
114        }
115      }
116    
117      /**
118       * Get the current reference count.
119       *
120       * @return                 The current reference count.
121       */
122      public int getReferenceCount() {
123        return status.get() & (~STATUS_CLOSED_MASK);
124      }
125    }