001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.data; 019 020import dev.enola.common.concurrent.Threads; 021import dev.enola.common.function.CheckedRunnable; 022 023import java.time.Duration; 024 025public class OptimisticLockingExceptionRetrier { 026 027 private final int maxRetries; 028 private final Duration retryDelay; 029 030 private OptimisticLockingExceptionRetrier(int maxRetries, Duration retryDelay) { 031 this.maxRetries = maxRetries; 032 this.retryDelay = retryDelay; 033 } 034 035 public OptimisticLockingExceptionRetrier() { 036 // TODO Make configurable, somehow/somewhere... Java system properties, probably? 037 this(7, Duration.ofMillis(100)); 038 } 039 040 /** 041 * Retry. 042 * 043 * @param action Action to try (and retry) running 044 * @param <E> Exception supertype which that Action may throw 045 * @throws E re-thrown the Action itself threw it 046 * @throws OptimisticLockingException thrown if maximum retries have been reached 047 */ 048 public <E extends Exception> void retry(CheckedRunnable<E> action) 049 throws E, OptimisticLockingException { 050 int attempts = 0; 051 while (attempts <= maxRetries) { 052 try { 053 action.run(); 054 return; 055 } catch (OptimisticLockingException ex) { 056 attempts++; 057 if (attempts <= maxRetries) { 058 // TODO Log? Hit some observability metric thing? 059 Threads.sleep(retryDelay); 060 } else { 061 throw new OptimisticLockingException("Max retries exceeded", ex); 062 } 063 } catch (Exception ex) { 064 // Re-throw all other exceptions immediately! 065 throw ex; 066 } 067 } 068 throw new IllegalStateException("BUG: This should never have been reached?!"); 069 } 070}