Issue 1:
I have searched the issues of this repository and believe that this is not a duplicate.

I have checked the FAQ of this repository and believe that this is not a duplicate.

Environment

Dubbo version: 2.6.3

Operating System version: mac

Java version: 1.8

Registry: zookeeper

Steps to reproduce this issue

use ReferenceConfig to get a service proxy

destroy ReferenceConfig

consumer node in Zookeeper has not been destroyed.

Cause

1、When ReferenceProtocol refer an invoker, the consumer node is first registered and then subscribed to the resources of the relevant provider in most cases.

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));

Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}

2、When ReferenceConfig is in destroy, it only cancels subscription to related resources, but not to

remove the consumer node.

@Override
public void destroy() {
if (isDestroyed()) {
return;
}
// unsubscribe.
try {
if (getConsumerUrl() != null && registry != null && registry.isAvailable()) {
registry.unsubscribe(getConsumerUrl(), this);
}
} catch (Throwable t) {
logger.warn("unexpected error when unsubscribe service " + serviceKey + "from registry" + registry.getUrl(), t);
}
super.destroy(); // must be executed after unsubscribing
try {
destroyAllInvokers();
} catch (Throwable t) {
logger.warn("Failed to destroy service " + serviceKey, t);
}
}

Issue 2:
I have searched the issues of this repository and believe that this is not a duplicate.

I have checked the FAQ of this repository and believe that this is not a duplicate.

Environment

Dubbo version: 2.7.3

Operating System version: macOs

Java version: 1.8

Steps to reproduce this issue

启动服务端，再启动客户端

服务端拉出，再拉入集群

在服务端主动关闭连接，或者通过网络设备导致客户端与服务端的连接中断

客户端发送请求，会报message channel is closed的错误，只有重启才能恢复。

Pls. provide [GitHub address] to reproduce this issue.

Expected Result

因为dubbo客户端有主动重新建连的功能，如果服务端主动关闭，应该只有少量请求报错，之后会重新建连恢复。如果网络设备出现问题，也应该在网络设备恢复后报错停止。

Actual Result

在部分情况下会一直报错，直到重启恢复。

服务端在拉出之后，客户端会替换成lazyUrl，这里设置的RECONNECT_KEY为false

org.apache.dubbo.rpc.protocol.dubbo.ReferenceCountExchangeClient

private void replaceWithLazyClient() {
URL lazyUrl = URLBuilder.from(url)
.addParameter(LAZY_CONNECT_INITIAL_STATE_KEY, Boolean.TRUE)
.addParameter(RECONNECT_KEY, Boolean.FALSE)
.addParameter(SEND_RECONNECT_KEY, Boolean.FALSE.toString())
.addParameter("warning", Boolean.TRUE.toString())
.addParameter(LazyConnectExchangeClient.REQUEST_WITH_WARNING_KEY, false)
.addParameter("_client_memo", "referencecounthandler.replacewithlazyclient")
.build();
if (!(client instanceof LazyConnectExchangeClient) || client.isClosed()) {
client = new LazyConnectExchangeClient(lazyUrl, client.getExchangeHandler());
}
}

因为是lazy连接，在下一个请求发起之前会尝试重新建连，在启动ReconnectTimerTask的时候会看下shouldReconnect(url)的状态是否要重新建连。

org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeClient

private void startReconnectTask(URL url) {
if (shouldReconnect(url)) {
AbstractTimerTask.ChannelProvider cp = () -> Collections.singletonList(HeaderExchangeClient.this);
int idleTimeout = getIdleTimeout(url);
long heartbeatTimeoutTick = calculateLeastDuration(idleTimeout);
this.reconnectTimerTask = new ReconnectTimerTask(cp, heartbeatTimeoutTick, idleTimeout);
IDLE_CHECK_TIMER.newTimeout(reconnectTimerTask, heartbeatTimeoutTick, TimeUnit.MILLISECONDS);
}
}

如果被拉出之后再拉入，这里的url就是默认的false，不会启动ReconnectTimerTask

private boolean shouldReconnect(URL url) {
return url.getParameter(Constants.RECONNECT_KEY, true);
}