11package tools .jackson .core .util ;
22
3+ import java .lang .invoke .MethodHandles ;
4+ import java .lang .invoke .VarHandle ;
35import java .util .Objects ;
4- import java .util .concurrent .atomic .AtomicReferenceArray ;
56
67/**
78 * This is a small utility class, whose main functionality is to allow
1819 * Rewritten in 2.16 to work with {@link RecyclerPool} abstraction.
1920 */
2021public class BufferRecycler
21- implements RecyclerPool .WithPool <BufferRecycler >
22+ implements RecyclerPool .WithPool <BufferRecycler >
2223{
24+ /**
25+ * VarHandle for atomic acquire/release access to Object[] array elements.
26+ * Used for the byte and char buffer slot arrays to apply minimal required
27+ * memory ordering semantics (acquire on alloc, release on release) rather
28+ * than the unconditional volatile barriers that AtomicReferenceArray imposes.
29+ */
30+ private static final VarHandle BUFFER_VH ;
31+
32+ static {
33+ try {
34+ BUFFER_VH = MethodHandles .arrayElementVarHandle (Object [].class );
35+ } catch (Exception e ) {
36+ throw new ExceptionInInitializerError (e );
37+ }
38+ }
39+
2340 /**
2441 * Tag-on interface to allow various other types to expose {@link BufferRecycler}
2542 * they are constructed with.
@@ -93,11 +110,13 @@ public interface Gettable {
93110 private final static int [] BYTE_BUFFER_LENGTHS = new int [] { 8000 , 8000 , 2000 , 2000 };
94111 private final static int [] CHAR_BUFFER_LENGTHS = new int [] { 4000 , 4000 , 200 , 200 };
95112
96- // Note: changed from simple array in 2.10:
97- protected final AtomicReferenceArray <byte []> _byteBuffers ;
113+ // Note: changed from AtomicReferenceArray in 3.2 to Object[] + VarHandle for
114+ // finer-grained memory ordering control (acquire/release instead of full volatile).
115+ protected final Object [] _byteBuffers ;
98116
99- // Note: changed from simple array in 2.10:
100- protected final AtomicReferenceArray <char []> _charBuffers ;
117+ // Note: changed from AtomicReferenceArray in 3.2 to Object[] + VarHandle for
118+ // finer-grained memory ordering control (acquire/release instead of full volatile).
119+ protected final Object [] _charBuffers ;
101120
102121 private RecyclerPool <BufferRecycler > _pool ;
103122
@@ -123,8 +142,8 @@ public BufferRecycler() {
123142 * @param cbCount Number of {@code char[]} buffers to allocate
124143 */
125144 protected BufferRecycler (int bbCount , int cbCount ) {
126- _byteBuffers = new AtomicReferenceArray <>( bbCount ) ;
127- _charBuffers = new AtomicReferenceArray <>( cbCount ) ;
145+ _byteBuffers = new Object [ bbCount ] ;
146+ _charBuffers = new Object [ cbCount ] ;
128147 }
129148
130149 /**
@@ -157,19 +176,30 @@ public byte[] allocByteBuffer(int ix, int minSize) {
157176 if (minSize < DEF_SIZE ) {
158177 minSize = DEF_SIZE ;
159178 }
160- byte [] buffer = _byteBuffers .getAndSet (ix , null );
179+ // getAndSetAcquire: atomically claims the slot and applies acquire semantics so
180+ // that all writes made by the thread that stored this buffer are visible to us.
181+ byte [] buffer = (byte []) BUFFER_VH .getAndSetAcquire (_byteBuffers , ix , null );
161182 if (buffer == null || buffer .length < minSize ) {
162183 buffer = balloc (minSize );
163184 }
164185 return buffer ;
165186 }
166187
167188 public void releaseByteBuffer (int ix , byte [] buffer ) {
168- // 13-Jan-2024, tatu: [core#1186] Replace only if beneficial:
169- byte [] oldBuffer = _byteBuffers .get (ix );
170- if ((oldBuffer == null ) || buffer .length > oldBuffer .length ) {
171- // Could use CAS, but should not really matter
172- _byteBuffers .set (ix , buffer );
189+ // CAS loop fixes the TOCTOU race in the original get+set pattern.
190+ // compareAndSet with release semantics publishes all our writes to the next
191+ // thread that acquires this buffer.
192+ // Modified to do a max number of attempts to recycle (hardcoded to 3)
193+ for (int i = 0 ; i < 3 ; i ++) {
194+ byte [] current = (byte []) BUFFER_VH .getAcquire (_byteBuffers , ix );
195+ if (current != null && current .length >= buffer .length ) {
196+ // Slot already holds a buffer at least as large; no benefit in replacing it.
197+ return ;
198+ }
199+ if (BUFFER_VH .compareAndSet (_byteBuffers , ix , current , buffer )) {
200+ return ;
201+ }
202+ // CAS lost the race; retry with the newly observed value.
173203 }
174204 }
175205
@@ -188,19 +218,25 @@ public char[] allocCharBuffer(int ix, int minSize) {
188218 if (minSize < DEF_SIZE ) {
189219 minSize = DEF_SIZE ;
190220 }
191- char [] buffer = _charBuffers .getAndSet (ix , null );
221+ // getAndSetAcquire: atomically claims the slot with acquire semantics.
222+ char [] buffer = (char []) BUFFER_VH .getAndSetAcquire (_charBuffers , ix , null );
192223 if (buffer == null || buffer .length < minSize ) {
193224 buffer = calloc (minSize );
194225 }
195226 return buffer ;
196227 }
197228
198229 public void releaseCharBuffer (int ix , char [] buffer ) {
199- // 13-Jan-2024, tatu: [core#1186] Replace only if beneficial:
200- char [] oldBuffer = _charBuffers .get (ix );
201- if ((oldBuffer == null ) || buffer .length > oldBuffer .length ) {
202- // Could use CAS, but should not really matter
203- _charBuffers .set (ix , buffer );
230+ // CAS loop fixes the TOCTOU race in the original get+set pattern.
231+ // compareAndSet with release semantics publishes our writes to the next thread.
232+ while (true ) {
233+ char [] current = (char []) BUFFER_VH .getAcquire (_charBuffers , ix );
234+ if (current != null && current .length >= buffer .length ) {
235+ return ;
236+ }
237+ if (BUFFER_VH .compareAndSet (_charBuffers , ix , current , buffer )) {
238+ return ;
239+ }
204240 }
205241 }
206242
0 commit comments