diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000000..1a716419111c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.java] +indent_style = space +indent_size = 2 + +[*.{xml,json,yml,yaml}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/guava-tests/test/com/google/common/util/concurrent/RetryExecutorTest.java b/guava-tests/test/com/google/common/util/concurrent/RetryExecutorTest.java new file mode 100644 index 000000000000..1434970ee31d --- /dev/null +++ b/guava-tests/test/com/google/common/util/concurrent/RetryExecutorTest.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; + +import com.google.common.base.Predicate; +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import junit.framework.TestCase; + +/** Tests for {@link RetryExecutor}. */ +public class RetryExecutorTest extends TestCase { + + private ScheduledExecutorService scheduler; + + @Override + protected void setUp() throws Exception { + super.setUp(); + scheduler = Executors.newScheduledThreadPool(2); + } + + @Override + protected void tearDown() throws Exception { + scheduler.shutdownNow(); + scheduler.awaitTermination(5, SECONDS); + super.tearDown(); + } + + public void testSuccessfulExecution() throws Exception { + RetryExecutor executor = + RetryExecutor.builder() + .setMaxRetries(3) + .setInitialDelay(Duration.ofMillis(10)) + .setScheduledExecutor(scheduler) + .build(); + + ListenableFuture future = executor.executeWithRetry(() -> "success"); + assertThat(future.get(5, SECONDS)).isEqualTo("success"); + assertThat(executor.getTotalAttempts()).isEqualTo(1); + assertThat(executor.getTotalSuccesses()).isEqualTo(1); + assertThat(executor.getTotalFailures()).isEqualTo(0); + } + + public void testRetryOnFailureThenSucceed() throws Exception { + AtomicInteger attempts = new AtomicInteger(0); + + RetryExecutor executor = + RetryExecutor.builder() + .setMaxRetries(3) + .setInitialDelay(Duration.ofMillis(10)) + .setScheduledExecutor(scheduler) + .build(); + + ListenableFuture future = + executor.executeWithRetry( + () -> { + if (attempts.incrementAndGet() < 3) { + throw new IOException("transient failure"); + } + return "recovered"; + }); + + assertThat(future.get(5, SECONDS)).isEqualTo("recovered"); + assertThat(attempts.get()).isEqualTo(3); + } + + public void testExhaustedRetries() throws Exception { + RetryExecutor executor = + RetryExecutor.builder() + .setMaxRetries(2) + .setInitialDelay(Duration.ofMillis(10)) + .setScheduledExecutor(scheduler) + .build(); + + ListenableFuture future = + executor.executeWithRetry( + (Callable) + () -> { + throw new IOException("persistent failure"); + }); + + try { + future.get(5, SECONDS); + fail("Expected exception"); + } catch (Exception e) { + assertThat(e.getCause()).isInstanceOf(IOException.class); + } + assertThat(executor.getTotalRetriesExhausted()).isEqualTo(1); + } + + public void testRetryPredicateFiltersExceptions() throws Exception { + AtomicInteger attempts = new AtomicInteger(0); + + RetryExecutor executor = + RetryExecutor.builder() + .setMaxRetries(5) + .setInitialDelay(Duration.ofMillis(10)) + .setRetryPredicate((Predicate) t -> t instanceof IOException) + .setScheduledExecutor(scheduler) + .build(); + + // IllegalArgumentException should NOT be retried + ListenableFuture future = + executor.executeWithRetry( + (Callable) + () -> { + attempts.incrementAndGet(); + throw new IllegalArgumentException("non-retryable"); + }); + + try { + future.get(5, SECONDS); + fail("Expected exception"); + } catch (Exception e) { + assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class); + } + assertThat(attempts.get()).isEqualTo(1); // No retries for non-matching exception + } + + public void testExponentialBackoffDelay() { + RetryExecutor executor = + RetryExecutor.builder() + .setMaxRetries(5) + .setInitialDelay(Duration.ofMillis(100)) + .setMaxDelay(Duration.ofSeconds(10)) + .setBackoffMultiplier(2.0) + .setJitterFactor(0.0) // No jitter for predictable testing + .setScheduledExecutor(scheduler) + .build(); + + long delay0 = executor.computeDelay(0); + long delay1 = executor.computeDelay(1); + long delay2 = executor.computeDelay(2); + + assertThat(delay0).isEqualTo(MILLISECONDS.toNanos(100)); + assertThat(delay1).isEqualTo(MILLISECONDS.toNanos(200)); + assertThat(delay2).isEqualTo(MILLISECONDS.toNanos(400)); + } + + public void testMaxDelayCapsBacking() { + RetryExecutor executor = + RetryExecutor.builder() + .setMaxRetries(10) + .setInitialDelay(Duration.ofSeconds(1)) + .setMaxDelay(Duration.ofSeconds(5)) + .setBackoffMultiplier(10.0) + .setJitterFactor(0.0) + .setScheduledExecutor(scheduler) + .build(); + + long delay = executor.computeDelay(5); + assertThat(delay).isAtMost(SECONDS.toNanos(5)); + } + + public void testCircuitBreakerOpens() throws Exception { + RetryExecutor executor = + RetryExecutor.builder() + .setMaxRetries(0) + .setInitialDelay(Duration.ofMillis(10)) + .setCircuitBreakerThreshold(3) + .setCircuitBreakerResetDuration(Duration.ofSeconds(60)) + .setScheduledExecutor(scheduler) + .build(); + + // Cause 3 failures to trip the circuit breaker + for (int i = 0; i < 3; i++) { + try { + executor + .executeWithRetry( + (Callable) + () -> { + throw new IOException("fail"); + }) + .get(1, SECONDS); + } catch (Exception ignored) { + } + } + + assertThat(executor.getCircuitState()).isEqualTo(RetryExecutor.CircuitState.OPEN); + + // Next request should fail with CircuitBreakerOpenException + ListenableFuture future = executor.executeWithRetry(() -> "should not run"); + try { + future.get(1, SECONDS); + fail("Expected circuit breaker exception"); + } catch (Exception e) { + assertThat(e.getCause()).isInstanceOf(RetryExecutor.CircuitBreakerOpenException.class); + } + } + + public void testCircuitBreakerResetsOnSuccess() throws Exception { + AtomicInteger callCount = new AtomicInteger(0); + + RetryExecutor executor = + RetryExecutor.builder() + .setMaxRetries(0) + .setInitialDelay(Duration.ofMillis(10)) + .setCircuitBreakerThreshold(5) + .setScheduledExecutor(scheduler) + .build(); + + // Two failures + for (int i = 0; i < 2; i++) { + try { + executor + .executeWithRetry( + (Callable) + () -> { + throw new IOException("fail"); + }) + .get(1, SECONDS); + } catch (Exception ignored) { + } + } + + // Then a success - should reset consecutive failure count + executor.executeWithRetry(() -> "ok").get(1, SECONDS); + assertThat(executor.getCircuitState()).isEqualTo(RetryExecutor.CircuitState.CLOSED); + } + + public void testRunnableRetry() throws Exception { + AtomicInteger counter = new AtomicInteger(0); + + RetryExecutor executor = + RetryExecutor.builder() + .setMaxRetries(3) + .setInitialDelay(Duration.ofMillis(10)) + .setScheduledExecutor(scheduler) + .build(); + + ListenableFuture future = + executor.executeWithRetry( + (Runnable) + () -> { + if (counter.incrementAndGet() < 2) { + throw new RuntimeException("transient"); + } + }); + + future.get(5, SECONDS); + assertThat(counter.get()).isEqualTo(2); + } + + public void testBuilderValidation() { + assertThrows( + IllegalArgumentException.class, + () -> RetryExecutor.builder().setMaxRetries(-1)); + + assertThrows( + IllegalArgumentException.class, + () -> RetryExecutor.builder().setBackoffMultiplier(0.5)); + + assertThrows( + IllegalArgumentException.class, + () -> RetryExecutor.builder().setJitterFactor(1.5)); + + assertThrows( + IllegalStateException.class, + () -> RetryExecutor.builder().build()); // No scheduled executor + } + + public void testMetricsTracking() throws Exception { + AtomicInteger attempts = new AtomicInteger(0); + + RetryExecutor executor = + RetryExecutor.builder() + .setMaxRetries(2) + .setInitialDelay(Duration.ofMillis(10)) + .setScheduledExecutor(scheduler) + .build(); + + // Successful call + executor.executeWithRetry(() -> "ok").get(5, SECONDS); + + // Failing call (exhausts retries) + try { + executor + .executeWithRetry( + (Callable) + () -> { + throw new RuntimeException("fail"); + }) + .get(5, SECONDS); + } catch (Exception ignored) { + } + + assertThat(executor.getTotalSuccesses()).isEqualTo(1); + assertThat(executor.getTotalFailures()).isGreaterThan(0L); + assertThat(executor.getTotalAttempts()).isGreaterThan(1L); + } +} diff --git a/guava/src/com/google/common/io/Files.java b/guava/src/com/google/common/io/Files.java index b7edbe43d6b6..b644c02a96c1 100644 --- a/guava/src/com/google/common/io/Files.java +++ b/guava/src/com/google/common/io/Files.java @@ -86,7 +86,17 @@ private Files() {} public static BufferedReader newReader(File file, Charset charset) throws FileNotFoundException { checkNotNull(file); checkNotNull(charset); - return new BufferedReader(new InputStreamReader(new FileInputStream(file), charset)); + FileInputStream fis = new FileInputStream(file); + try { + return new BufferedReader(new InputStreamReader(fis, charset)); + } catch (Throwable t) { + try { + fis.close(); + } catch (IOException e) { + // Suppressed; propagate the original exception. + } + throw t; + } } /** @@ -104,7 +114,17 @@ public static BufferedReader newReader(File file, Charset charset) throws FileNo public static BufferedWriter newWriter(File file, Charset charset) throws FileNotFoundException { checkNotNull(file); checkNotNull(charset); - return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), charset)); + FileOutputStream fos = new FileOutputStream(file); + try { + return new BufferedWriter(new OutputStreamWriter(fos, charset)); + } catch (Throwable t) { + try { + fos.close(); + } catch (IOException e) { + // Suppressed; propagate the original exception. + } + throw t; + } } /** diff --git a/guava/src/com/google/common/util/concurrent/MoreExecutors.java b/guava/src/com/google/common/util/concurrent/MoreExecutors.java index 0d2abe9c70a0..b29421ed5029 100644 --- a/guava/src/com/google/common/util/concurrent/MoreExecutors.java +++ b/guava/src/com/google/common/util/concurrent/MoreExecutors.java @@ -1037,10 +1037,11 @@ public static boolean shutdownAndAwaitTermination( service.awaitTermination(halfTimeoutNanos, NANOSECONDS); } } catch (InterruptedException ie) { - // Preserve interrupted status - Thread.currentThread().interrupt(); // (Re-)Cancel if current thread also interrupted service.shutdownNow(); + // Preserve interrupted status after shutdownNow to avoid potential deadlock + // where interrupt flag is set before shutdownNow completes + Thread.currentThread().interrupt(); } return service.isTerminated(); } diff --git a/guava/src/com/google/common/util/concurrent/RetryExecutor.java b/guava/src/com/google/common/util/concurrent/RetryExecutor.java new file mode 100644 index 000000000000..3e8a35f609e4 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/RetryExecutor.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Predicate; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.CheckForNull; + +/** + * An executor that retries failed operations with configurable retry policies, exponential backoff + * with jitter, and circuit breaker support. Retried operations return {@link ListenableFuture} for + * async composition. + * + *

Example usage: + * + *

{@code
+ * RetryExecutor executor = RetryExecutor.builder()
+ *     .setMaxRetries(3)
+ *     .setInitialDelay(Duration.ofMillis(100))
+ *     .setMaxDelay(Duration.ofSeconds(10))
+ *     .setBackoffMultiplier(2.0)
+ *     .setJitterFactor(0.1)
+ *     .setRetryPredicate(e -> e instanceof IOException)
+ *     .setScheduledExecutor(scheduledExecutor)
+ *     .build();
+ *
+ * ListenableFuture result = executor.executeWithRetry(() -> fetchData());
+ * }
+ * + * @since 33.0 + */ +@Beta +@GwtIncompatible +public final class RetryExecutor { + + /** The state of the circuit breaker. */ + enum CircuitState { + CLOSED, + OPEN, + HALF_OPEN + } + + private final int maxRetries; + private final long initialDelayNanos; + private final long maxDelayNanos; + private final double backoffMultiplier; + private final double jitterFactor; + private final Predicate retryPredicate; + private final ScheduledExecutorService scheduledExecutor; + + // Circuit breaker state + private final int circuitBreakerThreshold; + private final long circuitBreakerResetNanos; + private final AtomicReference circuitState; + private final AtomicInteger consecutiveFailures; + private final AtomicLong circuitOpenedAtNanos; + + // Metrics + private final AtomicLong totalAttempts; + private final AtomicLong totalSuccesses; + private final AtomicLong totalFailures; + private final AtomicLong totalRetriesExhausted; + + private RetryExecutor(Builder builder) { + this.maxRetries = builder.maxRetries; + this.initialDelayNanos = builder.initialDelayNanos; + this.maxDelayNanos = builder.maxDelayNanos; + this.backoffMultiplier = builder.backoffMultiplier; + this.jitterFactor = builder.jitterFactor; + this.retryPredicate = builder.retryPredicate; + this.scheduledExecutor = checkNotNull(builder.scheduledExecutor); + this.circuitBreakerThreshold = builder.circuitBreakerThreshold; + this.circuitBreakerResetNanos = builder.circuitBreakerResetNanos; + this.circuitState = new AtomicReference<>(CircuitState.CLOSED); + this.consecutiveFailures = new AtomicInteger(0); + this.circuitOpenedAtNanos = new AtomicLong(0); + this.totalAttempts = new AtomicLong(0); + this.totalSuccesses = new AtomicLong(0); + this.totalFailures = new AtomicLong(0); + this.totalRetriesExhausted = new AtomicLong(0); + } + + /** Returns a new {@link Builder} for configuring a {@code RetryExecutor}. */ + public static Builder builder() { + return new Builder(); + } + + /** + * Executes the given callable with retry logic. Returns a {@link ListenableFuture} that completes + * when the callable succeeds or all retries are exhausted. + */ + @CanIgnoreReturnValue + public ListenableFuture executeWithRetry(Callable callable) { + checkNotNull(callable, "callable"); + SettableFuture resultFuture = SettableFuture.create(); + attemptExecution(callable, 0, resultFuture); + return resultFuture; + } + + /** + * Executes the given runnable with retry logic. Returns a {@link ListenableFuture} that completes + * when the runnable succeeds or all retries are exhausted. + */ + @CanIgnoreReturnValue + public ListenableFuture executeWithRetry(Runnable runnable) { + checkNotNull(runnable, "runnable"); + return executeWithRetry( + () -> { + runnable.run(); + return null; + }); + } + + private void attemptExecution( + Callable callable, int attempt, SettableFuture resultFuture) { + if (resultFuture.isCancelled()) { + return; + } + + // Check circuit breaker + if (!isCircuitAllowingRequests()) { + resultFuture.setException( + new CircuitBreakerOpenException( + "Circuit breaker is open after " + consecutiveFailures.get() + " failures")); + return; + } + + totalAttempts.incrementAndGet(); + + try { + T result = callable.call(); + onSuccess(); + resultFuture.set(result); + } catch (Exception e) { + onFailure(); + + if (attempt >= maxRetries || !shouldRetry(e)) { + totalRetriesExhausted.incrementAndGet(); + resultFuture.setException(e); + return; + } + + long delayNanos = computeDelay(attempt); + scheduledExecutor.schedule( + () -> attemptExecution(callable, attempt + 1, resultFuture), + delayNanos, + TimeUnit.NANOSECONDS); + } + } + + private boolean shouldRetry(Throwable t) { + if (retryPredicate == null) { + return true; + } + return retryPredicate.apply(t); + } + + long computeDelay(int attempt) { + double delay = initialDelayNanos * Math.pow(backoffMultiplier, attempt); + delay = Math.min(delay, maxDelayNanos); + + if (jitterFactor > 0) { + double jitter = delay * jitterFactor; + double randomJitter = ThreadLocalRandom.current().nextDouble(-jitter, jitter); + delay = delay + randomJitter; + } + + return Math.max(0, (long) delay); + } + + private boolean isCircuitAllowingRequests() { + CircuitState state = circuitState.get(); + if (state == CircuitState.CLOSED) { + return true; + } + if (state == CircuitState.OPEN) { + long elapsed = System.nanoTime() - circuitOpenedAtNanos.get(); + if (elapsed >= circuitBreakerResetNanos) { + circuitState.compareAndSet(CircuitState.OPEN, CircuitState.HALF_OPEN); + return true; + } + return false; + } + // HALF_OPEN: allow one request through to test + return true; + } + + private void onSuccess() { + totalSuccesses.incrementAndGet(); + consecutiveFailures.set(0); + circuitState.set(CircuitState.CLOSED); + } + + private void onFailure() { + totalFailures.incrementAndGet(); + int failures = consecutiveFailures.incrementAndGet(); + if (circuitBreakerThreshold > 0 && failures >= circuitBreakerThreshold) { + if (circuitState.compareAndSet(CircuitState.CLOSED, CircuitState.OPEN) + || circuitState.compareAndSet(CircuitState.HALF_OPEN, CircuitState.OPEN)) { + circuitOpenedAtNanos.set(System.nanoTime()); + } + } + } + + /** Returns the total number of execution attempts. */ + public long getTotalAttempts() { + return totalAttempts.get(); + } + + /** Returns the total number of successful executions. */ + public long getTotalSuccesses() { + return totalSuccesses.get(); + } + + /** Returns the total number of failed attempts. */ + public long getTotalFailures() { + return totalFailures.get(); + } + + /** Returns the number of times retries were exhausted without success. */ + public long getTotalRetriesExhausted() { + return totalRetriesExhausted.get(); + } + + /** Returns the current circuit breaker state. */ + public CircuitState getCircuitState() { + return circuitState.get(); + } + + /** Exception thrown when the circuit breaker is open. */ + public static final class CircuitBreakerOpenException extends RuntimeException { + CircuitBreakerOpenException(String message) { + super(message); + } + } + + /** Builder for {@link RetryExecutor}. */ + public static final class Builder { + private int maxRetries = 3; + private long initialDelayNanos = TimeUnit.MILLISECONDS.toNanos(100); + private long maxDelayNanos = TimeUnit.SECONDS.toNanos(30); + private double backoffMultiplier = 2.0; + private double jitterFactor = 0.1; + @CheckForNull private Predicate retryPredicate; + @CheckForNull private ScheduledExecutorService scheduledExecutor; + private int circuitBreakerThreshold = 0; // 0 = disabled + private long circuitBreakerResetNanos = TimeUnit.SECONDS.toNanos(60); + + private Builder() {} + + /** Sets the maximum number of retries. Must be non-negative. */ + @CanIgnoreReturnValue + public Builder setMaxRetries(int maxRetries) { + checkArgument(maxRetries >= 0, "maxRetries must be non-negative, was %s", maxRetries); + this.maxRetries = maxRetries; + return this; + } + + /** Sets the initial delay between retries. */ + @CanIgnoreReturnValue + public Builder setInitialDelay(Duration delay) { + checkNotNull(delay); + checkArgument(!delay.isNegative(), "delay must be non-negative"); + this.initialDelayNanos = delay.toNanos(); + return this; + } + + /** Sets the maximum delay between retries (caps exponential backoff). */ + @CanIgnoreReturnValue + public Builder setMaxDelay(Duration maxDelay) { + checkNotNull(maxDelay); + checkArgument(!maxDelay.isNegative(), "maxDelay must be non-negative"); + this.maxDelayNanos = maxDelay.toNanos(); + return this; + } + + /** Sets the backoff multiplier for exponential backoff. Must be >= 1.0. */ + @CanIgnoreReturnValue + public Builder setBackoffMultiplier(double multiplier) { + checkArgument(multiplier >= 1.0, "multiplier must be >= 1.0, was %s", multiplier); + this.backoffMultiplier = multiplier; + return this; + } + + /** Sets the jitter factor (0.0 to 1.0). Adds randomness to delay. */ + @CanIgnoreReturnValue + public Builder setJitterFactor(double jitterFactor) { + checkArgument( + jitterFactor >= 0.0 && jitterFactor <= 1.0, + "jitterFactor must be between 0.0 and 1.0, was %s", + jitterFactor); + this.jitterFactor = jitterFactor; + return this; + } + + /** Sets a predicate to determine if a given exception should be retried. */ + @CanIgnoreReturnValue + public Builder setRetryPredicate(Predicate retryPredicate) { + this.retryPredicate = checkNotNull(retryPredicate); + return this; + } + + /** Sets the scheduled executor used for delayed retries. Required. */ + @CanIgnoreReturnValue + public Builder setScheduledExecutor(ScheduledExecutorService executor) { + this.scheduledExecutor = checkNotNull(executor); + return this; + } + + /** + * Enables circuit breaker with the given failure threshold. After {@code threshold} + * consecutive failures, the circuit opens and rejects requests until the reset duration elapses. + */ + @CanIgnoreReturnValue + public Builder setCircuitBreakerThreshold(int threshold) { + checkArgument(threshold >= 0, "threshold must be non-negative, was %s", threshold); + this.circuitBreakerThreshold = threshold; + return this; + } + + /** Sets how long the circuit breaker stays open before transitioning to half-open. */ + @CanIgnoreReturnValue + public Builder setCircuitBreakerResetDuration(Duration duration) { + checkNotNull(duration); + checkArgument(!duration.isNegative(), "duration must be non-negative"); + this.circuitBreakerResetNanos = duration.toNanos(); + return this; + } + + /** Builds the {@link RetryExecutor}. A scheduled executor must be set. */ + public RetryExecutor build() { + checkState(scheduledExecutor != null, "scheduledExecutor must be set"); + return new RetryExecutor(this); + } + } +} diff --git a/integration-tests/gradle/gradlew b/integration-tests/gradle/gradlew old mode 100755 new mode 100644 diff --git a/integration-tests/gradle/gradlew.bat b/integration-tests/gradle/gradlew.bat old mode 100755 new mode 100644 diff --git a/mvnw b/mvnw old mode 100755 new mode 100644 diff --git a/mvnw.cmd b/mvnw.cmd old mode 100755 new mode 100644 diff --git a/util/deploy_snapshot.sh b/util/deploy_snapshot.sh old mode 100755 new mode 100644 diff --git a/util/gradle_integration_tests.sh b/util/gradle_integration_tests.sh old mode 100755 new mode 100644 diff --git a/util/print_surefire_reports.sh b/util/print_surefire_reports.sh old mode 100755 new mode 100644 diff --git a/util/update_snapshot_docs.sh b/util/update_snapshot_docs.sh old mode 100755 new mode 100644