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 2013-2014 ForgeRock AS.
015 */
016package org.opends.server.core;
017
018import java.util.concurrent.atomic.AtomicInteger;
019
020import org.opends.server.types.DirectoryException;
021import org.opends.server.types.Operation;
022
023/**
024 * A QueueingStrategy that concurrently enqueues a bounded number of operations
025 * to the DirectoryServer work queue. If the maximum number of concurrently
026 * enqueued operations has been reached or if the work queue if full, then the
027 * operation will be executed on the current thread.
028 */
029public class BoundedWorkQueueStrategy implements QueueingStrategy
030{
031
032  /**
033   * The number of concurrently running operations for this
034   * BoundedWorkQueueStrategy.
035   */
036  private final AtomicInteger nbRunningOperations = new AtomicInteger(0);
037  /** Maximum number of concurrent operations. 0 means "unlimited". */
038  private final int maxNbConcurrentOperations;
039
040  /**
041   * Constructor for BoundedWorkQueueStrategy.
042   *
043   * @param maxNbConcurrentOperations
044   *          the maximum number of operations that can be concurrently enqueued
045   *          to the DirectoryServer work queue
046   */
047  public BoundedWorkQueueStrategy(Integer maxNbConcurrentOperations)
048  {
049    if (maxNbConcurrentOperations != null)
050    {
051      this.maxNbConcurrentOperations = maxNbConcurrentOperations;
052    }
053    else
054    {
055      int cpus = Runtime.getRuntime().availableProcessors();
056      this.maxNbConcurrentOperations =
057          Math.max(cpus, getNumWorkerThreads() * 25 / 100);
058    }
059  }
060
061  /**
062   * Return the maximum number of worker threads that can be used by the
063   * WorkQueue (The WorkQueue could have a thread pool which adjusts its size).
064   *
065   * @return the maximum number of worker threads that can be used by the
066   *         WorkQueue
067   */
068  protected int getNumWorkerThreads()
069  {
070    return DirectoryServer.getWorkQueue().getNumWorkerThreads();
071  }
072
073  /** {@inheritDoc} */
074  @Override
075  public void enqueueRequest(final Operation operation)
076      throws DirectoryException
077  {
078    if (!operation.getClientConnection().isConnectionValid())
079    {
080      // do not bother enqueueing
081      return;
082    }
083
084    if (maxNbConcurrentOperations == 0)
085    { // unlimited concurrent operations
086      if (!tryEnqueueRequest(operation))
087      { // avoid potential deadlocks by running in the current thread
088        operation.run();
089      }
090    }
091    else if (nbRunningOperations.getAndIncrement() > maxNbConcurrentOperations
092        || !tryEnqueueRequest(wrap(operation)))
093    { // avoid potential deadlocks by running in the current thread
094      try
095      {
096        operation.run();
097      }
098      finally
099      {
100        // only decrement when the operation is run synchronously.
101        // Otherwise it'll be decremented twice (once more in the wrapper).
102        nbRunningOperations.decrementAndGet();
103      }
104    }
105  }
106
107  /**
108   * Tries to add the provided operation to the work queue if not full so that
109   * it will be processed by one of the worker threads.
110   *
111   * @param op
112   *          The operation to be added to the work queue.
113   * @return true if the operation could be enqueued, false otherwise
114   * @throws DirectoryException
115   *           If a problem prevents the operation from being added to the queue
116   *           (e.g., the queue is full).
117   */
118  protected boolean tryEnqueueRequest(Operation op) throws DirectoryException
119  {
120    return DirectoryServer.tryEnqueueRequest(op);
121  }
122
123  private Operation wrap(final Operation operation)
124  {
125    if (operation instanceof AbandonOperation)
126    {
127      return new AbandonOperationWrapper((AbandonOperation) operation)
128      {
129        @Override
130        public void run()
131        {
132          runWrapped(operation);
133        }
134      };
135    }
136    else if (operation instanceof AddOperation)
137    {
138      return new AddOperationWrapper((AddOperation) operation)
139      {
140        @Override
141        public void run()
142        {
143          runWrapped(operation);
144        }
145      };
146    }
147    else if (operation instanceof BindOperation)
148    {
149      return new BindOperationWrapper((BindOperation) operation)
150      {
151        @Override
152        public void run()
153        {
154          runWrapped(operation);
155        }
156      };
157    }
158    else if (operation instanceof CompareOperation)
159    {
160      return new CompareOperationWrapper((CompareOperation) operation)
161      {
162        @Override
163        public void run()
164        {
165          runWrapped(operation);
166        }
167      };
168    }
169    else if (operation instanceof DeleteOperation)
170    {
171      return new DeleteOperationWrapper((DeleteOperation) operation)
172      {
173        @Override
174        public void run()
175        {
176          runWrapped(operation);
177        }
178      };
179    }
180    else if (operation instanceof ExtendedOperation)
181    {
182      return new ExtendedOperationWrapper((ExtendedOperation) operation)
183      {
184        @Override
185        public void run()
186        {
187          runWrapped(operation);
188        }
189      };
190    }
191    else if (operation instanceof ModifyDNOperation)
192    {
193      return new ModifyDNOperationWrapper((ModifyDNOperation) operation)
194      {
195        @Override
196        public void run()
197        {
198          runWrapped(operation);
199        }
200      };
201    }
202    else if (operation instanceof ModifyOperation)
203    {
204      return new ModifyOperationWrapper((ModifyOperation) operation)
205      {
206        @Override
207        public void run()
208        {
209          runWrapped(operation);
210        }
211      };
212    }
213    else if (operation instanceof SearchOperation)
214    {
215      return new SearchOperationWrapper((SearchOperation) operation)
216      {
217        @Override
218        public void run()
219        {
220          runWrapped(operation);
221        }
222      };
223    }
224    else if (operation instanceof UnbindOperation)
225    {
226      return new UnbindOperationWrapper((UnbindOperation) operation)
227      {
228        @Override
229        public void run()
230        {
231          runWrapped(operation);
232        }
233      };
234    }
235    else
236    {
237      throw new RuntimeException(
238          "Not implemented for " + operation == null ? null : operation
239              .getClass().getName());
240    }
241  }
242
243  /**
244   * Execute the provided operation and decrement the number of currently
245   * running operations after it has finished executing.
246   *
247   * @param the
248   *          operation to execute
249   */
250  private void runWrapped(final Operation operation)
251  {
252    try
253    {
254      operation.run();
255    }
256    finally
257    {
258      nbRunningOperations.decrementAndGet();
259    }
260  }
261}