Skip to content

Commit bd6b62c

Browse files
author
Alexander Kozlov
committed
Make ByteBufferRandomAccessSource thread safe
DEVSIX-6279
1 parent afca8df commit bd6b62c

File tree

1 file changed

+60
-46
lines changed

1 file changed

+60
-46
lines changed

io/src/main/java/com/itextpdf/io/source/ByteBufferRandomAccessSource.java

Lines changed: 60 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,24 @@ This file is part of the iText (R) project.
4444
package com.itextpdf.io.source;
4545

4646
import java.lang.reflect.Method;
47-
import java.nio.Buffer;
4847
import java.nio.BufferUnderflowException;
4948
import java.security.AccessController;
5049
import java.security.PrivilegedAction;
5150
import org.slf4j.Logger;
5251
import org.slf4j.LoggerFactory;
5352

54-
5553
/**
56-
* A RandomAccessSource that is based on an underlying {@link java.nio.ByteBuffer}. This class takes steps to ensure that the byte buffer
54+
* A RandomAccessSource that is based on an underlying {@link java.nio.ByteBuffer}. This class takes steps to ensure
55+
* that the byte buffer
5756
* is completely freed from memory during {@link ByteBufferRandomAccessSource#close()}
5857
*/
5958
class ByteBufferRandomAccessSource implements IRandomAccessSource {
6059

60+
/**
61+
* A flag to allow unmapping hack for cleaning mapped buffer
62+
*/
63+
private static boolean allowUnmapping = true;
64+
6165
/**
6266
* Internal cache of memory mapped buffers
6367
*/
@@ -72,6 +76,13 @@ public ByteBufferRandomAccessSource(java.nio.ByteBuffer byteBuffer) {
7276
this.byteBuffer = byteBuffer;
7377
}
7478

79+
/**
80+
* Enables unmapping hack
81+
*/
82+
public static void disableUnmapping() {
83+
allowUnmapping = false;
84+
}
85+
7586
/**
7687
* {@inheritDoc}
7788
* <p>
@@ -80,14 +91,15 @@ public ByteBufferRandomAccessSource(java.nio.ByteBuffer byteBuffer) {
8091
* @param position the position to read the byte from - must be less than Integer.MAX_VALUE
8192
*/
8293
public int get(long position) {
83-
if (position > Integer.MAX_VALUE)
94+
if (position > Integer.MAX_VALUE) {
8495
throw new IllegalArgumentException("Position must be less than Integer.MAX_VALUE");
96+
}
8597
try {
8698

87-
if (position >= ((Buffer) byteBuffer).limit())
99+
if (position >= byteBuffer.limit()) {
88100
return -1;
89-
byte b = byteBuffer.get((int) position);
90-
return b & 0xff;
101+
}
102+
return byteBuffer.duplicate().get((int) position) & 0xff;
91103
} catch (BufferUnderflowException e) {
92104
// EOF
93105
return -1;
@@ -102,16 +114,18 @@ public int get(long position) {
102114
* @param position the position to read the byte from - must be less than Integer.MAX_VALUE
103115
*/
104116
public int get(long position, byte[] bytes, int off, int len) {
105-
if (position > Integer.MAX_VALUE)
117+
if (position > Integer.MAX_VALUE) {
106118
throw new IllegalArgumentException("Position must be less than Integer.MAX_VALUE");
119+
}
107120

108-
if (position >= ((Buffer) byteBuffer).limit())
121+
if (position >= byteBuffer.limit()) {
109122
return -1;
123+
}
110124

111-
// Not thread safe!
112-
((Buffer) byteBuffer).position((int) position);
113-
int bytesFromThisBuffer = Math.min(len, byteBuffer.remaining());
114-
byteBuffer.get(bytes, off, bytesFromThisBuffer);
125+
final java.nio.ByteBuffer byteBufferCopy = byteBuffer.duplicate();
126+
byteBufferCopy.position((int) position);
127+
final int bytesFromThisBuffer = Math.min(len, byteBufferCopy.remaining());
128+
byteBufferCopy.get(bytes, off, bytesFromThisBuffer);
115129

116130
return bytesFromThisBuffer;
117131
}
@@ -121,15 +135,17 @@ public int get(long position, byte[] bytes, int off, int len) {
121135
* {@inheritDoc}
122136
*/
123137
public long length() {
124-
return ((Buffer) byteBuffer).limit();
138+
return byteBuffer.limit();
125139
}
126140

127141
/**
128142
* @see java.io.RandomAccessFile#close()
129143
* Cleans the mapped bytebuffers and closes the channel
130144
*/
131145
public void close() throws java.io.IOException {
132-
clean(byteBuffer);
146+
if (allowUnmapping) {
147+
clean(byteBuffer);
148+
}
133149
}
134150

135151

@@ -144,11 +160,8 @@ public void close() throws java.io.IOException {
144160
private static final BufferCleaner CLEANER;
145161

146162
static {
147-
final Object hack = AccessController.doPrivileged(new PrivilegedAction<Object>() {
148-
public Object run() {
149-
return BufferCleaner.unmapHackImpl();
150-
}
151-
});
163+
final Object hack = AccessController.doPrivileged(
164+
(PrivilegedAction<Object>) BufferCleaner::unmapHackImpl);
152165
if (hack instanceof BufferCleaner) {
153166
CLEANER = (BufferCleaner) hack;
154167
UNMAP_SUPPORTED = true;
@@ -162,37 +175,38 @@ public Object run() {
162175
* invokes the clean method on the ByteBuffer's cleaner
163176
*
164177
* @param buffer ByteBuffer
178+
*
165179
* @return boolean true on success
166180
*/
167181
private static boolean clean(final java.nio.ByteBuffer buffer) {
168-
if (buffer == null || !buffer.isDirect())
182+
if (buffer == null || !buffer.isDirect()) {
169183
return false;
184+
}
170185

171-
Boolean b = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
172-
public Boolean run() {
173-
Boolean success = Boolean.FALSE;
174-
try {
175-
// java 9
176-
if (UNMAP_SUPPORTED)
177-
CLEANER.freeBuffer(buffer.toString(), buffer);
178-
// java 8 and lower
179-
else {
180-
Method getCleanerMethod = buffer.getClass().getMethod("cleaner", (Class<?>[]) null);
181-
getCleanerMethod.setAccessible(true);
182-
Object cleaner = getCleanerMethod.invoke(buffer, (Object[]) null);
183-
Method clean = cleaner.getClass().getMethod("clean", (Class<?>[]) null);
184-
clean.invoke(cleaner, (Object[]) null);
185-
}
186-
success = Boolean.TRUE;
187-
} catch (Exception e) {
188-
// This really is a show stopper on windows
189-
Logger logger = LoggerFactory.getLogger(ByteBufferRandomAccessSource.class);
190-
logger.debug(e.getMessage());
191-
}
192-
return success;
193-
}
194-
});
186+
return AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> cleanByUnmapping(buffer));
187+
}
195188

196-
return b;
189+
private static boolean cleanByUnmapping(final java.nio.ByteBuffer buffer) {
190+
Boolean success = Boolean.FALSE;
191+
try {
192+
// java 9
193+
if (UNMAP_SUPPORTED) {
194+
CLEANER.freeBuffer(buffer.toString(), buffer);
195+
}
196+
// java 8 and lower
197+
else {
198+
Method getCleanerMethod = buffer.getClass().getMethod("cleaner", (Class<?>[]) null);
199+
getCleanerMethod.setAccessible(true);
200+
Object cleaner = getCleanerMethod.invoke(buffer, (Object[]) null);
201+
Method clean = cleaner.getClass().getMethod("clean", (Class<?>[]) null);
202+
clean.invoke(cleaner, (Object[]) null);
203+
}
204+
success = Boolean.TRUE;
205+
} catch (Exception e) {
206+
// This really is a show stopper on windows
207+
Logger logger = LoggerFactory.getLogger(ByteBufferRandomAccessSource.class);
208+
logger.debug(e.getMessage());
209+
}
210+
return success;
197211
}
198212
}

0 commit comments

Comments
 (0)