**Build scan:**
https://gradle-enterprise.elastic.co/s/dxnvahurwubcu/tests/:server:internalClusterTest/org.elasticsearch.action.admin.indices.create.CreateIndexIT/testCreateAndDeleteIndexConcurrently

**Reproduction line:**
```
./gradlew ':server:internalClusterTest' --tests "org.elasticsearch.action.admin.indices.create.CreateIndexIT.testCreateAndDeleteIndexConcurrently" -Dtests.seed=3ED3285ADA7A0152 -Dtests.locale=is -Dtests.timezone=Africa/Tunis -Druntime.java=20
```

**Applicable branches:**
main, 8.8

**Reproduces locally?:**
No

**Failure history:**
https://gradle-enterprise.elastic.co/scans/tests?tests.container=org.elasticsearch.action.admin.indices.create.CreateIndexIT&tests.test=testCreateAndDeleteIndexConcurrently

**Failure excerpt:**
```
java.lang.Exception: Test abandoned because suite timeout was reached.

  at __randomizedtesting.SeedInfo.seed([3ED3285ADA7A0152]:0)

```
server/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java
}, listener);
    }
    /**
     * Updating (creating/deleting) a retention lease is a little complicated because we must do the update itself under a primary permit,
     * then release the primary permit, and then wait for the update to be synced to the rest of the replication group before proceeding.
     * <p>
     * This method encapsulates that pattern so that callers can focus on doing their stuff.
     *
     * @param updateLeaseFunction A function which updates (creates/deletes) the lease and returns a result to be passed to the
     *                            {@code outerListener} on success. It receives a listener which must be completed on a {@code GENERIC}
     *                            thread when the updated leases are synced with the rest of the replication group.
     * @param outerListener       The listener to be completed (with the result of {@code updateLeaseFunction}) once the leases are updated
     *                            and synced.
     */
    private <R> void updateRetentionLease(Function<ActionListener<Void>, R> updateLeaseFunction, ActionListener<R> outerListener) {
        final var leasesSyncedStep = new SubscribableListener<Void>();
        runUnderPrimaryPermit(
            resultListener -> ActionListener.completeWith(
                
                resultListener,
                () -> updateLeaseFunction.apply(leasesSyncedStep)
            ),
            shard,
            cancellableThreads,
            
            outerListener.<R>delegateFailure((l, result) -> leasesSyncedStep.addListener(l.map(ignored -> result)))
        );
    }
    /**
     * Methods to update retention leases asynchronously yield a {@link ReplicationResponse} which we can discard. Moreover they yield it