/* * Copyright (C) 2006-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.jlan.server.filesys.db; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.server.filesys.cache.FileState; import org.alfresco.jlan.server.filesys.cache.FileStateCache; import org.alfresco.jlan.server.filesys.loader.BackgroundFileLoader; import org.alfresco.jlan.server.filesys.loader.CachedFileInfo; import org.alfresco.jlan.server.filesys.loader.FileRequest; import org.alfresco.jlan.server.filesys.loader.FileRequestQueue; import org.alfresco.jlan.server.filesys.loader.MultipleFileRequest; import org.alfresco.jlan.server.filesys.loader.SingleFileRequest; /** * Background Load Save Class * * <p>Utility class that can be used by FileLoader or DBInterface implementations to provide a worker thread pool * to load/save the file data using a queue of file load/save requests. * * @author gkspencer */ public class MemoryBackgroundLoadSave { // Status codes returned from the load/save worker thread processing public final static int StsSuccess = 0; public final static int StsRequeue = 1; public final static int StsError = 2; // Default/minimum/maximum number of worker threads to use public static final int DefaultWorkerThreads = 4; public static final int MinimumWorkerThreads = 1; public static final int MaximumWorkerThreads = 50; // Maximum in-memory request queue size and low water mark public static final int RequestQueueMaxSize = 5000; public static final int RequestQueueMinSize = 50; public static final int RequestQueueDefaultSize = 200; public static final int RequestQueueLowWaterMark = 50; // Minimum number of requests that must be in the in-memory queue when a requeue occurs to continue // without sleeping public static final int RequeueMinSize = 20; public static final long RequeueWaitTime = 500; // milliseconds // Default worker thread prefix private static final String DefaultThreadName = "MemLoadSave_"; // Attributes attached to the file state public static final String DBFileSegmentInfo = "DBFileSegmentInfo"; // File state timeout values public static final long SequentialFileExpire = 3000L; // milliseconds public static final long RequestProcessedExpire = 3000L; // " public static final long RequestQueuedExpire = 10000L; // " // Transaction timeout default, minimum and maximum values public static final long DefaultTransactionTimeout = 5000L; // milliseconds public static final long MinimumTransactionTimeout = 2000L; // " public static final long MaximumTransactionTimeout = 60000L; // " // Name, used to prefix worker thread names private String m_name; // Queue of file requests private FileRequestQueue m_readQueue; private FileRequestQueue m_writeQueue; // Maximum in-memory file request size and low water mark private int m_maxQueueSize; private int m_lowQueueSize; // File request worker thread pools private ThreadWorker[] m_readThreads; private ThreadWorker[] m_writeThreads; // Number of worker threads to create for read/write requests private int m_readWorkers; private int m_writeWorkers; // Enable debug output private boolean m_debug; // File state cache and default expiry timeout private FileStateCache m_stateCache; private long m_stateTimeout; // Associated file loader called by the worker threads to do the actual file load/save private BackgroundFileLoader m_fileLoader; /** * Thread Worker Inner Class */ protected class ThreadWorker implements Runnable { // Worker thread private Thread mi_thread; // Worker unique id private int mi_id; // Associated request queue private FileRequestQueue mi_queue; // Shutdown flag private boolean mi_shutdown = false; /** * Class constructor * * @param name String * @param id int * @param queue FileRequestQueue */ public ThreadWorker(String name, int id, FileRequestQueue queue) { mi_id = id; mi_queue = queue; mi_thread = new Thread(this); mi_thread.setName(name); mi_thread.setDaemon(true); mi_thread.start(); } /** * Request the worker thread to shutdown */ public final void shutdownRequest() { mi_shutdown = true; try { mi_thread.interrupt(); } catch (Exception ex) { } } /** * Run the thread */ public void run() { // Loop until shutdown FileRequest fileReq = null; while ( mi_shutdown == false) { try { // Wait for a file request to be queued fileReq = mi_queue.removeRequest(); } catch (InterruptedException ex) { // Check for shutdown if ( mi_shutdown == true) break; } // If the file request is valid process it if ( fileReq != null) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("BackgroundLoadSave loader=" + getName() + ", fileReq=" + fileReq + ", queued=" + mi_queue.numberOfRequests()); // Process the file request int reqSts = StsRequeue; try { // Set the thread id of the worker processing the request fileReq.setThreadId(mi_id); // File data load if ( fileReq.isType() == FileRequest.LOAD) { // Load the file reqSts = getFileLoader().loadFile(fileReq); } else if ( fileReq.isType() == FileRequest.SAVE || fileReq.isType() == FileRequest.TRANSSAVE) { // Save the file reqSts = getFileLoader().storeFile(fileReq); } } catch (Exception ex) { // DEBUG if ( Debug.EnableError && hasDebug()) { Debug.println("BackgroundLoadSave exception=" + ex.toString()); Debug.println(ex); } } // Check if the request was processed successfully if ( reqSts == StsSuccess || reqSts == StsError) { // Reset the associated file state(s) to expire in a short while if ( fileReq instanceof MultipleFileRequest) { // Get the multiple file request details MultipleFileRequest multiReq = (MultipleFileRequest) fileReq; // Calculate the expiry time long expireAt = System.currentTimeMillis() + RequestProcessedExpire; // Reset all the associated file states to expire in a short while for ( int i = 0; i < multiReq.getNumberOfFiles(); i++) { CachedFileInfo finfo = multiReq.getFileInfo(i); if ( finfo.hasFileState()) finfo.getFileState().setExpiryTime(expireAt); } } else { // Reset the associated file state to expire in a short while SingleFileRequest singleReq = (SingleFileRequest) fileReq; if ( singleReq.hasFileState()) singleReq.getFileState().setExpiryTime(System.currentTimeMillis() + RequestProcessedExpire); } // DEBUG if ( Debug.EnableInfo && reqSts == StsError && hasDebug()) Debug.println("BackgroundLoadSave Error request=" + fileReq); } // If the file request was not processed requeue it else if ( reqSts == StsRequeue) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("BackgroundLoadSave ReQueue request=" + fileReq); // Check if we need to pause, if there are only a few requests in the in-memory queue then we will // sleep for a short while so as not to process the requeued request again too quickly if ( mi_queue.numberOfRequests() < RequeueMinSize) { // Sleep for a while before requeueing the request try { Thread.sleep(RequeueWaitTime); } catch (Exception ex) { } } // Add the request to the end of the in-memory queue mi_queue.addRequest(fileReq); } } } // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("BackgroundLoadSave thread=" + mi_thread.getName() + " shutdown"); } }; /** * Class constructor * * @param stateCache FileStateCache * @param bgLoader BackgroundFileLoader */ public MemoryBackgroundLoadSave(FileStateCache stateCache, BackgroundFileLoader bgLoader) { // Save the state cache and background loader details m_stateCache = stateCache; m_fileLoader = bgLoader; // Create the file request queues m_readQueue = new FileRequestQueue(); m_writeQueue = new FileRequestQueue(); // Set the in-memory queue size and low water mark m_maxQueueSize = RequestQueueDefaultSize; m_lowQueueSize = RequestQueueLowWaterMark; // Set the default worker thread prefix setName(DefaultThreadName); } /** * Class constructor * * @param name String * @param stateCache FileStateCache * @param bgLoader BackgroundFileLoader */ public MemoryBackgroundLoadSave(String name, FileStateCache stateCache, BackgroundFileLoader bgLoader) { // Save the state cache and background loader details m_stateCache = stateCache; m_fileLoader = bgLoader; // Create the file request queues m_readQueue = new FileRequestQueue(); m_writeQueue = new FileRequestQueue(); // Set the in-memory queue size and low water mark m_maxQueueSize = RequestQueueDefaultSize; m_lowQueueSize = RequestQueueLowWaterMark; // Set the worker thread prefix setName(name); } /** * Start the background load/save thread pool */ public final void startThreads() { // Create the read thread pool m_readThreads = new ThreadWorker[m_readWorkers]; for ( int i = 0; i < m_readWorkers; i++) m_readThreads[i] = new ThreadWorker(getName() + "_RD_" + (i+1), i, m_readQueue); // Create the write thread pool m_writeThreads = new ThreadWorker[m_writeWorkers]; for ( int i = 0; i < m_writeWorkers; i++) m_writeThreads[i] = new ThreadWorker(getName() + "_WR_" + (i+1), i, m_writeQueue); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("FileLoader threadPool read=" + m_readWorkers + ", write=" + m_writeWorkers); } /** * Shutdown the background load/save thread pool */ public final void shutdownThreads() { // Shutdown the worker threads if ( m_readThreads != null) { for ( int i = 0; i < m_readThreads.length; i++) m_readThreads[i].shutdownRequest(); } if ( m_writeThreads != null) { for ( int i = 0; i < m_writeThreads.length; i++) m_writeThreads[i].shutdownRequest(); } } /** * Request file data to be loaded/saved * * @param req FileRequest */ public void queueFileRequest(FileRequest req) { // Make sure the associated file state stays in memory for a short time, if the queue is small // the request may get processed soon. if ( req instanceof SingleFileRequest) { // Get the request details SingleFileRequest fileReq = (SingleFileRequest) req; if ( fileReq.hasFileState()) { // Lock the file state so it does not get expired during the load/save fileReq.getFileState().setExpiryTime(FileState.NoTimeout); } // Check if the request is a load or save if ( fileReq.isType() == FileRequest.LOAD) m_readQueue.addRequest( fileReq); else m_writeQueue.addRequest( fileReq); } } /** * Check if debug output is enabled * * @return boolean */ public final boolean hasDebug() { return m_debug; } /** * Return the file loader interface * * @return BackgroundFileLoader */ public final BackgroundFileLoader getFileLoader() { return m_fileLoader; } /** * Return the default file state timeout * * @return int */ public final long getFileStateTimeout() { return m_stateTimeout; } /** * Return the maximum in-memory file request queue size * * @return int */ public final int getMaximumQueueSize() { return m_maxQueueSize; } /** * Return the in-memory file request queue low water mark level * * @return int */ public final int getLowQueueSize() { return m_lowQueueSize; } /** * Return the worker thread prefix * * @return String */ public final String getName() { return m_name; } /** * Return the read request queue * * @return FileRequestQueue */ protected final FileRequestQueue getReadQueue() { return m_readQueue; } /** * Return the write request queue * * @return FileRequestQueue */ protected final FileRequestQueue getWriteQueue() { return m_writeQueue; } /** * Return the number of read worker threads * * @return int */ public final int getReadWorkers( ) { return m_readWorkers; } /** * Return the number of write worker threads * * @return int */ public final int getWriteWorkers( ) { return m_writeWorkers; } /** * Return the file state cache * * @return FileStateCache */ protected final FileStateCache getStateCache() { return m_stateCache; } /** * Set the worker thread name prefix * * @param name String */ public final void setName(String name) { m_name = name; } /** * Enable/disable debug output * * @param dbg boolean */ public final void setDebug(boolean dbg) { m_debug = dbg; } /** * Set the maximum in-memory file request queue size * * @param qsize int */ public final void setMaximumQueueSize(int qsize) { m_maxQueueSize = qsize; } /** * Set the in-memory file request queue low water mark level * * @param lowqSize */ public final void setLowQueueSize(int lowqSize) { m_lowQueueSize = lowqSize; } /** * Set the number of read worker threads * * @param rdWorkers int */ public final void setReadWorkers( int rdWorkers) { m_readWorkers = rdWorkers; } /** * Set the number of write worker threads * * @param wrWorkers int */ public final void setWriteWorkers( int wrWorkers) { m_writeWorkers = wrWorkers; } }