Repository: symfony/symfony
File: src/Symfony/Component/Semaphore/Store/LockStore.php (PHP)

Code before:
                $lock->release();
            }

            throw new SemaphoreExpiredException($key, 'One or multiple locks were not even acquired.');
        }
    }

    /**
     * @return array<LockInterface>
     */
    private function getLocks(Key $key, float $ttlInSecond, bool $create): array
    {
        $lockName = base64_encode($key->__toString());

        $locks = [];
        if (!$key->hasState(__CLASS__) && $create) {
            $limit = $key->getLimit();

            // use a random start point to have a higher chance to catch a free slot directly
            $startPoint = rand(0, $limit - 1);

            for ($i = 0; $i < $limit; ++$i) {
                $index = ($startPoint + $i) % $limit;

                $lock = $this->lockFactory->createLock($lockName.'_'.$index, $ttlInSecond);
                if ($lock->acquire(blocking: false)) { // use lock if we can acquire it else try to catch next lock
                    $locks[] = $lock;

                    if (\count($locks) === $key->getWeight()) {
                        break;
                    }
                }
            }

            if (\count($locks) !== $key->getWeight()) {
                foreach ($locks as $lock) {
                    $lock->release();
                }

                throw new SemaphoreAcquiringException($key, 'There where no free locks found');
            }

            $key->setState(__CLASS__, $locks);
        } elseif ($key->hasState(__CLASS__)) {
            $locks = $key->getState(__CLASS__);
            \assert((function () use ($locks) {
                foreach ($locks as $lock) {
                    if (!$lock instanceof LockInterface) {
                        return false;
                    }
                }

Review comment (suggestion):
one could early break, when there is no chance of acquiring the next lock

```
if ($lock->acquire(blocking: false)) {
  // ...
} else {
  $acquired = \count($locks);
  $required = $key->getWeight();
  $remaining = $key->getLimit() - $i;
  if ($acquired + $remaining < $required) {
    break; // no chance to achieve the goal
  }
}

```

Code after:
    /**
     * @param array<LockInterface> $locks
     */
    private function releaseLocks(array $locks, Key $key): void
    {
        $lockReleasingException = null;
        foreach ($locks as $lock) {
            try {
                $lock->release();
            } catch (LockReleasingException $e) {
                $lockReleasingException ??= $e;
            }
        }

        $key->removeState(__CLASS__);

        if ($lockReleasingException) {
            throw new SemaphoreReleasingException($key, $lockReleasingException->getMessage());
        }
    }

    /**
     * @return array<LockInterface>
     */
    private function getExistingLocks(Key $key): array
    {
        return $key->hasState(__CLASS__) ? $key->getState(__CLASS__) : [];
    }

    /**
     * @return array<LockInterface>
     */
    private function createLocks(Key $key, float $ttlInSecond): array
    {
        $locks = [];
        $lockName = (string) $key;
        $limit = $key->getLimit();

        // use a random start point to have a higher chance to catch a free slot directly
        $startPoint = random_int(0, $limit - 1);

        $previousException = null;
        try {
            for ($i = 0; $i < $limit; ++$i) {
                $index = ($startPoint + $i) % $limit;

                $lock = $this->lockFactory->createLock($lockName.'_'.$index, $ttlInSecond, false);
                if ($lock->acquire(false)) { // use lock if we can acquire it else try to catch next lock
                    $locks[] = $lock;

                    if (\count($locks) >= $key->getWeight()) {