You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@commons.apache.org by "benjamin-confino (via GitHub)" <gi...@apache.org> on 2023/10/09 16:04:25 UTC

[PR] Create LazyInitializerWithDisposer and test [commons-lang]

benjamin-confino opened a new pull request, #1119:
URL: https://github.com/apache/commons-lang/pull/1119

   This Pull Request creates a variant of LazyInitializer that also allows you to dispose of the wrapped object.
   
   The use case is if you have an object that might or might not be needed; but if it is created it needs to be disposed. LazyInitalizer does not allow you to check if the wrapped object has been created, so the only way to dispose it is to call get() then invoke a disposal method on the acquired object. Creating the wrapped object just so you can dispose it defeats the point, so this new class allows for this use case.
   
   Here are the design decisions I made, and the rationales for them: 
   
   - Using FailableConsumer and FailableSupplier instead of making LazyInitializerWithDisposer an abstract class like LazyInitializer.
   - - I think being able to create a new LazyInitializerWithDisposer in one line using Lambdas or `::` method reference operators provided enough of an advantage to justify using a different design to LazyInitializer.
   - Not having any way to reset the object after calling `dispose()` or `close()`
   - - I chose this because it makes it easier to reason about the lifecylce of a LazyInitializerWithDisposer. If you ever observe that it has been disposed you never have to worry about the possibility it will be reset elsewhere. I thought this was more valuable than the ability to push a change in state to all objects with a reference to LazyInitializerWithDisposer.
   - Using AlreadyDisposedException instead of ConcurrentException
   - - Even though I'm not pleased with having the statement `throws ConcurrentException` refer to both AlreadyDisposedException and ConcurrentException I thought that it was useful enough to have separate catch blocks for when the Initalizer is disposed and when something goes wrong inside the supplier or consumer to justify this. 


-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory commented on code in PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#discussion_r1355669508


