<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I have searched the <a href="https://github.com/apache/incubator-dubbo/issues">issues</a> of this repository and believe that this is not a duplicate.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I have checked the <a href="https://github.com/apache/incubator-dubbo/blob/master/FAQ.md">FAQ</a> of this repository and believe that this is not a duplicate.</li>
</ul>
<h3 dir="auto">Environment</h3>
<ul dir="auto">
<li>Dubbo version: 2.6.3</li>
<li>Operating System version: mac</li>
<li>Java version: 1.8</li>
<li>Registry: zookeeper</li>
</ul>
<h3 dir="auto">Steps to reproduce this issue</h3>
<ol dir="auto">
<li>use ReferenceConfig to get a service proxy</li>
<li>destroy ReferenceConfig</li>
<li>consumer node in Zookeeper has not been destroyed.</li>
</ol>
<h3 dir="auto">Cause</h3>
<p dir="auto">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.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="
private &lt;T&gt; Invoker&lt;T&gt; doRefer(Cluster cluster, Registry registry, Class&lt;T&gt; type, URL url) {
        RegistryDirectory&lt;T&gt; directory = new RegistryDirectory&lt;T&gt;(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map&lt;String, String&gt; parameters = new HashMap&lt;String, String&gt;(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())
                &amp;&amp; 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
                        + &quot;,&quot; + Constants.CONFIGURATORS_CATEGORY
                        + &quot;,&quot; + Constants.ROUTERS_CATEGORY));

        Invoker invoker = cluster.join(directory);
        ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
        return invoker;
    }
"><pre class="notranslate"><code class="notranslate">
private &lt;T&gt; Invoker&lt;T&gt; doRefer(Cluster cluster, Registry registry, Class&lt;T&gt; type, URL url) {
        RegistryDirectory&lt;T&gt; directory = new RegistryDirectory&lt;T&gt;(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map&lt;String, String&gt; parameters = new HashMap&lt;String, String&gt;(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())
                &amp;&amp; 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;
    }

</code></pre></div>
<p dir="auto">2、When ReferenceConfig is in destroy,  it only cancels subscription to related resources, but not to<br>
remove the consumer node.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="
@Override
    public void destroy() {
        if (isDestroyed()) {
            return;
        }
        // unsubscribe.
        try {
            if (getConsumerUrl() != null &amp;&amp; registry != null &amp;&amp; registry.isAvailable()) {
                registry.unsubscribe(getConsumerUrl(), this);
            }
        } catch (Throwable t) {
            logger.warn(&quot;unexpected error when unsubscribe service &quot; + serviceKey + &quot;from registry&quot; + registry.getUrl(), t);
        }
        super.destroy(); // must be executed after unsubscribing
        try {
            destroyAllInvokers();
        } catch (Throwable t) {
            logger.warn(&quot;Failed to destroy service &quot; + serviceKey, t);
        }
    }
"><pre class="notranslate"><code class="notranslate">
@Override
    public void destroy() {
        if (isDestroyed()) {
            return;
        }
        // unsubscribe.
        try {
            if (getConsumerUrl() != null &amp;&amp; registry != null &amp;&amp; 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);
        }
    }

</code></pre></div>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> I have searched the <a href="https://github.com/apache/dubbo/issues">issues</a> of this repository and believe that this is not a duplicate.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> I have checked the <a href="https://github.com/apache/dubbo/blob/master/FAQ.md">FAQ</a> of this repository and believe that this is not a duplicate.</li>
</ul>
<h3 dir="auto">Environment</h3>
<ul dir="auto">
<li>Dubbo version: 2.7.3</li>
<li>Operating System version: macOs</li>
<li>Java version: 1.8</li>
</ul>
<h3 dir="auto">Steps to reproduce this issue</h3>
<ol dir="auto">
<li>启动服务端，再启动客户端</li>
<li>服务端拉出，再拉入集群</li>
<li>在服务端主动关闭连接，或者通过网络设备导致客户端与服务端的连接中断</li>
<li>客户端发送请求，会报message channel is closed的错误，只有重启才能恢复。</li>
</ol>
<p dir="auto">Pls. provide [GitHub address] to reproduce this issue.</p>
<h3 dir="auto">Expected Result</h3>
<p dir="auto">因为dubbo客户端有主动重新建连的功能，如果服务端主动关闭，应该只有少量请求报错，之后会重新建连恢复。如果网络设备出现问题，也应该在网络设备恢复后报错停止。</p>
<h3 dir="auto">Actual Result</h3>
<p dir="auto">在部分情况下会一直报错，直到重启恢复。</p>
<p dir="auto">服务端在拉出之后，客户端会替换成lazyUrl，这里设置的RECONNECT_KEY为false</p>
<p dir="auto">org.apache.dubbo.rpc.protocol.dubbo.ReferenceCountExchangeClient</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="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(&quot;warning&quot;, Boolean.TRUE.toString())
            .addParameter(LazyConnectExchangeClient.REQUEST_WITH_WARNING_KEY, false)
            .addParameter(&quot;_client_memo&quot;, &quot;referencecounthandler.replacewithlazyclient&quot;)
            .build();
    if (!(client instanceof LazyConnectExchangeClient) || client.isClosed()) {
        client = new LazyConnectExchangeClient(lazyUrl, client.getExchangeHandler());
    }
}"><pre class="notranslate"><code class="notranslate">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());
    }
}
</code></pre></div>
<p dir="auto">因为是lazy连接，在下一个请求发起之前会尝试重新建连，在启动ReconnectTimerTask的时候会看下shouldReconnect(url)的状态是否要重新建连。<br>
org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeClient</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="private void startReconnectTask(URL url) {
    if (shouldReconnect(url)) {
        AbstractTimerTask.ChannelProvider cp = () -&gt; 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);
    }
}"><pre class="notranslate"><code class="notranslate">private void startReconnectTask(URL url) {
    if (shouldReconnect(url)) {
        AbstractTimerTask.ChannelProvider cp = () -&gt; 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);
    }
}
</code></pre></div>
<p dir="auto">如果被拉出之后再拉入，这里的url就是默认的false，不会启动ReconnectTimerTask</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="private boolean shouldReconnect(URL url) {
    return url.getParameter(Constants.RECONNECT_KEY, true);
}"><pre class="notranslate"><code class="notranslate">private boolean shouldReconnect(URL url) {
    return url.getParameter(Constants.RECONNECT_KEY, true);
}
</code></pre></div>