codeconsole commented on code in PR #16149:
URL: https://github.com/apache/grails-core/pull/16149#discussion_r3888770694


##########
grails-web-databinding/src/main/groovy/grails/web/databinding/DataBindingUtils.java:
##########
@@ -774,4 +800,65 @@ private static Object unwrapGString(Object value) {
         }
         return value;
     }
+
+    /**
+     * The data binding beans of a single {@link ApplicationContext}, resolved 
on first use and kept for as long as
+     * that context is the one being bound against.
+     */
+    private static final class ContextBoundBeans {
+
+        private final ApplicationContext applicationContext;
+        private final CachedBean<DataBindingSourceRegistry> 
dataBindingSourceRegistry =
+                new CachedBean<>(DataBindingSourceRegistry.BEAN_NAME, 
DataBindingSourceRegistry.class);
+        private final CachedBean<MimeTypeResolver> mimeTypeResolver =
+                new CachedBean<>(MimeTypeResolver.BEAN_NAME, 
MimeTypeResolver.class);
+        private final CachedBean<DataBinder> dataBinder =
+                new CachedBean<>(DATA_BINDER_BEAN_NAME, DataBinder.class);
+
+        private ContextBoundBeans(ApplicationContext applicationContext) {
+            this.applicationContext = applicationContext;
+        }
+
+        private DataBindingSourceRegistry getDataBindingSourceRegistry() {
+            return dataBindingSourceRegistry.get(applicationContext);
+        }
+
+        private MimeTypeResolver getMimeTypeResolver() {
+            return mimeTypeResolver.get(applicationContext);
+        }
+
+        private DataBinder getDataBinder() {
+            return dataBinder.get(applicationContext);
+        }
+    }
+
+    /**
+     * A singleton bean looked up at most once per {@link ApplicationContext}. 
Each bean is resolved lazily so that
+     * asking for one of them never triggers the creation of another.
+     * <p>
+     * Both fields are volatile and {@code resolved} is written last, so a 
thread which sees {@code resolved} also
+     * sees the bean it was resolved to. Two threads racing simply look the 
same singleton up twice.
+     *
+     * @param <T> the type of the bean
+     */
+    private static final class CachedBean<T> {
+
+        private final String beanName;
+        private final Class<T> beanType;
+        private volatile T bean;
+        private volatile boolean resolved;
+
+        private CachedBean(String beanName, Class<T> beanType) {
+            this.beanName = beanName;
+            this.beanType = beanType;
+        }
+
+        private T get(ApplicationContext applicationContext) {
+            if (!resolved) {
+                bean = applicationContext.containsBean(beanName) ? 
applicationContext.getBean(beanName, beanType) : null;
+                resolved = true;
+            }
+            return bean;
+        }

Review Comment:
   Good catch — you're right, and it's fixed in cfc47a55fe.
   
   The miss was memoized as firmly as a hit: `bean = null` and `resolved = 
true` were set together, so every later call short-circuited on `resolved` and 
returned null for the life of the context. A bean registered after that first 
attempt was never seen.
   
   I took your suggestion of keying on the bean and dropping `resolved`, with 
one small change — only writing the field when a bean is actually found, which 
avoids a redundant volatile write on the miss path and keeps Checkstyle happy 
about the ternary:
   
   ```java
   private T get(ApplicationContext applicationContext) {
       T resolved = bean;
       if (resolved == null && applicationContext.containsBean(beanName)) {
           resolved = applicationContext.getBean(beanName, beanType);
           bean = resolved;
       }
       return resolved;
   }
   ```
   
   Worth noting for this PR in particular: the hot path did not get slower. 
Once the bean is found this reads **one** volatile field instead of two, so the 
steady state is marginally cheaper than what it replaces. The repeated 
`containsBean` only happens while a bean is genuinely absent, which is a 
context still being populated rather than the per-request path this PR is about.
   
   Added a regression test, `DataBindingUtilsSpec.'test a bean registered after 
a lookup found nothing is still picked up'`. I confirmed it actually catches 
this by reverting the implementation and watching it fail, then restoring it:
   
   ```
   // with the original implementation
   DataBindingUtilsSpec > test a bean registered after a lookup found nothing 
is still picked up FAILED
   8 tests completed, 1 failed
   ```
   
   `grails-web-databinding`, `grails-databinding` and `grails-controllers` are 
green, and `codeStyle` is clean.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to