This is an automated email from the ASF dual-hosted git repository.

albumenj pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.0 by this push:
     new 47e4b81  [3.0] fix urlInvokerMap concurrent access issue (#8701)
47e4b81 is described below

commit 47e4b813f096e8e0aed560396bb530ce5be6ff24
Author: zrlw <[email protected]>
AuthorDate: Wed Sep 22 00:08:04 2021 +0800

    [3.0] fix urlInvokerMap concurrent access issue (#8701)
    
    * fix localUrlInvokerMap concurrent access problem
    
    * add forbidden check at isAvailable()
    
    * fix RegistryDirectory localUrlInvokerMap concurrent access problem
    
    * format code
    
    * change oldUrlInvokerMap to LinkedHashMap and set default size
    
    * change the initial capacity to avoid resizing
    
    * define DEFAULT_HASHMAP_LOAD_FACTOR constant
---
 .../dubbo/common/constants/RegistryConstants.java  |  2 ++
 .../client/ServiceDiscoveryRegistryDirectory.java  | 22 +++++++++++++++------
 .../registry/integration/RegistryDirectory.java    | 23 ++++++++++++++++------
 3 files changed, 35 insertions(+), 12 deletions(-)

diff --git 
a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java
 
b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java
index f76440b..6029293 100644
--- 
a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java
+++ 
b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java
@@ -130,4 +130,6 @@ public interface RegistryConstants {
 
     String REGISTRY_SERVICE_REFERENCE_PATH = 
"org.apache.dubbo.registry.RegistryService";
     String INIT = "INIT";
+
+    float DEFAULT_HASHMAP_LOAD_FACTOR = 0.75f;
 }
diff --git 
a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java
 
b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java
index 4590355..ab5c47f 100644
--- 
a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java
+++ 
b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java
@@ -41,12 +41,14 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import static org.apache.dubbo.common.constants.CommonConstants.DISABLED_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.ENABLED_KEY;
+import static 
org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_HASHMAP_LOAD_FACTOR;
 import static 
org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
 import static 
org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_TYPE_KEY;
 import static 
org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_TYPE;
@@ -126,7 +128,7 @@ public class ServiceDiscoveryRegistryDirectory<T> extends 
DynamicDirectory<T> {
 
     @Override
     public boolean isAvailable() {
-        if (isDestroyed()) {
+        if (isDestroyed() || this.forbidden) {
             return false;
         }
         Map<String, Invoker<T>> localUrlInvokerMap = urlInvokerMap;
@@ -248,12 +250,18 @@ public class ServiceDiscoveryRegistryDirectory<T> extends 
DynamicDirectory<T> {
             destroyAllInvokers(); // Close all invokers
         } else {
             this.forbidden = false; // Allow accessing
-            Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // 
local reference
             if (CollectionUtils.isEmpty(invokerUrls)) {
                 return;
             }
 
-            Map<String, Invoker<T>> newUrlInvokerMap = 
toInvokers(invokerUrls);// Translate url list to Invoker map
+            // can't use local reference because this.urlInvokerMap might be 
accessed at isAvailable() by main thread concurrently.
+            Map<String, Invoker<T>> oldUrlInvokerMap = null;
+            if (this.urlInvokerMap != null) {
+                // the initial capacity should be set greater than the maximum 
number of entries divided by the load factor to avoid resizing.
+                oldUrlInvokerMap = new LinkedHashMap<>(Math.round(1 + 
this.urlInvokerMap.size() / DEFAULT_HASHMAP_LOAD_FACTOR));
+                this.urlInvokerMap.forEach(oldUrlInvokerMap::put);
+            }
+            Map<String, Invoker<T>> newUrlInvokerMap = 
toInvokers(oldUrlInvokerMap, invokerUrls);// Translate url list to Invoker map
             logger.info("Refreshed invoker size " + newUrlInvokerMap.size());
 
             if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
@@ -282,11 +290,13 @@ public class ServiceDiscoveryRegistryDirectory<T> extends 
DynamicDirectory<T> {
 
     /**
      * Turn urls into invokers, and if url has been refer, will not 
re-reference.
+     * the items that will be put into newUrlInvokeMap will be removed from 
oldUrlInvokerMap.
      *
+     * @param oldUrlInvokerMap
      * @param urls
      * @return invokers
      */
-    private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
+    private Map<String, Invoker<T>> toInvokers(Map<String, Invoker<T>> 
oldUrlInvokerMap, List<URL> urls) {
         Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<>();
         if (urls == null || urls.isEmpty()) {
             return newUrlInvokerMap;
@@ -311,7 +321,7 @@ public class ServiceDiscoveryRegistryDirectory<T> extends 
DynamicDirectory<T> {
                 instanceAddressURL = 
overrideWithConfigurator(instanceAddressURL);
             }
 
-            Invoker<T> invoker = urlInvokerMap == null ? null : 
urlInvokerMap.get(instanceAddressURL.getAddress());
+            Invoker<T> invoker = oldUrlInvokerMap == null ? null : 
oldUrlInvokerMap.get(instanceAddressURL.getAddress());
             if (invoker == null || urlChanged(invoker, instanceAddressURL)) { 
// Not in the cache, refer again
                 try {
                     boolean enabled = true;
@@ -331,7 +341,7 @@ public class ServiceDiscoveryRegistryDirectory<T> extends 
DynamicDirectory<T> {
                 }
             } else {
                 newUrlInvokerMap.put(instanceAddressURL.getAddress(), invoker);
-                urlInvokerMap.remove(instanceAddressURL.getAddress(), invoker);
+                oldUrlInvokerMap.remove(instanceAddressURL.getAddress(), 
invoker);
             }
         }
         return newUrlInvokerMap;
diff --git 
a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
 
b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
index b39aa9a..d1badc5 100644
--- 
a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
+++ 
b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
@@ -48,6 +48,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -69,6 +70,7 @@ import static 
org.apache.dubbo.common.constants.RegistryConstants.COMPATIBLE_CON
 import static 
org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY;
 import static 
org.apache.dubbo.common.constants.RegistryConstants.CONSUMERS_CATEGORY;
 import static 
org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_CATEGORY;
+import static 
org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_HASHMAP_LOAD_FACTOR;
 import static 
org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_CONFIGURATORS_CATEGORY;
 import static 
org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
 import static 
org.apache.dubbo.common.constants.RegistryConstants.PROVIDERS_CATEGORY;
@@ -209,7 +211,7 @@ public class RegistryDirectory<T> extends 
DynamicDirectory<T> {
             destroyAllInvokers(); // Close all invokers
         } else {
             this.forbidden = false; // Allow to access
-            Map<URL, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // 
local reference
+            
             if (invokerUrls == Collections.<URL>emptyList()) {
                 invokerUrls = new ArrayList<>();
             }
@@ -222,7 +224,15 @@ public class RegistryDirectory<T> extends 
DynamicDirectory<T> {
             if (invokerUrls.isEmpty()) {
                 return;
             }
-            Map<URL, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// 
Translate url list to Invoker map
+            
+            // can't use local reference because this.urlInvokerMap might be 
accessed at isAvailable() by main thread concurrently.
+            Map<URL, Invoker<T>> oldUrlInvokerMap = null;
+            if (this.urlInvokerMap != null) {
+                // the initial capacity should be set greater than the maximum 
number of entries divided by the load factor to avoid resizing.
+                oldUrlInvokerMap = new LinkedHashMap<>(Math.round(1 + 
this.urlInvokerMap.size() / DEFAULT_HASHMAP_LOAD_FACTOR));
+                this.urlInvokerMap.forEach(oldUrlInvokerMap::put);
+            }
+            Map<URL, Invoker<T>> newUrlInvokerMap = 
toInvokers(oldUrlInvokerMap, invokerUrls);// Translate url list to Invoker map
 
             /**
              * If the calculation is wrong, it is not processed.
@@ -313,11 +323,13 @@ public class RegistryDirectory<T> extends 
DynamicDirectory<T> {
 
     /**
      * Turn urls into invokers, and if url has been refer, will not 
re-reference.
+     * the items that will be put into newUrlInvokeMap will be removed from 
oldUrlInvokerMap.
      *
+     * @param oldUrlInvokerMap     
      * @param urls
      * @return invokers
      */
-    private Map<URL, Invoker<T>> toInvokers(List<URL> urls) {
+    private Map<URL, Invoker<T>> toInvokers(Map<URL, Invoker<T>> 
oldUrlInvokerMap, List<URL> urls) {
         Map<URL, Invoker<T>> newUrlInvokerMap = new ConcurrentHashMap<>();
         if (urls == null || urls.isEmpty()) {
             return newUrlInvokerMap;
@@ -351,8 +363,7 @@ public class RegistryDirectory<T> extends 
DynamicDirectory<T> {
             URL url = mergeUrl(providerUrl);
 
             // Cache key is url that does not merge with consumer side 
parameters, regardless of how the consumer combines parameters, if the server 
url changes, then refer again
-            Map<URL, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // 
local reference
-            Invoker<T> invoker = localUrlInvokerMap == null ? null : 
localUrlInvokerMap.remove(url);
+            Invoker<T> invoker = oldUrlInvokerMap == null ? null : 
oldUrlInvokerMap.remove(url);
             if (invoker == null) { // Not in the cache, refer again
                 try {
                     boolean enabled = true;
@@ -567,7 +578,7 @@ public class RegistryDirectory<T> extends 
DynamicDirectory<T> {
 
     @Override
     public boolean isAvailable() {
-        if (isDestroyed()) {
+        if (isDestroyed() || this.forbidden) {
             return false;
         }
         Map<URL, Invoker<T>> localUrlInvokerMap = urlInvokerMap;

Reply via email to