/******************************************************************************* * Copyright (c) 2005, 2010 IBM Corporation 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 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.osgi.baseadaptor.bundlefile; import java.io.IOException; import java.util.Map; import org.eclipse.osgi.framework.eventmgr.*; /** * A simple/quick/small implementation of an MRU (Most Recently Used) list to keep * track of open BundleFiles. The MRU will use the file limit specified by the property * "osgi.bundlefile.limit" by default unless the MRU is constructed with a specific * file limit. * @since 3.2 */ public class MRUBundleFileList implements EventDispatcher<Object, Object, BundleFile> { private static final String PROP_FILE_LIMIT = "osgi.bundlefile.limit"; //$NON-NLS-1$ private static final int MIN = 10; private static final int PROP_FILE_LIMIT_VALUE; private static final ThreadLocal<BundleFile> closingBundleFile = new ThreadLocal<BundleFile>(); static { int propValue = 100; // enable to 100 open files by default try { String prop = BundleFile.secureAction.getProperty(PROP_FILE_LIMIT); if (prop != null) propValue = Integer.parseInt(prop); } catch (NumberFormatException e) { //MRU will be disabled } PROP_FILE_LIMIT_VALUE = propValue; } // list of open bundle files final private BundleFile[] bundleFileList; // list of open bundle files use stamps final private long[] useStampList; // the limit of open files to allow before least used bundle file is closed final private int fileLimit; // value < MIN will disable MRU final private EventManager bundleFileCloserManager; final private Map<Object, Object> bundleFileCloser; // the current number of open bundle files private int numOpen = 0; // the current use stamp private long curUseStamp = 0; // used to work around bug 275166 private boolean firstDispatch = true; public MRUBundleFileList() { this(PROP_FILE_LIMIT_VALUE); } public MRUBundleFileList(int fileLimit) { // only enable the MRU if the initFileLimit is > MIN this.fileLimit = fileLimit; if (fileLimit >= MIN) { this.bundleFileList = new BundleFile[fileLimit]; this.useStampList = new long[fileLimit]; this.bundleFileCloserManager = new EventManager("Bundle File Closer"); //$NON-NLS-1$ this.bundleFileCloser = new CopyOnWriteIdentityMap<Object, Object>(); this.bundleFileCloser.put(this, this); } else { this.bundleFileList = null; this.useStampList = null; this.bundleFileCloserManager = null; this.bundleFileCloser = null; } } /** * Adds a BundleFile which is about to be opened to the MRU list. If * the number of open BundleFiles == the fileLimit then the least * recently used BundleFile is closed. * @param bundleFile the bundle file about to be opened. */ public void add(BundleFile bundleFile) { if (fileLimit < MIN) return; // MRU is disabled BundleFile toRemove = null; synchronized (this) { if (bundleFile.getMruIndex() >= 0) return; // do nothing; someone is trying add a bundleFile that is already in an MRU list int index = 0; // default to the first slot if (numOpen < fileLimit) { // numOpen does not exceed the fileLimit // find the first null slot to use in the MRU for (int i = 0; i < fileLimit; i++) if (bundleFileList[i] == null) { index = i; break; } } else { // numOpen has reached the fileLimit // find the least recently used bundleFile and close it // and use it slot for the new bundleFile to be opened. index = 0; for (int i = 1; i < fileLimit; i++) if (useStampList[i] < useStampList[index]) index = i; toRemove = bundleFileList[index]; if (toRemove.getMruIndex() != index) throw new IllegalStateException("The BundleFile has the incorrect mru index: " + index + " != " + toRemove.getMruIndex()); //$NON-NLS-1$//$NON-NLS-2$ removeInternal(toRemove); } // found an index to place to bundleFile to be opened bundleFileList[index] = bundleFile; bundleFile.setMruIndex(index); incUseStamp(index); numOpen++; } // must not close the toRemove bundle file while holding the lock of another bundle file (bug 161976) // This queues the bundle file for close asynchronously. closeBundleFile(toRemove); } /** * Removes a bundle file which is about to be closed * @param bundleFile the bundle file about to be closed * @return true if the bundleFile existed in the MRU; false otherwise */ public boolean remove(BundleFile bundleFile) { if (fileLimit < MIN) return false; // MRU is disabled synchronized (this) { int index = bundleFile.getMruIndex(); if ((index >= 0 && index < fileLimit) && bundleFileList[index] == bundleFile) { removeInternal(bundleFile); return true; } } return false; } // must be called while synchronizing "this" private void removeInternal(BundleFile bundleFile) { int index = bundleFile.getMruIndex(); bundleFile.setMruIndex(-1); bundleFileList[index] = null; useStampList[index] = -1; numOpen--; } /** * Increments the use stamp of a bundle file * @param bundleFile the bundle file to increment the use stamp for */ public void use(BundleFile bundleFile) { if (fileLimit < MIN) return; // MRU is disabled synchronized (this) { int index = bundleFile.getMruIndex(); if ((index >= 0 && index < fileLimit) && bundleFileList[index] == bundleFile) incUseStamp(index); } } // must be called while synchronizing "this" private void incUseStamp(int index) { if (curUseStamp == Long.MAX_VALUE) { // we hit the curUseStamp max better reset all the stamps for (int i = 0; i < fileLimit; i++) useStampList[i] = 0; curUseStamp = 0; } useStampList[index] = ++curUseStamp; } public final void dispatchEvent(Object eventListener, Object listenerObject, int eventAction, BundleFile eventObject) { if (firstDispatch) { // used to work around bug 275166; we don't want to leak the TCCL in this thread. Thread.currentThread().setContextClassLoader(null); firstDispatch = false; } try { closingBundleFile.set(eventObject); eventObject.close(); } catch (IOException e) { // TODO should log ?? } finally { closingBundleFile.set(null); } } private void closeBundleFile(BundleFile toRemove) { if (toRemove == null) return; try { /* queue to hold set of listeners */ ListenerQueue<Object, Object, BundleFile> queue = new ListenerQueue<Object, Object, BundleFile>(bundleFileCloserManager); /* add bundle file closer to the queue */ queue.queueListeners(bundleFileCloser.entrySet(), this); /* dispatch event to set of listeners */ queue.dispatchEventAsynchronous(0, toRemove); } catch (Throwable t) { // we cannot propagate exceptions out of this method // failing to queue a bundle close should not cause an error (bug 283797) // TODO should consider logging } } /** * Closes the bundle file closer thread for the MRU list */ public void shutdown() { if (bundleFileCloserManager != null) bundleFileCloserManager.close(); } /** * Returns true if this MRUBundleFileList is currently closing the specified bundle file on the current thread. * @param bundleFile the bundle file * @return true if the bundle file is being closed on the current thread */ public boolean isClosing(BundleFile bundleFile) { if (fileLimit < MIN) return false; // MRU is disabled // check the thread local variable return closingBundleFile.get() == bundleFile; } public boolean isEnabled() { return fileLimit >= MIN; } }