Skip to content
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

Add BufferRecyclerProvider configuration #1083

Merged
merged 2 commits into from
Aug 22, 2023

Conversation

cowtowncoder
Copy link
Member

No description provided.


public abstract BufferRecycler acquireBufferRecycler(TokenStreamFactory forFactory);

public abstract void releaseBufferRecycler(BufferRecycler r);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure this is needed by life-cycle, but I think it may be.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the most general case I believe that this method is necessary, even though for some implementations, like the ThreadLocal based one, it could be empty. If you turn this abstract class into an interface as I was suggesting, maybe you can also provide a default implementation for this method with an empty body?

{
private static final long serialVersionUID = 1L;

public abstract BufferRecycler acquireBufferRecycler(TokenStreamFactory forFactory);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't sure of naming for get/return etc, open to different names than acquire/release.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming wise I believe that what's wrong here is the name of the class. The fact that is a pool should be reflected in its name, so we should call it BufferRecyclerPool. Once you do this acquire and release will be the most natural choices, but of course I'm open to other suggestions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmh. Ok. Not sure how I managed to miss this comment before merging.

I agree that if there's life-cycle, "provider" is not quite the right choice.
Not sure I like "pool" but maybe that'd be better...
Let me first merge things as they are and the consider renaming.

*
* @since 2.16
*/
public static class ThreadLocalBufferRecyclerProvider
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was going back and forth between creating a main-level class vs nested here; mostly located here to keep close to defaultProvider() defined above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it's better to keep all default implementations in one place and have them internal to the main level class. By the way we will also need another implementation not pooling anything but simply returning a new BufferRecycler every time that acquire is called. I believe that this "dummy" implementation should be also placed here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering about "no-op" implementation but for now it did not seem necessary. But maybe I am wrong.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. The reason -- maybe not great one -- was that the check for "do not recycle" is using a simple Feature, and by-passes provider/pool altogether.

Maybe using and passing no-op instance would make more sense, nonetheless, instead of using null provider/pool.

@cowtowncoder
Copy link
Member Author

cc @mariofusco @pjfanning this would be building block for #1064: defined as separate PR so I can merge this scaffolding to master (for 3.0) first, since merging will get bit messy overall.

Copy link
Contributor

@mariofusco mariofusco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making the BufferRecyclerProvider (or better Pool imo) pluggable is actually a great decision, also because it will allow users to provide their own implementation, possibly based on an already existing pooling library, without forcing jackson-core to also directly depend on that library. Why didn't I think about it? :)

My only outstanding doubt is about the IOContext constructor and life-cycle. The IOContext is actually the only user and owner of the BufferRecycler, so it's evident that their 2 life-cycles are related and the second should be returned to the pool only when the first is closed. So my doubts are:

  • Is it reasonable to allow to create an IOContext without going through the JsonFactory? At the moment we have many test cases doing this, but I don't understand if this is a valid use case and for which situation.
  • If so should there exist a constructor of the IOContext that doesn't take the BufferRecycler as an argument? I introduced it in my other pull request because I wanted to make it clear that the first is the owner of the second and also controls its life-cycle. In general I still believe that this was a good idea (if a resource has only one exclusive owner it should be possibly created by that owner to fully encapsulate its life-cycle in the owner implementation), but this specific situation maybe doesn't fit that general good practice.

Once you will have clarified this doubts, and eventually addressed my other comments in the code, please feel free to merge this pull request asap and I will take care to rebase mine against it and rework it accordingly.

@pjfanning
Copy link
Member

I think the IOContext should normally be tried to a JsonFactory and that there is an argument that the a buffer recycler could be reused by all IOContexts associated with a JsonFactory instance.

If there is test code that creates IOContexts directly, then there might be an argument for us to change those to use JsonFactories.

@cowtowncoder can clarify if IOContexts need to creatable without Json Factories.

@cowtowncoder
Copy link
Member Author

cowtowncoder commented Aug 22, 2023

@mariofusco @pjfanning You are correct: IOContext should never be created by anything other than JsonFactory (and it subtypes). Some tests do create it to simplify set-up but that should not be done by non-test code.
Ideally these test dependencies would be reduced to minimum or removed; but if not, perhaps IOContext should have specific test support method(s) (factory method probably).

EDIT: Ok actually there is at least one alternate/secondary owner for BufferRecyclers: ObjectMapper methods writeValueAsBytes() (and ...AsString()).
They do not use IOContext, just recycler. So perhaps supporting would be easy enough.

This leads to another thing that is bit open: need for BufferRecyclerProvider.releaseBufferRecycler() (or lack thereof).
To use that, IOContext would need to be passed both Provider and Recycler -- but it might be simpler and safer to make Provider pass itself (or just Function?) to Recycler, to be called when closing.

I think I will remove this method before merging; it (or similar) can easily be added when scaffolding is ready; for now only acquire method is really needed.

@cowtowncoder cowtowncoder merged commit c5f647e into 2.16 Aug 22, 2023
5 checks passed
@cowtowncoder cowtowncoder deleted the tatu/2.16/configurable-buffer-recycler-provider branch August 22, 2023 01:01
@cowtowncoder
Copy link
Member Author

Merged: looking at code I do think that BufferRecycler itself should probably have release() (or similar) that will release itself to provider (pool) that created it. Not needed for ThreadLocal-based provider, fwtw; but for others.
Doing this would require adding BufferRecycler constructor that takes BufferRecyclerProvider and retains reference to it.

WDYT?

@cowtowncoder
Copy link
Member Author

Quick note: I will do another PR with at least following changes:

  1. Rename Provider -> Pool
  2. Add "release" method back -- I finally grokked why this is almost certainly needed (I think we'll pass Pool instance through BufferRecycler, so no custom sub-classes)
  3. Add no-op implementation

@cowtowncoder
Copy link
Member Author

Did #1085, merged it. So I think this is closer to working baseline.

@mariofusco
Copy link
Contributor

Merged: looking at code I do think that BufferRecycler itself should probably have release() (or similar) that will release itself to provider (pool) that created it. Not needed for ThreadLocal-based provider, fwtw; but for others. Doing this would require adding BufferRecycler constructor that takes BufferRecyclerProvider and retains reference to it.

WDYT?

Agreed, and I did something similar in my pull request. Actually there also BufferRecycler implements AutoCloseable and its close() method behaves as a release() returning the instance to the pool. I'm no longer sure if what I did is semantically correct because there you're not really closing the instance, but only making it available to the pool again, so yes, I guess that I will use release() also in my pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants