-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Implement faster ThreadContextMap #2330
Conversation
@jvz not sure what your schedule is, but I'll be unavailable next week and unable to respond to comments. I've lined up a coworker to watch the conversation and help where possible. |
Our application ran into a JDK performance issue which makes constructors slow whenever the object has a final member variable. We're removing finals from hotspots, and the code in this PR contains several instances of this problem. Any objection if we remove the keyword final from those member variables? Not sure if it'll violate enforced styling. |
@jengebr, very sharp of you! Thanks for chasing this! Keeping OTOH, given the impact surface of this problem and its severity, I expect it to be promptly addressed by OpenJDK. Maybe we should put a reminder to remove this in a year or so. WDYT? |
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 seems as a very promising successor of DefaultThreadContextMap
. I would just:
- repackage it to
org.apache.logging.log4j.internal.map
(or something similar). This package will not be exported from the JPMS module, so it can not be used outside oflog4j-api
. If people show interest in using this implementation outside of Log4j API, you can resubmit it to Commons Collections, - remove the support for inheritable
ThreadLocal
s. IMHO it is an anachronistic way to propagate contexts between threads, that might have been useful, when people usednew Thread
directly.
Regarding the second point I thought about introducing soon (after the 2.23.1
release) two methods to ThreadContextMap
:
/**
* Saves the current context map data.
* <p>
* The returned object is not affected by later changes to the context map.
* </p>
* @return An opaque immutable version of the internal context map representation used.
* @see #restoreContextMap
* @since 2.24
*/
Object saveContextMap();
/**
* Restores the context map data, from a saved version.
* @param contextMap An opaque immutable version of the internal context map representation used.
* @see #saveContextMap
* @since 2.24
*/
void restoreContextMap(Object contextMap);
These methods will solve two problems:
- Will allow us to enter the modern era of context propagation. For example we can use them to easily implement
ThreadLocalAccessor
from the Micrometer Context Propagation library, - Will solve your original problem with
CloseableThreadContext
: we can usesaveContextMap/restoreContextMap
to efficiently restore the initial context data.
log4j-api/src/main/java/org/apache/logging/log4j/spi/StringArrayThreadContextMap.java
Outdated
Show resolved
Hide resolved
log4j-api/src/main/java/org/apache/logging/log4j/spi/StringArrayThreadContextMap.java
Outdated
Show resolved
Hide resolved
log4j-api/src/main/java/org/apache/logging/log4j/spi/UnmodifiableArrayBackedMap.java
Outdated
Show resolved
Hide resolved
log4j-api/src/main/java/org/apache/logging/log4j/spi/StringArrayThreadContextMap.java
Outdated
Show resolved
Hide resolved
@vy I think the reminder is a very good idea. How do you suggest we do that? Is there a github mechanism, or just a comment in the code? |
I've pushed the updated code, working through unrelated build failures. May revise further if something actually is related. |
Local build completed successfully with no further revisions. I'll be "back" in about 1.5 weeks, will address comments quickly after that. |
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.
LGTM
Thank your for your contribution. I'll enable it by default in the 2.24 branch. |
Thank you for your encouragement! I hope this helps other as much as it'll help us. |
@jengebr, would you mind reminding me why did we settle on an copy-on-write map instead of a mutable one (and cloning it if |
Hi @vy - copy-on-write was the previous state, where it would fully copy the existing HashMap into a new one, then pass that around. To answer the immediate question, I implemented copy-on-write because that was the current behavior. As for why it was first introduced, I speculate it's because the Map object itself can be exposed to the caller, and therefore is conceivably at risk of concurrent updates. The cost of copying a HashMap is quite high (slow iteration across the original, object allocations to create the new) and the perf advantage of the new one comes from drastically reducing it. If we dropped the copy-and-modify requirement, we'd see better performance than either HashMap or this. |
Currently, direct updates to getMap() are visible to the ThreadContext(), and updates to ThreadContext() are visible to the get*Map() methods. This would be a breaking change, although possibly an acceptable cost. |
@jengebr, thanks so much for the quick and elaborate response! 😍
I guess, while we can still retain these behaviours using a mutable map, I am more inclined to treat these as misuses rather than features. Right now I am firefighting with #2946 (the issue that we couldn't completely resolve in |
Thanks for working on this! I added one suggestion on the PR (streams add overhead), but I like how much easier it is to read that code. I look forward to the mutable array! Please let me know once it's ready and I can support with some performance analysis. |
By looking closely at Note that |
This implements a version of
ThreadContextMap
that is faster than `DefaultThreadContextMap, and is suitable for applications with nested classloaders, such as web applications running in containers. Key characteristics of this version are:Object[]
). This solves the classloader reference problem.DefaultThreadContextMap
except forget()
operations, where performance becomes linear.See PR #2321 for additional context and related conversations.