001/* 002 * ============================================================================ 003 * Copyright © 2002-2024 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.util.internal; 019 020import static java.util.UUID.randomUUID; 021import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; 022import static java.util.concurrent.TimeUnit.MILLISECONDS; 023import static org.apiguardian.api.API.Status.INTERNAL; 024import static org.apiguardian.api.API.Status.STABLE; 025import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 026 027import java.io.Serial; 028import java.lang.ref.Cleaner; 029import java.lang.ref.Cleaner.Cleanable; 030import java.time.Duration; 031import java.time.Instant; 032import java.util.LinkedHashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.TimerTask; 036import java.util.UUID; 037import java.util.concurrent.ScheduledExecutorService; 038import java.util.concurrent.Semaphore; 039 040import org.apiguardian.api.API; 041import org.tquadrat.foundation.annotation.ClassVersion; 042import org.tquadrat.foundation.lang.AutoLock; 043import org.tquadrat.foundation.util.AutoSemaphore; 044 045/** 046 * An implementation for 047 * {@link org.tquadrat.foundation.util.AutoSemaphore} 048 * that allows a timeout for the permits. 049 * 050 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 051 * @version $Id: TimeoutSemaphoreImpl.java 1136 2024-05-30 18:25:38Z tquadrat $ 052 * @since 0.4.8 053 * 054 * @UMLGraph.link 055 */ 056@ClassVersion( sourceVersion = "$Id: TimeoutSemaphoreImpl.java 1136 2024-05-30 18:25:38Z tquadrat $" ) 057@API( status = STABLE, since = "0.4.8" ) 058public final class TimeoutSemaphoreImpl extends Semaphore implements AutoSemaphore 059{ 060 /*---------------*\ 061 ====** Inner Classes **==================================================== 062 \*---------------*/ 063 /** 064 * <p>{@summary The {@code Janitor} for the owning 065 * {@link TimeoutSemaphoreImpl} 066 * instance.}</p> 067 * 068 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 069 * @version $Id: TimeoutSemaphoreImpl.java 1136 2024-05-30 18:25:38Z tquadrat $ 070 * @since 0.4.8 071 * 072 * @UMLGraph.link 073 */ 074 @ClassVersion( sourceVersion = "$Id: TimeoutSemaphoreImpl.java 1136 2024-05-30 18:25:38Z tquadrat $" ) 075 @API( status = INTERNAL, since = "0.4.8" ) 076 private static final class Janitor implements Runnable 077 { 078 /*------------*\ 079 ====** Attributes **=================================================== 080 \*------------*/ 081 /** 082 * The reference for the 083 * {@link TimeoutSemaphoreImpl#m_ReaperExecutor}. 084 */ 085 private final ScheduledExecutorService m_ReaperExecutor; 086 087 /*--------------*\ 088 ====** Constructors **================================================= 089 \*--------------*/ 090 /** 091 * Creates a new instance of {@code Janitor}. 092 * 093 * @param reaperExecutor The reference for the 094 * {@link TimeoutSemaphoreImpl#m_ReaperExecutor}. 095 */ 096 public Janitor( final ScheduledExecutorService reaperExecutor ) 097 { 098 m_ReaperExecutor = reaperExecutor; 099 } // Janitor() 100 101 /*---------*\ 102 ====** Methods **====================================================== 103 \*---------*/ 104 /** 105 * Performs the housekeeping. 106 */ 107 @Override 108 public final void run() 109 { 110 //---* Kill the executor *----------------------------------------- 111 m_ReaperExecutor.close(); 112 } // run() 113 } 114 // class Janitor 115 116 /** 117 * <p>{@summary The reaper thread.}</p> 118 * 119 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 120 * @version $Id: TimeoutSemaphoreImpl.java 1136 2024-05-30 18:25:38Z tquadrat $ 121 * @since 0.4.8 122 * 123 * @UMLGraph.link 124 */ 125 @SuppressWarnings( "resource" ) 126 @ClassVersion( sourceVersion = "$Id: TimeoutSemaphoreImpl.java 1136 2024-05-30 18:25:38Z tquadrat $" ) 127 @API( status = INTERNAL, since = "0.4.8" ) 128 private final class Reaper extends TimerTask 129 { 130 /*--------------*\ 131 ====** Constructors **================================================= 132 \*--------------*/ 133 /** 134 * Creates a new instance of {@code Reaper}. 135 */ 136 public Reaper() { /* Just exists */ } 137 138 /*---------*\ 139 ====** Methods **====================================================== 140 \*---------*/ 141 /** 142 * {@inheritDoc} 143 */ 144 @Override 145 public final void run() 146 { 147 final var now = Instant.now(); 148 try( final var $ = m_Lock.lock() ) 149 { 150 final var tokens = List.copyOf( m_Registry.values() ); 151 for( final var token : tokens ) 152 { 153 if( token.getEndOfLife().isBefore( now ) ) 154 { 155 m_Registry.remove( token.getId() ); 156 } 157 } 158 } 159 } // run() 160 } 161 // class Reaper 162 163 /** 164 * <p>{@summary The token that holds the permits to be released when a 165 * {@code try-with-resources} block is left or the timeout has 166 * expired.}</p> 167 * 168 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 169 * @version $Id: TimeoutSemaphoreImpl.java 1136 2024-05-30 18:25:38Z tquadrat $ 170 * @since 0.4.8 171 * 172 * @UMLGraph.link 173 */ 174 @SuppressWarnings( "NewClassNamingConvention" ) 175 @ClassVersion( sourceVersion = "$Id: TimeoutSemaphoreImpl.java 1136 2024-05-30 18:25:38Z tquadrat $" ) 176 @API( status = INTERNAL, since = "0.4.8" ) 177 private final class Token implements AutoCloseable 178 { 179 /*------------*\ 180 ====** Attributes **=================================================== 181 \*------------*/ 182 /** 183 * The end-of-life for this permit. 184 */ 185 private final Instant m_EndOfLife; 186 187 /** 188 * The id for this permit. 189 */ 190 private final UUID m_Id; 191 192 /** 193 * The number of permits to release on close. 194 */ 195 private final int m_Permits; 196 197 /*--------------*\ 198 ====** Constructors **================================================= 199 \*--------------*/ 200 /** 201 * Creates a new instance of {@code Token}. 202 * 203 * @param permits The number of the acquired permits. 204 * @param endOfLife The end-of-life for this permit. 205 */ 206 public Token( final int permits, final Instant endOfLife ) 207 { 208 m_Permits = permits; 209 m_EndOfLife = requireNonNullArgument( endOfLife, "endOfLife" ); 210 m_Id = randomUUID(); 211 } // Token() 212 213 /*---------*\ 214 ====** Methods **====================================================== 215 \*---------*/ 216 /** 217 * {@inheritDoc} 218 */ 219 @Override 220 public final void close() throws Exception 221 { 222 TimeoutSemaphoreImpl.this.release( m_Id ); 223 } // close() 224 225 /** 226 * Returns the time for the end-of-life. 227 * 228 * @return The end-of-life instant. 229 */ 230 public final Instant getEndOfLife() { return m_EndOfLife; } 231 232 /** 233 * Returns the id of the token. 234 * 235 * @return The id. 236 */ 237 public final UUID getId() { return m_Id; } 238 239 /** 240 * Returns the number of permits for this token. 241 * 242 * @return The number of permits. 243 */ 244 public final int getPermits() { return m_Permits; } 245 } 246 // class Token 247 248 /*-----------*\ 249 ====** Constants **======================================================== 250 \*-----------*/ 251 /** 252 * The cycle time for the reaper thread in milliseconds: {@value} ms. 253 */ 254 public static final long REAPER_CYCLE = 1000L; 255 256 /*------------*\ 257 ====** Attributes **======================================================= 258 \*------------*/ 259 /** 260 * <p>{@summary The 261 * {@link Cleanable} 262 * for this instance.} As instances of this class does not support 263 * {@code close()} or something similar, keeping the reference to the 264 * {@code Cleanable} is considered obsolete. But we keep it anyway, in 265 * case this changes.</p> 266 */ 267 @SuppressWarnings( {"unused", "FieldCanBeLocal"} ) 268 private final transient Cleanable m_Cleanable; 269 270 /** 271 * The janitor for instances of this class. 272 */ 273 @SuppressWarnings( {"UseOfConcreteClass", "FieldCanBeLocal"} ) 274 private final transient Janitor m_Janitor; 275 276 /** 277 * The lock that guards the access to 278 * {@link #m_Registry}. 279 */ 280 private final transient AutoLock m_Lock; 281 282 /** 283 * The timeout duration. 284 * 285 * @serial 286 */ 287 private final Duration m_Timeout; 288 289 /** 290 * The executor for the reaper. 291 */ 292 private final transient ScheduledExecutorService m_ReaperExecutor; 293 294 /** 295 * The permit registry. 296 */ 297 private final transient Map<UUID,Token> m_Registry = new LinkedHashMap<>(); 298 299 /*------------------------*\ 300 ====** Static Initialisations **=========================================== 301 \*------------------------*/ 302 /** 303 * The serial version UID for objects of this class: {@value}. 304 * 305 * @hidden 306 */ 307 @Serial 308 private static final long serialVersionUID = 539879857L; 309 310 /** 311 * The cleaner that is used for the instances of this class. 312 */ 313 private static final Cleaner m_Cleaner; 314 315 static 316 { 317 m_Cleaner = Cleaner.create(); 318 } 319 320 /*--------------*\ 321 ====** Constructors **===================================================== 322 \*--------------*/ 323 /** 324 * Creates an {@code TimeoutSemaphoreImpl} instance with the given number 325 * of permits, the given timeout duration and non-fair fairness setting. 326 * 327 * @param permits The initial number of permits available. This value may 328 * be negative, in which case releases must occur before any acquires 329 * will be granted. 330 * @param timeout The timeout. 331 */ 332 public TimeoutSemaphoreImpl( final int permits, final Duration timeout ) 333 { 334 this( permits, false, timeout ); 335 } // TimeoutSemaphoreImpl() 336 337 /** 338 * Creates an {@code TimeoutSemaphoreImpl} instance with the given number 339 * of permits, the given timeout duration and the given fairness setting. 340 * 341 * @param permits The initial number of permits available. This value may 342 * be negative, in which case releases must occur before any acquires 343 * will be granted. 344 * @param fair {@code true} if this semaphore will guarantee first-in 345 * first-out granting of permits under contention, else {@code false}. 346 * @param timeout The timeout. 347 */ 348 public TimeoutSemaphoreImpl( final int permits, final boolean fair, final Duration timeout ) 349 { 350 super( permits, fair ); 351 m_Timeout = requireNonNullArgument( timeout, "timeout" ); 352 m_Lock = AutoLock.of(); 353 354 m_ReaperExecutor = newSingleThreadScheduledExecutor(); 355 m_ReaperExecutor.scheduleWithFixedDelay( new Reaper(), REAPER_CYCLE, REAPER_CYCLE, MILLISECONDS ); 356 357 m_Janitor = new Janitor( m_ReaperExecutor ); 358 m_Cleanable = registerJanitor( m_Janitor ); 359 } // TimeoutSemaphoreImpl() 360 361 /*---------*\ 362 ====** Methods **========================================================== 363 \*---------*/ 364 /** 365 * {@inheritDoc} 366 */ 367 @SuppressWarnings( "ReturnOfInnerClass" ) 368 @Override 369 public AutoCloseable acquireToken( final int permits ) throws InterruptedException, IllegalArgumentException 370 { 371 acquire( permits ); 372 final var retValue = createToken( permits ); 373 374 //---* Done *---------------------------------------------------------- 375 return retValue; 376 } // acquireToken() 377 378 /** 379 * {@inheritDoc} 380 */ 381 @SuppressWarnings( "ReturnOfInnerClass" ) 382 @Override 383 public AutoCloseable acquireTokenUninterruptibly( final int permits ) throws IllegalArgumentException 384 { 385 acquireUninterruptibly( permits ); 386 final var retValue = createToken( permits ); 387 388 //---* Done *---------------------------------------------------------- 389 return retValue; 390 } // acquireTokenUninterruptibly() 391 392 /** 393 * Creates a token and adds to the registry. 394 * 395 * @param permits The number of permits to acquire. 396 * @return The new token. 397 */ 398 private final Token createToken( final int permits ) 399 { 400 final var endOfLife = Instant.now().plus( m_Timeout ); 401 final var retValue = new Token( permits, endOfLife ); 402 m_Lock.execute( () -> m_Registry.put( retValue.getId(), retValue ) ); 403 404 //---* Done *---------------------------------------------------------- 405 return retValue; 406 } // createToken() 407 408 /** 409 * {@inheritDoc} 410 */ 411 @Override 412 public final Semaphore getSemaphore() { return this; } 413 414 /** 415 * Registers the janitor that is doing the housekeeping on garbage 416 * collection. 417 * 418 * @param janitor The janitor. 419 * @return The 420 * {@link Cleanable} 421 * for this instance. 422 */ 423 private final Cleanable registerJanitor( final Runnable janitor ) 424 { 425 final var retValue = m_Cleaner.register( this, requireNonNullArgument( janitor, "janitor" ) ); 426 427 //---* Done *---------------------------------------------------------- 428 return retValue; 429 } // registerJanitor() 430 431 /** 432 * <p>{@summary Releases the number of permits associated with the token 433 * with the given id, returning them to the semaphore.}</p> 434 * <p>Releases that number of permits, increasing the number of available 435 * permits by that amount.</p> 436 * <p>Nothing happens if the token with given id had died already.</p> 437 * 438 * @param id The id of the token. 439 */ 440 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 441 public final void release( final UUID id ) 442 { 443 m_Lock.execute( () -> m_Registry.remove( id ) ) 444 .ifPresent( token -> release( token.getPermits() ) ); 445 } // release() 446} 447// class TimeoutSemaphoreImpl 448 449/* 450 * End of File 451 */