001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2015 ForgeRock AS. 016 */ 017package org.opends.server.extensions; 018 019import static org.opends.messages.ConfigMessages.*; 020import static org.opends.messages.CoreMessages.*; 021 022import java.util.ArrayList; 023import java.util.List; 024import java.util.concurrent.ConcurrentLinkedQueue; 025import java.util.concurrent.Semaphore; 026import java.util.concurrent.TimeUnit; 027import java.util.concurrent.atomic.AtomicLong; 028 029import org.forgerock.i18n.LocalizableMessage; 030import org.forgerock.i18n.slf4j.LocalizedLogger; 031import org.forgerock.opendj.config.server.ConfigException; 032import org.forgerock.opendj.ldap.ResultCode; 033import org.opends.server.admin.server.ConfigurationChangeListener; 034import org.opends.server.admin.std.server.ParallelWorkQueueCfg; 035import org.opends.server.api.WorkQueue; 036import org.opends.server.core.DirectoryServer; 037import org.opends.server.monitors.ParallelWorkQueueMonitor; 038import org.opends.server.types.CancelRequest; 039import org.forgerock.opendj.config.server.ConfigChangeResult; 040import org.opends.server.types.DirectoryException; 041import org.opends.server.types.InitializationException; 042import org.opends.server.types.Operation; 043 044/** 045 * This class defines a data structure for storing and interacting with the 046 * Directory Server work queue. 047 */ 048public class ParallelWorkQueue 049 extends WorkQueue<ParallelWorkQueueCfg> 050 implements ConfigurationChangeListener<ParallelWorkQueueCfg> 051{ 052 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 053 054 055 056 057 /** 058 * The maximum number of times to retry getting the next operation from the 059 * queue if an unexpected failure occurs. 060 */ 061 private static final int MAX_RETRY_COUNT = 5; 062 063 064 065 /** The set of worker threads that will be used to process this work queue. */ 066 private ArrayList<ParallelWorkerThread> workerThreads; 067 068 /** 069 * The number of operations that have been submitted to the work queue for 070 * processing. 071 */ 072 private AtomicLong opsSubmitted; 073 074 /** 075 * Indicates whether one or more of the worker threads needs to be killed at 076 * the next convenient opportunity. 077 */ 078 private boolean killThreads; 079 080 /** Indicates whether the Directory Server is shutting down. */ 081 private boolean shutdownRequested; 082 083 /** The thread number used for the last worker thread that was created. */ 084 private int lastThreadNumber; 085 086 /** 087 * The number of worker threads that should be active (or will be shortly if a 088 * configuration change has not been completely applied). 089 */ 090 private int numWorkerThreads; 091 092 /** The queue that will be used to actually hold the pending operations. */ 093 private ConcurrentLinkedQueue<Operation> opQueue; 094 095 /** The lock used to provide threadsafe access for the queue. */ 096 private final Object queueLock = new Object(); 097 098 099 private final Semaphore queueSemaphore = new Semaphore(0, false); 100 101 102 /** 103 * Creates a new instance of this work queue. All initialization should be 104 * performed in the <CODE>initializeWorkQueue</CODE> method. 105 */ 106 public ParallelWorkQueue() 107 { 108 // No implementation should be performed here. 109 } 110 111 112 113 /** {@inheritDoc} */ 114 @Override 115 public void initializeWorkQueue(ParallelWorkQueueCfg configuration) 116 throws ConfigException, InitializationException 117 { 118 shutdownRequested = false; 119 killThreads = false; 120 opsSubmitted = new AtomicLong(0); 121 122 // Register to be notified of any configuration changes. 123 configuration.addParallelChangeListener(this); 124 125 // Get the necessary configuration from the provided entry. 126 numWorkerThreads = 127 computeNumWorkerThreads(configuration.getNumWorkerThreads()); 128 129 // Create the actual work queue. 130 opQueue = new ConcurrentLinkedQueue<>(); 131 132 // Create the set of worker threads that should be used to service the work queue. 133 workerThreads = new ArrayList<>(numWorkerThreads); 134 for (lastThreadNumber = 0; lastThreadNumber < numWorkerThreads; 135 lastThreadNumber++) 136 { 137 ParallelWorkerThread t = 138 new ParallelWorkerThread(this, lastThreadNumber); 139 t.start(); 140 workerThreads.add(t); 141 } 142 143 144 // Create and register a monitor provider for the work queue. 145 try 146 { 147 ParallelWorkQueueMonitor monitor = 148 new ParallelWorkQueueMonitor(this); 149 monitor.initializeMonitorProvider(null); 150 DirectoryServer.registerMonitorProvider(monitor); 151 } 152 catch (Exception e) 153 { 154 logger.traceException(e); 155 logger.error(ERR_CONFIG_WORK_QUEUE_CANNOT_CREATE_MONITOR, ParallelWorkQueueMonitor.class, e); 156 } 157 } 158 159 160 161 /** {@inheritDoc} */ 162 @Override 163 public void finalizeWorkQueue(LocalizableMessage reason) 164 { 165 shutdownRequested = true; 166 167 168 // Send responses to any operations in the pending queue to indicate that 169 // they won't be processed because the server is shutting down. 170 CancelRequest cancelRequest = new CancelRequest(true, reason); 171 ArrayList<Operation> pendingOperations = new ArrayList<>(); 172 opQueue.removeAll(pendingOperations); 173 174 for (Operation o : pendingOperations) 175 { 176 try 177 { 178 // The operation has no chance of responding to the cancel 179 // request so avoid waiting for a cancel response. 180 if (o.getCancelResult() == null) { 181 o.abort(cancelRequest); 182 } 183 } 184 catch (Exception e) 185 { 186 logger.traceException(e); 187 logger.warn(WARN_QUEUE_UNABLE_TO_CANCEL, o, e); 188 } 189 } 190 191 192 // Notify all the worker threads of the shutdown. 193 for (ParallelWorkerThread t : workerThreads) 194 { 195 try 196 { 197 t.shutDown(); 198 } 199 catch (Exception e) 200 { 201 logger.traceException(e); 202 logger.warn(WARN_QUEUE_UNABLE_TO_NOTIFY_THREAD, t.getName(), e); 203 } 204 } 205 } 206 207 208 209 /** 210 * Indicates whether this work queue has received a request to shut down. 211 * 212 * @return <CODE>true</CODE> if the work queue has recieved a request to shut 213 * down, or <CODE>false</CODE> if not. 214 */ 215 public boolean shutdownRequested() 216 { 217 return shutdownRequested; 218 } 219 220 221 222 /** 223 * Submits an operation to be processed by one of the worker threads 224 * associated with this work queue. 225 * 226 * @param operation The operation to be processed. 227 * 228 * @throws DirectoryException If the provided operation is not accepted for 229 * some reason (e.g., if the server is shutting 230 * down or the pending operation queue is already 231 * at its maximum capacity). 232 */ 233 @Override 234 public void submitOperation(Operation operation) throws DirectoryException 235 { 236 if (shutdownRequested) 237 { 238 LocalizableMessage message = WARN_OP_REJECTED_BY_SHUTDOWN.get(); 239 throw new DirectoryException(ResultCode.UNAVAILABLE, message); 240 } 241 242 opQueue.add(operation); 243 queueSemaphore.release(); 244 245 opsSubmitted.incrementAndGet(); 246 } 247 248 /** {@inheritDoc} */ 249 @Override 250 public boolean trySubmitOperation(Operation operation) 251 throws DirectoryException 252 { 253 submitOperation(operation); 254 return true; 255 } 256 257 258 /** 259 * Retrieves the next operation that should be processed by one of the worker 260 * threads, blocking if necessary until a new request arrives. This method 261 * should only be called by a worker thread associated with this work queue. 262 * 263 * @param workerThread The worker thread that is requesting the operation. 264 * 265 * @return The next operation that should be processed, or <CODE>null</CODE> 266 * if the server is shutting down and no more operations will be 267 * processed. 268 */ 269 public Operation nextOperation(ParallelWorkerThread workerThread) 270 { 271 return retryNextOperation(workerThread, 0); 272 } 273 274 275 276 /** 277 * Retrieves the next operation that should be processed by one of the worker 278 * threads following a previous failure attempt. A maximum of five 279 * consecutive failures will be allowed before returning <CODE>null</CODE>, 280 * which will cause the associated thread to exit. 281 * 282 * @param workerThread The worker thread that is requesting the operation. 283 * @param numFailures The number of consecutive failures that the worker 284 * thread has experienced so far. If this gets too 285 * high, then this method will return <CODE>null</CODE> 286 * rather than retrying. 287 * 288 * @return The next operation that should be processed, or <CODE>null</CODE> 289 * if the server is shutting down and no more operations will be 290 * processed, or if there have been too many consecutive failures. 291 */ 292 private Operation retryNextOperation( 293 ParallelWorkerThread workerThread, 294 int numFailures) 295 { 296 // See if we should kill off this thread. This could be necessary if the 297 // number of worker threads has been decreased with the server online. If 298 // so, then return null and the thread will exit. 299 if (killThreads) 300 { 301 synchronized (queueLock) 302 { 303 try 304 { 305 int currentThreads = workerThreads.size(); 306 if (currentThreads > numWorkerThreads) 307 { 308 if (workerThreads.remove(Thread.currentThread())) 309 { 310 currentThreads--; 311 } 312 313 if (currentThreads <= numWorkerThreads) 314 { 315 killThreads = false; 316 } 317 318 workerThread.setStoppedByReducedThreadNumber(); 319 return null; 320 } 321 } 322 catch (Exception e) 323 { 324 logger.traceException(e); 325 } 326 } 327 } 328 329 if (shutdownRequested || numFailures > MAX_RETRY_COUNT) 330 { 331 if (numFailures > MAX_RETRY_COUNT) 332 { 333 logger.error(ERR_CONFIG_WORK_QUEUE_TOO_MANY_FAILURES, Thread 334 .currentThread().getName(), numFailures, MAX_RETRY_COUNT); 335 } 336 337 return null; 338 } 339 340 try 341 { 342 while (true) 343 { 344 Operation nextOperation = null; 345 if (queueSemaphore.tryAcquire(5, TimeUnit.SECONDS)) { 346 nextOperation = opQueue.poll(); 347 } 348 if (nextOperation == null) 349 { 350 // There was no work to do in the specified length of time. See if 351 // we should shutdown, and if not then just check again. 352 if (shutdownRequested) 353 { 354 return null; 355 } 356 else if (killThreads) 357 { 358 synchronized (queueLock) 359 { 360 try 361 { 362 int currentThreads = workerThreads.size(); 363 if (currentThreads > numWorkerThreads) 364 { 365 if (workerThreads.remove(Thread.currentThread())) 366 { 367 currentThreads--; 368 } 369 370 if (currentThreads <= numWorkerThreads) 371 { 372 killThreads = false; 373 } 374 375 workerThread.setStoppedByReducedThreadNumber(); 376 return null; 377 } 378 } 379 catch (Exception e) 380 { 381 logger.traceException(e); 382 } 383 } 384 } 385 } 386 else 387 { 388 return nextOperation; 389 } 390 } 391 } 392 catch (Exception e) 393 { 394 logger.traceException(e); 395 396 // This should not happen. The only recourse we have is to log a message 397 // and try again. 398 logger.warn(WARN_WORKER_WAITING_UNCAUGHT_EXCEPTION, Thread.currentThread().getName(), e); 399 return retryNextOperation(workerThread, numFailures + 1); 400 } 401 } 402 403 404 405 /** 406 * Attempts to remove the specified operation from this queue if it has not 407 * yet been picked up for processing by one of the worker threads. 408 * 409 * @param operation The operation to remove from the queue. 410 * 411 * @return <CODE>true</CODE> if the provided request was present in the queue 412 * and was removed successfully, or <CODE>false</CODE> it not. 413 */ 414 public boolean removeOperation(Operation operation) 415 { 416 return opQueue.remove(operation); 417 } 418 419 420 421 /** 422 * Retrieves the total number of operations that have been successfully 423 * submitted to this work queue for processing since server startup. This 424 * does not include operations that have been rejected for some reason like 425 * the queue already at its maximum capacity. 426 * 427 * @return The total number of operations that have been successfully 428 * submitted to this work queue since startup. 429 */ 430 public long getOpsSubmitted() 431 { 432 return opsSubmitted.longValue(); 433 } 434 435 436 437 /** 438 * Retrieves the number of pending operations in the queue that have not yet 439 * been picked up for processing. Note that this method is not a 440 * constant-time operation and can be relatively inefficient, so it should be 441 * used sparingly. 442 * 443 * @return The number of pending operations in the queue that have not yet 444 * been picked up for processing. 445 */ 446 public int size() 447 { 448 return opQueue.size(); 449 } 450 451 452 453 /** {@inheritDoc} */ 454 @Override 455 public boolean isConfigurationChangeAcceptable( 456 ParallelWorkQueueCfg configuration, 457 List<LocalizableMessage> unacceptableReasons) 458 { 459 return true; 460 } 461 462 463 464 /** {@inheritDoc} */ 465 @Override 466 public ConfigChangeResult applyConfigurationChange( 467 ParallelWorkQueueCfg configuration) 468 { 469 int newNumThreads = 470 computeNumWorkerThreads(configuration.getNumWorkerThreads()); 471 472 // Apply a change to the number of worker threads if appropriate. 473 int currentThreads = workerThreads.size(); 474 if (newNumThreads != currentThreads) 475 { 476 synchronized (queueLock) 477 { 478 try 479 { 480 int threadsToAdd = newNumThreads - currentThreads; 481 if (threadsToAdd > 0) 482 { 483 for (int i = 0; i < threadsToAdd; i++) 484 { 485 ParallelWorkerThread t = 486 new ParallelWorkerThread(this, lastThreadNumber++); 487 workerThreads.add(t); 488 t.start(); 489 } 490 491 killThreads = false; 492 } 493 else 494 { 495 killThreads = true; 496 } 497 498 numWorkerThreads = newNumThreads; 499 } 500 catch (Exception e) 501 { 502 logger.traceException(e); 503 } 504 } 505 } 506 return new ConfigChangeResult(); 507 } 508 509 510 511 /** {@inheritDoc} */ 512 @Override 513 public boolean isIdle() 514 { 515 if (!opQueue.isEmpty()) { 516 return false; 517 } 518 519 synchronized (queueLock) 520 { 521 for (ParallelWorkerThread t : workerThreads) 522 { 523 if (t.isActive()) 524 { 525 return false; 526 } 527 } 528 529 return true; 530 } 531 } 532 533 /** 534 * Return the number of worker threads used by this WorkQueue. 535 * 536 * @return the number of worker threads used by this WorkQueue 537 */ 538 @Override 539 public int getNumWorkerThreads() 540 { 541 return this.numWorkerThreads; 542 } 543}