-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Recycler API #1401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recycler API #1401
Conversation
This adds executions during process-sources and process-test-sources to ensure that modified files are formatted properly.
This will allow for reuse of this API in places where ThreadLocal-recycled objects are currently used. This will be beneficial to runtimes where workloads don't correspond to threads such as virtual threads, coroutines, and reactive streams. This adds an optional dependency on JCTools to log4j-api for one of the QueueingRecycler implementations.
Signed-off-by: Matt Sicker <mattsicker@apache.org>
Also updates ThreadLocalRecyclerFactory to support nested calls to acquire() by using an ArrayDeque to track potentially more than one object. Signed-off-by: Matt Sicker <mattsicker@apache.org>
Signed-off-by: Matt Sicker <mattsicker@apache.org>
- Split cleaner concerns in RecyclerFactory: eager versus lazy cleaning. This makes the Recycler API work naturally with existing StringBuilder post-usage trimming. - Uses a simpler method for checking for the presence of JCTools. Given that it's an optional (static) dependency, we can compile direct references to it in classes and see if our classes throw LinkageError-related exceptions at runtime when attempting to link to JCTools. - Rename QueueSupplier to QueueFactory - Extract a Queues util for easier access to different JCTools queue implementations when available - Update implementations of AbstractStringLayout to use Recycler API - CsvLogEventLayout - CsvParameterLayout - GelfLayout - HtmlLayout - Log4j1SyslogLayout - Log4j1XmlLayout - PatternLayout - Rfc5424Layout - SyslogLayout Signed-off-by: Matt Sicker <mattsicker@apache.org>
Signed-off-by: Matt Sicker <mattsicker@apache.org>
…actories.java Co-authored-by: Volkan Yazıcı <volkan@yazi.ci>
Signed-off-by: Matt Sicker <mattsicker@apache.org>
Signed-off-by: Matt Sicker <mattsicker@apache.org>
Signed-off-by: Matt Sicker <mattsicker@apache.org>
Signed-off-by: Matt Sicker <mattsicker@apache.org>
Signed-off-by: Matt Sicker <mattsicker@apache.org>
Signed-off-by: Matt Sicker <mattsicker@apache.org>
Signed-off-by: Matt Sicker <mattsicker@apache.org>
This simplifies DefaultLogBuilder as reuse is now handled by the recycler. This also adds a place to store a default RecyclerFactory in LoggingSystem. Signed-off-by: Matt Sicker <mattsicker@apache.org>
`LogBuilder` suppliers in the `Logger` class always return a no-op builder if the log message level is higher than the configured level. This way global filters are always skipped. This patch always returns a functional builder if we detect the presence of a global filter.
Classes implementing this interface are aware of recyclers and can release themselves after usage.
h3. Progress report I am done with code changes. I will proceed with the documentation update. |
I was actually looking for this today as I will be needing to add another ThreadLocal (as much as I really don't want to). But in looking at this PR again I do have some concerns about it.
|
Thanks for taking time to share your feedback @rgoers, much appreciated! 🙇
I assume you mean a static registry as in
|
I am thinking of when Scoped Values come into play. Or if thread inheritance is required. I don't want to have to go into the code and change the "name" passed to the factory. Instead, I would like to do:
where name represents the particular instance I want to access and spec is the Specification object. The Spec might contain private boolean inheritable; or other attributes. Based on this the factory could switch from using a ThreadLocal Recylcer to a ScopedVariableRecycler once it is available by only updating the Registry logic.
Since the Recyclers above are just thin veneer around a ThreadLocal or queue the above two items cannot really be done. One other thing worth noting is that Scoped Values will be joined at the hip with Virtual Threads so the Recycler will inherently have to allow an instance to provide a method to "run" while the Recycler is in scope. Note that this is quite different from how the Recycler defined above behaves. So in addition to the method above we might also require:
or similar. Then from within the Function you can call RecyclerRegistry.get("name") to retrieve the value(s) associated with that name. This could be implemented with either a ThreadLocal or a ScopedValue. Also, saying static registries are a problem flies in the face that Log4j has a ServiceRegistry, PropertiesUtil (the Property Registry), a Plugin registry, the LoggerContext is a registry of the application's Loggers, and the LoggerContextSelector is a registry of the LoggerContexts. The DI system is also essentially a registry much as Spring's IOC is. There are probably more but that is just what immediately came to mind. You would have to provide some concrete evidence that registries are a problem for me to buy that argument. FWIW, it is quite easy to create an intelligently designed registry be able to generate and use Mock objects for testing. For example, this is quite easy to do with Spring. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See previous comments
That pattern Ralph describes is how resilience4j works. Check out https://resilience4j.readme.io/docs |
Dependency Injection (DI) vs Static Registries
There are several JIRA tickets and mailing list threads from users (and committers, PMC members!) complaining that they cannot test logging without forking a fresh JVM each time. Since we both like Spring, let me continue with an example from there. Why do we need to inject I think hardcoding the functionality in static registries was the right decision when it was first introduced to Log4j; it was (still is!) convenient to use, the core was small, and there weren't a DI system. But things have changed, in a positive way: we now have a powerful DI system. Let's put that into use!
Yes and with any other system run by a decent DI, you shouldn't need static access. JTL contains the most complicated DI usage out there and there is not a single call to static
I have provided several examples demonstrating why static registries are bad:
I am curious to know if there is a flaw in my line of thinking.
Definitely! But this is all possible since nothing is statically wired but glued together at runtime by DI. Scoped Values
Scoped Values are indeed on the horizon and we should cater for that. We might indeed need to massage the current Recycler API to accommodate that. In particular, scoped values require a lambda (i.e., Requesting thread-related functionality while creating a recycler
I have my reservations about this Ralph. Recycler is merely an efficient object pool. I think requesting thread-related functionality would break that assumption and sophisticate the design. Do you have a particular use case in mind that requires, e.g., thread inheritance? Having a recycler registry
Do you have a [non-hypothetical] use case in mind for the recycler sharing you mention?
Let me again share a similar example from Spring. Imagine a |
Unfortunately, the DI system as it is today is broken. I discovered this when working on context properties and Matt is aware of this. The problem is that we have a bunch of "Constants" that really just check a property when the "constant" is initialized. However, that makes it impossible to make those LoggerContext specific. In addition, if you don't happen to have the "correct" Injector instance you are kind of screwed. I'm not saying these can't be fixed, but the DI system is not going to solve every problem in Log4j. Certainly not the items I am raising here.
Yes, the DI system doesn't have a static anchor. Instead we have many DI instances anchored in many places. Our DI system does have static methods (which I don't have a problem with). Again, the DI system really has nothing to do with what I am asking for.
No they don't. So long as you can register mock objects it makes no difference whether the registry has a static anchor or not.
Huh? That would be a horrible mistake. That is like complaining because you cannot provide your own DI implementation. Somethings shouldn't be replaceable.
If our DI system worked properly I might agree with you. It does not. Note - a "Registry" does not necessarily require being anchored by a static. It could very well be anchored using the DI system, if that indeed was possible.
Here you are pretty much repeating back to me what I said.
Well of course. I have two.
Well, maybe. I simply haven't looked. But we use so many ThreadLocals that I have a suspicion that a few of them share the same lifecycle. In that case, in my mind it makes sense to share the recycler. But again, without inspecting every use of ThreadLocal in Log4j, no I can't firmly give you an example.
I have to stop you right there. The problem in Log4j is that we have several Lifecycles:
Matt made a good attempt to make the DI system deal with all these different lifecycles in an appropriate manner, except it doesn't quite work (which probably isn't the fault of the DI code itself but other stuff that he didn't fix - like constants that shouldn't be constants. I will note that you also didn't address the idea of using a Spec object instead of a simple String that directly names the implementation. |
If so, I think we should pause stacking features on top of it and first fix it. I would appreciate it if you can raise this observation of yours with some more detail in the mailing list. A solution proposal would also come pretty handy.
Ralph, I am tired of this. I am trying to be polite, giving you concrete examples, but you are slamming the door with hand waving. Either show me a green CI build with
This is a concern of the
I don't see how your
I will postpone the idea of a recycler registry until a need arises. We can introduce one when needed in a backward compatible way.
I did that on purpose, since I haven't heard a single use-case where it would be necessary. Until I hear one, I am inclined to save it for future. |
Besides the merge conflict, I think you should take a look at the updated DI system to see if it fits in with the lifecycles you need. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job @vy!
Since we are introducing new interfaces and static register classes in log4j-api
, I think we should take some time to choose were to "hide" those classes to minimize the disruption to Log4j implementors.
Implementors of the Log4j API need to provide an implementation of each interface in o.a.l.l.spi
. As far as I understand semantic versioning of packages, each time we make a minor version bump of the package (e.g. we introduce a new implementation of RecyclerFactory
), they need to check if it does not break their implementation.
So ideally the o.a.l.l.spi
package should probably only contain interfaces and we should move other classes to other packages. E.g. abstract utility classes could be in o.a.l.l.spi.support
, while our implementations in o.a.l.l.spi.internal
.
What do you think?
log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java
Show resolved
Hide resolved
log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringsTest.java
Show resolved
Hide resolved
log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java
Show resolved
Hide resolved
public interface BlockingQueueFactory<E> { | ||
public interface BlockingQueueFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This interface should be removed in favor of BoundedQueueFactory
.
log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
Show resolved
Hide resolved
I have applied this convention in the context of recyclers in #2104. For other packages/functionality, it is a whole different story. |
@vy, Thanks. Remark that this is just my understanding of how SPIs should work. We should probably consult with experts and write some sort of policy for future extensions of the API. The old SPIs for BC are a whole different story, as you remark. |
How does Recycler ensure thread safety? org.apache.logging.log4j.util.Unbox,Replace the ThreadLocal in this class with Recycler? Thanks. |
Thread-safety is a part of the recycler contract. All Log4j-provided implementations are thread-safe. @tcmot, if you happen to find a place where they aren't, please submit a bug report. |
This work moves
Recycler
et al. fromlog4j-layout-template-json
tolog4j-api
and replacesThreadLocal
usages withRecycler
s wherever possible. (It is based on @jvz's draft in #1194.)Why is this a good idea?
ThreadLocal
sRecycler
abstraction addresses these pain points byThreadLocal
-based implementation, or provide a custom oneSee JSON Template Layout's documentation on it for details.
What are the change highlights?
Configuration#getRecyclerStrategy()
andLoggingSystem.getRecyclerStrategy()
ThreadLocal
s in appenders and layouts are replaced with recyclersReusableLogEvent
interface (implemented byMutableLogEvent
andRingBufferLogEvent
)StringBuilderEncoder
What further needs to be done?