/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 com.sun.jini.mercury; import com.sun.jini.logging.Levels; import java.io.EOFException; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.RandomAccessFile; import java.io.SyncFailedException; import java.util.HashMap; import java.util.LinkedList; import java.util.logging.Level; import java.util.logging.Logger; /** * This class provides a pool of <tt>LogStream</tt> objects. Each * <tt>LogStream</tt> has an associated <tt>FileDescriptor</tt>, which * is the system resource we are trying to manage. This pool limits the * (user configurable) number of concurrent, open <tt>FileDescriptor</tt>s. * * @author Sun Microsystems, Inc. * * @since 1.1 */ class StreamPool { // Class fields /** Logger for lease related messages */ private static final Logger persistenceLogger = MailboxImpl.persistenceLogger; /** * Maximum limit for the number of concurrent <tt>LogStream</tt>s * in the stream pool. */ private final int maxPoolSize; /** Holds stream references by associated key */ private final HashMap pool; /** * Holds stream references in least recently used (released) order. * It's used in order determine which stream to discard upon * reaching the maximum pool limit. */ private final LinkedList freeList; /** * Simple constructor that creates a pool of given <code>size</code>. * * @exception IllegalArgumentException Thrown if the value of * <tt>maxPoolSize</tt> is less than 1. */ StreamPool(int size) { if (size < 1) throw new IllegalArgumentException( "Pool size must be greater than 0."); maxPoolSize = size; pool = new HashMap(maxPoolSize); freeList = new LinkedList(); if (persistenceLogger.isLoggable(Level.FINEST)) { persistenceLogger.log(Level.FINEST, "Created StreamPool of size {0}", new Integer(maxPoolSize)); } } /** * Returns a <tt>ControlLog</tt> object for the specified <tt>file</tt> * from the pool if it already exists. * Otherwise, it creates a new instance and adds it to the pool. * * @exception IOException if an I/O error occurs */ synchronized ControlLog getControlLog(File file) throws IOException { StreamKey key = new StreamKey(file, StreamType.CONTROL); ControlLog log = (ControlLog)pool.get(key); if (log != null) { // found it! if (freeList.remove(key) == false) throw new InternalMailboxException("Did not find re-used control log " + "stream in freeList."); } else { // Log was not found, so attempt to add it ensurePoolSpace(); // // Create new ControlLog and add it the pool. // log = new ControlLog(file, key); pool.put(key, log); if(freeList.remove(key)) throw new InternalMailboxException("Found newly created ControlLog " + "in freeList"); } return log; } /** * Returns a <tt>LogInputStream</tt> object from the pool if it * already exists. Otherwise, it creates a new instance and adds * it to the pool. * * @exception IOException if an I/O error occurs */ synchronized LogInputStream getLogInputStream(File file, long offset) throws IOException { StreamKey key = new StreamKey(file, StreamType.INPUT); LogInputStream in = (LogInputStream)pool.get(key); if (in != null) { //found it! if (freeList.remove(key) == false) throw new InternalMailboxException("Did not find re-used input log " + "stream in freelist."); } if (in == null || // if log not found OR in.getOffset() > offset) // current read offset is past desired { // then create a new log and add it ensurePoolSpace(); in = new LogInputStream(file, key); pool.put(key, in); if(freeList.remove(key)) throw new InternalMailboxException("Found newly created ControlLog " + "on freeList"); } // Sanity check for offset value if (offset > file.length()) throw new EOFException("Attempting to read past end of file."); // Check if log offset needs adjusting. // By this point in.offset <= offset. while (in.getOffset() < offset) { in.skip(offset - in.getOffset()); } return in; } /** * Returns a <tt>LogOutputStream</tt> object for the specified <tt>file</tt> * from the pool if it already exists. * Otherwise, it creates a new instance and adds it to the pool. * * @exception IOException if an I/O error occurs */ synchronized LogOutputStream getLogOutputStream(File file, long offset) throws IOException { StreamKey key = new StreamKey(file, StreamType.OUTPUT); LogOutputStream out = (LogOutputStream)pool.get(key); if (out != null) { // found it! if (freeList.remove(key) == false) throw new InternalMailboxException("Did not find re-used output log " + "stream in freelist"); // Sanity check to see if we are still in sync. If not, // then we need to close this stream and create another // one (done in the next code block). if (out.getOffset() != offset) { removeLogStream(out); out = null; } } // Check to see if we need to create another log. if (out == null) { ensurePoolSpace(); if (offset == 0L) { // Create new log, without appending out = new LogOutputStream(file, key, false); } else { // Create new log, appending to existing file long len = file.length(); if (offset > len) throw new EOFException("Attempting to write past end " + "of file"); if (offset < len) { RandomAccessFile raf = new RandomAccessFile(file, "rw"); raf.setLength(offset); raf.close(); } out = new LogOutputStream(file, key, true); } pool.put(key, out); if (freeList.remove(key)) throw new InternalMailboxException("Found newly created output log " + "in freeList"); } return out; } /** * Ensures that room is available in the pool. If the pool is currently * full, then the least recently used <tt>LogStream</tt> will be removed * and closed to make room. This method will block if the pool is full and * no <tt>LogStream</tt> objects can be closed. * * @exception IOException if an I/O error occurs */ private synchronized void ensurePoolSpace() throws IOException { if (pool.size() >= maxPoolSize) { while(freeList.size() < 1) { try { wait(); } catch (InterruptedException ie) { ; } } StreamKey key = (StreamKey)freeList.removeFirst(); LogStream els = (LogStream)pool.remove(key); els.close(); } } /** * Marks a stream as available for closing. * A log will only be closed if a new log is requested and the * pool has reached its maximum size. */ synchronized void releaseLogStream(LogStream stream) { StreamKey key = (StreamKey)stream.getKey(); if (pool.get(key) == null) throw new InternalMailboxException("Not managing stream: " + stream + ":" + key + " -- release failed"); freeList.add(key); notifyAll(); } /** * Removes the given <tt>LogStream</tt> from the pool and closes it, * if possible. The intent is for this method to be called for * unusable logs so that they will no longer be returned by a * subsequent call to one of the "get" methods. */ synchronized void removeLogStream(LogStream stream) { StreamKey key = (StreamKey)stream.getKey(); if (pool.remove(key) == null) throw new InternalMailboxException("Not managing stream: " + stream + ":" + key + " -- remove failed"); // Remove it from freeList, if present freeList.remove(key); try { stream.close(); } catch (IOException ioe) { // Note the exception, but otherwise ignore if (persistenceLogger.isLoggable(Levels.HANDLED)) { persistenceLogger.log(Levels.HANDLED, "Exception closing Log", ioe); } } } // // Debug use only! // synchronized int getPoolSize() { return pool.size(); } synchronized int getFreeSize() { return freeList.size(); } synchronized void dump() { System.out.println("Pool:\n" + pool); System.out.println("Free:\n" + freeList); } }