The existing uses a synchronize block. See https://github.com/spring-projects/spring-boot/pull/8650#issuecomment-287809311
Comment From: wilkinsona
If we don't care too much about the order of the traces then here's a possible implementation of a non-blocking, circular buffer that could be used by a TraceRepository
implementation: https://github.com/wilkinsona/spring-boot/tree/gh-8679.
Comment From: gaganis
Hello,
I have made an attempt to implement a lock free implementation for the InMemoryTraceRepository. Unfortunately my implementation did not perform well so no progress came out from that work. :disappointed:
I have created some JMH benchmarks, and I decided to share them, hoping they might prove useful to someone else. I have published them here: https://github.com/gaganis/trace-repo-perftest
some notes... - The circular buffer proposed by @wilkinsona performs impressively well - kudos!. I expect that many users will use the trace repository to debug applications and looking at the latest ones. So even for 100 entries it would be difficult to locate what they are looking for. So IMHO I believe that order is important for this feature. - I have not thoroughly reasoned about nor tested the correctness of my implementation, but since it is not performing there is not point to do that. - This is the first time I have used JMH so the setup the benchmarks and my interpretations of the results might not be correct.
A small description of the ideas behind my implementation:
The idea of my implementation was to replace the non-thread safe LinkedList
with a ConcurrentLinkedDeque
and remove the synchronized blocks. To keep the internal capacity in check I used an AtomicInteger as a counter for the entries that are currently stored and removed the extra elements whenever the store becomes bigger than capacity.
To ensure that findAll
would have access to at least capacity
number of elements the add method defers removal, which is then done by findAll
. So adds that run while a find is running will add elements and not block.
On the other hand only one thread can run findAll and only when adds are not removing elements.
Thanks for reading! Giorgos
PS here is a sample benchmark summary :smile:
# Run complete. Total time: 00:01:32
Benchmark Mode Cnt Score Error Units
BlockingInMemoryBenchmark.g thrpt 5 10584245.101 ± 1009913.120 ops/s
BlockingInMemoryBenchmark.g:testAdd thrpt 5 10555367.910 ± 1004002.421 ops/s
BlockingInMemoryBenchmark.g:testFind thrpt 5 28877.192 ± 13163.471 ops/s
CircularBufferInMemoryBenchmark.g thrpt 5 25514885.289 ± 1843593.821 ops/s
CircularBufferInMemoryBenchmark.g:testAdd thrpt 5 25476595.201 ± 1826544.420 ops/s
CircularBufferInMemoryBenchmark.g:testFind thrpt 5 38290.087 ± 18251.569 ops/s
LockFreeInMemoryBenchmark.g thrpt 5 8124761.537 ± 1651855.995 ops/s
LockFreeInMemoryBenchmark.g:testAdd thrpt 5 8107624.543 ± 1657146.079 ops/s
LockFreeInMemoryBenchmark.g:testFind thrpt 5 17136.995 ± 6305.657 ops/s
noncontention.BlockingInMemoryNoContentionBenchmark.testAdd thrpt 5 89548233.963 ± 8708153.372 ops/s
noncontention.BlockingInMemoryNoContentionBenchmark.testFind thrpt 5 117178593.048 ± 15751186.156 ops/s
noncontention.CircularBufferNoContentionBenchmark.testAdd thrpt 5 71260826.010 ± 9124265.148 ops/s
noncontention.CircularBufferNoContentionBenchmark.testFind thrpt 5 3383888455.416 ± 423124043.662 ops/s
noncontention.LockFreeNoContentionBenchmark.testAdd thrpt 5 41675362.606 ± 3174082.398 ops/s
noncontention.LockFreeNoContentionBenchmark.testFind thrpt 5 2294318.197 ± 80518.053 ops/s
Comment From: wilkinsona
Given that we now disable tracing by default and strongly encourage the use of a "proper" tracing solution outside of a development environment, I don't think we need this any more.