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.io.compress;
019    
020    import java.util.ArrayList;
021    import java.util.HashMap;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.concurrent.atomic.AtomicInteger;
025    
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    import org.apache.hadoop.classification.InterfaceAudience;
029    import org.apache.hadoop.classification.InterfaceStability;
030    import org.apache.hadoop.conf.Configuration;
031    import org.apache.hadoop.util.ReflectionUtils;
032    
033    import com.google.common.cache.CacheBuilder;
034    import com.google.common.cache.CacheLoader;
035    import com.google.common.cache.LoadingCache;
036    
037    /**
038     * A global compressor/decompressor pool used to save and reuse 
039     * (possibly native) compression/decompression codecs.
040     */
041    @InterfaceAudience.Public
042    @InterfaceStability.Evolving
043    public class CodecPool {
044      private static final Log LOG = LogFactory.getLog(CodecPool.class);
045      
046      /**
047       * A global compressor pool used to save the expensive 
048       * construction/destruction of (possibly native) decompression codecs.
049       */
050      private static final Map<Class<Compressor>, List<Compressor>> compressorPool = 
051        new HashMap<Class<Compressor>, List<Compressor>>();
052      
053      /**
054       * A global decompressor pool used to save the expensive 
055       * construction/destruction of (possibly native) decompression codecs.
056       */
057      private static final Map<Class<Decompressor>, List<Decompressor>> decompressorPool = 
058        new HashMap<Class<Decompressor>, List<Decompressor>>();
059    
060      private static <T> LoadingCache<Class<T>, AtomicInteger> createCache(
061          Class<T> klass) {
062        return CacheBuilder.newBuilder().build(
063            new CacheLoader<Class<T>, AtomicInteger>() {
064              @Override
065              public AtomicInteger load(Class<T> key) throws Exception {
066                return new AtomicInteger();
067              }
068            });
069      }
070    
071      /**
072       * Map to track the number of leased compressors
073       */
074      private static final LoadingCache<Class<Compressor>, AtomicInteger> compressorCounts =
075          createCache(Compressor.class);
076    
077       /**
078       * Map to tracks the number of leased decompressors
079       */
080      private static final LoadingCache<Class<Decompressor>, AtomicInteger> decompressorCounts =
081          createCache(Decompressor.class);
082    
083      private static <T> T borrow(Map<Class<T>, List<T>> pool,
084                                 Class<? extends T> codecClass) {
085        T codec = null;
086        
087        // Check if an appropriate codec is available
088        synchronized (pool) {
089          if (pool.containsKey(codecClass)) {
090            List<T> codecList = pool.get(codecClass);
091            
092            if (codecList != null) {
093              synchronized (codecList) {
094                if (!codecList.isEmpty()) {
095                  codec = codecList.remove(codecList.size()-1);
096                }
097              }
098            }
099          }
100        }
101        
102        return codec;
103      }
104    
105      private static <T> void payback(Map<Class<T>, List<T>> pool, T codec) {
106        if (codec != null) {
107          Class<T> codecClass = ReflectionUtils.getClass(codec);
108          synchronized (pool) {
109            if (!pool.containsKey(codecClass)) {
110              pool.put(codecClass, new ArrayList<T>());
111            }
112    
113            List<T> codecList = pool.get(codecClass);
114            synchronized (codecList) {
115              codecList.add(codec);
116            }
117          }
118        }
119      }
120      
121      @SuppressWarnings("unchecked")
122      private static <T> int getLeaseCount(
123          LoadingCache<Class<T>, AtomicInteger> usageCounts,
124          Class<? extends T> codecClass) {
125        return usageCounts.getUnchecked((Class<T>) codecClass).get();
126      }
127    
128      private static <T> void updateLeaseCount(
129          LoadingCache<Class<T>, AtomicInteger> usageCounts, T codec, int delta) {
130        if (codec != null) {
131          Class<T> codecClass = ReflectionUtils.getClass(codec);
132          usageCounts.getUnchecked(codecClass).addAndGet(delta);
133        }
134      }
135    
136      /**
137       * Get a {@link Compressor} for the given {@link CompressionCodec} from the 
138       * pool or a new one.
139       *
140       * @param codec the <code>CompressionCodec</code> for which to get the 
141       *              <code>Compressor</code>
142       * @param conf the <code>Configuration</code> object which contains confs for creating or reinit the compressor
143       * @return <code>Compressor</code> for the given 
144       *         <code>CompressionCodec</code> from the pool or a new one
145       */
146      public static Compressor getCompressor(CompressionCodec codec, Configuration conf) {
147        Compressor compressor = borrow(compressorPool, codec.getCompressorType());
148        if (compressor == null) {
149          compressor = codec.createCompressor();
150          LOG.info("Got brand-new compressor ["+codec.getDefaultExtension()+"]");
151        } else {
152          compressor.reinit(conf);
153          if(LOG.isDebugEnabled()) {
154            LOG.debug("Got recycled compressor");
155          }
156        }
157        updateLeaseCount(compressorCounts, compressor, 1);
158        return compressor;
159      }
160      
161      public static Compressor getCompressor(CompressionCodec codec) {
162        return getCompressor(codec, null);
163      }
164      
165      /**
166       * Get a {@link Decompressor} for the given {@link CompressionCodec} from the
167       * pool or a new one.
168       *  
169       * @param codec the <code>CompressionCodec</code> for which to get the 
170       *              <code>Decompressor</code>
171       * @return <code>Decompressor</code> for the given 
172       *         <code>CompressionCodec</code> the pool or a new one
173       */
174      public static Decompressor getDecompressor(CompressionCodec codec) {
175        Decompressor decompressor = borrow(decompressorPool, codec.getDecompressorType());
176        if (decompressor == null) {
177          decompressor = codec.createDecompressor();
178          LOG.info("Got brand-new decompressor ["+codec.getDefaultExtension()+"]");
179        } else {
180          if(LOG.isDebugEnabled()) {
181            LOG.debug("Got recycled decompressor");
182          }
183        }
184        updateLeaseCount(decompressorCounts, decompressor, 1);
185        return decompressor;
186      }
187      
188      /**
189       * Return the {@link Compressor} to the pool.
190       * 
191       * @param compressor the <code>Compressor</code> to be returned to the pool
192       */
193      public static void returnCompressor(Compressor compressor) {
194        if (compressor == null) {
195          return;
196        }
197        // if the compressor can't be reused, don't pool it.
198        if (compressor.getClass().isAnnotationPresent(DoNotPool.class)) {
199          return;
200        }
201        compressor.reset();
202        payback(compressorPool, compressor);
203        updateLeaseCount(compressorCounts, compressor, -1);
204      }
205      
206      /**
207       * Return the {@link Decompressor} to the pool.
208       * 
209       * @param decompressor the <code>Decompressor</code> to be returned to the 
210       *                     pool
211       */
212      public static void returnDecompressor(Decompressor decompressor) {
213        if (decompressor == null) {
214          return;
215        }
216        // if the decompressor can't be reused, don't pool it.
217        if (decompressor.getClass().isAnnotationPresent(DoNotPool.class)) {
218          return;
219        }
220        decompressor.reset();
221        payback(decompressorPool, decompressor);
222        updateLeaseCount(decompressorCounts, decompressor, -1);
223      }
224    
225      /**
226       * Return the number of leased {@link Compressor}s for this
227       * {@link CompressionCodec}
228       */
229      public static int getLeasedCompressorsCount(CompressionCodec codec) {
230        return (codec == null) ? 0 : getLeaseCount(compressorCounts,
231            codec.getCompressorType());
232      }
233    
234      /**
235       * Return the number of leased {@link Decompressor}s for this
236       * {@link CompressionCodec}
237       */
238      public static int getLeasedDecompressorsCount(CompressionCodec codec) {
239        return (codec == null) ? 0 : getLeaseCount(decompressorCounts,
240            codec.getDecompressorType());
241      }
242    }