Skip to content

Commit 28b0dd3

Browse files
authored
Fix #1195: add actual BufferRecycler reuse (#1196)
1 parent 5176ae9 commit 28b0dd3

File tree

7 files changed

+164
-9
lines changed

7 files changed

+164
-9
lines changed

src/main/java/com/fasterxml/jackson/core/JsonFactory.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -2181,12 +2181,29 @@ public RecyclerPool<BufferRecycler> _getRecyclerPool() {
21812181
* @return I/O context created
21822182
*/
21832183
protected IOContext _createContext(ContentReference contentRef, boolean resourceManaged) {
2184-
// 21-Mar-2021, tatu: Bit of defensive coding for backwards compatibility
2184+
BufferRecycler br = null;
2185+
boolean recyclerExternal = false;
2186+
21852187
if (contentRef == null) {
21862188
contentRef = ContentReference.unknown();
2189+
} else {
2190+
Object content = contentRef.getRawContent();
2191+
// 18-Jan-2024, tatu: [core#1195] Let's see if we can reuse already allocated recycler
2192+
// (is the case when SegmentedStringWriter / ByteArrayBuilder passed)
2193+
if (content instanceof BufferRecycler.Gettable) {
2194+
br = ((BufferRecycler.Gettable) content).bufferRecycler();
2195+
recyclerExternal = (br != null);
2196+
}
21872197
}
2188-
return new IOContext(_streamReadConstraints, _streamWriteConstraints, _errorReportConfiguration,
2189-
_getBufferRecycler(), contentRef, resourceManaged);
2198+
if (br == null) {
2199+
br = _getBufferRecycler();
2200+
}
2201+
IOContext ctxt = new IOContext(_streamReadConstraints, _streamWriteConstraints, _errorReportConfiguration,
2202+
br, contentRef, resourceManaged);
2203+
if (recyclerExternal) {
2204+
ctxt.markBufferRecyclerReleased();
2205+
}
2206+
return ctxt;
21902207
}
21912208

21922209
/**

src/main/java/com/fasterxml/jackson/core/io/IOContext.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ public class IOContext implements AutoCloseable
6464
*/
6565
protected final BufferRecycler _bufferRecycler;
6666

67+
/**
68+
* Flag that indicates whether this context instance should release
69+
* configured {@code _bufferRecycler} or not: if it does, it needs to call
70+
* (via {@link BufferRecycler#releaseToPool()} when closed; if not,
71+
* should do nothing (recycler life-cycle is externally managed)
72+
*
73+
* @since 2.17
74+
*/
75+
protected boolean _releaseRecycler = true;
76+
6777
/**
6878
* @since 2.15
6979
*/
@@ -194,6 +204,18 @@ public IOContext(BufferRecycler br, Object rawContent, boolean managedResource)
194204
this(br, ContentReference.rawReference(rawContent), managedResource);
195205
}
196206

207+
/**
208+
* Method to call to prevent {@link #_bufferRecycler} release upon
209+
* {@link #close()}: called when {@link #_bufferRecycler} life-cycle is
210+
* externally managed.
211+
*
212+
* @since 2.17
213+
*/
214+
public IOContext markBufferRecyclerReleased() {
215+
_releaseRecycler = false;
216+
return this;
217+
}
218+
197219
/*
198220
/**********************************************************************
199221
/* Public API, accessors
@@ -469,8 +491,11 @@ private IllegalArgumentException wrongBuf() {
469491
@Override
470492
public void close() {
471493
if (!_closed) {
472-
_bufferRecycler.releaseToPool();
473494
_closed = true;
495+
if (_releaseRecycler) {
496+
_releaseRecycler = false;
497+
_bufferRecycler.releaseToPool();
498+
}
474499
}
475500
}
476501
}

src/main/java/com/fasterxml/jackson/core/util/BufferRecycler.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public interface Gettable {
105105

106106
/*
107107
/**********************************************************
108-
/* Construction
108+
/* Life-cycle
109109
/**********************************************************
110110
*/
111111

@@ -131,6 +131,16 @@ protected BufferRecycler(int bbCount, int cbCount) {
131131
_charBuffers = new AtomicReferenceArray<char[]>(cbCount);
132132
}
133133

134+
/**
135+
* @return True if this recycler is linked to pool and may be released
136+
* with {@link #releaseToPool()}; false if no linkage exists.
137+
*
138+
* @since 2.17
139+
*/
140+
public boolean isLinkedWithPool() {
141+
return _pool != null;
142+
}
143+
134144
/*
135145
/**********************************************************
136146
/* Public API, byte buffers

src/main/java/com/fasterxml/jackson/core/util/ByteArrayBuilder.java

+27-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public int size() {
9595
/**
9696
* Clean up method to call to release all buffers this object may be
9797
* using. After calling the method, no other accessors can be used (and
98-
* attempt to do so may result in an exception)
98+
* attempt to do so may result in an exception).
9999
*/
100100
public void release() {
101101
reset();
@@ -182,6 +182,26 @@ public byte[] toByteArray()
182182
return result;
183183
}
184184

