001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.main; 029 030import org.opencms.db.CmsSecurityManager; 031import org.opencms.publish.CmsPublishJobRunning; 032import org.opencms.publish.CmsPublishManager; 033import org.opencms.report.A_CmsReportThread; 034import org.opencms.util.CmsUUID; 035 036import java.util.HashSet; 037import java.util.Hashtable; 038import java.util.Iterator; 039import java.util.Map; 040import java.util.Set; 041 042import org.apache.commons.logging.Log; 043 044/** 045 * The OpenCms "Grim Reaper" thread store were all system Threads are maintained.<p> 046 * 047 * This thread executes all 60 seconds and checks if report threads are still active. 048 * A report thread usually waits for a user to get the contents written to the report. 049 * However, if the user does not request the reports content (e.g. because the 050 * browser was closed), then the report thread becomes abandoned. This Grim Reaper 051 * will collect all such abandoned report threads and remove them after further 052 * 60 seconds.<p> 053 * 054 * Moreover, the Grim Reaper checks for all invalid user sessions that have times out for 055 * 5 or more minutes, and removes them as well.<p> 056 * 057 * @since 6.0.0 058 */ 059public final class CmsThreadStore extends Thread { 060 061 /** The log object for this class. */ 062 private static final Log LOG = CmsLog.getLog(CmsThreadStore.class); 063 064 /** The number of milliseconds in a minute. */ 065 private static final int ONE_MINUTE_IN_MILLIS = 60000; 066 067 /** The interval, in minutes, for checking threads. */ 068 private static final int UPDATE_MINUTES_INTERVAL = 5; 069 070 /** Indicates that this thread store is alive. */ 071 private boolean m_alive; 072 073 /** The security manager instance. */ 074 private CmsSecurityManager m_securityManager; 075 076 /** A map to store all system Threads in. */ 077 private Map<CmsUUID, A_CmsReportThread> m_threads; 078 079 /** 080 * Hides the public constructor.<p> 081 * 082 * @param securityManager needed for scheduling "undercover-jobs" 083 * that increase stability and fault tolerance 084 */ 085 protected CmsThreadStore(CmsSecurityManager securityManager) { 086 087 super(new ThreadGroup("OpenCms Thread Store"), "OpenCms: Grim Reaper"); 088 setDaemon(true); 089 // Hashtable is still the most efficient form of a synchronized HashMap 090 m_threads = new Hashtable<CmsUUID, A_CmsReportThread>(); 091 m_alive = true; 092 m_securityManager = securityManager; 093 start(); 094 } 095 096 /** 097 * Adds a Thread to this Thread store.<p> 098 * 099 * @param thread the Thread to add 100 */ 101 public void addThread(A_CmsReportThread thread) { 102 103 m_threads.put(thread.getUUID(), thread); 104 if (LOG.isDebugEnabled()) { 105 dumpThreads(); 106 } 107 } 108 109 /** 110 * Retrieves a Thread from this Thread store.<p> 111 * 112 * @param key the key of the Thread to retrieve 113 * @return the Thread form this Thread store that matches the given key 114 */ 115 public A_CmsReportThread retrieveThread(CmsUUID key) { 116 117 if (LOG.isDebugEnabled()) { 118 dumpThreads(); 119 } 120 return m_threads.get(key); 121 } 122 123 /** 124 * @see java.lang.Runnable#run() 125 */ 126 @Override 127 public void run() { 128 129 boolean checkPublishQueue = true; 130 int minutesForCheck = 0; 131 while (m_alive) { 132 // the Grim Reaper is eternal, of course 133 try { 134 // one minute sleep time 135 sleep(ONE_MINUTE_IN_MILLIS); 136 } catch (InterruptedException e) { 137 // let's go on reaping... 138 } 139 handleDoomedThreads(); 140 141 // check the session manager for invalid sessions not removed for whatever reason 142 minutesForCheck++; 143 if (minutesForCheck < UPDATE_MINUTES_INTERVAL) { 144 continue; 145 } 146 minutesForCheck = 0; 147 checkPublishQueue = !checkPublishQueue; 148 149 // work every n minutes 150 validateSessions(); 151 persistData(); 152 if (checkPublishQueue) { 153 // every 2*n minutes 154 checkPublishQueue(); 155 } 156 } 157 } 158 159 /** 160 * Checks the publish queue for abandoned publish jobs.<p> 161 */ 162 protected void checkPublishQueue() { 163 164 // check the publish manager if the current thread is still active 165 try { 166 CmsPublishManager publishManager = OpenCms.getPublishManager(); 167 if (publishManager == null) { 168 // this can happen during shutdown 169 return; 170 } 171 // get the current publish job 172 CmsPublishJobRunning publishJob = publishManager.getCurrentPublishJob(); 173 if (publishJob == null) { 174 // try to start next job 175 publishManager.checkCurrentPublishJobThread(); 176 return; 177 } 178 // get the thread id of the current publish job 179 CmsUUID uid = publishJob.getThreadUUID(); 180 if ((uid == null) || (uid.isNullUUID())) { 181 return; 182 } 183 // find the thread 184 A_CmsReportThread thread = m_threads.get(uid); 185 if (thread == null) { 186 return; 187 } 188 // check if the report still has output and so is active 189 if ((System.currentTimeMillis() - thread.getLastEntryTime()) > (UPDATE_MINUTES_INTERVAL 190 * ONE_MINUTE_IN_MILLIS)) { 191 // remove it 192 m_threads.remove(thread); 193 // abandon thread 194 publishManager.abandonThread(); 195 } 196 } catch (Throwable t) { 197 LOG.error(Messages.get().getBundle().key(Messages.LOG_THREADSTORE_CHECK_PUBLISH_THREAD_ERROR_0), t); 198 } 199 } 200 201 /** 202 * Handles doomed threads.<p> 203 */ 204 protected void handleDoomedThreads() { 205 206 try { 207 Iterator<CmsUUID> i = m_threads.keySet().iterator(); 208 Set<CmsUUID> doomed = new HashSet<CmsUUID>(); 209 // first collect all doomed Threads 210 while (i.hasNext()) { 211 CmsUUID key = i.next(); 212 A_CmsReportThread thread = m_threads.get(key); 213 if (thread.isDoomed()) { 214 doomed.add(key); 215 if (LOG.isDebugEnabled()) { 216 LOG.debug( 217 Messages.get().getBundle().key( 218 Messages.LOG_THREADSTORE_DOOMED_2, 219 thread.getName(), 220 thread.getUUID())); 221 } 222 } 223 } 224 i = doomed.iterator(); 225 // no remove all doomed Threads from the Thread store 226 while (i.hasNext()) { 227 m_threads.remove(i.next()); 228 } 229 if (LOG.isDebugEnabled()) { 230 dumpThreads(); 231 } 232 } catch (Throwable t) { 233 // the Grim Reaper must not be stopped by any error 234 LOG.error(Messages.get().getBundle().key(Messages.LOG_THREADSTORE_CHECK_THREADS_ERROR_0), t); 235 } 236 } 237 238 /** 239 * Takes care of the persistence of data normally held in memory.<p> 240 */ 241 protected void persistData() { 242 243 try { 244 // save the resource locks to db 245 m_securityManager.writeLocks(); 246 // save the log entries to db 247 m_securityManager.updateLog(); 248 } catch (Throwable t) { 249 if (LOG.isErrorEnabled()) { 250 LOG.error( 251 org.opencms.lock.Messages.get().getBundle().key(org.opencms.lock.Messages.ERR_WRITE_LOCKS_0), 252 t); 253 } 254 } 255 } 256 257 /** 258 * Shut down this thread store.<p> 259 */ 260 protected synchronized void shutDown() { 261 262 m_alive = false; 263 interrupt(); 264 } 265 266 /** 267 * Checks for invalid sessions.<p> 268 */ 269 protected void validateSessions() { 270 271 try { 272 CmsSessionManager sessionInfoManager = OpenCms.getSessionManager(); 273 if (sessionInfoManager != null) { 274 // will be null if only the shell is running 275 sessionInfoManager.validateSessionInfos(); 276 } 277 } catch (Throwable t) { 278 LOG.error(Messages.get().getBundle().key(Messages.LOG_THREADSTORE_CHECK_SESSIONS_ERROR_0), t); 279 } 280 } 281 282 /** 283 * Method to dump all currently known Threads.<p> 284 */ 285 private void dumpThreads() { 286 287 if (LOG.isDebugEnabled()) { 288 StringBuffer b = new StringBuffer(512); 289 Iterator<CmsUUID> i = m_threads.keySet().iterator(); 290 while (i.hasNext()) { 291 CmsUUID key = i.next(); 292 A_CmsReportThread thread = m_threads.get(key); 293 b.append(thread.getName()); 294 b.append(" - "); 295 b.append(thread.getUUID()); 296 b.append('\n'); 297 } 298 LOG.debug( 299 Messages.get().getBundle().key( 300 Messages.LOG_THREADSTORE_POOL_CONTENT_2, 301 Integer.valueOf(m_threads.size()), 302 b.toString())); 303 } 304 } 305}