Hey Stephan, I have already fixed the issue both in `2.x` and `main` branches – I've updated your ticket accordingly.
> 1. The first is similar to what you proposed, but the check `if > (currentTime > cachedTime)` would only be used to decide whether the cached > time should be updated or not That is exactly what I had in mind. > 2. ... the same cached formatted time > could be reused as long as `instant.getEpochSecond() == > cached.epochSecond` We discussed this privately with Piotr and he has already shared his approach in his email pointing to his PR. What I think ideally should be done is to derive a comparison function from the pattern. That is, if the pattern is `%y-%m-%d`, the cache is still usable if the time difference is less than 1 day. There is a limited number of date-time pattern directives, we just need a table that maps each pattern to a precision which eventually points to a comparison function. For the records, I still have the impression that our date-time formatting infrastructure is sort of over-engineered. We have 3 competing implementations in the code Log4j base. In a setup where the human-readable date-time formatting of your logger is the bottleneck, I wonder if it is the logger that should be improved. In short, I am hesitant to introduce more sophistication into date-time formatting. On Thu, May 25, 2023 at 9:35 PM Stephan Markwalder <step...@markwalder.net> wrote: > Hi Volkan, > > Thank you very much for your feedback. > > I have created a GitHub issue: > https://github.com/apache/logging-log4j2/issues/1490 > > I also have an almost-ready mini application to reproduce the problem which > I will link in the GitHub issue in the next days, probably on Sunday. > > You wrote: > > shouldn't those `if (currentTime != cachedTime)` checks around the > compare-and-swap be replaced with `if (currentTime > cachedTime)`. > > I think the current strict inequality test is correct. Since different > threads can reach this code "out of order" (compared to when the event time > has been recorded), we cannot make any assumptions about whether the > current time is greater, equal, or less than the cached time. All the code > can do is re-used the cached formatted timestamp if the current time is > equal to the cached time. > > But there are two other improvements which could be made: > > 1. The first is similar to what you proposed, but the check `if > (currentTime > cachedTime)` would only be used to decide whether the cached > time should be updated or not. The assumption is that events will usually > (in the absence of concurrent threads) be logged with increasing times. So > the next event that will be logged in the future will most likely have a > time which is greater than or equal to `max(currentTime, cachedTime)`. By > only updating the cached time when it is less than the current time, the > code would increase the likelihood of a cache hit on the next method > invocation. > > 2. As of today, the cached formatted time is only reused if an event is > logged with the exact same `epochSecond` and `nanoOfSeconds` as the cached > time. But if the DatePatternConverter is configured with a time pattern > like "HH:mm:ss" which ignores everything more accurate than seconds, then > the value of `nanoOfSeconds` is actually irrelevant. No matter what this > value is, the formatted time will only be different if the value of > `epochSecond` is different. In other words, the same cached formatted time > could be reused as long as `instant.getEpochSecond() == > cached.epochSecond`. All events logged within the same second could benefit > from the cached formatted time. With some more logic, a similar improvement > could be achieved when using the default time pattern "HH:mm:ss,SSS", only > that the code now would have to compare the values of `nanoOfSeconds / > 1000000`. > > Best, > Stephan > > > On Tue, May 23, 2023 at 11:41 AM Volkan Yazıcı <vol...@yazi.ci> wrote: > > > Thanks so much for the report and the diligent analysis, Stephan! > > Much appreciated! 🙏 > > > > I think your analysis is correct. > > Would you mind creating a GitHub Issue, please? > > I will push a fix some time tonight (CET). > > > > I skimmed through JSON Template Layout's `InstantFormatter`, and it is > > looking fine. > > I want to investigate further if there are other places/layouts where > > the issue manifests itself. > > I wish I would have time to strap some jcstress harness around certain > > parts of Log4j. > > > > For the records, it always makes me wonder shouldn't those `if > (currentTime > > != cachedTime)` checks around the compare-and-swap be replaced with `if > > (currentTime > cachedTime)`. 🤔 > > > > @Piotr, @Christian, yet another supporting case for my "Unified date-time > > formatting" venture. > > > > On Sun, May 21, 2023 at 4:22 PM Stephan Markwalder < > > stephan.markwal...@fnz.com> wrote: > > > > > Hi, > > > > > > I'm not 100% sure if my analysis is correct. I therefore write to you > > > before opening an issue on GitHub. > > > > > > If two threads are calling the method > > > DatePatternConverter.formatWithoutThreadLocals(Instant,StringBuilder) > at > > > the same time, one of the threads may incorrectly use the formatted > date > > of > > > the other thread in the output. In other words, the event of one thread > > > could be written to a log file with the date of the other thread's > event. > > > > > > The following sequence shows how this could happen. All line numbers > are > > > based on this source code: > > > > > > > > > https://github.com/apache/logging-log4j2/blob/98c3ceb655d485b0adb48e05d84c8269716f59e8/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java#L327-L338 > > > > > > 0. cachedTime contains time t0. > > > > > > Thread 1: > > > 1. Line 327: Calls the method formatWithoutThreadLocals(instant,output) > > > with instant = t1, where t1 > t0. > > > 2. Line 328: Reads t0 from cachedTime into local variable cached. > > > 3. Line 329: if condition evaluates to true (t1 != t0). > > > 4. Line 330: Creates a new CachedTime(t1) and stores it in local > variable > > > newTime. > > > > > > Thread 2: > > > 5. Line 327: Calls the method formatWithoutThreadLocals(instant,output) > > > with instant = t2, where t2 > t1. > > > 6. Line 328: Reads t0 from cachedTime into local variable cached. > > > 7. Line 329: if condition evaluates to true (t2 != t0). > > > 8. Line 330: Creates a new CachedTime(t2) and stores it in local > variable > > > newTime. > > > > > > Thread 1: > > > 9. Line 331: Calls cachedTime.compareAndSet(cached, newTime): This will > > > set the shared cachedTime to thread 1's newTime t1 and return true > > > (t0.compareAndSet(t0,t1) == true). > > > 10. Line 332: Sets local variable cached = newTime (t1). > > > 11. Line 337: Appends cached.formatted (t1) to output. > > > > > > Thread 2: > > > 12. Line 331: Calls cachedTime.compareAndSet(cached, newTime): This > will > > > NOT change the shared cachedTime and return false > > (t1.compareAndSet(t0,t2) > > > == false). > > > 13. Line 334: Sets local variable cached = cachedTime.get(). <-- This > > will > > > read t1 into local variable cached! > > > 14. Line 337: Appends cached.formatted (t1!) to output. > > > > > > Result: The event recorded by thread 2 at time t2 will be logged with a > > > formatted date based on t1, the time when thread 1 has recorded its own > > > event. In other words, both events would be written to the log file > with > > > the same date and time, and the time of event 2 is completely lost. > Also, > > > in the presence of multiple appenders, the same event can be written to > > > different log files with a different date and time. > > > > > > I think the problem is found at line 334 where thread 2 is reading the > > > time that was previously written to cachedTime by thread 1. In order to > > use > > > the correct time, thread 2 should instead set cached = newTime. This > > means > > > that the if block (line 330) and else block (line 332) would be > > identical. > > > So the return value of cachedTime.compareAndSet(cached, newTime) would > > not > > > be needed, and the code could be simplified a lot. > > > > > > Is my analysis correct, or am I missing something? > > > > > > Best regards, > > > Stephan > > > > > > PS 1: This issue has been found when using Log4j in a web application. > > > This means that the use of thread-locals for "gc-free logging" is > > disabled > > > by default. > > > PS 2: The web application currently uses only synchronous loggers and > > > appenders. > > > Email Disclaimer > > > FNZ (UK) Ltd registered in England and Wales (05435760) 10th Floor, 135 > > > Bishopsgate, London EC2M 3TP, FNZ (UK) Ltd is authorised and regulated > by > > > the Financial Conduct Authority (438687); FNZ TA Services Ltd > registered > > in > > > England and Wales (09571767) 10th Floor, 135 Bishopsgate, London EC2M > > 3TP, > > > FNZ TA Services Ltd is authorised and regulated by the Financial > Conduct > > > Authority (932253); FNZ Securities Ltd registered in England and Wales > > > (09486463) 10th Floor, 135 Bishopsgate, London EC2M 3TP, FNZ Securities > > > Ltd, is authorised and regulated by the Financial Conduct Authority > > > (733400); JHC Systems Limited registered in England and Wales > (08729370) > > > Temple Point 6th Floor, 1 Temple Row, Birmingham, West Midlands, B2 > 5LG; > > > FNZ (Europe) DAC registered in Ireland (657886) Block C, Irish Life > > > Centre, Lower Abbey Street, Dublin 1, D01V9F5, Ireland; FNZ SA (Pty) > Ltd > > > registered under the laws of South Africa (2018/547997/07), 1st floor, > > > Newport House, Prestwich Street, Greenpoint, western Cape, 8001; FNZ > > > Limited registered in New Zealand (1797706) FNZ House, Level 3, 29A > > Brandon > > > Street, Wellington, 6011 New Zealand; FNZ (Australia) Pty Ltd > registered > > in > > > Australia (138 819 119) Level 1, 99 Elizabeth St, Sydney 2000; FNZ > (Hong > > > Kong) Limited registered in Hong Kong (1305362) 6A-1, Koshun House, 331 > > > Nathan Road, Hong Kong; FNZ (Singapore) Services Pte. Ltd. registered > in > > > Singapore (201307199R) 61 Robinson Road, #13-03A, Robinson Centre, > > > Singapore (068893); and FNZ (China) Ltd registered in China > > > (91310115MA1K3G4K6T) [中国(上海)自由贸易试验区世纪大道1196 号二 座20 层. > > > This message is intended solely for the addressee and may contain > > > confidential information. If you have received this message in error, > > > please send it back to us, and immediately and permanently delete it. > Do > > > not use, copy or disclose the information contained in this message or > in > > > any attachment. > > > Emails sent to and from FNZ may be monitored and read for legitimate > > > business purposes. Emails cannot be guaranteed to be secure or > > error-free, > > > and you should protect your systems. FNZ does not accept any liability > > > arising from interception, corruption, loss or destruction of this > email, > > > or if it arrives late or incomplete or with errors or viruses. > > > For more information about the FNZ group please visit our website here > > > where you can also find links to our policies including our Privacy > > policy > > > which provides information about what we do with your personal data. > > > > > > > > >