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 d40bc14e..5e06f6fa 100644 --- a/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java +++ b/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java @@ -1512,9 +1512,12 @@ public static CompletableFuture> thenMApplyFailFastAsync( CompletableFuture cfThis, Executor executor, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + // Defensive shallow copy of input array argument by `clone`, + // since it is used asynchronously in `thenCompose` and could be mutated by caller (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))); } /** @@ -1546,9 +1549,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).clone(); - return cfThis.thenCompose(v -> allSuccessResultsOf0(valueIfFailed, wrapFunctions0(executor, v, fns))); + return cfThis.thenCompose(v -> allSuccessResultsOf0(valueIfFailed, wrapFunctions0(executor, v, copy))); } /** @@ -1581,10 +1584,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).clone(); return cfThis.thenCompose(v -> mostSuccessResultsOf0( - executor, valueIfNotSuccess, timeout, unit, wrapFunctions0(executor, v, fns))); + executor, valueIfNotSuccess, timeout, unit, wrapFunctions0(executor, v, copy))); } /** @@ -1615,9 +1618,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).clone(); - return cfThis.thenCompose(v -> allResultsOf0(false, wrapFunctions0(executor, v, fns))); + return cfThis.thenCompose(v -> allResultsOf0(false, wrapFunctions0(executor, v, copy))); } /** @@ -1648,9 +1651,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).clone(); - return cfThis.thenCompose(v -> anySuccessOf0(wrapFunctions0(executor, v, fns))); + return cfThis.thenCompose(v -> anySuccessOf0(wrapFunctions0(executor, v, copy))); } /** @@ -1681,9 +1684,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).clone(); - 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( @@ -1724,9 +1727,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).clone(); - return cfThis.thenCompose(v -> allFailFastOf0(wrapConsumers0(executor, v, actions))); + return cfThis.thenCompose(v -> allFailFastOf0(wrapConsumers0(executor, v, copy))); } /** @@ -1762,9 +1765,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).clone(); - return cfThis.thenCompose(v -> CompletableFuture.allOf(wrapConsumers0(executor, v, actions))); + return cfThis.thenCompose(v -> CompletableFuture.allOf(wrapConsumers0(executor, v, copy))); } /** @@ -1800,9 +1803,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).clone(); - return cfThis.thenCompose(v -> anySuccessOf0(wrapConsumers0(executor, v, actions))); + return cfThis.thenCompose(v -> anySuccessOf0(wrapConsumers0(executor, v, copy))); } /** @@ -1838,9 +1841,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).clone(); - 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) { @@ -1867,9 +1870,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).clone(); - return cfThis.thenCompose(unused -> allFailFastOf0(wrapRunnables0(executor, actions))); + return cfThis.thenCompose(unused -> allFailFastOf0(wrapRunnables0(executor, copy))); } /** @@ -1892,9 +1895,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).clone(); - return cfThis.thenCompose(unused -> CompletableFuture.allOf(wrapRunnables0(executor, actions))); + return cfThis.thenCompose(unused -> CompletableFuture.allOf(wrapRunnables0(executor, copy))); } /** @@ -1917,9 +1920,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).clone(); - return cfThis.thenCompose(unused -> anySuccessOf0(wrapRunnables0(executor, actions))); + return cfThis.thenCompose(unused -> anySuccessOf0(wrapRunnables0(executor, copy))); } /** @@ -1942,9 +1945,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).clone(); - return cfThis.thenCompose(unused -> f_cast(CompletableFuture.anyOf(wrapRunnables0(executor, actions)))); + return cfThis.thenCompose(unused -> f_cast(CompletableFuture.anyOf(wrapRunnables0(executor, copy)))); } // endregion