99public class TinyAsyncBuilder {
1010 private AsyncCaller caller ;
1111 private boolean threaded ;
12+ private boolean useRecursionSafeCaller ;
1213 private ExecutorService executor ;
1314 private ExecutorService callerExecutor ;
1415 private ScheduledExecutorService scheduler ;
@@ -32,6 +33,24 @@ public TinyAsyncBuilder threaded(boolean threaded) {
3233 return this ;
3334 }
3435
36+ /**
37+ * Configure that all caller invocations should use a recursion safe mechanism. In the normal
38+ * case this doesn't change the behaviour of caller and threadedCaller, but when deep recursion
39+ * is detected in the current thread the next recursive call is deferred to a separate thread.
40+ * <p>
41+ * Recursion is tracked for all threads that call the AsyncCallers.
42+ * <p>
43+ * This will make even the non-threaded caller use a thread in the case of deep recursion.
44+ *
45+ * @param useRecursionSafeCaller Set {@code true} if all caller invocations should be done with
46+ * a recursion safe mechanism, {@code false} otherwise.
47+ * @return This builder.
48+ */
49+ public TinyAsyncBuilder recursionSafeAsyncCaller (boolean useRecursionSafeCaller ) {
50+ this .useRecursionSafeCaller = useRecursionSafeCaller ;
51+ return this ;
52+ }
53+
3554 /**
3655 * Specify an asynchronous caller implementation.
3756 * <p>
@@ -119,11 +138,17 @@ private AsyncCaller setupThreadedCaller(AsyncCaller caller, ExecutorService call
119138 return caller ;
120139 }
121140
122- if (callerExecutor ! = null ) {
123- return new ExecutorAsyncCaller ( callerExecutor , caller ) ;
141+ if (callerExecutor = = null ) {
142+ return null ;
124143 }
125144
126- return null ;
145+ AsyncCaller threadedCaller = new ExecutorAsyncCaller (callerExecutor , caller );
146+
147+ if (useRecursionSafeCaller ) {
148+ threadedCaller = new RecursionSafeAsyncCaller (callerExecutor , threadedCaller );
149+ }
150+
151+ return threadedCaller ;
127152 }
128153
129154 private ExecutorService setupDefaultExecutor () {
@@ -164,18 +189,28 @@ private ExecutorService setupCallerExecutor(ExecutorService defaultExecutor) {
164189 * @return A caller implementation according to the provided configuration.
165190 */
166191 private AsyncCaller setupCaller () {
167- if (caller == null ) {
168- return new PrintStreamDefaultAsyncCaller (
169- System .err , Executors .newSingleThreadExecutor (new ThreadFactory () {
170- @ Override
171- public Thread newThread (final Runnable r ) {
172- final Thread thread = new Thread (r );
173- thread .setName ("tiny-async-deferrer" );
174- return thread ;
175- }
176- }));
192+ if (caller != null ) {
193+ if (useRecursionSafeCaller && callerExecutor != null ) {
194+ // Wrap user supplied AsyncCaller
195+ return new RecursionSafeAsyncCaller (callerExecutor , caller );
196+ }
197+ return caller ;
198+ }
199+
200+ AsyncCaller newCaller = new PrintStreamDefaultAsyncCaller (
201+ System .err , Executors .newSingleThreadExecutor (new ThreadFactory () {
202+ @ Override
203+ public Thread newThread (final Runnable r ) {
204+ final Thread thread = new Thread (r );
205+ thread .setName ("tiny-async-deferrer" );
206+ return thread ;
207+ }
208+ }));
209+
210+ if (useRecursionSafeCaller && callerExecutor != null ) {
211+ newCaller = new RecursionSafeAsyncCaller (callerExecutor , newCaller );
177212 }
178213
179- return caller ;
214+ return newCaller ;
180215 }
181216}
0 commit comments