001/*
002 * Copyright 2018-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2018-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2018-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util;
037
038
039
040import java.io.Closeable;
041import java.util.concurrent.TimeUnit;
042import java.util.concurrent.TimeoutException;
043import java.util.concurrent.locks.ReentrantReadWriteLock;
044
045import static com.unboundid.util.UtilityMessages.*;
046
047
048
049/**
050 * This class provides an implementation of a reentrant read-write lock that can
051 * be used with the Java try-with-resources facility.  With a read-write lock,
052 * either exactly one thread can hold the write lock while no other threads hold
053 * read locks, or zero or more threads can hold read locks while no thread holds
054 * the write lock.  The one exception to this policy is that the thread that
055 * holds the write lock can downgrade will be permitted to acquire a read lock
056 * before it releases the write lock to downgrade from a write lock to a read
057 * lock while ensuring that no other thread is permitted to acquire the write
058 * lock while it is in the process of downgrading.
059 * <BR><BR>
060 * This class does not implement the
061 * {@code java.util.concurrent.locks.ReadWriteLock} interface in order to ensure
062 * that it can only be used through the try-with-resources mechanism, but it
063 * uses a {@code java.util.concurrent.locks.ReentrantReadWriteLock} behind the
064 * scenes to provide its functionality.
065 * <BR><BR>
066 * <H2>Example</H2>
067 * The following example demonstrates how to use this lock using the Java
068 * try-with-resources facility:
069 * <PRE>
070 * // Wait for up to 5 seconds to acquire the lock.
071 * try (CloseableReadWriteLock.WriteLock writeLock =
072 *           closeableReadWriteLock.tryLock(5L, TimeUnit.SECONDS))
073 * {
074 *   // NOTE:  If you don't reference the lock object inside the try block, the
075 *   // compiler will issue a warning.
076 *   writeLock.avoidCompilerWarning();
077 *
078 *   // Do something while the lock is held.  The lock will automatically be
079 *   // released once code execution leaves this block.
080 * }
081 * catch (final InterruptedException e)
082 * {
083 *   // The thread was interrupted before the lock could be acquired.
084 * }
085 * catch (final TimeoutException)
086 * {
087 *   // The lock could not be acquired within the specified 5-second timeout.
088 * }
089 * </PRE>
090 */
091@Mutable()
092@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
093public final class CloseableReadWriteLock
094{
095  // The closeable read lock.
096  private final ReadLock readLock;
097
098  // The Java lock that is used behind the scenes for all locking functionality.
099  private final ReentrantReadWriteLock readWriteLock;
100
101  // The closeable write lock.
102  private final WriteLock writeLock;
103
104
105
106  /**
107   * Creates a new instance of this read-write lock with a non-fair ordering
108   * policy.
109   */
110  public CloseableReadWriteLock()
111  {
112    this(false);
113  }
114
115
116
117  /**
118   * Creates a new instance of this read-write lock with the specified ordering
119   * policy.
120   *
121   * @param  fair  Indicates whether the lock should use fair ordering.  If
122   *               {@code true}, then if multiple threads are waiting on the
123   *               lock, then the one that has been waiting the longest is the
124   *               one that will get it.  If {@code false}, then no guarantee
125   *               will be made about the order.  Fair ordering can incur a
126   *               performance penalty.
127   */
128  public CloseableReadWriteLock(final boolean fair)
129  {
130    readWriteLock = new ReentrantReadWriteLock(fair);
131    readLock = new ReadLock(readWriteLock.readLock());
132    writeLock = new WriteLock(readWriteLock.writeLock());
133  }
134
135
136
137  /**
138   * Acquires the write lock, blocking until the lock is available.
139   *
140   * @return  The {@link WriteLock} instance that may be used to perform the
141   *          unlock via the try-with-resources facility.
142   */
143  public WriteLock lockWrite()
144  {
145    readWriteLock.writeLock().lock();
146    return writeLock;
147  }
148
149
150
151  /**
152   * Acquires the write lock, blocking until the lock is available.
153   *
154   * @return  The {@link WriteLock} instance that may be used to perform the
155   *          unlock via the try-with-resources facility.
156   *
157   * @throws  InterruptedException  If the thread is interrupted while waiting
158   *                                to acquire the lock.
159   */
160  public WriteLock lockWriteInterruptibly()
161         throws InterruptedException
162  {
163    readWriteLock.writeLock().lockInterruptibly();
164    return writeLock;
165  }
166
167
168
169  /**
170   * Tries to acquire the write lock, waiting up to the specified length of time
171   * for it to become available.
172   *
173   * @param  waitTime  The maximum length of time to wait for the lock.  It must
174   *                   be greater than zero.
175   * @param  timeUnit  The time unit that should be used when evaluating the
176   *                   {@code waitTime} value.
177   *
178   * @return  The {@link WriteLock} instance that may be used to perform the
179   *          unlock via the try-with-resources facility.
180   *
181   * @throws  InterruptedException  If the thread is interrupted while waiting
182   *                                to acquire the lock.
183   *
184   * @throws  TimeoutException  If the lock could not be acquired within the
185   *                            specified length of time.
186   */
187  public WriteLock tryLockWrite(final long waitTime, final TimeUnit timeUnit)
188         throws InterruptedException, TimeoutException
189  {
190    if (waitTime <= 0)
191    {
192      Validator.violation(
193           "CloseableLock.tryLockWrite.waitTime must be greater than zero.  " +
194                "The provided value was " + waitTime);
195    }
196
197    if (readWriteLock.writeLock().tryLock(waitTime, timeUnit))
198    {
199      return writeLock;
200    }
201    else
202    {
203      throw new TimeoutException(
204           ERR_CLOSEABLE_RW_LOCK_TRY_LOCK_WRITE_TIMEOUT.get(
205                StaticUtils.millisToHumanReadableDuration(
206                     timeUnit.toMillis(waitTime))));
207    }
208  }
209
210
211
212  /**
213   * Acquires a read lock, blocking until the lock is available.
214   *
215   * @return  The {@link ReadLock} instance that may be used to perform the
216   *          unlock via the try-with-resources facility.
217   */
218  public ReadLock lockRead()
219  {
220    readWriteLock.readLock().lock();
221    return readLock;
222  }
223
224
225
226  /**
227   * Acquires a read lock, blocking until the lock is available.
228   *
229   * @return  The {@link ReadLock} instance that may be used to perform the
230   *          unlock via the try-with-resources facility.
231   *
232   * @throws  InterruptedException  If the thread is interrupted while waiting
233   *                                to acquire the lock.
234   */
235  public ReadLock lockReadInterruptibly()
236         throws InterruptedException
237  {
238    readWriteLock.readLock().lockInterruptibly();
239    return readLock;
240  }
241
242
243
244  /**
245   * Tries to acquire a read lock, waiting up to the specified length of time
246   * for it to become available.
247   *
248   * @param  waitTime  The maximum length of time to wait for the lock.  It must
249   *                   be greater than zero.
250   * @param  timeUnit  The time unit that should be used when evaluating the
251   *                   {@code waitTime} value.
252   *
253   * @return  The {@link ReadLock} instance that may be used to perform the
254   *          unlock via the try-with-resources facility.
255   *
256   * @throws  InterruptedException  If the thread is interrupted while waiting
257   *                                to acquire the lock.
258   *
259   * @throws  TimeoutException  If the lock could not be acquired within the
260   *                            specified length of time.
261   */
262  public ReadLock tryLockRead(final long waitTime, final TimeUnit timeUnit)
263         throws InterruptedException, TimeoutException
264  {
265    if (waitTime <= 0)
266    {
267      Validator.violation(
268           "CloseableLock.tryLockRead.waitTime must be greater than zero.  " +
269                "The provided value was " + waitTime);
270    }
271
272    if (readWriteLock.readLock().tryLock(waitTime, timeUnit))
273    {
274      return readLock;
275    }
276    else
277    {
278      throw new TimeoutException(
279           ERR_CLOSEABLE_RW_LOCK_TRY_LOCK_READ_TIMEOUT.get(
280                StaticUtils.millisToHumanReadableDuration(
281                     timeUnit.toMillis(waitTime))));
282    }
283  }
284
285
286
287  /**
288   * Indicates whether this lock uses fair ordering.
289   *
290   * @return  {@code true} if this lock uses fair ordering, or {@code false} if
291   *          not.
292   */
293  public boolean isFair()
294  {
295    return readWriteLock.isFair();
296  }
297
298
299
300  /**
301   * Indicates whether the write lock is currently held by any thread.
302   *
303   * @return  {@code true} if the write lock is currently held by any thread, or
304   *          {@code false} if not.
305   */
306  public boolean isWriteLocked()
307  {
308    return readWriteLock.isWriteLocked();
309  }
310
311
312
313  /**
314   * Indicates whether the write lock is currently held by the current thread.
315   *
316   * @return  {@code true} if the write lock is currently held by the current
317   *          thread, or {@code false} if not.
318   */
319  public boolean isWriteLockedByCurrentThread()
320  {
321    return readWriteLock.isWriteLockedByCurrentThread();
322  }
323
324
325
326  /**
327   * Retrieves the number of holds that the current thread has on the write
328   * lock.
329   *
330   * @return  The number of holds that the current thread has on the write lock.
331   */
332  public int getWriteHoldCount()
333  {
334    return readWriteLock.getWriteHoldCount();
335  }
336
337
338
339  /**
340   * Retrieves the number of threads that currently hold the read lock.
341   *
342   * @return  The number of threads that currently hold the read lock.
343   */
344  public int getReadLockCount()
345  {
346    return readWriteLock.getReadLockCount();
347  }
348
349
350
351  /**
352   * Retrieves the number of holds that the current thread has on the read lock.
353   *
354   * @return  The number of holds that the current thread has on the read lock.
355   */
356  public int getReadHoldCount()
357  {
358    return readWriteLock.getReadHoldCount();
359  }
360
361
362
363  /**
364   * Indicates whether any threads are currently waiting to acquire either the
365   * write or read lock.
366   *
367   * @return  {@code true} if any threads are currently waiting to acquire
368   *          either the write or read lock, or {@code false} if not.
369   */
370  public boolean hasQueuedThreads()
371  {
372    return readWriteLock.hasQueuedThreads();
373  }
374
375
376
377  /**
378   * Indicates whether the specified thread is currently waiting to acquire
379   * either the write or read lock.
380   *
381   * @param  thread  The thread for which to make the determination.  It must
382   *                 not be {@code null}.
383   *
384   * @return  {@code true} if the specified thread is currently waiting to
385   *          acquire either the write or read lock, or {@code false} if not.
386   */
387  public boolean hasQueuedThread(final Thread thread)
388  {
389    return readWriteLock.hasQueuedThread(thread);
390  }
391
392
393
394  /**
395   * Retrieves an estimate of the number of threads currently waiting to acquire
396   * either the write or read lock.
397   *
398   * @return  An estimate of the number of threads currently waiting to acquire
399   *          either the write or read lock.
400   */
401  public int getQueueLength()
402  {
403    return readWriteLock.getQueueLength();
404  }
405
406
407
408  /**
409   * Retrieves a string representation of this read-write lock.
410   *
411   * @return  A string representation of this read-write lock.
412   */
413  @Override()
414  public String toString()
415  {
416    return "CloseableReadWriteLock(lock=" + readWriteLock.toString() + ')';
417  }
418
419
420
421  /**
422   * This class provides a {@code Closeable} implementation that may be used to
423   * unlock a {@link CloseableReadWriteLock}'s read lock via Java's
424   * try-with-resources facility.
425   */
426  public final class ReadLock
427         implements Closeable
428  {
429    // The associated read lock.
430    private final ReentrantReadWriteLock.ReadLock lock;
431
432
433
434    /**
435     * Creates a new instance with the provided lock.
436     *
437     * @param  lock  The lock that will be unlocked when the [@link #close()}
438     *               method is called.  This must not be {@code null}.
439     */
440    private ReadLock(final ReentrantReadWriteLock.ReadLock lock)
441    {
442      this.lock = lock;
443    }
444
445
446
447    /**
448     * This method does nothing.  However, calling it inside a try block when
449     * used in the try-with-resources framework can help avoid a compiler
450     * warning that the JVM will give you if you don't reference the
451     * {@code Closeable} object inside the try block.
452     */
453    public void avoidCompilerWarning()
454    {
455      // No implementation is required.
456    }
457
458
459
460    /**
461     * Unlocks the associated lock.
462     */
463    @Override()
464    public void close()
465    {
466      lock.unlock();
467    }
468  }
469
470
471
472  /**
473   * This class provides a {@code Closeable} implementation that may be used to
474   * unlock a {@link CloseableReadWriteLock}'s write lock via Java's
475   * try-with-resources facility.
476   */
477  public final class WriteLock
478         implements Closeable
479  {
480    // The associated read lock.
481    private final ReentrantReadWriteLock.WriteLock lock;
482
483
484
485    /**
486     * Creates a new instance with the provided lock.
487     *
488     * @param  lock  The lock that will be unlocked when the [@link #close()}
489     *               method is called.  This must not be {@code null}.
490     */
491    private WriteLock(final ReentrantReadWriteLock.WriteLock lock)
492    {
493      this.lock = lock;
494    }
495
496
497
498    /**
499     * This method does nothing.  However, calling it inside a try block when
500     * used in the try-with-resources framework can help avoid a compiler
501     * warning that the JVM will give you if you don't reference the
502     * {@code Closeable} object inside the try block.
503     */
504    public void avoidCompilerWarning()
505    {
506      // No implementation is required.
507    }
508
509
510
511    /**
512     * Unlocks the associated lock.
513     */
514    @Override()
515    public void close()
516    {
517      lock.unlock();
518    }
519  }
520}