001/*
002 * Copyright 2018-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2018-2024 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-2024 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  @NotNull private final ReadLock readLock;
097
098  // The Java lock that is used behind the scenes for all locking functionality.
099  @NotNull private final ReentrantReadWriteLock readWriteLock;
100
101  // The closeable write lock.
102  @NotNull 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  @NotNull()
144  public WriteLock lockWrite()
145  {
146    readWriteLock.writeLock().lock();
147    return writeLock;
148  }
149
150
151
152  /**
153   * Acquires the write lock, blocking until the lock is available.
154   *
155   * @return  The {@link WriteLock} instance that may be used to perform the
156   *          unlock via the try-with-resources facility.
157   *
158   * @throws  InterruptedException  If the thread is interrupted while waiting
159   *                                to acquire the lock.
160   */
161  @NotNull()
162  public WriteLock lockWriteInterruptibly()
163         throws InterruptedException
164  {
165    readWriteLock.writeLock().lockInterruptibly();
166    return writeLock;
167  }
168
169
170
171  /**
172   * Tries to acquire the write lock, waiting up to the specified length of time
173   * for it to become available.
174   *
175   * @param  waitTime  The maximum length of time to wait for the lock.  It must
176   *                   be greater than zero.
177   * @param  timeUnit  The time unit that should be used when evaluating the
178   *                   {@code waitTime} value.
179   *
180   * @return  The {@link WriteLock} instance that may be used to perform the
181   *          unlock via the try-with-resources facility.
182   *
183   * @throws  InterruptedException  If the thread is interrupted while waiting
184   *                                to acquire the lock.
185   *
186   * @throws  TimeoutException  If the lock could not be acquired within the
187   *                            specified length of time.
188   */
189  @NotNull()
190  public WriteLock tryLockWrite(final long waitTime,
191                                @NotNull final TimeUnit timeUnit)
192         throws InterruptedException, TimeoutException
193  {
194    if (waitTime <= 0)
195    {
196      Validator.violation(
197           "CloseableLock.tryLockWrite.waitTime must be greater than zero.  " +
198                "The provided value was " + waitTime);
199    }
200
201    if (readWriteLock.writeLock().tryLock(waitTime, timeUnit))
202    {
203      return writeLock;
204    }
205    else
206    {
207      throw new TimeoutException(
208           ERR_CLOSEABLE_RW_LOCK_TRY_LOCK_WRITE_TIMEOUT.get(
209                StaticUtils.millisToHumanReadableDuration(
210                     timeUnit.toMillis(waitTime))));
211    }
212  }
213
214
215
216  /**
217   * Acquires a read lock, blocking until the lock is available.
218   *
219   * @return  The {@link ReadLock} instance that may be used to perform the
220   *          unlock via the try-with-resources facility.
221   */
222  @NotNull()
223  public ReadLock lockRead()
224  {
225    readWriteLock.readLock().lock();
226    return readLock;
227  }
228
229
230
231  /**
232   * Acquires a read lock, blocking until the lock is available.
233   *
234   * @return  The {@link ReadLock} instance that may be used to perform the
235   *          unlock via the try-with-resources facility.
236   *
237   * @throws  InterruptedException  If the thread is interrupted while waiting
238   *                                to acquire the lock.
239   */
240  @NotNull()
241  public ReadLock lockReadInterruptibly()
242         throws InterruptedException
243  {
244    readWriteLock.readLock().lockInterruptibly();
245    return readLock;
246  }
247
248
249
250  /**
251   * Tries to acquire a read lock, waiting up to the specified length of time
252   * for it to become available.
253   *
254   * @param  waitTime  The maximum length of time to wait for the lock.  It must
255   *                   be greater than zero.
256   * @param  timeUnit  The time unit that should be used when evaluating the
257   *                   {@code waitTime} value.
258   *
259   * @return  The {@link ReadLock} instance that may be used to perform the
260   *          unlock via the try-with-resources facility.
261   *
262   * @throws  InterruptedException  If the thread is interrupted while waiting
263   *                                to acquire the lock.
264   *
265   * @throws  TimeoutException  If the lock could not be acquired within the
266   *                            specified length of time.
267   */
268  @NotNull()
269  public ReadLock tryLockRead(final long waitTime,
270                              @NotNull final TimeUnit timeUnit)
271         throws InterruptedException, TimeoutException
272  {
273    if (waitTime <= 0)
274    {
275      Validator.violation(
276           "CloseableLock.tryLockRead.waitTime must be greater than zero.  " +
277                "The provided value was " + waitTime);
278    }
279
280    if (readWriteLock.readLock().tryLock(waitTime, timeUnit))
281    {
282      return readLock;
283    }
284    else
285    {
286      throw new TimeoutException(
287           ERR_CLOSEABLE_RW_LOCK_TRY_LOCK_READ_TIMEOUT.get(
288                StaticUtils.millisToHumanReadableDuration(
289                     timeUnit.toMillis(waitTime))));
290    }
291  }
292
293
294
295  /**
296   * Indicates whether this lock uses fair ordering.
297   *
298   * @return  {@code true} if this lock uses fair ordering, or {@code false} if
299   *          not.
300   */
301  public boolean isFair()
302  {
303    return readWriteLock.isFair();
304  }
305
306
307
308  /**
309   * Indicates whether the write lock is currently held by any thread.
310   *
311   * @return  {@code true} if the write lock is currently held by any thread, or
312   *          {@code false} if not.
313   */
314  public boolean isWriteLocked()
315  {
316    return readWriteLock.isWriteLocked();
317  }
318
319
320
321  /**
322   * Indicates whether the write lock is currently held by the current thread.
323   *
324   * @return  {@code true} if the write lock is currently held by the current
325   *          thread, or {@code false} if not.
326   */
327  public boolean isWriteLockedByCurrentThread()
328  {
329    return readWriteLock.isWriteLockedByCurrentThread();
330  }
331
332
333
334  /**
335   * Retrieves the number of holds that the current thread has on the write
336   * lock.
337   *
338   * @return  The number of holds that the current thread has on the write lock.
339   */
340  public int getWriteHoldCount()
341  {
342    return readWriteLock.getWriteHoldCount();
343  }
344
345
346
347  /**
348   * Retrieves the number of threads that currently hold the read lock.
349   *
350   * @return  The number of threads that currently hold the read lock.
351   */
352  public int getReadLockCount()
353  {
354    return readWriteLock.getReadLockCount();
355  }
356
357
358
359  /**
360   * Retrieves the number of holds that the current thread has on the read lock.
361   *
362   * @return  The number of holds that the current thread has on the read lock.
363   */
364  public int getReadHoldCount()
365  {
366    return readWriteLock.getReadHoldCount();
367  }
368
369
370
371  /**
372   * Indicates whether any threads are currently waiting to acquire either the
373   * write or read lock.
374   *
375   * @return  {@code true} if any threads are currently waiting to acquire
376   *          either the write or read lock, or {@code false} if not.
377   */
378  public boolean hasQueuedThreads()
379  {
380    return readWriteLock.hasQueuedThreads();
381  }
382
383
384
385  /**
386   * Indicates whether the specified thread is currently waiting to acquire
387   * either the write or read lock.
388   *
389   * @param  thread  The thread for which to make the determination.  It must
390   *                 not be {@code null}.
391   *
392   * @return  {@code true} if the specified thread is currently waiting to
393   *          acquire either the write or read lock, or {@code false} if not.
394   */
395  public boolean hasQueuedThread(@NotNull final Thread thread)
396  {
397    return readWriteLock.hasQueuedThread(thread);
398  }
399
400
401
402  /**
403   * Retrieves an estimate of the number of threads currently waiting to acquire
404   * either the write or read lock.
405   *
406   * @return  An estimate of the number of threads currently waiting to acquire
407   *          either the write or read lock.
408   */
409  public int getQueueLength()
410  {
411    return readWriteLock.getQueueLength();
412  }
413
414
415
416  /**
417   * Retrieves a string representation of this read-write lock.
418   *
419   * @return  A string representation of this read-write lock.
420   */
421  @Override()
422  @NotNull()
423  public String toString()
424  {
425    return "CloseableReadWriteLock(lock=" + readWriteLock.toString() + ')';
426  }
427
428
429
430  /**
431   * This class provides a {@code Closeable} implementation that may be used to
432   * unlock a {@link CloseableReadWriteLock}'s read lock via Java's
433   * try-with-resources facility.
434   */
435  public final class ReadLock
436         implements Closeable
437  {
438    // The associated read lock.
439    @NotNull private final ReentrantReadWriteLock.ReadLock lock;
440
441
442
443    /**
444     * Creates a new instance with the provided lock.
445     *
446     * @param  lock  The lock that will be unlocked when the [@link #close()}
447     *               method is called.  This must not be {@code null}.
448     */
449    private ReadLock(@NotNull final ReentrantReadWriteLock.ReadLock lock)
450    {
451      this.lock = lock;
452    }
453
454
455
456    /**
457     * This method does nothing.  However, calling it inside a try block when
458     * used in the try-with-resources framework can help avoid a compiler
459     * warning that the JVM will give you if you don't reference the
460     * {@code Closeable} object inside the try block.
461     */
462    public void avoidCompilerWarning()
463    {
464      // No implementation is required.
465    }
466
467
468
469    /**
470     * Unlocks the associated lock.
471     */
472    @Override()
473    public void close()
474    {
475      lock.unlock();
476    }
477  }
478
479
480
481  /**
482   * This class provides a {@code Closeable} implementation that may be used to
483   * unlock a {@link CloseableReadWriteLock}'s write lock via Java's
484   * try-with-resources facility.
485   */
486  public final class WriteLock
487         implements Closeable
488  {
489    // The associated read lock.
490    @NotNull private final ReentrantReadWriteLock.WriteLock lock;
491
492
493
494    /**
495     * Creates a new instance with the provided lock.
496     *
497     * @param  lock  The lock that will be unlocked when the [@link #close()}
498     *               method is called.  This must not be {@code null}.
499     */
500    private WriteLock(@NotNull final ReentrantReadWriteLock.WriteLock lock)
501    {
502      this.lock = lock;
503    }
504
505
506
507    /**
508     * This method does nothing.  However, calling it inside a try block when
509     * used in the try-with-resources framework can help avoid a compiler
510     * warning that the JVM will give you if you don't reference the
511     * {@code Closeable} object inside the try block.
512     */
513    public void avoidCompilerWarning()
514    {
515      // No implementation is required.
516    }
517
518
519
520    /**
521     * Unlocks the associated lock.
522     */
523    @Override()
524    public void close()
525    {
526      lock.unlock();
527    }
528  }
529}