##########
src/main/java/org/apache/commons/lang3/concurrent/LazyInitializerWithDisposer.java:
##########
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.Optional;
+
+import org.apache.commons.lang3.function.FailableConsumer;
+import org.apache.commons.lang3.function.FailableSupplier;
+
+/**
+ * This class provides a generic implementation of the lazy initialization
+ * pattern and also contains a way to dispose of the wrapped object if it has
+ * been previously created by using a supplier and a consumer respectively.
+ * To ensure the supplier and disposer are only used once each this class 
+ * uses the double-check idiom for an instance field as discussed in  Joshua 
+ * Bloch's "Effective Java", 2nd edition, item 71. 
+ *
+ * <p>
+ * Sometimes an application has to deal with an object only under certain
+ * circumstances, e.g. when the user selects a specific menu item or if a
+ * special event is received. If the creation of the object is costly or the
+ * consumption of memory or other system resources is significant, it may make
+ * sense to defer the creation of this object until it is really needed. This is
+ * a use case for the lazy initialization pattern.
+ * </p>
+ *
+ * <p>
+ * If these objects must be disposed of, it would not make sense to create them
+ * just so they can be disposed of. Therefore this class provides the ability to
+ * dispose of objects if and only if they have been created.
+ * </p>
+ *
+ * <p>
+ * Access to the data object is provided through the {@code get()} method. and
+ * the data object is disposed with the {@code dispose()} So, code that obtains
+ * and eventually disposes of the {@code ComplexObject} instance would simply look
+ * like this:
+ * </p>
+ *
+ * <pre>
+ * // Create the supplier and disposer: 
+ * Supplier<ComplexObject> initializer = () -> new ComplexObject();
+ * Consumer<ComplexObject> disposer = complexObject -> complexObject.shutDown();
+ *
+ * // Create an instance of the lazy initializer
+ * ComplexObjectInitializer initializer = new ComplexObjectInitializer(initializer, disposer);
+ * ...
+ * // When the object is actually needed:
+ * ComplexObject cobj = initializer.get();
+ * 
+ * // When it is time to dispose of the object
+ * initializer.dispose();
+ * </pre>
+ *
+ * <p>
+ * If multiple threads call the {@code get()} method when the object has not yet
+ * been created, they are blocked until initialization completes. The algorithm
+ * guarantees that only a single instance of the wrapped object class is
+ * created, which is passed to all callers. Once initialized, calls to the
+ * {@code get()} method are pretty fast because no synchronization is needed
+ * (only an access to a <b>volatile</b> member field).
+ * </p>
+ *
+ * @since 3.14.0
+ * @param <T> the type of the object managed by this initializer class
+ */
+public class LazyInitializerWithDisposer<T> implements ConcurrentInitializer<T> {
+
+    //NO_INIT serves double duty as the lock object to prevent any other class acquiring the monitor
+    private final Object NO_INIT = new Object(){};
+    private final Object DISPOSED = new Object(){};
+
+    private FailableConsumer<? super T, ? extends Exception> disposer;
+    private FailableSupplier<? extends T, ? extends Exception> initializer;
+
+    // Stores the managed object.
+    private volatile T object = (T) NO_INIT;
+
+    private int allowedTries;
+    Exception firstFailure = null;
+
+    /** 
+     * Constructs a LazyInitializerWithDisposer with a given initializer and disposer
+     *
+     * @param initializer an implimentation of the FailableSupplier functional interface which will create the wrapped object
+     * @param disposer an implimentation of the FailableConsumer functional interface which will dispose of the wrapped object
+     * @param allowedTries how many calls to get() will be allowed to attempt to initialize in total, before a failure is cached and becomes persistent. Set to a negative number for infinite retries.
+     */
+    public LazyInitializerWithDisposer(FailableSupplier<? extends T, ? extends Exception> initializer, FailableConsumer<? super T, ? extends Exception> disposer, int allowedTries) {
+        if (allowedTries == 0) {
+           throw new IllegalArgumentException("allowedTries must be a positive or negative number");
+        }
+        this.allowedTries = allowedTries;
+
+        this.initializer = initializer;
+        this.disposer = disposer;
+    }
+
+    /** 
+     * Constructs a LazyInitializerWithDisposer wtih a given initializer and disposer
+     *
+     * @param initializer an implimentation of the FailableSupplier functional interface which will create the wrapped object
+     * @param disposer an implimentation of the FailableConsumer functional interface which will dispose of the wrapped object
+     */
+    public LazyInitializerWithDisposer(FailableSupplier<? extends T, ? extends Exception> initializer, FailableConsumer<? super T, ? extends Exception> disposer) {
+        this(initializer, disposer, 4);

Review Comment:
   4 seems random to me. It's not clear to me why this would be needed. Closable and AutoCloseable don't support anything like that for example.



-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "benjamin-confino (via GitHub)" <gi...@apache.org>.
benjamin-confino commented on code in PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#discussion_r1355795978


##########
src/main/java/org/apache/commons/lang3/concurrent/LazyInitializerWithDisposer.java:
##########
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.Optional;
+
+import org.apache.commons.lang3.function.FailableConsumer;
+import org.apache.commons.lang3.function.FailableSupplier;
+
+/**
+ * This class provides a generic implementation of the lazy initialization
+ * pattern and also contains a way to dispose of the wrapped object if it has
+ * been previously created by using a supplier and a consumer respectively.
+ * To ensure the supplier and disposer are only used once each this class 
+ * uses the double-check idiom for an instance field as discussed in  Joshua 
+ * Bloch's "Effective Java", 2nd edition, item 71. 
+ *
+ * <p>
+ * Sometimes an application has to deal with an object only under certain
+ * circumstances, e.g. when the user selects a specific menu item or if a
+ * special event is received. If the creation of the object is costly or the
+ * consumption of memory or other system resources is significant, it may make
+ * sense to defer the creation of this object until it is really needed. This is
+ * a use case for the lazy initialization pattern.
+ * </p>
+ *
+ * <p>
+ * If these objects must be disposed of, it would not make sense to create them
+ * just so they can be disposed of. Therefore this class provides the ability to
+ * dispose of objects if and only if they have been created.
+ * </p>
+ *
+ * <p>
+ * Access to the data object is provided through the {@code get()} method. and
+ * the data object is disposed with the {@code dispose()} So, code that obtains
+ * and eventually disposes of the {@code ComplexObject} instance would simply look
+ * like this:
+ * </p>
+ *
+ * <pre>
+ * // Create the supplier and disposer: 
+ * Supplier<ComplexObject> initializer = () -> new ComplexObject();
+ * Consumer<ComplexObject> disposer = complexObject -> complexObject.shutDown();
+ *
+ * // Create an instance of the lazy initializer
+ * ComplexObjectInitializer initializer = new ComplexObjectInitializer(initializer, disposer);
+ * ...
+ * // When the object is actually needed:
+ * ComplexObject cobj = initializer.get();
+ * 
+ * // When it is time to dispose of the object
+ * initializer.dispose();
+ * </pre>
+ *
+ * <p>
+ * If multiple threads call the {@code get()} method when the object has not yet
+ * been created, they are blocked until initialization completes. The algorithm
+ * guarantees that only a single instance of the wrapped object class is
+ * created, which is passed to all callers. Once initialized, calls to the
+ * {@code get()} method are pretty fast because no synchronization is needed
+ * (only an access to a <b>volatile</b> member field).
+ * </p>
+ *
+ * @since 3.14.0
+ * @param <T> the type of the object managed by this initializer class
+ */
+public class LazyInitializerWithDisposer<T> implements ConcurrentInitializer<T> {
+
+    //NO_INIT serves double duty as the lock object to prevent any other class acquiring the monitor
+    private final Object NO_INIT = new Object(){};
+    private final Object DISPOSED = new Object(){};
+
+    private FailableConsumer<? super T, ? extends Exception> disposer;
+    private FailableSupplier<? extends T, ? extends Exception> initializer;
+
+    // Stores the managed object.
+    private volatile T object = (T) NO_INIT;
+
+    private int allowedTries;
+    Exception firstFailure = null;
+
+    /** 
+     * Constructs a LazyInitializerWithDisposer with a given initializer and disposer
+     *
+     * @param initializer an implimentation of the FailableSupplier functional interface which will create the wrapped object
+     * @param disposer an implimentation of the FailableConsumer functional interface which will dispose of the wrapped object
+     * @param allowedTries how many calls to get() will be allowed to attempt to initialize in total, before a failure is cached and becomes persistent. Set to a negative number for infinite retries.
+     */
+    public LazyInitializerWithDisposer(FailableSupplier<? extends T, ? extends Exception> initializer, FailableConsumer<? super T, ? extends Exception> disposer, int allowedTries) {
+        if (allowedTries == 0) {
+           throw new IllegalArgumentException("allowedTries must be a positive or negative number");
+        }
+        this.allowedTries = allowedTries;
+
+        this.initializer = initializer;
+        this.disposer = disposer;
+    }
+
+    /** 
+     * Constructs a LazyInitializerWithDisposer wtih a given initializer and disposer
+     *
+     * @param initializer an implimentation of the FailableSupplier functional interface which will create the wrapped object
+     * @param disposer an implimentation of the FailableConsumer functional interface which will dispose of the wrapped object
+     */
+    public LazyInitializerWithDisposer(FailableSupplier<? extends T, ? extends Exception> initializer, FailableConsumer<? super T, ? extends Exception> disposer) {
+        this(initializer, disposer, 4);

Review Comment:
   There is a logic to that 4. Three retries is a standard default. And I'm using a variable representing tries rather than retries to distinguish between 0 tries left - a fail state - and -1 or below representing infinite tries.
   
   As for why have retries at all. LazyInitializers are most useful when it is expensive to initialize something. Needing some form of IO is both a common reason for an operation to be expensive, and a case where trying again after a failure might actually work. So I think it is useful to make retries possible. 



-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "benjamin-confino (via GitHub)" <gi...@apache.org>.
benjamin-confino commented on PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#issuecomment-1760010484

   @elharo We used this pattern for the OpenTelemetry integration into OpenLiberty. It ensures we have only one instance of OpenTelemetry per application, and clean it up on application shutdown.
   
   @garydgregory
    Creating `LazyInitializer#isInitialized()` would be sufficient for a viable solution. It does mean that the API user is responsible for handling the concurrency around shutdown, which feels like the sort of low level building block where it would be good to have a solution in a library. But in answer to your question `LazyInitializer#isInitialized()` would have been sufficient minimum viable solution for my use case.
    
   Regarding the three discreet features you brought up. I looked at all implementations of ConcurrentInitializer and I think it would be easy to make an `isInitialized()` method for all of them, and from my tests I think I making them accept a `Supplier` in a backwards compatible way would also be simple. Would you like me to create a PR for each or would this be premature? As for a closer function, I would like to have one and am interested to hear how you are thinking it should be designed? 


-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "benjamin-confino (via GitHub)" <gi...@apache.org>.
benjamin-confino commented on PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#issuecomment-1760341143

   Defaulting to a `new Object()` is the plan. Not only dose it help with write `isInitialized()`, it will fix a bug I spotted in `AtomicReference`. As per the `AtomicReference` javadoc
   
   > the algorithm outlined above guarantees that {@link #get()} always returns the same object though.
   
   However if you had an initializer method that returns null in some cases (lets say it reads a value from a network and returns null on a network error) you could call get() twice, get a null the first time, and a non-null the second time. 
   
   Its late in my part of the world but I should be able to on `isInitialized()` tomorrow. 


-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory closed pull request #1119: Create LazyInitializerWithDisposer and test
URL: https://github.com/apache/commons-lang/pull/1119


-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory commented on PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#issuecomment-1760125201

   Hello @benjamin-confino 
   I'd like to see a PR for implementations of `isInitialized()` first but I'm not sure how that is possible for implementations based on `AtomicReference` since you can't tell when an `AtomicReference` itself has been set once. The issue I see is that it should be legal for `initialize` to return null to indicate that for whatever reason `null` is the right answer. In that case the initializer has been initalized to null, which is the same as the default for an `AtomicReference`. Maybe the trick here is for `AtomicReference` to default to a `new Object()` value for its value like to do in `LazyInitializer`. Make sure you pick up the latest from git master.


-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "benjamin-confino (via GitHub)" <gi...@apache.org>.
benjamin-confino commented on PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#issuecomment-1758167976

   As requested, I have opened a JIRA issue at https://issues.apache.org/jira/browse/LANG-1716


-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "elharo (via GitHub)" <gi...@apache.org>.
elharo commented on PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#issuecomment-1759525815

   My initial thought is that this is too esoteric to be worth the added API surface and maintenance burden. Is there any existing code or project you can show that would benefit heavily from this?


-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory commented on PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#issuecomment-1773162951

   Closing, replaced by #1123. TY @benjamin-confino ! 🥇 
   


-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory commented on PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#issuecomment-1759818221

   > My initial thought is that this is too esoteric to be worth the added API surface and maintenance burden. Is there any existing code or project you can show that would benefit heavily from this?
   
   I agree with @elharo. 
   
   There is also the question of the other `ConcurrentInitializer` implementations like `AtomicInitializer`, `AtomicSafeInitializer`, and so on.
   
   One scenario that is worth handling though is: "My app is shutting down, I want to release all resources, but I can't release what's in my lazy-initlializer without allocating it in the first place." As a simple and pragmatic solution to this issue, I think it is worth adding a tester method `LazyInitializer#isInitialized()`, so that an app can check to see if calling `get()` would allocate the (expensive) resource. Then, we can see which of the other `ConcurrentInitializer` implementations can provide an `#isInitialized()` method. Then we can consider pulling-up `#isInitialized()` to `ConcurrentInitializer` if indeed all `ConcurrentInitializer` can provide a sensible implementation.
   
   We should also consider a much simpler implementation and much reduced feature set to see how that would handle @benjamin-confino's use cases. The above might be enough for the stated use case.
   
   One feature I do not like is for the new initializer class to track state. Especially WRT disposal/closing. Only the receiver of the close call (I'm not calling it "dispose"), really knows if the call was processed normally, even if no exception was thrown.
   
   I do like the idea of providing a supplier as instead of subclassing, so I think I'll experiment with that as well. I also like the idea of a closer function. IOW, all of this makes up a few discrete features worth considering, but not in one giant PR IMO, especially since we have many `ConcurrentInitializer` implementations and it would be nice to provide some level of consistency where sensible.
   
   


-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory commented on PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#issuecomment-1758170973

   Can't we merge the feature into the existing class? A new class feels over the top... needs studying...


-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Create LazyInitializerWithDisposer and test [commons-lang]

Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory commented on code in PR #1119:
URL: https://github.com/apache/commons-lang/pull/1119#discussion_r1356867376


##########
src/main/java/org/apache/commons/lang3/concurrent/LazyInitializerWithDisposer.java:
##########
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.Optional;
+
+import org.apache.commons.lang3.function.FailableConsumer;
+import org.apache.commons.lang3.function.FailableSupplier;
+
+/**
+ * This class provides a generic implementation of the lazy initialization
+ * pattern and also contains a way to dispose of the wrapped object if it has
+ * been previously created by using a supplier and a consumer respectively.
+ * To ensure the supplier and disposer are only used once each this class 
+ * uses the double-check idiom for an instance field as discussed in  Joshua 
+ * Bloch's "Effective Java", 2nd edition, item 71. 
+ *
+ * <p>
+ * Sometimes an application has to deal with an object only under certain
+ * circumstances, e.g. when the user selects a specific menu item or if a
+ * special event is received. If the creation of the object is costly or the
+ * consumption of memory or other system resources is significant, it may make
+ * sense to defer the creation of this object until it is really needed. This is
+ * a use case for the lazy initialization pattern.
+ * </p>
+ *
+ * <p>
+ * If these objects must be disposed of, it would not make sense to create them
+ * just so they can be disposed of. Therefore this class provides the ability to
+ * dispose of objects if and only if they have been created.
+ * </p>
+ *
+ * <p>
+ * Access to the data object is provided through the {@code get()} method. and
+ * the data object is disposed with the {@code dispose()} So, code that obtains
+ * and eventually disposes of the {@code ComplexObject} instance would simply look
+ * like this:
+ * </p>
+ *
+ * <pre>
+ * // Create the supplier and disposer: 
+ * Supplier<ComplexObject> initializer = () -> new ComplexObject();
+ * Consumer<ComplexObject> disposer = complexObject -> complexObject.shutDown();
+ *
+ * // Create an instance of the lazy initializer
+ * ComplexObjectInitializer initializer = new ComplexObjectInitializer(initializer, disposer);
+ * ...
+ * // When the object is actually needed:
+ * ComplexObject cobj = initializer.get();
+ * 
+ * // When it is time to dispose of the object
+ * initializer.dispose();
+ * </pre>
+ *
+ * <p>
+ * If multiple threads call the {@code get()} method when the object has not yet
+ * been created, they are blocked until initialization completes. The algorithm
+ * guarantees that only a single instance of the wrapped object class is
+ * created, which is passed to all callers. Once initialized, calls to the
+ * {@code get()} method are pretty fast because no synchronization is needed
+ * (only an access to a <b>volatile</b> member field).
+ * </p>
+ *
+ * @since 3.14.0
+ * @param <T> the type of the object managed by this initializer class
+ */
+public class LazyInitializerWithDisposer<T> implements ConcurrentInitializer<T> {
+
+    //NO_INIT serves double duty as the lock object to prevent any other class acquiring the monitor
+    private final Object NO_INIT = new Object(){};
+    private final Object DISPOSED = new Object(){};
+
+    private FailableConsumer<? super T, ? extends Exception> disposer;
+    private FailableSupplier<? extends T, ? extends Exception> initializer;
+
+    // Stores the managed object.
+    private volatile T object = (T) NO_INIT;
+
+    private int allowedTries;
+    Exception firstFailure = null;
+
+    /** 
+     * Constructs a LazyInitializerWithDisposer with a given initializer and disposer
+     *
+     * @param initializer an implimentation of the FailableSupplier functional interface which will create the wrapped object
+     * @param disposer an implimentation of the FailableConsumer functional interface which will dispose of the wrapped object
+     * @param allowedTries how many calls to get() will be allowed to attempt to initialize in total, before a failure is cached and becomes persistent. Set to a negative number for infinite retries.
+     */
+    public LazyInitializerWithDisposer(FailableSupplier<? extends T, ? extends Exception> initializer, FailableConsumer<? super T, ? extends Exception> disposer, int allowedTries) {
+        if (allowedTries == 0) {
+           throw new IllegalArgumentException("allowedTries must be a positive or negative number");
+        }
+        this.allowedTries = allowedTries;
+
+        this.initializer = initializer;
+        this.disposer = disposer;
+    }
+
+    /** 
+     * Constructs a LazyInitializerWithDisposer wtih a given initializer and disposer
+     *
+     * @param initializer an implimentation of the FailableSupplier functional interface which will create the wrapped object
+     * @param disposer an implimentation of the FailableConsumer functional interface which will dispose of the wrapped object
+     */
+    public LazyInitializerWithDisposer(FailableSupplier<? extends T, ? extends Exception> initializer, FailableConsumer<? super T, ? extends Exception> disposer) {
+        this(initializer, disposer, 4);
+    }
+
+    /**
+     * Returns the object wrapped by this instance. On first access the object
+     * is created. After that it is cached and can be accessed pretty fast.
+     *
+     * @return the object initialized by this {@link LazyInitializer}
+     * @throws ConcurrentException if an error occurred during initialization of
+     * the object. Or enough previous errors have occurred to use up all allowed tries.
+     * @throws AlreadyDisposedException if dispose() or close() has already been called.
+     */
+    @Override
+    public T get() throws ConcurrentException {
+        return getInternal();
+    }
+
+    /**
+     * Returns the object wrapped by this instance inside an Optional. On first access
+     * the object is created. After that it is cached and can be accessed pretty fast.
+     *
+     * @return an Optional wrapping object initialized by this {@link LazyInitializer}
+     * or an empty Optional if dispose() or close() has already been called.
+     * @throws ConcurrentException if an error occurred during initialization of
+     * the object. Or enough previous errors have occurred to use up all allowed tries.
+     */
+    public Optional<T> getIfPossible() throws ConcurrentException {
+        try {
+            return Optional.ofNullable(getInternal());
+        } catch (AlreadyDisposedException e) {
+            return Optional.empty();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private T getInternal() throws ConcurrentException {
+        // use a temporary variable to reduce the number of reads of the
+        // volatile field
+        T result = object;
+
+        if (result == DISPOSED) {
+            throw new AlreadyDisposedException();
+        }
+
+        if (result == NO_INIT) {
+            synchronized (NO_INIT) {
+                result = object;
+
+                if (result == DISPOSED) {
+                    throw new AlreadyDisposedException();
+                }
+
+                if (result == NO_INIT) {
+                    if (allowedTries == 0) {
+                        this.initializer = null;
+                        ConcurrentException ce = new ConcurrentException();
+                        ce.addSuppressed(firstFailure);
+                        throw ce;
+                    } else if (allowedTries > 0) {
+                        allowedTries --;
+                    }
+
+                    try {
+                        object = result = initializer.get();
+                        this.initializer = null;
+                    } catch (RuntimeException e) {//So it doesn't get wrapped in the next block.
+                        if (firstFailure == null) {
+                            firstFailure = e;
+                        } else {
+                            firstFailure.addSuppressed(e); 
+                        }
+                        throw e; 
+                    } catch (Exception e) {
+                        if (firstFailure == null) {//Duplicate this code rather than use a finally block to avoid going into a finally block on every successful get
+                            firstFailure = e;
+                        } else {
+                            firstFailure.addSuppressed(e); 
+                        }
+                        throw new ConcurrentException(e); 
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Disposes the object wrapped by this instance if it has been created and closes
+     * this intializer.
+     *
+     * This method will only attempt to dispose an object if it has successfully been
+     * initialized. It will then close this LazyInitializerWithDisposer permemently 
+     * regardless of the state before the method was called, or any exceptions during
+     * disposal. Subsequent calls will have no effect.
+     *
+     * @return true if the object was successfully disposed, otherwise false
+     * @throws ConcurrentException if an error occurred during disposal of
+     * the object. The state will still be set to disposed.
+     */
+    public boolean dispose() throws ConcurrentException {
+        return disposeInternal(disposer);
+    }
+
+    /**
+     * Closes this initializer without disposing the wrapped object. This is equivalent
+     * to calling dispose() with a no-op consumer.
+     * 
+     * @return true if the object was previously created and not previously disposed, otherwise false
+     */
+    public boolean close() {
+        try {
+            return disposeInternal(ignored -> {});
+        } catch (ConcurrentException ignored) {//a no-op consumer will never throw anything.
+            return false;
+        }
+    }
+
+    private boolean disposeInternal(FailableConsumer<? super T, ? extends Exception> disposer) throws ConcurrentException {
+        T disposable = object;
+
+        if (disposable != DISPOSED) {
+            synchronized (NO_INIT) {
+                disposable = object;
+
+                if (disposable == DISPOSED) {
+                    return false;
+                }
+
+                if (disposable == NO_INIT) {
+                    object = (T) DISPOSED;
+                    return false;
+                }
+
+                try {
+                    object = (T) DISPOSED;
+                    disposer.accept(disposable);
+                    return true;
+                } catch (RuntimeException e) {//So it doesn't get wrapped in the next block.
+                    throw e; 
+                } catch (Exception e) {
+                    throw new ConcurrentException(e); 

Review Comment:
   Just FYI: `throw new ConcurrentException(ExceptionUtils.throwUnchecked(e));`



-- 
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: issues-unsubscribe@commons.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org