Skip to content

Commit 47ff3fd

Browse files
committed
Rework RecyclerFactories (#2016)
1 parent d029b33 commit 47ff3fd

File tree

6 files changed

+126
-104
lines changed

6 files changed

+126
-104
lines changed

log4j-api-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ void ThreadLocalRecyclerFactory_should_work() {
3636
Assertions.assertThat(actualThreadLocalRecyclerFactory)
3737
.asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
3838
.extracting(ThreadLocalRecyclerFactory::getCapacity)
39-
.isEqualTo(RecyclerFactories.DEFAULT_QUEUE_CAPACITY);
39+
.isEqualTo(RecyclerFactories.CAPACITY);
4040
}
4141

4242
@Test

log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public class LoggingSystem {
104104
environmentLazy.map(environment -> () -> getProvider().createContextMap(environment));
105105
private final Lazy<Supplier<ThreadContextStack>> threadContextStackFactoryLazy =
106106
environmentLazy.map(environment -> () -> getProvider().createContextStack(environment));
107-
private final Lazy<RecyclerFactory> recyclerFactoryLazy = Lazy.relaxed(RecyclerFactories::getDefault);
107+
private final Lazy<RecyclerFactory> recyclerFactoryLazy = Lazy.lazy(() -> RecyclerFactories.INSTANCE);
108108

109109
public LoggingSystem() {}
110110

log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueingRecyclerFactory.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,28 @@ public class QueueingRecyclerFactory implements RecyclerFactory {
3030

3131
private final QueueFactory queueFactory;
3232

33-
public QueueingRecyclerFactory(final QueueFactory queueFactory) {
33+
private final int capacity;
34+
35+
public QueueingRecyclerFactory(final QueueFactory queueFactory, final int capacity) {
36+
if (capacity < 1) {
37+
throw new IllegalArgumentException("was expecting `capacity > 0`, found: " + capacity);
38+
}
3439
this.queueFactory = requireNonNull(queueFactory, "queueFactory");
40+
this.capacity = capacity;
41+
}
42+
43+
/**
44+
* @return the maximum number of objects retained per thread in recyclers created
45+
*/
46+
public int getCapacity() {
47+
return capacity;
3548
}
3649

3750
@Override
3851
public <V> Recycler<V> create(final Supplier<V> supplier, final Consumer<V> cleaner) {
3952
requireNonNull(supplier, "supplier");
4053
requireNonNull(cleaner, "cleaner");
41-
final Queue<V> queue = queueFactory.create();
54+
final Queue<V> queue = queueFactory.create(capacity);
4255
return new QueueingRecycler<>(supplier, cleaner, queue);
4356
}
4457

log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,83 @@
1616
*/
1717
package org.apache.logging.log4j.spi;
1818

19+
import static java.util.Objects.requireNonNull;
1920
import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled;
2021

2122
import java.util.Map;
2223
import java.util.Set;
24+
import org.apache.logging.log4j.util.InternalApi;
2325
import org.apache.logging.log4j.util.QueueFactories;
2426
import org.apache.logging.log4j.util.QueueFactory;
2527
import org.apache.logging.log4j.util.StringParameterParser;
2628

29+
/**
30+
* Stores the default {@link RecyclerFactory} instance.
31+
*/
32+
@InternalApi
2733
public final class RecyclerFactories {
2834

29-
// Visible for testing
30-
static final int DEFAULT_QUEUE_CAPACITY = Math.max(2 * Runtime.getRuntime().availableProcessors() + 1, 8);
35+
/**
36+
* The default recycler capacity.
37+
*/
38+
public static final int CAPACITY = Math.max(2 * Runtime.getRuntime().availableProcessors() + 1, 8);
3139

32-
private RecyclerFactories() {}
40+
/**
41+
* The default recycler instance.
42+
*/
43+
public static final RecyclerFactory INSTANCE = isThreadLocalsEnabled()
44+
? new ThreadLocalRecyclerFactory(CAPACITY)
45+
: new QueueingRecyclerFactory(QueueFactories.MPMC, CAPACITY);
3346

34-
public static RecyclerFactory getDefault() {
35-
return isThreadLocalsEnabled()
36-
? new ThreadLocalRecyclerFactory(DEFAULT_QUEUE_CAPACITY)
37-
: new QueueingRecyclerFactory(QueueFactories.MPMC.factory(DEFAULT_QUEUE_CAPACITY));
38-
}
47+
private RecyclerFactories() {}
3948

49+
/**
50+
* Creates a {@link RecyclerFactory} instance using the provided specification.
51+
* <p>
52+
* The recycler factory specification string must be formatted as follows:
53+
* </p>
54+
* <pre>{@code
55+
* recyclerFactorySpec = dummySpec
56+
* | threadLocalRecyclerFactorySpec
57+
* | queueingRecyclerFactorySpec
58+
*
59+
* dummySpec = "dummy"
60+
*
61+
* threadLocalRecyclerFactorySpec = "threadLocal" , [ ":" , capacityArg ]
62+
* capacityArg = "capacity=" , integer
63+
*
64+
* queueingRecyclerFactorySpec = "queue" , [ ":" , queueingRecyclerFactoryArgs ]
65+
* queueingRecyclerFactoryArgs = queueingRecyclerFactoryArg , [ "," , queueingRecyclerFactoryArg ]*
66+
* queueingRecyclerFactoryArg = capacityArg
67+
* | queueSupplierArg
68+
* queueSupplierArg = ( classPath , ".new" )
69+
* | ( classPath , "." , methodName )
70+
* }</pre>
71+
* <p>
72+
* If not specified, {@code capacity} will be set to {@code max(8, 2*C+1)}, where {@code C} denotes the value returned by {@link Runtime#availableProcessors()}.
73+
* </p>
74+
* <p>
75+
* You can find some examples below.
76+
* </p>
77+
* <ul>
78+
* <li><code>{@code dummy}</code></li>
79+
* <li><code>{@code threadLocal}</code></li>
80+
* <li><code>{@code threadLocal:capacity=13}</code></li>
81+
* <li><code>{@code queue}</code></li>
82+
* <li><code>{@code queue:supplier=java.util.ArrayDeque.new}</code></li>
83+
* <li><code>{@code queue:capacity=100}</code></li>
84+
* <li><code>{@code queue:supplier=com.acme.AwesomeQueue.create,capacity=42}</code></li>
85+
* </ul>
86+
* @param recyclerFactorySpec the recycler factory specification string
87+
* @return a recycler factory instance
88+
*/
4089
public static RecyclerFactory ofSpec(final String recyclerFactorySpec) {
4190

42-
// TLA-, MPMC-, or ABQ-based queueing factory -- if nothing is specified.
43-
if (recyclerFactorySpec == null) {
44-
return getDefault();
45-
}
91+
// Check arguments
92+
requireNonNull(recyclerFactorySpec, "recyclerFactorySpec");
4693

4794
// Is a dummy factory requested?
48-
else if (recyclerFactorySpec.equals("dummy")) {
95+
if (recyclerFactorySpec.equals("dummy")) {
4996
return DummyRecyclerFactory.getInstance();
5097
}
5198

@@ -98,24 +145,29 @@ private static RecyclerFactory readQueueingRecyclerFactory(final String recycler
98145
: supplierValue.toString();
99146

100147
// Execute the read spec
101-
final QueueFactory queueFactory = supplierPath != null
102-
? QueueFactories.createQueueFactory(supplierPath, capacity)
103-
: QueueFactories.MPMC.factory(capacity);
104-
return new QueueingRecyclerFactory(queueFactory);
148+
final QueueFactory queueFactory =
149+
supplierPath != null ? QueueFactories.ofSupplier(supplierPath) : QueueFactories.MPMC;
150+
return new QueueingRecyclerFactory(queueFactory, capacity);
105151
}
106152

107153
private static int readQueueCapacity(
108154
final String factorySpec, final Map<String, StringParameterParser.Value> parsedValues) {
109155
final StringParameterParser.Value capacityValue = parsedValues.get("capacity");
110156
if (capacityValue == null || capacityValue instanceof StringParameterParser.NullValue) {
111-
return DEFAULT_QUEUE_CAPACITY;
157+
return CAPACITY;
112158
} else {
159+
final int capacity;
113160
try {
114-
return Integer.parseInt(capacityValue.toString());
161+
capacity = Integer.parseInt(capacityValue.toString());
115162
} catch (final NumberFormatException error) {
116163
throw new IllegalArgumentException(
117164
"failed reading `capacity` in recycler factory: " + factorySpec, error);
118165
}
166+
if (capacity < 1) {
167+
throw new IllegalArgumentException(
168+
"was expecting `capacity > 0` in the recycler factory: " + factorySpec);
169+
}
170+
return capacity;
119171
}
120172
}
121173
}

log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactories.java

Lines changed: 37 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
import java.lang.reflect.Method;
2121
import java.util.Queue;
2222
import java.util.concurrent.ArrayBlockingQueue;
23+
import java.util.function.Supplier;
2324
import org.jctools.queues.MpmcArrayQueue;
2425
import org.jctools.queues.SpscArrayQueue;
2526

2627
/**
27-
* Provides {@link QueueFactory} and {@link Queue} instances for different use cases.
28+
* Provides {@link QueueFactory} instances for different use cases.
2829
* <p>
2930
* Implementations provided by <a href="https://jctools.github.io/JCTools/">JCTools</a> will be preferred, if available at runtime.
3031
* Otherwise, {@link ArrayBlockingQueue} will be used.
@@ -33,34 +34,54 @@
3334
* @since 3.0.0
3435
*/
3536
@InternalApi
36-
public enum QueueFactories {
37+
public enum QueueFactories implements QueueFactory {
3738

3839
/**
3940
* Provides a bounded queue for single-producer/single-consumer usage.
4041
*/
41-
SPSC(Lazy.lazy(JCToolsQueueFactory.SPSC::load)),
42+
SPSC(() -> SpscArrayQueue::new),
4243

4344
/**
4445
* Provides a bounded queue for multi-producer/multi-consumer usage.
4546
*/
46-
MPMC(Lazy.lazy(JCToolsQueueFactory.MPMC::load));
47+
MPMC(() -> MpmcArrayQueue::new);
4748

48-
private final Lazy<BoundedQueueFactory> queueFactory;
49+
private final QueueFactory queueFactory;
4950

50-
QueueFactories(final Lazy<BoundedQueueFactory> queueFactory) {
51-
this.queueFactory = queueFactory;
51+
QueueFactories(final Supplier<QueueFactory> queueFactoryProvider) {
52+
this.queueFactory = getOrReplaceQueueFactory(queueFactoryProvider);
5253
}
5354

54-
public QueueFactory factory(final int capacity) {
55-
return new ProxyQueueFactory(queueFactory.get(), capacity);
55+
private static QueueFactory getOrReplaceQueueFactory(final Supplier<QueueFactory> queueFactoryProvider) {
56+
try {
57+
final QueueFactory queueFactory = queueFactoryProvider.get();
58+
// Test with a large enough capacity to avoid any `IllegalArgumentExceptions` from trivial queues
59+
queueFactory.create(16);
60+
return queueFactory;
61+
} catch (final LinkageError ignored) {
62+
return ArrayBlockingQueueFactory.INSTANCE;
63+
}
5664
}
5765

66+
@Override
5867
public <E> Queue<E> create(final int capacity) {
59-
return queueFactory.get().create(capacity);
68+
return queueFactory.create(capacity);
69+
}
70+
71+
private static final class ArrayBlockingQueueFactory implements QueueFactory {
72+
73+
private static final ArrayBlockingQueueFactory INSTANCE = new ArrayBlockingQueueFactory();
74+
75+
private ArrayBlockingQueueFactory() {}
76+
77+
@Override
78+
public <E> Queue<E> create(final int capacity) {
79+
return new ArrayBlockingQueue<>(capacity);
80+
}
6081
}
6182

6283
/**
63-
* Creates a {@link QueueFactory} producing queues of provided capacity from the provided supplier.
84+
* Creates a {@link QueueFactory} using the provided supplier.
6485
* <p>
6586
* A supplier path must be formatted as follows:
6687
* <ul>
@@ -70,10 +91,9 @@ public <E> Queue<E> create(final int capacity) {
7091
* </p>
7192
*
7293
* @param supplierPath a queue supplier path (e.g., {@code org.jctools.queues.MpmcArrayQueue.new}, {@code com.acme.Queues.createBoundedQueue})
73-
* @param capacity the capacity that will be passed to the queue supplier
7494
* @return a new {@link QueueFactory} instance
7595
*/
76-
public static QueueFactory createQueueFactory(final String supplierPath, final int capacity) {
96+
public static QueueFactory ofSupplier(final String supplierPath) {
7797
final int supplierPathSplitterIndex = supplierPath.lastIndexOf('.');
7898
if (supplierPathSplitterIndex < 0) {
7999
final String message = String.format("invalid queue factory supplier path: `%s`", supplierPath);
@@ -83,84 +103,21 @@ public static QueueFactory createQueueFactory(final String supplierPath, final i
83103
final String supplierMethodName = supplierPath.substring(supplierPathSplitterIndex + 1);
84104
try {
85105
final Class<?> supplierClass = LoaderUtil.loadClass(supplierClassName);
86-
final BoundedQueueFactory queueFactory;
87106
if ("new".equals(supplierMethodName)) {
88107
final Constructor<?> supplierCtor = supplierClass.getDeclaredConstructor(int.class);
89-
queueFactory = new ConstructorProvidedQueueFactory(supplierCtor);
108+
return new ConstructorProvidedQueueFactory(supplierCtor);
90109
} else {
91110
final Method supplierMethod = supplierClass.getMethod(supplierMethodName, int.class);
92-
queueFactory = new StaticMethodProvidedQueueFactory(supplierMethod);
111+
return new StaticMethodProvidedQueueFactory(supplierMethod);
93112
}
94-
return new ProxyQueueFactory(queueFactory, capacity);
95113
} catch (final ReflectiveOperationException | LinkageError | SecurityException error) {
96114
final String message =
97115
String.format("failed to create the queue factory using the supplier path `%s`", supplierPath);
98116
throw new RuntimeException(message, error);
99117
}
100118
}
101119

102-
private static final class ProxyQueueFactory implements QueueFactory {
103-
104-
private final BoundedQueueFactory factory;
105-
106-
private final int capacity;
107-
108-
private ProxyQueueFactory(final BoundedQueueFactory factory, final int capacity) {
109-
this.factory = factory;
110-
this.capacity = capacity;
111-
}
112-
113-
@Override
114-
public <E> Queue<E> create() {
115-
return factory.create(capacity);
116-
}
117-
}
118-
119-
@FunctionalInterface
120-
private interface BoundedQueueFactory {
121-
122-
<E> Queue<E> create(final int capacity);
123-
}
124-
125-
private static final class ArrayBlockingQueueFactory implements BoundedQueueFactory {
126-
127-
private static final ArrayBlockingQueueFactory INSTANCE = new ArrayBlockingQueueFactory();
128-
129-
private ArrayBlockingQueueFactory() {}
130-
131-
@Override
132-
public <E> Queue<E> create(final int capacity) {
133-
return new ArrayBlockingQueue<>(capacity);
134-
}
135-
}
136-
137-
private enum JCToolsQueueFactory implements BoundedQueueFactory {
138-
SPSC {
139-
@Override
140-
public <E> Queue<E> create(final int capacity) {
141-
return new SpscArrayQueue<>(capacity);
142-
}
143-
},
144-
145-
MPMC {
146-
@Override
147-
public <E> Queue<E> create(final int capacity) {
148-
return new MpmcArrayQueue<>(capacity);
149-
}
150-
};
151-
152-
private BoundedQueueFactory load() {
153-
try {
154-
// Test with a large enough capacity to avoid any `IllegalArgumentExceptions` from trivial queues
155-
create(16);
156-
return this;
157-
} catch (final LinkageError ignored) {
158-
return ArrayBlockingQueueFactory.INSTANCE;
159-
}
160-
}
161-
}
162-
163-
private static final class ConstructorProvidedQueueFactory implements BoundedQueueFactory {
120+
private static final class ConstructorProvidedQueueFactory implements QueueFactory {
164121

165122
private final Constructor<?> constructor;
166123

@@ -179,7 +136,7 @@ public <E> Queue<E> create(final int capacity) {
179136
}
180137
}
181138

182-
private static final class StaticMethodProvidedQueueFactory implements BoundedQueueFactory {
139+
private static final class StaticMethodProvidedQueueFactory implements QueueFactory {
183140

184141
private final Method method;
185142

log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@
2727
@FunctionalInterface
2828
public interface QueueFactory {
2929

30-
<E> Queue<E> create();
30+
<E> Queue<E> create(final int capacity);
3131
}

0 commit comments

Comments
 (0)