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

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git

commit 5520ad42627e6216ae49d21881c8136c674f17fd
Author: Alex Heneveld <a...@cloudsoft.io>
AuthorDate: Mon Feb 26 11:01:24 2024 +0000

    don't use synch blocks for type coercion; can cause race if task submitted 
for coercion
    
    could use RW locks, but simpler we now use immutable lists and maps, 
replacing them in their entirety
---
 .../javalang/coerce/TypeCoercerExtensible.java     | 97 +++++++++++-----------
 1 file changed, 48 insertions(+), 49 deletions(-)

diff --git 
a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
 
b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
index 630b697d52..c235d537ab 100644
--- 
a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
+++ 
b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
@@ -85,10 +85,10 @@ public class TypeCoercerExtensible implements TypeCoercer {
     }
 
     /** Store the coercion {@link Function functions} in a {@link Table 
table}. */
-    private final Table<Class<?>, Class<?>, Function<?,?>> registry = 
HashBasedTable.create();
+    private Table<Class<?>, Class<?>, Function<?,?>> registry = 
HashBasedTable.create();
 
-    /** Store the generic coercers, ordered by the name. */
-    private final Map<String,TryCoercer> genericCoercersByName = 
Maps.newTreeMap();
+    /** Store the generic coercers, ordered by the name; reset each time 
updated */
+    private SortedMap<String,TryCoercer> genericCoercersByName = 
Maps.newTreeMap();
     /** Put the list in a cache, reset each time the map is updated. */
     private List<TryCoercer> genericCoercers = new ArrayList<>();
 
@@ -201,44 +201,43 @@ public class TypeCoercerExtensible implements TypeCoercer 
{
         }
         
         //now look in registry
