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 019 020 021import static org.opends.messages.ConfigMessages.*; 022import static org.opends.messages.CoreMessages.*; 023 024import java.util.ArrayList; 025import java.util.List; 026import java.util.concurrent.LinkedBlockingQueue; 027import java.util.concurrent.TimeUnit; 028import java.util.concurrent.atomic.AtomicLong; 029import java.util.concurrent.locks.ReentrantReadWriteLock; 030import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; 031import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; 032 033import org.forgerock.i18n.LocalizableMessage; 034import org.forgerock.i18n.slf4j.LocalizedLogger; 035import org.forgerock.opendj.config.server.ConfigException; 036import org.forgerock.opendj.ldap.ResultCode; 037import org.opends.server.admin.server.ConfigurationChangeListener; 038import org.opends.server.admin.std.server.TraditionalWorkQueueCfg; 039import org.opends.server.api.WorkQueue; 040import org.opends.server.core.DirectoryServer; 041import org.opends.server.monitors.TraditionalWorkQueueMonitor; 042import org.opends.server.types.CancelRequest; 043import org.forgerock.opendj.config.server.ConfigChangeResult; 044import org.opends.server.types.DirectoryException; 045import org.opends.server.types.InitializationException; 046import org.opends.server.types.Operation; 047 048 049 050/** 051 * This class defines a data structure for storing and interacting with the 052 * Directory Server work queue. 053 */ 054public class TraditionalWorkQueue extends WorkQueue<TraditionalWorkQueueCfg> 055 implements ConfigurationChangeListener<TraditionalWorkQueueCfg> 056{ 057 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 058 059 /** 060 * The maximum number of times to retry getting the next operation from the 061 * queue if an unexpected failure occurs. 062 */ 063 private static final int MAX_RETRY_COUNT = 5; 064 065 /** The set of worker threads that will be used to process this work queue. */ 066 private final ArrayList<TraditionalWorkerThread> workerThreads = new ArrayList<>(); 067 068 /** The number of operations that have been submitted to the work queue for processing. */ 069 private AtomicLong opsSubmitted; 070 071 /** 072 * The number of times that an attempt to submit a new request has been 073 * rejected because the work queue is already at its maximum capacity. 074 */ 075 private AtomicLong queueFullRejects; 076 077 /** 078 * Indicates whether one or more of the worker threads needs to be killed at 079 * the next convenient opportunity. 080 */ 081 private boolean killThreads; 082 083 /** Indicates whether the Directory Server is shutting down. */ 084 private boolean shutdownRequested; 085 086 /** The thread number used for the last worker thread that was created. */ 087 private int lastThreadNumber; 088 089 /** 090 * The maximum number of pending requests that this work queue will allow 091 * before it will start rejecting them. 092 */ 093 private int maxCapacity; 094 095 /** 096 * The number of worker threads that should be active (or will be shortly if a 097 * configuration change has not been completely applied). 098 */ 099 private int numWorkerThreads; 100 101 /** 102 * The queue overflow policy: true indicates that operations will be blocked 103 * until the queue has available capacity, otherwise operations will be 104 * rejected. 105 * <p> 106 * This is hard-coded to true for now because a reject on full policy does not 107 * seem to have a valid use case. 108 * </p> 109 */ 110 private final boolean isBlocking = true; 111 112 /** The queue that will be used to actually hold the pending operations. */ 113 private LinkedBlockingQueue<Operation> opQueue; 114 115 /** 116 * The lock used to provide threadsafe access for the queue, used for 117 * non-config changes. 118 */ 119 private final ReadLock queueReadLock; 120 121 /** 122 * The lock used to provide threadsafe access for the queue, used for config 123 * changes. 124 */ 125 private final WriteLock queueWriteLock; 126 { 127 ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 128 queueReadLock = lock.readLock(); 129 queueWriteLock = lock.writeLock(); 130 } 131 132 133 134 /** 135 * Creates a new instance of this work queue. All initialization should be 136 * performed in the <CODE>initializeWorkQueue</CODE> method. 137 */ 138 public TraditionalWorkQueue() 139 { 140 // No implementation should be performed here. 141 } 142 143 144 145 /** {@inheritDoc} */ 146 @Override 147 public void initializeWorkQueue(TraditionalWorkQueueCfg configuration) 148 throws ConfigException, InitializationException 149 { 150 queueWriteLock.lock(); 151 try 152 { 153 shutdownRequested = false; 154 killThreads = false; 155 opsSubmitted = new AtomicLong(0); 156 queueFullRejects = new AtomicLong(0); 157 158 // Register to be notified of any configuration changes. 159 configuration.addTraditionalChangeListener(this); 160 161 // Get the necessary configuration from the provided entry. 162 numWorkerThreads = 163 computeNumWorkerThreads(configuration.getNumWorkerThreads()); 164 maxCapacity = configuration.getMaxWorkQueueCapacity(); 165 166 // Create the actual work queue. 167 if (maxCapacity > 0) 168 { 169 opQueue = new LinkedBlockingQueue<>(maxCapacity); 170 } 171 else 172 { 173 // This will never be the case, since the configuration definition 174 // ensures that the capacity is always finite. 175 opQueue = new LinkedBlockingQueue<>(); 176 } 177 178 // Create the set of worker threads that should be used to service the 179 // work queue. 180 for (lastThreadNumber = 0; lastThreadNumber < numWorkerThreads; 181 lastThreadNumber++) 182 { 183 TraditionalWorkerThread t = new TraditionalWorkerThread(this, 184 lastThreadNumber); 185 t.start(); 186 workerThreads.add(t); 187 } 188 189 // Create and register a monitor provider for the work queue. 190 try 191 { 192 TraditionalWorkQueueMonitor monitor = new TraditionalWorkQueueMonitor( 193 this); 194 monitor.initializeMonitorProvider(null); 195 DirectoryServer.registerMonitorProvider(monitor); 196 } 197 catch (Exception e) 198 { 199 logger.traceException(e); 200 logger.error(ERR_CONFIG_WORK_QUEUE_CANNOT_CREATE_MONITOR, TraditionalWorkQueueMonitor.class, e); 201 } 202 } 203 finally 204 { 205 queueWriteLock.unlock(); 206 } 207 } 208 209 210 211 /** {@inheritDoc} */ 212 @Override 213 public void finalizeWorkQueue(LocalizableMessage reason) 214 { 215 queueWriteLock.lock(); 216 try 217 { 218 shutdownRequested = true; 219 } 220 finally 221 { 222 queueWriteLock.unlock(); 223 } 224 225 // From now on no more operations can be enqueued or dequeued. 226 227 // Send responses to any operations in the pending queue to indicate that 228 // they won't be processed because the server is shutting down. 229 CancelRequest cancelRequest = new CancelRequest(true, reason); 230 ArrayList<Operation> pendingOperations = new ArrayList<>(); 231 opQueue.removeAll(pendingOperations); 232 for (Operation o : pendingOperations) 233 { 234 try 235 { 236 // The operation has no chance of responding to the cancel 237 // request so avoid waiting for a cancel response. 238 if (o.getCancelResult() == null) 239 { 240 o.abort(cancelRequest); 241 } 242 } 243 catch (Exception e) 244 { 245 logger.traceException(e); 246 logger.warn(WARN_QUEUE_UNABLE_TO_CANCEL, o, e); 247 } 248 } 249 250 // Notify all the worker threads of the shutdown. 251 for (TraditionalWorkerThread t : workerThreads) 252 { 253 try 254 { 255 t.shutDown(); 256 } 257 catch (Exception e) 258 { 259 logger.traceException(e); 260 logger.warn(WARN_QUEUE_UNABLE_TO_NOTIFY_THREAD, t.getName(), e); 261 } 262 } 263 } 264 265 266 267 /** 268 * Indicates whether this work queue has received a request to shut down. 269 * 270 * @return <CODE>true</CODE> if the work queue has recieved a request to shut 271 * down, or <CODE>false</CODE> if not. 272 */ 273 public boolean shutdownRequested() 274 { 275 queueReadLock.lock(); 276 try 277 { 278 return shutdownRequested; 279 } 280 finally 281 { 282 queueReadLock.unlock(); 283 } 284 } 285 286 287 288 /** 289 * Submits an operation to be processed by one of the worker threads 290 * associated with this work queue. 291 * 292 * @param operation 293 * The operation to be processed. 294 * @throws DirectoryException 295 * If the provided operation is not accepted for some reason (e.g., 296 * if the server is shutting down or the pending operation queue is 297 * already at its maximum capacity). 298 */ 299 @Override 300 public void submitOperation(Operation operation) throws DirectoryException 301 { 302 submitOperation(operation, isBlocking); 303 } 304 305 /** {@inheritDoc} */ 306 @Override 307 public boolean trySubmitOperation(Operation operation) 308 throws DirectoryException 309 { 310 try 311 { 312 submitOperation(operation, false); 313 return true; 314 } 315 catch (DirectoryException e) 316 { 317 if (ResultCode.BUSY == e.getResultCode()) 318 { 319 return false; 320 } 321 throw e; 322 } 323 } 324 325 private void submitOperation(Operation operation, 326 boolean blockEnqueuingWhenFull) throws DirectoryException 327 { 328 queueReadLock.lock(); 329 try 330 { 331 if (shutdownRequested) 332 { 333 LocalizableMessage message = WARN_OP_REJECTED_BY_SHUTDOWN.get(); 334 throw new DirectoryException(ResultCode.UNAVAILABLE, message); 335 } 336 337 if (blockEnqueuingWhenFull) 338 { 339 try 340 { 341 // If the queue is full and there is an administrative change taking 342 // place then starvation could arise: this thread will hold the read 343 // lock, the admin thread will be waiting on the write lock, and the 344 // worker threads may be queued behind the admin thread. Since the 345 // worker threads cannot run, the queue will never empty and allow 346 // this thread to proceed. To help things out we can periodically 347 // yield the read lock when the queue is full. 348 while (!opQueue.offer(operation, 1, TimeUnit.SECONDS)) 349 { 350 queueReadLock.unlock(); 351 Thread.yield(); 352 queueReadLock.lock(); 353 354 if (shutdownRequested) 355 { 356 LocalizableMessage message = WARN_OP_REJECTED_BY_SHUTDOWN.get(); 357 throw new DirectoryException(ResultCode.UNAVAILABLE, message); 358 } 359 } 360 } 361 catch (InterruptedException e) 362 { 363 // We cannot handle the interruption here. Reject the request and 364 // re-interrupt this thread. 365 Thread.currentThread().interrupt(); 366 367 queueFullRejects.incrementAndGet(); 368 369 LocalizableMessage message = WARN_OP_REJECTED_BY_QUEUE_INTERRUPT.get(); 370 throw new DirectoryException(ResultCode.BUSY, message); 371 } 372 } 373 else 374 { 375 if (!opQueue.offer(operation)) 376 { 377 queueFullRejects.incrementAndGet(); 378 379 LocalizableMessage message = WARN_OP_REJECTED_BY_QUEUE_FULL.get(maxCapacity); 380 throw new DirectoryException(ResultCode.BUSY, message); 381 } 382 } 383 384 opsSubmitted.incrementAndGet(); 385 } 386 finally 387 { 388 queueReadLock.unlock(); 389 } 390 } 391 392 393 394 /** 395 * Retrieves the next operation that should be processed by one of the worker 396 * threads, blocking if necessary until a new request arrives. This method 397 * should only be called by a worker thread associated with this work queue. 398 * 399 * @param workerThread 400 * The worker thread that is requesting the operation. 401 * @return The next operation that should be processed, or <CODE>null</CODE> 402 * if the server is shutting down and no more operations will be 403 * processed. 404 */ 405 public Operation nextOperation(TraditionalWorkerThread workerThread) 406 { 407 return retryNextOperation(workerThread, 0); 408 } 409 410 411 412 /** 413 * Retrieves the next operation that should be processed by one of the worker 414 * threads following a previous failure attempt. A maximum of five consecutive 415 * failures will be allowed before returning <CODE>null</CODE>, which will 416 * cause the associated thread to exit. 417 * 418 * @param workerThread 419 * The worker thread that is requesting the operation. 420 * @param numFailures 421 * The number of consecutive failures that the worker thread has 422 * experienced so far. If this gets too high, then this method will 423 * return <CODE>null</CODE> rather than retrying. 424 * @return The next operation that should be processed, or <CODE>null</CODE> 425 * if the server is shutting down and no more operations will be 426 * processed, or if there have been too many consecutive failures. 427 */ 428 private Operation retryNextOperation(TraditionalWorkerThread workerThread, 429 int numFailures) 430 { 431 // See if we should kill off this thread. This could be necessary if the 432 // number of worker threads has been decreased with the server online. If 433 // so, then return null and the thread will exit. 434 queueReadLock.lock(); 435 try 436 { 437 if (shutdownRequested) 438 { 439 return null; 440 } 441 442 if (killThreads && tryKillThisWorkerThread(workerThread)) 443 { 444 return null; 445 } 446 447 if (numFailures > MAX_RETRY_COUNT) 448 { 449 logger.error(ERR_CONFIG_WORK_QUEUE_TOO_MANY_FAILURES, Thread 450 .currentThread().getName(), numFailures, MAX_RETRY_COUNT); 451 452 return null; 453 } 454 455 while (true) 456 { 457 Operation nextOperation = opQueue.poll(5, TimeUnit.SECONDS); 458 if (nextOperation != null) 459 { 460 return nextOperation; 461 } 462 463 // There was no work to do in the specified length of time. Release the 464 // read lock allowing shutdown or config changes to proceed and then see 465 // if we should give up or check again. 466 queueReadLock.unlock(); 467 Thread.yield(); 468 queueReadLock.lock(); 469 470 if (shutdownRequested) 471 { 472 return null; 473 } 474 475 if (killThreads && tryKillThisWorkerThread(workerThread)) 476 { 477 return null; 478 } 479 } 480 } 481 catch (InterruptedException ie) 482 { 483 // This is somewhat expected so don't log. 484 // assert debugException(CLASS_NAME, "retryNextOperation", ie); 485 486 // If this occurs, then the worker thread must have been interrupted for 487 // some reason. This could be because the Directory Server is shutting 488 // down, in which case we should return null. 489 if (shutdownRequested) 490 { 491 return null; 492 } 493 494 // If we've gotten here, then the worker thread was interrupted for some 495 // other reason. This should not happen, and we need to log a message. 496 logger.warn(WARN_WORKER_INTERRUPTED_WITHOUT_SHUTDOWN, Thread.currentThread().getName(), ie); 497 } 498 catch (Exception e) 499 { 500 logger.traceException(e); 501 502 // This should not happen. The only recourse we have is to log a message 503 // and try again. 504 logger.warn(WARN_WORKER_WAITING_UNCAUGHT_EXCEPTION, Thread.currentThread().getName(), e); 505 } 506 finally 507 { 508 queueReadLock.unlock(); 509 } 510 511 // An exception has occurred - retry. 512 return retryNextOperation(workerThread, numFailures + 1); 513 } 514 515 516 517 /** 518 * Kills this worker thread if needed. This method assumes that the read lock 519 * is already taken and ensure that it is taken on exit. 520 * 521 * @param workerThread 522 * The worker thread associated with this thread. 523 * @return {@code true} if this thread was killed or is about to be killed as 524 * a result of shutdown. 525 */ 526 private boolean tryKillThisWorkerThread(TraditionalWorkerThread workerThread) 527 { 528 queueReadLock.unlock(); 529 queueWriteLock.lock(); 530 try 531 { 532 if (shutdownRequested) 533 { 534 // Shutdown may have been requested between unlock/lock. This thread is 535 // about to shutdown anyway, so return true. 536 return true; 537 } 538 539 int currentThreads = workerThreads.size(); 540 if (currentThreads > numWorkerThreads) 541 { 542 if (workerThreads.remove(Thread.currentThread())) 543 { 544 currentThreads--; 545 } 546 547 if (currentThreads <= numWorkerThreads) 548 { 549 killThreads = false; 550 } 551 552 workerThread.setStoppedByReducedThreadNumber(); 553 return true; 554 } 555 } 556 finally 557 { 558 queueWriteLock.unlock(); 559 queueReadLock.lock(); 560 561 if (shutdownRequested) 562 { 563 // Shutdown may have been requested between unlock/lock. This thread is 564 // about to shutdown anyway, so return true. 565 return true; 566 } 567 } 568 return false; 569 } 570 571 572 573 /** 574 * Retrieves the total number of operations that have been successfully 575 * submitted to this work queue for processing since server startup. This does 576 * not include operations that have been rejected for some reason like the 577 * queue already at its maximum capacity. 578 * 579 * @return The total number of operations that have been successfully 580 * submitted to this work queue since startup. 581 */ 582 public long getOpsSubmitted() 583 { 584 return opsSubmitted.longValue(); 585 } 586 587 588 589 /** 590 * Retrieves the total number of operations that have been rejected because 591 * the work queue was already at its maximum capacity. 592 * 593 * @return The total number of operations that have been rejected because the 594 * work queue was already at its maximum capacity. 595 */ 596 public long getOpsRejectedDueToQueueFull() 597 { 598 return queueFullRejects.longValue(); 599 } 600 601 602 603 /** 604 * Retrieves the number of pending operations in the queue that have not yet 605 * been picked up for processing. Note that this method is not a constant-time 606 * operation and can be relatively inefficient, so it should be used 607 * sparingly. 608 * 609 * @return The number of pending operations in the queue that have not yet 610 * been picked up for processing. 611 */ 612 public int size() 613 { 614 queueReadLock.lock(); 615 try 616 { 617 return opQueue.size(); 618 } 619 finally 620 { 621 queueReadLock.unlock(); 622 } 623 } 624 625 626 627 /** {@inheritDoc} */ 628 @Override 629 public boolean isConfigurationChangeAcceptable( 630 TraditionalWorkQueueCfg configuration, List<LocalizableMessage> unacceptableReasons) 631 { 632 return true; 633 } 634 635 636 637 /** {@inheritDoc} */ 638 @Override 639 public ConfigChangeResult applyConfigurationChange( 640 TraditionalWorkQueueCfg configuration) 641 { 642 int newNumThreads = 643 computeNumWorkerThreads(configuration.getNumWorkerThreads()); 644 int newMaxCapacity = configuration.getMaxWorkQueueCapacity(); 645 646 // Apply a change to the number of worker threads if appropriate. 647 int currentThreads = workerThreads.size(); 648 if (newNumThreads != currentThreads) 649 { 650 queueWriteLock.lock(); 651 try 652 { 653 int threadsToAdd = newNumThreads - currentThreads; 654 if (threadsToAdd > 0) 655 { 656 for (int i = 0; i < threadsToAdd; i++) 657 { 658 TraditionalWorkerThread t = new TraditionalWorkerThread(this, 659 lastThreadNumber++); 660 workerThreads.add(t); 661 t.start(); 662 } 663 664 killThreads = false; 665 } 666 else 667 { 668 killThreads = true; 669 } 670 671 numWorkerThreads = newNumThreads; 672 } 673 catch (Exception e) 674 { 675 logger.traceException(e); 676 } 677 finally 678 { 679 queueWriteLock.unlock(); 680 } 681 } 682 683 684 // Apply a change to the maximum capacity if appropriate. Since we can't 685 // change capacity on the fly, then we'll have to create a new queue and 686 // transfer any remaining items into it. Any thread that is waiting on the 687 // original queue will time out after at most a few seconds and further 688 // checks will be against the new queue. 689 if (newMaxCapacity != maxCapacity) 690 { 691 // First switch the queue with the exclusive lock. 692 queueWriteLock.lock(); 693 LinkedBlockingQueue<Operation> oldOpQueue; 694 try 695 { 696 LinkedBlockingQueue<Operation> newOpQueue = null; 697 if (newMaxCapacity > 0) 698 { 699 newOpQueue = new LinkedBlockingQueue<>(newMaxCapacity); 700 } 701 else 702 { 703 newOpQueue = new LinkedBlockingQueue<>(); 704 } 705 706 oldOpQueue = opQueue; 707 opQueue = newOpQueue; 708 709 maxCapacity = newMaxCapacity; 710 } 711 finally 712 { 713 queueWriteLock.unlock(); 714 } 715 716 // Now resubmit any pending requests - we'll need the shared lock. 717 Operation pendingOperation = null; 718 queueReadLock.lock(); 719 try 720 { 721 // We have to be careful when adding any existing pending operations 722 // because the new capacity could be less than what was already 723 // backlogged in the previous queue. If that happens, we may have to 724 // loop a few times to get everything in there. 725 while ((pendingOperation = oldOpQueue.poll()) != null) 726 { 727 opQueue.put(pendingOperation); 728 } 729 } 730 catch (InterruptedException e) 731 { 732 // We cannot handle the interruption here. Cancel pending requests and 733 // re-interrupt this thread. 734 Thread.currentThread().interrupt(); 735 736 LocalizableMessage message = WARN_OP_REJECTED_BY_QUEUE_INTERRUPT.get(); 737 CancelRequest cancelRequest = new CancelRequest(true, message); 738 if (pendingOperation != null) 739 { 740 pendingOperation.abort(cancelRequest); 741 } 742 while ((pendingOperation = oldOpQueue.poll()) != null) 743 { 744 pendingOperation.abort(cancelRequest); 745 } 746 } 747 finally 748 { 749 queueReadLock.unlock(); 750 } 751 } 752 753 return new ConfigChangeResult(); 754 } 755 756 757 758 /** {@inheritDoc} */ 759 @Override 760 public boolean isIdle() 761 { 762 queueReadLock.lock(); 763 try 764 { 765 if (!opQueue.isEmpty()) 766 { 767 return false; 768 } 769 770 for (TraditionalWorkerThread t : workerThreads) 771 { 772 if (t.isActive()) 773 { 774 return false; 775 } 776 } 777 778 return true; 779 } 780 finally 781 { 782 queueReadLock.unlock(); 783 } 784 } 785 786 /** 787 * Return the number of worker threads used by this WorkQueue. 788 * 789 * @return the number of worker threads used by this WorkQueue 790 */ 791 @Override 792 public int getNumWorkerThreads() 793 { 794 return this.numWorkerThreads; 795 } 796}