185+
/**
186+
* Method functionally same as calling:
187+
*<pre>
188+
* byte[] bytes = toByteArray();
189+
* release();
190+
* return bytes;
191+
*</pre>
192+
* that is; aggregates output contained in the builder (if any),
193+
* clear state; returns buffer(s) to {@link BufferRecycler} configured,
194+
* if any, and returns output to caller.
195+
*
196+
* @since 2.17
197+
*/
198+
public byte[] getClearAndRelease()
199+
{
200+
byte[] result = toByteArray();
201+
release();
202+
return result;
203+
}
204+
185205
/*
186206
/**********************************************************
187207
/* BufferRecycler.Gettable implementation
@@ -273,7 +293,12 @@ public void write(int b) {
273293
append(b);
274294
}
275295

276-
@Override public void close() { /* NOP */ }
296+
@Override
297+
public void close() {
298+
// 18-Jan-2024, tatu: Ideally would call `release()` but currently
299+
// not possible due to existing usage
300+
}
301+
277302
@Override public void flush() { /* NOP */ }
278303

279304
/*

src/test/java/com/fasterxml/jackson/core/io/BufferRecyclerPoolTest.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,11 @@ public BufferRecycler acquirePooled() {
100100
}
101101

102102
@Override
103-
public void releasePooled(BufferRecycler recycler) {
104-
this.bufferRecycler = recycler;
103+
public void releasePooled(BufferRecycler r) {
104+
if (bufferRecycler == r) { // just sanity check for this test
105+
throw new IllegalStateException("BufferRecyler released more than once");
106+
}
107+
bufferRecycler = r;
105108
}
106109
}
107110
}

src/test/java/com/fasterxml/jackson/core/io/SegmentedStringWriterTest.java

+35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.fasterxml.jackson.core.io;
22

3+
import com.fasterxml.jackson.core.*;
4+
import com.fasterxml.jackson.core.base.GeneratorBase;
35
import com.fasterxml.jackson.core.util.BufferRecycler;
6+
import com.fasterxml.jackson.core.util.JsonRecyclerPools;
47

58
public class SegmentedStringWriterTest
69
extends com.fasterxml.jackson.core.BaseTest
@@ -44,4 +47,36 @@ public void testSimple() throws Exception
4447
String act = w.getAndClear();
4548
assertEquals(exp.toString(), act);
4649
}
50+
51+
// [core#1195]: Try to verify that BufferRecycler instance is indeed reused
52+
public void testBufferRecyclerReuse() throws Exception
53+
{
54+
JsonFactory f = new JsonFactory();
55+
BufferRecycler br = new BufferRecycler()
56+
// need to link with some pool
57+
.withPool(JsonRecyclerPools.newBoundedPool(3));
58+
59+
SegmentedStringWriter ssw = new SegmentedStringWriter(br);
60+
assertSame(br, ssw.bufferRecycler());
61+
62+
JsonGenerator g = f.createGenerator(ssw);
63+
IOContext ioCtxt = ((GeneratorBase) g).ioContext();
64+
assertSame(br, ioCtxt.bufferRecycler());
65+
assertTrue(ioCtxt.bufferRecycler().isLinkedWithPool());
66+
67+
g.writeStartArray();
68+
g.writeEndArray();
69+
g.close();
70+
71+
// Generator.close() should NOT release buffer recycler
72+
assertTrue(br.isLinkedWithPool());
73+
74+
// Nor accessing contents
75+
assertEquals("[]", ssw.getAndClear());
76+
assertTrue(br.isLinkedWithPool());
77+
78+
// only explicit release does
79+
br.releaseToPool();
80+
assertFalse(br.isLinkedWithPool());
81+
}
4782
}

src/test/java/com/fasterxml/jackson/core/util/ByteArrayBuilderTest.java

+40
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
package com.fasterxml.jackson.core.util;
22

3+
import java.nio.charset.StandardCharsets;
4+
35
import org.junit.Assert;
46

7+
import com.fasterxml.jackson.core.JsonFactory;
8+
import com.fasterxml.jackson.core.JsonGenerator;
9+
import com.fasterxml.jackson.core.base.GeneratorBase;
10+
import com.fasterxml.jackson.core.io.IOContext;
11+
512
public class ByteArrayBuilderTest extends com.fasterxml.jackson.core.BaseTest
613
{
714
public void testSimple() throws Exception
@@ -27,4 +34,37 @@ public void testSimple() throws Exception
2734
b.release();
2835
b.close();
2936
}
37+
38+
// [core#1195]: Try to verify that BufferRecycler instance is indeed reused
39+
public void testBufferRecyclerReuse() throws Exception
40+
{
41+
JsonFactory f = new JsonFactory();
42+
BufferRecycler br = new BufferRecycler()
43+
// need to link with some pool
44+
.withPool(JsonRecyclerPools.newBoundedPool(3));
45+
46+
ByteArrayBuilder bab = new ByteArrayBuilder(br, 20);
47+
assertSame(br, bab.bufferRecycler());
48+
49+
JsonGenerator g = f.createGenerator(bab);
50+
IOContext ioCtxt = ((GeneratorBase) g).ioContext();
51+
assertSame(br, ioCtxt.bufferRecycler());
52+
assertTrue(ioCtxt.bufferRecycler().isLinkedWithPool());
53+
54+
g.writeStartArray();
55+
g.writeEndArray();
56+
g.close();
57+
58+
// Generator.close() should NOT release buffer recycler
59+
assertTrue(br.isLinkedWithPool());
60+
61+
byte[] result = bab.getClearAndRelease();
62+
assertEquals("[]", new String(result, StandardCharsets.UTF_8));
63+
// Nor accessing contents
64+
assertTrue(br.isLinkedWithPool());
65+
66+
// only explicit release does
67+
br.releaseToPool();
68+
assertFalse(br.isLinkedWithPool());
69+
}
3070
}

0 commit comments

Comments
 (0)