-        synchronized (registry) {
-            Map<Class<?>, Function<?,?>> adapters = registry.row(targetType);
-            for (Map.Entry<Class<?>, Function<?,?>> entry : 
adapters.entrySet()) {
-                if (entry.getKey().isInstance(value)) {
-                    try {
-                        T resultT = 
((Function<Object,T>)entry.getValue()).apply(value);
-                        
-                        // Check if need to unwrap again (e.g. if want 
List<Integer> and are given a String "1,2,3"
-                        // then we'll have so far converted to List.of("1", 
"2", "3"). Call recursively.
-                        // First check that value has changed, to avoid stack 
overflow!
-                        if (!Objects.equal(value, resultT) && 
targetTypeToken.getType() instanceof ParameterizedType) {
-                            // Could duplicate check for `result instanceof 
Collection` etc; but recursive call
-                            // will be fine as if that doesn't match we'll 
safely reach `targetType.isInstance(value)`
-                            // and just return the result.
-                            Maybe<T> resultM = tryCoerce(resultT, 
targetTypeToken);
-                            if (resultM!=null) {
-                                if (resultM.isPresent()) return resultM;
-                                // if couldn't coerce parameterized types then 
back out of this coercer
-                                // but remember the error if we were first
-                                errors.add(resultM);
-                            }
-                        } else {
-                            return Maybe.of(resultT);
-                        }
-                    } catch (Exception e) {
-                        Exceptions.propagateIfFatal(e);
-                        if (log.isDebugEnabled()) {
-                            log.debug("When coercing, registry adapter 
"+entry+" gave error on "+value+" -> "+targetType+" "
-                                + (errors.isEmpty() ? "(rethrowing)" : 
"(adding as secondary error as there is already another)")
-                                + ": "+e, e);
-                        }
-                        if (e instanceof ClassCoercionException) {
-                            errors.add(Maybe.absent(e));
-                        } else {
-                            errors.add(Maybe.absent(new 
ClassCoercionException("Cannot coerce type 
"+value.getClass().getCanonicalName()+" to "+targetTypeToken+" ("+value+"): 
registered coercer failed", e)));
+        //previously synched on registry; but now we make the registry 
immutable
+        Map<Class<?>, Function<?,?>> adapters = registry.row(targetType);
+        for (Map.Entry<Class<?>, Function<?,?>> entry : adapters.entrySet()) {
+            if (entry.getKey().isInstance(value)) {
+                try {
+                    T resultT = 
((Function<Object,T>)entry.getValue()).apply(value);
+
+                    // Check if need to unwrap again (e.g. if want 
List<Integer> and are given a String "1,2,3"
+                    // then we'll have so far converted to List.of("1", "2", 
"3"). Call recursively.
+                    // First check that value has changed, to avoid stack 
overflow!
+                    if (!Objects.equal(value, resultT) && 
targetTypeToken.getType() instanceof ParameterizedType) {
+                        // Could duplicate check for `result instanceof 
Collection` etc; but recursive call
+                        // will be fine as if that doesn't match we'll safely 
reach `targetType.isInstance(value)`
+                        // and just return the result.
+                        Maybe<T> resultM = tryCoerce(resultT, targetTypeToken);
+                        if (resultM!=null) {
+                            if (resultM.isPresent()) return resultM;
+                            // if couldn't coerce parameterized types then 
back out of this coercer
+                            // but remember the error if we were first
+                            errors.add(resultM);
                         }
-                        continue;
+                    } else {
+                        return Maybe.of(resultT);
+                    }
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    if (log.isDebugEnabled()) {
+                        log.debug("When coercing, registry adapter "+entry+" 
gave error on "+value+" -> "+targetType+" "
+                            + (errors.isEmpty() ? "(rethrowing)" : "(adding as 
secondary error as there is already another)")
+                            + ": "+e, e);
                     }
+                    if (e instanceof ClassCoercionException) {
+                        errors.add(Maybe.absent(e));
+                    } else {
+                        errors.add(Maybe.absent(new 
ClassCoercionException("Cannot coerce type 
"+value.getClass().getCanonicalName()+" to "+targetTypeToken+" ("+value+"): 
registered coercer failed", e)));
+                    }
+                    continue;
                 }
             }
         }
@@ -342,25 +341,25 @@ public class TypeCoercerExtensible implements TypeCoercer 
{
     /** Registers an adapter for use with type coercion. Returns any old 
adapter registered for this pair. */
     @SuppressWarnings("unchecked")
     public synchronized <A,B> Function<? super A,B> registerAdapter(Class<A> 
sourceType, Class<B> targetType, Function<? super A,B> fn) {
-        return (Function<? super A,B>) registry.put(targetType, sourceType, 
fn);
+        HashBasedTable<Class<?>, Class<?>, Function<?, ?>> newRegistry = 
HashBasedTable.create(registry);
+        Function<? super A, B> result = (Function<? super A, B>) 
newRegistry.put(targetType, sourceType, fn);
+        registry = newRegistry;
+        return result;
     }
     
     /** Registers a generic adapter for use with type coercion. */
     @Beta
     public synchronized void registerAdapter(String nameAndOrder, TryCoercer 
fn) {
-        synchronized (genericCoercersByName) {
-            genericCoercersByName.put(nameAndOrder, fn);
-            genericCoercers = 
ImmutableList.copyOf(genericCoercersByName.values());
-        }
+        TreeMap<String, TryCoercer> gcn = 
Maps.newTreeMap(genericCoercersByName);
+        gcn.put(nameAndOrder, fn);
+        genericCoercersByName = gcn;
+        genericCoercers = ImmutableList.copyOf(genericCoercersByName.values());
     }
     
     /** @deprecated since introduction, use {@link #registerAdapter(String, 
TryCoercer)} */
     @Beta @Deprecated
-    public synchronized void registerAdapter(TryCoercer fn) {
-        synchronized (genericCoercersByName) {
-            
genericCoercersByName.put(Time.makeDateStampString()+"-"+Strings.makePaddedString(""+(genericCoercersByName.size()),
 3, "0", ""), fn);
-            genericCoercers = 
ImmutableList.copyOf(genericCoercersByName.values());
-        }
+    public void registerAdapter(TryCoercer fn) {
+        
registerAdapter(Time.makeDateStampString()+"-"+Strings.makePaddedString(""+(genericCoercersByName.size()),
 3, "0", ""), fn);
     }
 
 }

Reply via email to