Repository: apache/dubbo
File: dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java (Java)

Code before:
            try {
                if (!invokerRefreshReadLock.tryLock(LockUtils.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)) {
                    throw new RpcException("Failed to acquire read lock on invokerRefreshLock within timeout");
                }
                lockAcquired = true;
                // use clone to avoid being modified at doList().
                if (invokersInitialized) {
                    availableInvokers = validInvokers.clone();
                } else {
                    availableInvokers = invokers.clone();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RpcException(
                        "Interrupted while acquiring read lock for invoker access, cause: " + e.getMessage(), e);
            } finally {
                if (lockAcquired) {
                    invokerRefreshReadLock.unlock();
                }
            }

            if (routerChain != null) {
                singleChain = routerChain.getSingleChain(getConsumerUrl(), availableInvokers, invocation);
                singleChain.getLock().readLock().lock();
            }
            List<Invoker<T>> routedResult = doList(singleChain, availableInvokers, invocation);
            if (routedResult.isEmpty()) {
                // 2-2 - No provider available.

                logger.warn(
                        CLUSTER_NO_VALID_PROVIDER,
                        "provider server or registry center crashed",
                        "",
                        "No provider available after connectivity filter for the service "
                                + getConsumerUrl().getServiceKey()
                                + " All routed invokers' size: " + routedResult.size()
                                + " from registry " + this
                                + " on the consumer " + NetUtils.getLocalHost()
                                + " using the dubbo version " + Version.getVersion() + ".");
            }
            return Collections.unmodifiableList(routedResult);
        } finally {
            if (singleChain != null) {
                singleChain.getLock().readLock().unlock();
            }
            if (routerChain != null) {
                routerChain.getLock().readLock().unlock();
            }
        }
    }


Review comment (bug):
The conversion from ReentrantLock to ReentrantReadWriteLock is a significant concurrency change that improves read performance but introduces new complexity with the read/write lock semantics. This change should be covered by concurrent access tests that verify:
1. Multiple threads can successfully acquire the read lock simultaneously during list() calls
2. Write operations properly block read operations when modifying invoker lists
3. The timeout mechanism works correctly under contention
4. No deadlocks occur when acquiring multiple locks in the nested pattern used in list()

Consider adding tests that simulate the scenario described in the PR: concurrent read routes from consumers during service provider release periods.

Code after:
            try {
                if (!invokerRefreshReadLock.tryLock(LockUtils.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)) {
                    throw new RpcException(
                            "Failed to acquire read lock on invokerRefreshLock within timeout. " + "Timeout: "
                                    + LockUtils.DEFAULT_TIMEOUT + "ms, " + "Lock state: [readLockHeld="
                                    + invokerRefreshLock.getReadLockCount() + ", writeLockHeld="
                                    + invokerRefreshLock.isWriteLocked() + ", writeLockHeldByCurrentThread="
                                    + invokerRefreshLock.isWriteLockedByCurrentThread() + "], Service: "
                                    + getConsumerUrl().getServiceKey());
                }
                lockAcquired = true;
                // use clone to avoid being modified at doList().
                if (invokersInitialized) {
                    availableInvokers = validInvokers.clone();
                } else {
                    availableInvokers = invokers.clone();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RpcException(
                        "Interrupted while acquiring read lock for invoker access, cause: " + e.getMessage(), e);
            } finally {
                if (lockAcquired) {
                    invokerRefreshReadLock.unlock();
                }
            }

            if (routerChain != null) {
                singleChain = routerChain.getSingleChain(getConsumerUrl(), availableInvokers, invocation);
                singleChain.getLock().readLock().lock();
            }
            List<Invoker<T>> routedResult = doList(singleChain, availableInvokers, invocation);
            if (routedResult.isEmpty()) {
                // 2-2 - No provider available.

                logger.warn(
                        CLUSTER_NO_VALID_PROVIDER,
                        "provider server or registry center crashed",
                        "",
                        "No provider available after connectivity filter for the service "
                                + getConsumerUrl().getServiceKey()
                                + " All routed invokers' size: " + routedResult.size()
                                + " from registry " + this
                                + " on the consumer " + NetUtils.getLocalHost()
                                + " using the dubbo version " + Version.getVersion() + ".");
            }
            return Collections.unmodifiableList(routedResult);
        } finally {
            if (singleChain != null) {
                singleChain.getLock().readLock().unlock();
            }