-
-
Notifications
You must be signed in to change notification settings - Fork 797
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
Be ready for Virtual Thread: avoid ThreadLocal pooling #919
Comments
Each of the 2 proposed solution got its own (not critical) problems anyway:
|
Yes, ThreadLocals will need to go. The problem is work prioritisation. |
I think it would be in most people's interest (certainly our Quarkus users who love Jackson) to work together to come up with a solution instead of having to settle for some lesser library |
@pjfanning thanks for the quick feedback; We're very happy Jackson users and maybe there are solutions that won't involve changes in the current priority management on this project eg exposing an SPI for the buffer pooling to let users provide their own (or disable it) |
There's #835 - if someone wants to do a PR for that that also takes this into account, that would be great. I would not read too much into the 2.15 label on that issue because we have users crying out for a 2.15 release and not much time to squeeze extra items into that release. And a 2.16 release would probably not happen for quite some time after that. |
In the meantime, has any Loom fan tried playing with the |
TBH I was personally too scared to do it eheh but it's a great suggestion and it can be a no-brain workaround for this, in the meantime - although it would just save creating the thread local, while keeping the same problem ie allocating big buffer(s) based on the default sizes i.e. jackson-core/src/main/java/com/fasterxml/jackson/core/util/BufferRecycler.java Lines 76 to 77 in 62c1ef9
The other problem is, given that's a wide sys level setting, it will impact everyone, while, as Netty users we can have mixed type of threads using jackson, and will hit the I/O threads too, that are poor "platform" threads |
@franz1981 do you need help setting that in Quarkus? |
In Quarkus we can likely limit the blast radious of this by using separate ObjectMapper objects if needed |
Yes, this is sort of expected problem but tricky one to resolve, esp. wrt configurability. I would definitely suggest trying @franz1981 Just to make sure: this is not really system-wide setting, but per-JsonFactory (which then is per-ObjectMapper), with all of pros and cons. I have not added use of System properties due to issues with global scope and use by different frameworks; Netty is a good example. |
Ultimately, the use of ThreadLocal here is a form of resource pooling and we could use a pool implementation explicitly instead. Thing is, pool implementations are not necessarily very efficient themselves. Anyone have a suggestion about low latency object pool implementations? |
Aside from legit questions about buffer pooling, recycling, there's the question of how to interface this with existing JsonFactory/JsonParser/JsonGenerator implementations. Looking at existing recycling, the core abstraction really is a single And it seems that trying to build life-cycle for Then again, maybe requiring Maybe I should try figuring out how to do this refactoring after all. |
Yep, I'm a developer of the JCTools library (see https://github.com/JCTools/JCTools/blob/master/pom.xml#L42), that implement few multi producer multi consumer queues that could be used as a "simple" shared pool (the other option is a The other option, instead, is to complain with loom-dev (in a kind and respectful way), because "they" have the right abstraction for that (mentioned in #919 (comment)) i.e. Per-carrier-thread cache.
I cannot find a central point thats state that a BufferRecycler can be safely released/recycled ( |
@franz1981 The way release would have to happen, I think, is with
Given just a bit of time I could refactor things bit by bit for 2.15. But... time is limited alas :) |
Another alternative Has advantage: can validate object (similar as jdbc connection pools do) |
@franz1981 BTW Quarkus has its own HikariCP Killer: agroal jdbc connection pool Is is possible to extract core-pool from it? |
Just to make sure: I do not think we want to include anything complicated or sizable within It looks like Fast Object Pool might be good base for such an extension. |
@magicprinc the question about Agroal can be answered by @barreiro |
no, it's not possible. Agroal is a specialized JDBC pool. it doesn't abstract away in order to obtain the best performance. |
BTW, How Spring 6 has solved this problem? |
@cowtowncoder
|
FWTW we do have non-ThreadLocal buffer recycler pool now, although need some final tweaks (wrt #1106). Default for 2.16 at least will remain ThreadLocal-backed one but beyond that we could consider changing the default. And frameworks are obviously free to re-configure defaults as they see fit starting with 2.16 (but may be limited wrt range of Jackson version supported). |
Already adding pluggability for 2.16; only one use of |
@franz1981 Are there any benchmarks: MPMC Queue -vs- https://github.com/DanielYWoo/fast-object-pool -vs- same fast-object-pool but with disruptor (an available option)? PS: I know two other pools, but they are not separate project, but classes inside main project |
Sorry for misinformation about Spring. They still use ThreadLocal (e.g. https://github.com/spring-projects/spring-framework/blob/main/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java ), So: Thread.start → startTransaction → session created → obj1.methodA → obj2.methodB → commit Transaction → session closed → thread.stop No difference for platform thread or virtual thread. So, this is completely another story :-( |
Users of quarkus (which is not here the right place to discuss this, so please raise the question on the quarkus github repo) can customize jackson which include, when the new release with the custom pool, the ability to use the one they prefer. If it was my call/choice I won't still pick
Said that, @mariofusco is using a proper JMH microbench to evaluate different options, including the ones currently offered in jackson, by default. |
@franz1981 Thank You! One hundred likes! 👍 My finest solution, how to work with ANY version of Jackson in Virtual Threads without ANY object pool. ⇒ Combining the strengths of different pools.
@Test void _jacksonInVtConcept () throws InterruptedException {
Executor handMadeVirtualThreadExecutor = r->new Thread(r).start();// empty ThreadLocal
Callable<String> taskReqThreadLocal = ()->{
var m = Jack.MAPPER.readValue("{a:1,b:true}", Map.class);
return Jack.MAPPER.writeValueAsString(m);
};
var doneSignal = new CountDownLatch(2);
//1 slow "classic"
handMadeVirtualThreadExecutor.execute(Unchecked.runnable(()->{
var jsonStr = taskReqThreadLocal.call();
assertEquals("{\"a\":1,\"b\":true}", jsonStr);
doneSignal.countDown();
}));
//2 fast "magic"
handMadeVirtualThreadExecutor.execute(Unchecked.runnable(()->{
var jsonStr = ForkJoinPool.commonPool().submit(taskReqThreadLocal).get();
assertEquals("{\"a\":1,\"b\":true}", jsonStr);
doneSignal.countDown();
}));
assertTrue(doneSignal.await(2, TimeUnit.SECONDS));
} |
@cowtowncoder I think we can happily close this :) |
Yes! Thank you for reminder @franz1981. |
hi, does there are conclusion to support Virtual thread? I haven't see any PR code change for As for this one, it's work around, but some performance slow compare with directly execute.
//2 fast "magic"
handMadeVirtualThreadExecutor.execute(Unchecked.runnable(()->{
var jsonStr = ForkJoinPool.commonPool().submit(taskReqThreadLocal).get();
assertEquals("{\"a\":1,\"b\":true}", jsonStr);
doneSignal.countDown();
})); @magicprinc This one will slow 2~2.5x compare with directly execute. I just test on my MacOS with a simple object. By the way, using OS thread will slow 7x |
Need a feature can using locked of I though the usage can be similiar this snippet:
The It will be a perfect solution if JDK can support |
@cowtowncoder I think this issue could be closed due to #1061 being already merged |
@franz1981 Issue was closed a while ago. @LoranceChen Let's not discuss things on closed threads: if more work/improvements suggested, please file a new issue (with link back to this one as background, if that makes sense). |
get it. I will consult with another topic if needed. |
Hi team.
this has been raised because, as Jackson users i.e https://github.com/quarkusio/quarkus with existing (experimental) support for https://quarkus.io/guides/virtual-threads, we would like to help fixing any integration issue with such technology.
Based on https://openjdk.org/jeps/425 and https://openjdk.org/jeps/429, re thread local pooling:
In short, https://openjdk.org/jeps/429 warn against using
ThreadLocal
s as pooling pattern, and thatScoped Values
mechanism won't save the day nor will be able to replace such mechanism.said that, where Jackson exibit this problem?
Let me attach some data:
In the above flamegraph, a lot of cycles are spent in
jackson-core/src/main/java/com/fasterxml/jackson/core/util/BufferRecyclers.java
Line 67 in 390472b
ThreadLocal
and subsequent pooled buffers allocations.The common usage scenario for virtual threads is to NOT cache them (as the Loom team advertise against it) and to be "a lot" and short lived; meaning that Jackson will end up creating tons of useless garbage that won't be reused, defeating the purpose and benefit of pooling.
What solution exists here?
For 1 we can make uses of JCtools mpmc queues designed to be very good with contention (see https://github.com/JCTools/JCTools/wiki/Getting-Started-With-JCTools#example-a-simple-object-pool for more info), but still, if we care about be friendly on contention, we could use a striped approach a-la Striped64 or simpler too e.g array of atomic ref where to perform
getAndSet
orcompareAndSet
(and distribute the first picking choice with Knuth multiplicative hashing + xorshifts).For 2 I suggest to raise the problem (I can help in case) to the loom-dev list and ask if the JDK team got plan to open up that nice (and dangerous) API in some (better and less risky) form, to allow
ThreadLocal
pooling users to still use the same exact mechanism.The text was updated successfully, but these errors were encountered: