From 5086b404788efc334f10fc80867465938412cbb7 Mon Sep 17 00:00:00 2001 From: mariofusco Date: Tue, 18 Jul 2023 15:49:00 +0200 Subject: [PATCH] introducing BufferRecyclerPool --- .../jackson/core/TokenStreamFactory.java | 24 +------ .../jackson/core/base/GeneratorBase.java | 6 +- .../tools/jackson/core/base/ParserBase.java | 1 + .../java/tools/jackson/core/io/IOContext.java | 18 +++-- .../tools/jackson/core/json/JsonFactory.java | 2 +- .../jackson/core/util/BufferRecyclerPool.java | 13 ++++ .../tools/jackson/core/util/ObjectPool.java | 65 +++++++++++++++++++ .../java/tools/jackson/core/BaseTest.java | 1 - .../tools/jackson/core/io/TestIOContext.java | 1 - .../jackson/core/io/TestMergedStream.java | 1 - .../tools/jackson/core/io/UTF8WriterTest.java | 15 ++--- .../util/ReadConstrainedTextBufferTest.java | 1 - .../jackson/core/write/UTF8GeneratorTest.java | 2 - .../write/WriterBasedJsonGeneratorTest.java | 2 - 14 files changed, 106 insertions(+), 46 deletions(-) create mode 100644 src/main/java/tools/jackson/core/util/BufferRecyclerPool.java create mode 100644 src/main/java/tools/jackson/core/util/ObjectPool.java diff --git a/src/main/java/tools/jackson/core/TokenStreamFactory.java b/src/main/java/tools/jackson/core/TokenStreamFactory.java index 9c0b182b2f..a646d20d9e 100644 --- a/src/main/java/tools/jackson/core/TokenStreamFactory.java +++ b/src/main/java/tools/jackson/core/TokenStreamFactory.java @@ -20,7 +20,6 @@ import tools.jackson.core.sym.PropertyNameMatcher; import tools.jackson.core.sym.SimpleNameMatcher; import tools.jackson.core.util.BufferRecycler; -import tools.jackson.core.util.BufferRecyclers; import tools.jackson.core.util.JacksonFeature; import tools.jackson.core.util.Named; import tools.jackson.core.util.Snapshottable; @@ -1182,25 +1181,6 @@ public JsonGenerator createGenerator(Writer w) throws JacksonException { /********************************************************************** */ - /** - * Method used by factory to create buffer recycler instances - * for parsers and generators. - *

- * Note: only public to give access for {@code ObjectMapper} - * - * @return BufferRecycler instance to use - */ - public BufferRecycler _getBufferRecycler() - { - // 23-Apr-2015, tatu: Let's allow disabling of buffer recycling - // scheme, for cases where it is considered harmful (possibly - // on Android, for example) - if (Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING.enabledIn(_factoryFeatures)) { - return BufferRecyclers.getBufferRecycler(); - } - return new BufferRecycler(); - } - /** * Overridable factory method that actually instantiates desired * context object. @@ -1212,7 +1192,7 @@ public BufferRecycler _getBufferRecycler() */ protected IOContext _createContext(ContentReference contentRef, boolean resourceManaged) { return new IOContext(_streamReadConstraints, _streamWriteConstraints, - _getBufferRecycler(), contentRef, + contentRef, resourceManaged, null); } @@ -1229,7 +1209,7 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource protected IOContext _createContext(ContentReference contentRef, boolean resourceManaged, JsonEncoding enc) { return new IOContext(_streamReadConstraints, _streamWriteConstraints, - _getBufferRecycler(), contentRef, + contentRef, resourceManaged, enc); } diff --git a/src/main/java/tools/jackson/core/base/GeneratorBase.java b/src/main/java/tools/jackson/core/base/GeneratorBase.java index cc83a5fb56..a32079935c 100644 --- a/src/main/java/tools/jackson/core/base/GeneratorBase.java +++ b/src/main/java/tools/jackson/core/base/GeneratorBase.java @@ -307,7 +307,11 @@ public JsonGenerator writeTree(TreeNode rootNode) throws JacksonException { */ // @Override public abstract void flush(); - @Override public void close() { _closed = true; } + @Override public void close() { + _closed = true; + _ioContext.close(); + } + @Override public boolean isClosed() { return _closed; } /* diff --git a/src/main/java/tools/jackson/core/base/ParserBase.java b/src/main/java/tools/jackson/core/base/ParserBase.java index ae130eef18..6e0d776fe3 100644 --- a/src/main/java/tools/jackson/core/base/ParserBase.java +++ b/src/main/java/tools/jackson/core/base/ParserBase.java @@ -292,6 +292,7 @@ public Object currentValue() { // as per [JACKSON-324], do in finally block // Also, internal buffer(s) can now be released as well _releaseBuffers(); + _ioContext.close(); } } } diff --git a/src/main/java/tools/jackson/core/io/IOContext.java b/src/main/java/tools/jackson/core/io/IOContext.java index 920eae1cba..6b825294cf 100644 --- a/src/main/java/tools/jackson/core/io/IOContext.java +++ b/src/main/java/tools/jackson/core/io/IOContext.java @@ -1,11 +1,13 @@ package tools.jackson.core.io; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import tools.jackson.core.JsonEncoding; import tools.jackson.core.StreamReadConstraints; import tools.jackson.core.StreamWriteConstraints; import tools.jackson.core.util.BufferRecycler; +import tools.jackson.core.util.BufferRecyclerPool; import tools.jackson.core.util.TextBuffer; import tools.jackson.core.util.ReadConstrainedTextBuffer; @@ -15,7 +17,7 @@ * readers and writers are combined under this object. One instance * is created for each reader and writer. */ -public class IOContext +public class IOContext implements AutoCloseable { /* /********************************************************************** @@ -99,6 +101,8 @@ public class IOContext */ protected char[] _nameCopyBuffer; + private volatile AtomicBoolean closed = new AtomicBoolean(false); + /* /********************************************************************** /* Life-cycle @@ -110,19 +114,18 @@ public class IOContext * * @param streamReadConstraints constraints for streaming reads * @param streamWriteConstraints constraints for streaming writes - * @param br BufferRecycler to use, if any ({@code null} if none) * @param contentRef Input source reference for location reporting * @param managedResource Whether input source is managed (owned) by Jackson library * @param enc Encoding in use */ public IOContext(StreamReadConstraints streamReadConstraints, StreamWriteConstraints streamWriteConstraints, - BufferRecycler br, ContentReference contentRef, boolean managedResource, + ContentReference contentRef, boolean managedResource, JsonEncoding enc) { _streamReadConstraints = Objects.requireNonNull(streamReadConstraints); _streamWriteConstraints = Objects.requireNonNull(streamWriteConstraints); - _bufferRecycler = br; + _bufferRecycler = BufferRecyclerPool.borrowBufferRecycler(); _contentReference = contentRef; _managedResource = managedResource; _encoding = enc; @@ -362,4 +365,11 @@ private IllegalArgumentException wrongBuf() { // sanity check failed; trying to return different, smaller buffer. return new IllegalArgumentException("Trying to release buffer smaller than original"); } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + BufferRecyclerPool.offerBufferRecycler(_bufferRecycler); + } + } } diff --git a/src/main/java/tools/jackson/core/json/JsonFactory.java b/src/main/java/tools/jackson/core/json/JsonFactory.java index fb31c9ea5b..8a8bc1ad7a 100644 --- a/src/main/java/tools/jackson/core/json/JsonFactory.java +++ b/src/main/java/tools/jackson/core/json/JsonFactory.java @@ -351,7 +351,7 @@ public JsonParser createNonBlockingByteBufferParser(ObjectReadContext readCtxt) } protected IOContext _createNonBlockingContext(Object srcRef) { - return new IOContext(_streamReadConstraints, _streamWriteConstraints, _getBufferRecycler(), + return new IOContext(_streamReadConstraints, _streamWriteConstraints, ContentReference.rawReference(srcRef), false, JsonEncoding.UTF8); } diff --git a/src/main/java/tools/jackson/core/util/BufferRecyclerPool.java b/src/main/java/tools/jackson/core/util/BufferRecyclerPool.java new file mode 100644 index 0000000000..e1d93132e9 --- /dev/null +++ b/src/main/java/tools/jackson/core/util/BufferRecyclerPool.java @@ -0,0 +1,13 @@ +package tools.jackson.core.util; + +public class BufferRecyclerPool { + + private static final ObjectPool pool = ObjectPool.newLockFreePool(BufferRecycler::new); + + public static BufferRecycler borrowBufferRecycler() { + return pool.borrow(); + } + public static void offerBufferRecycler(BufferRecycler bufferRecycler) { + pool.offer(bufferRecycler); + } +} diff --git a/src/main/java/tools/jackson/core/util/ObjectPool.java b/src/main/java/tools/jackson/core/util/ObjectPool.java new file mode 100644 index 0000000000..7ec874c12c --- /dev/null +++ b/src/main/java/tools/jackson/core/util/ObjectPool.java @@ -0,0 +1,65 @@ +package tools.jackson.core.util; + +import java.util.Queue; +import java.util.concurrent.LinkedTransferQueue; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public interface ObjectPool extends AutoCloseable { + + T borrow(); + void offer(T t); + + default void withPooledObject(Consumer objectConsumer) { + T t = borrow(); + try { + objectConsumer.accept(t); + } finally { + offer(t); + } + } + + static ObjectPool newLockFreePool(Supplier factory) { + return new LockFreeObjectPool<>(factory); + } + + static ObjectPool newLockFreePool(Supplier factory, Consumer destroyer) { + return new LockFreeObjectPool<>(factory, destroyer); + } + + class LockFreeObjectPool implements ObjectPool { + private final Supplier factory; + private final Consumer destroyer; + + private final Queue pool = new LinkedTransferQueue<>(); + + public LockFreeObjectPool(Supplier factory) { + this(factory, null); + } + + public LockFreeObjectPool(Supplier factory, Consumer destroyer) { + this.factory = factory; + this.destroyer = destroyer; + } + + @Override + public T borrow() { +// System.out.println("Before borrow size: " + pool.size()); + T t = pool.poll(); + return t != null ? t : factory.get(); + } + + @Override + public void offer(T t) { + pool.offer(t); +// System.out.println("After offer size: " + pool.size()); + } + + @Override + public void close() throws Exception { + if (destroyer != null) { + pool.forEach(destroyer); + } + } + } +} diff --git a/src/test/java/tools/jackson/core/BaseTest.java b/src/test/java/tools/jackson/core/BaseTest.java index 64499026c6..a00e4cbd10 100644 --- a/src/test/java/tools/jackson/core/BaseTest.java +++ b/src/test/java/tools/jackson/core/BaseTest.java @@ -622,7 +622,6 @@ protected JsonFactoryBuilder streamFactoryBuilder() { protected IOContext ioContextForTests(Object source) { return new IOContext(StreamReadConstraints.defaults(), StreamWriteConstraints.defaults(), - new BufferRecycler(), ContentReference.rawReference(source), true, JsonEncoding.UTF8); } diff --git a/src/test/java/tools/jackson/core/io/TestIOContext.java b/src/test/java/tools/jackson/core/io/TestIOContext.java index 10bc103a4e..392342e7b0 100644 --- a/src/test/java/tools/jackson/core/io/TestIOContext.java +++ b/src/test/java/tools/jackson/core/io/TestIOContext.java @@ -12,7 +12,6 @@ public void testAllocations() throws Exception { IOContext ctxt = new IOContext(StreamReadConstraints.defaults(), StreamWriteConstraints.defaults(), - new BufferRecycler(), ContentReference.rawReference("N/A"), true, JsonEncoding.UTF8); diff --git a/src/test/java/tools/jackson/core/io/TestMergedStream.java b/src/test/java/tools/jackson/core/io/TestMergedStream.java index c1e7da6d18..3f7076e02d 100644 --- a/src/test/java/tools/jackson/core/io/TestMergedStream.java +++ b/src/test/java/tools/jackson/core/io/TestMergedStream.java @@ -14,7 +14,6 @@ public void testSimple() throws Exception { IOContext ctxt = new IOContext(StreamReadConstraints.defaults(), StreamWriteConstraints.defaults(), - new BufferRecycler(), ContentReference.UNKNOWN_CONTENT, false, JsonEncoding.UTF8); // bit complicated; must use recyclable buffer... byte[] first = ctxt.allocReadIOBuffer(); diff --git a/src/test/java/tools/jackson/core/io/UTF8WriterTest.java b/src/test/java/tools/jackson/core/io/UTF8WriterTest.java index e8a6abcadb..bf3e25c821 100644 --- a/src/test/java/tools/jackson/core/io/UTF8WriterTest.java +++ b/src/test/java/tools/jackson/core/io/UTF8WriterTest.java @@ -105,12 +105,11 @@ public void testSurrogatesOk() throws Exception @SuppressWarnings("resource") public void testSurrogatesFail() throws Exception { - BufferRecycler rec = new BufferRecycler(); ByteArrayOutputStream out; UTF8Writer w; out = new ByteArrayOutputStream(); - w = new UTF8Writer( _ioContext(rec), out); + w = new UTF8Writer( _ioContext(), out); try { w.write(0xDE03); fail("should not pass"); @@ -119,7 +118,7 @@ public void testSurrogatesFail() throws Exception } out = new ByteArrayOutputStream(); - w = new UTF8Writer(_ioContext(rec), out); + w = new UTF8Writer(_ioContext(), out); w.write(0xD83D); try { w.write('a'); @@ -129,7 +128,7 @@ public void testSurrogatesFail() throws Exception } out = new ByteArrayOutputStream(); - w = new UTF8Writer(_ioContext(rec), out); + w = new UTF8Writer(_ioContext(), out); try { w.write("\uDE03"); fail("should not pass"); @@ -138,7 +137,7 @@ public void testSurrogatesFail() throws Exception } out = new ByteArrayOutputStream(); - w = new UTF8Writer(_ioContext(rec), out); + w = new UTF8Writer(_ioContext(), out); try { w.write("\uD83Da"); fail("should not pass"); @@ -148,11 +147,7 @@ public void testSurrogatesFail() throws Exception } private IOContext _ioContext() { - return _ioContext(new BufferRecycler()); - } - - private IOContext _ioContext(BufferRecycler br) { - return new IOContext(StreamReadConstraints.defaults(), StreamWriteConstraints.defaults(), br, + return new IOContext(StreamReadConstraints.defaults(), StreamWriteConstraints.defaults(), ContentReference.unknown(), false, JsonEncoding.UTF8); } } diff --git a/src/test/java/tools/jackson/core/util/ReadConstrainedTextBufferTest.java b/src/test/java/tools/jackson/core/util/ReadConstrainedTextBufferTest.java index 72ced5fd66..172c5cc6b0 100644 --- a/src/test/java/tools/jackson/core/util/ReadConstrainedTextBufferTest.java +++ b/src/test/java/tools/jackson/core/util/ReadConstrainedTextBufferTest.java @@ -49,7 +49,6 @@ private static TextBuffer makeConstrainedBuffer(int maxStringLen) { IOContext ioContext = new IOContext( streamReadConstraints, StreamWriteConstraints.defaults(), - new BufferRecycler(), ContentReference.rawReference("N/A"), true, JsonEncoding.UTF8); return ioContext.constructReadConstrainedTextBuffer(); } diff --git a/src/test/java/tools/jackson/core/write/UTF8GeneratorTest.java b/src/test/java/tools/jackson/core/write/UTF8GeneratorTest.java index 1767cb73a6..fd9f352be4 100644 --- a/src/test/java/tools/jackson/core/write/UTF8GeneratorTest.java +++ b/src/test/java/tools/jackson/core/write/UTF8GeneratorTest.java @@ -51,7 +51,6 @@ public void testNestingDepthWithSmallLimit() throws Exception ByteArrayOutputStream bytes = new ByteArrayOutputStream(); IOContext ioc = new IOContext(StreamReadConstraints.defaults(), StreamWriteConstraints.builder().maxNestingDepth(1).build(), - new BufferRecycler(), ContentReference.rawReference(bytes), true, JsonEncoding.UTF8); try (JsonGenerator gen = new UTF8JsonGenerator(ObjectWriteContext.empty(), ioc, 0, 0, bytes, @@ -72,7 +71,6 @@ public void testNestingDepthWithSmallLimitNestedObject() throws Exception ByteArrayOutputStream bytes = new ByteArrayOutputStream(); IOContext ioc = new IOContext(StreamReadConstraints.defaults(), StreamWriteConstraints.builder().maxNestingDepth(1).build(), - new BufferRecycler(), ContentReference.rawReference(bytes), true, JsonEncoding.UTF8); try (JsonGenerator gen = new UTF8JsonGenerator(ObjectWriteContext.empty(), ioc, 0, 0, bytes, diff --git a/src/test/java/tools/jackson/core/write/WriterBasedJsonGeneratorTest.java b/src/test/java/tools/jackson/core/write/WriterBasedJsonGeneratorTest.java index 09d191b651..300f79211c 100644 --- a/src/test/java/tools/jackson/core/write/WriterBasedJsonGeneratorTest.java +++ b/src/test/java/tools/jackson/core/write/WriterBasedJsonGeneratorTest.java @@ -22,7 +22,6 @@ public void testNestingDepthWithSmallLimit() throws Exception StringWriter sw = new StringWriter(); IOContext ioc = new IOContext(StreamReadConstraints.defaults(), StreamWriteConstraints.builder().maxNestingDepth(1).build(), - new BufferRecycler(), ContentReference.rawReference(sw), true, JsonEncoding.UTF8); try (JsonGenerator gen = new WriterBasedJsonGenerator(ObjectWriteContext.empty(), ioc, 0, 0, sw, @@ -43,7 +42,6 @@ public void testNestingDepthWithSmallLimitNestedObject() throws Exception StringWriter sw = new StringWriter(); IOContext ioc = new IOContext(StreamReadConstraints.defaults(), StreamWriteConstraints.builder().maxNestingDepth(1).build(), - new BufferRecycler(), ContentReference.rawReference(sw), true, JsonEncoding.UTF8); try (JsonGenerator gen = new WriterBasedJsonGenerator(ObjectWriteContext.empty(), ioc, 0, 0, sw,