diff --git a/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java b/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java index 58378e08..a3e422d5 100644 --- a/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java +++ b/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java @@ -1513,9 +1513,12 @@ public static CompletableFuture> thenMApplyFailFastAsync( CompletableFuture cfThis, Executor executor, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + // Implementation Note: create defensive copy of input array argument + // since it is used asynchronously in `thenCompose` and could be mutated (not thread-safe). + // this same defensive copying pattern is used in similar methods below. + Function[] copy = requireArrayAndEleNonNull("fn", fns).clone(); - return cfThis.thenCompose(v -> allResultsOf0(true, wrapFunctions0(executor, v, fns))); + return cfThis.thenCompose(v -> allResultsOf0(true, wrapFunctions0(executor, v, copy))); } /** @@ -1547,9 +1550,9 @@ public static CompletableFuture> thenMApplyAllSuccessAsync( @Nullable U valueIfFailed, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + Function[] copy = requireArrayAndEleNonNull("fn", fns); - return cfThis.thenCompose(v -> allSuccessResultsOf0(valueIfFailed, wrapFunctions0(executor, v, fns))); + return cfThis.thenCompose(v -> allSuccessResultsOf0(valueIfFailed, wrapFunctions0(executor, v, copy))); } /** @@ -1582,10 +1585,10 @@ public static CompletableFuture> thenMApplyMostSuccessAsync( requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); requireNonNull(unit, "unit is null"); - requireArrayAndEleNonNull("fn", fns); + Function[] copy = requireArrayAndEleNonNull("fn", fns); return cfThis.thenCompose(v -> mostSuccessResultsOf0( - executor, valueIfNotSuccess, timeout, unit, wrapFunctions0(executor, v, fns))); + executor, valueIfNotSuccess, timeout, unit, wrapFunctions0(executor, v, copy))); } /** @@ -1616,9 +1619,9 @@ public static CompletableFuture> thenMApplyAsync( CompletableFuture cfThis, Executor executor, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + Function[] copy = requireArrayAndEleNonNull("fn", fns); - return cfThis.thenCompose(v -> allResultsOf0(false, wrapFunctions0(executor, v, fns))); + return cfThis.thenCompose(v -> allResultsOf0(false, wrapFunctions0(executor, v, copy))); } /** @@ -1649,9 +1652,9 @@ public static CompletableFuture thenMApplyAnySuccessAsync( CompletableFuture cfThis, Executor executor, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + Function[] copy = requireArrayAndEleNonNull("fn", fns); - return cfThis.thenCompose(v -> anySuccessOf0(wrapFunctions0(executor, v, fns))); + return cfThis.thenCompose(v -> anySuccessOf0(wrapFunctions0(executor, v, copy))); } /** @@ -1682,9 +1685,9 @@ public static CompletableFuture thenMApplyAnyAsync( CompletableFuture cfThis, Executor executor, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + Function[] copy = requireArrayAndEleNonNull("fn", fns); - return cfThis.thenCompose(v -> f_cast(CompletableFuture.anyOf(wrapFunctions0(executor, v, fns)))); + return cfThis.thenCompose(v -> f_cast(CompletableFuture.anyOf(wrapFunctions0(executor, v, copy)))); } private static CompletableFuture[] wrapFunctions0( @@ -1725,9 +1728,9 @@ public static CompletableFuture thenMAcceptFailFastAsync( CompletableFuture cfThis, Executor executor, Consumer... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Consumer[] copy = requireArrayAndEleNonNull("action", actions); - return cfThis.thenCompose(v -> allFailFastOf0(wrapConsumers0(executor, v, actions))); + return cfThis.thenCompose(v -> allFailFastOf0(wrapConsumers0(executor, v, copy))); } /** @@ -1763,9 +1766,9 @@ public static CompletableFuture thenMAcceptAsync( CompletableFuture cfThis, Executor executor, Consumer... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Consumer[] copy = requireArrayAndEleNonNull("action", actions); - return cfThis.thenCompose(v -> CompletableFuture.allOf(wrapConsumers0(executor, v, actions))); + return cfThis.thenCompose(v -> CompletableFuture.allOf(wrapConsumers0(executor, v, copy))); } /** @@ -1801,9 +1804,9 @@ public static CompletableFuture thenMAcceptAnySuccessAsync( CompletableFuture cfThis, Executor executor, Consumer... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Consumer[] copy = requireArrayAndEleNonNull("action", actions); - return cfThis.thenCompose(v -> anySuccessOf0(wrapConsumers0(executor, v, actions))); + return cfThis.thenCompose(v -> anySuccessOf0(wrapConsumers0(executor, v, copy))); } /** @@ -1839,9 +1842,9 @@ public static CompletableFuture thenMAcceptAnyAsync( CompletableFuture cfThis, Executor executor, Consumer... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Consumer[] copy = requireArrayAndEleNonNull("action", actions); - return cfThis.thenCompose(v -> f_cast(CompletableFuture.anyOf(wrapConsumers0(executor, v, actions)))); + return cfThis.thenCompose(v -> f_cast(CompletableFuture.anyOf(wrapConsumers0(executor, v, copy)))); } private static CompletableFuture[] wrapConsumers0(Executor executor, T v, Consumer[] actions) { @@ -1868,9 +1871,9 @@ public static CompletableFuture thenMRunFailFastAsync( CompletableFuture cfThis, Executor executor, Runnable... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Runnable[] copy = requireArrayAndEleNonNull("action", actions); - return cfThis.thenCompose(unused -> allFailFastOf0(wrapRunnables0(executor, actions))); + return cfThis.thenCompose(unused -> allFailFastOf0(wrapRunnables0(executor, copy))); } /** @@ -1893,9 +1896,9 @@ public static CompletableFuture thenMRunAsync( CompletableFuture cfThis, Executor executor, Runnable... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Runnable[] copy = requireArrayAndEleNonNull("action", actions); - return cfThis.thenCompose(unused -> CompletableFuture.allOf(wrapRunnables0(executor, actions))); + return cfThis.thenCompose(unused -> CompletableFuture.allOf(wrapRunnables0(executor, copy))); } /** @@ -1918,9 +1921,9 @@ public static CompletableFuture thenMRunAnySuccessAsync( CompletableFuture cfThis, Executor executor, Runnable... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Runnable[] copy = requireArrayAndEleNonNull("action", actions); - return cfThis.thenCompose(unused -> anySuccessOf0(wrapRunnables0(executor, actions))); + return cfThis.thenCompose(unused -> anySuccessOf0(wrapRunnables0(executor, copy))); } /** @@ -1943,9 +1946,9 @@ public static CompletableFuture thenMRunAnyAsync( CompletableFuture cfThis, Executor executor, Runnable... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Runnable[] copy = requireArrayAndEleNonNull("action", actions); - return cfThis.thenCompose(unused -> f_cast(CompletableFuture.anyOf(wrapRunnables0(executor, actions)))); + return cfThis.thenCompose(unused -> f_cast(CompletableFuture.anyOf(wrapRunnables0(executor, copy)))); } // endregion