Skip to content

Commit 30b66e5

Browse files
committed
Fixed issue with thread-pool by not using ThreadLocal
1 parent 261a0a1 commit 30b66e5

File tree

5 files changed

+53
-58
lines changed

5 files changed

+53
-58
lines changed

src/main/java/com/maxmind/db/ThreadBuffer.java renamed to src/main/java/com/maxmind/db/BufferHolder.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212

1313
import com.maxmind.db.Reader.FileMode;
1414

15-
final class ThreadBuffer extends ThreadLocal<ByteBuffer> implements Closeable {
15+
final class BufferHolder implements Closeable {
1616
// DO NOT PASS THESE OUTSIDE THIS CLASS. Doing so will remove thread
1717
// safety.
1818
private final ByteBuffer buffer;
1919
private final RandomAccessFile raf;
2020
private final FileChannel fc;
2121

22-
ThreadBuffer(File database, FileMode mode) throws IOException {
22+
BufferHolder(File database, FileMode mode) throws IOException {
2323
this.raf = new RandomAccessFile(database, "r");
2424
this.fc = this.raf.getChannel();
2525
if (mode == FileMode.MEMORY) {
@@ -41,7 +41,7 @@ final class ThreadBuffer extends ThreadLocal<ByteBuffer> implements Closeable {
4141
* @throws NullPointerException
4242
* if you provide a NULL InputStream
4343
*/
44-
ThreadBuffer(InputStream stream) throws IOException {
44+
BufferHolder(InputStream stream) throws IOException {
4545
if (null == stream) {
4646
throw new NullPointerException("Unable to use a NULL InputStream");
4747
}
@@ -57,14 +57,13 @@ final class ThreadBuffer extends ThreadLocal<ByteBuffer> implements Closeable {
5757
}
5858

5959
// This is just to ease unit testing
60-
ThreadBuffer(ByteBuffer buffer) {
60+
BufferHolder(ByteBuffer buffer) {
6161
this.buffer = buffer;
6262
this.raf = null;
6363
this.fc = null;
6464
}
6565

66-
@Override
67-
protected synchronized ByteBuffer initialValue() {
66+
synchronized ByteBuffer get() {
6867
return this.buffer.duplicate();
6968
}
7069

src/main/java/com/maxmind/db/Decoder.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
import com.fasterxml.jackson.databind.node.ObjectNode;
1919
import com.fasterxml.jackson.databind.node.TextNode;
2020

21+
/*
22+
* Decoder for MaxMind DB data.
23+
*
24+
* This class CANNOT be shared between threads
25+
*/
2126
final class Decoder {
2227
// XXX - This is only for unit testings. We should possibly make a
2328
// constructor to set this
@@ -26,7 +31,7 @@ final class Decoder {
2631

2732
private final ObjectMapper objectMapper;
2833

29-
private final ThreadBuffer threadBuffer;
34+
private final ByteBuffer buffer;
3035

3136
static enum Type {
3237
EXTENDED, POINTER, UTF8_STRING, DOUBLE, BYTES, UINT16, UINT32, MAP, INT32, UINT64, UINT128, ARRAY, CONTAINER, END_MARKER, BOOLEAN, FLOAT;
@@ -73,22 +78,21 @@ void setOffset(int offset) {
7378

7479
}
7580

76-
Decoder(ThreadBuffer threadBuffer, long pointerBase) {
81+
Decoder(ByteBuffer buffer, long pointerBase) {
7782
this.pointerBase = pointerBase;
78-
this.threadBuffer = threadBuffer;
83+
this.buffer = buffer;
7984
this.objectMapper = new ObjectMapper();
8085
}
8186

8287
Result decode(int offset) throws IOException {
83-
ByteBuffer buffer = this.threadBuffer.get();
84-
if (offset >= buffer.capacity()) {
88+
if (offset >= this.buffer.capacity()) {
8589
throw new InvalidDatabaseException(
8690
"The MaxMind DB file's data section contains bad data: "
8791
+ "pointer larger than the database.");
8892
}
8993

90-
buffer.position(offset);
91-
int ctrlByte = 0xFF & buffer.get();
94+
this.buffer.position(offset);
95+
int ctrlByte = 0xFF & this.buffer.get();
9296
offset++;
9397

9498
Type type = Type.fromControlByte(ctrlByte);
@@ -109,7 +113,7 @@ Result decode(int offset) throws IOException {
109113
}
110114

111115
if (type.equals(Type.EXTENDED)) {
112-
int nextByte = buffer.get();
116+
int nextByte = this.buffer.get();
113117

114118
int typeNum = nextByte + 7;
115119

@@ -188,7 +192,7 @@ private Result decodePointer(int ctrlByte, int offset) {
188192
}
189193

190194
private String decodeString(int size) {
191-
ByteBuffer buffer = this.threadBuffer.get().slice();
195+
ByteBuffer buffer = this.buffer.slice();
192196
buffer.limit(size);
193197
return Charset.forName("UTF-8").decode(buffer).toString();
194198
}
@@ -202,10 +206,9 @@ private IntNode decodeInt32(int size) {
202206
}
203207

204208
private long decodeLong(int size) {
205-
ByteBuffer buffer = this.threadBuffer.get();
206209
long integer = 0;
207210
for (int i = 0; i < size; i++) {
208-
integer = (integer << 8) | (buffer.get() & 0xFF);
211+
integer = (integer << 8) | (this.buffer.get() & 0xFF);
209212
}
210213
return integer;
211214
}
@@ -219,8 +222,7 @@ private int decodeInteger(int size) {
219222
}
220223

221224
private int decodeInteger(int base, int size) {
222-
ByteBuffer buffer = this.threadBuffer.get();
223-
return Decoder.decodeInteger(buffer, base, size);
225+
return Decoder.decodeInteger(this.buffer, base, size);
224226
}
225227

226228
static int decodeInteger(ByteBuffer buffer, int base, int size) {
@@ -242,7 +244,7 @@ private DoubleNode decodeDouble(int size) throws InvalidDatabaseException {
242244
"The MaxMind DB file's data section contains bad data: "
243245
+ "invalid size of double.");
244246
}
245-
return new DoubleNode(this.threadBuffer.get().getDouble());
247+
return new DoubleNode(this.buffer.getDouble());
246248
}
247249

248250
private FloatNode decodeFloat(int size) throws InvalidDatabaseException {
@@ -251,7 +253,7 @@ private FloatNode decodeFloat(int size) throws InvalidDatabaseException {
251253
"The MaxMind DB file's data section contains bad data: "
252254
+ "invalid size of float.");
253255
}
254-
return new FloatNode(this.threadBuffer.get().getFloat());
256+
return new FloatNode(this.buffer.getFloat());
255257
}
256258

257259
private static BooleanNode decodeBoolean(int size)
@@ -317,7 +319,7 @@ private int[] sizeFromCtrlByte(int ctrlByte, int offset) {
317319
}
318320

319321
private byte[] getByteArray(int length) {
320-
return Decoder.getByteArray(this.threadBuffer.get(), length);
322+
return Decoder.getByteArray(this.buffer, length);
321323
}
322324

323325
private static byte[] getByteArray(ByteBuffer buffer, int length) {

src/main/java/com/maxmind/db/Reader.java

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@ public final class Reader implements Closeable {
2020
'c', 'o', 'm' };
2121

2222
private int ipV4Start;
23-
private final Decoder decoder;
2423
private final Metadata metadata;
25-
private final ThreadBuffer threadBuffer;
24+
private final BufferHolder bufferHolder;
2625

2726
/**
2827
* The file mode to use when opening a MaxMind DB.
@@ -63,7 +62,7 @@ public Reader(File database) throws IOException {
6362
* if there is an error reading from the Stream.
6463
*/
6564
public Reader(InputStream source) throws IOException {
66-
this(new ThreadBuffer(source), "<InputStream>");
65+
this(new BufferHolder(source), "<InputStream>");
6766
}
6867

6968
/**
@@ -78,17 +77,17 @@ public Reader(InputStream source) throws IOException {
7877
* if there is an error opening or reading from the file.
7978
*/
8079
public Reader(File database, FileMode fileMode) throws IOException {
81-
this(new ThreadBuffer(database, fileMode), database.getName());
80+
this(new BufferHolder(database, fileMode), database.getName());
8281
}
8382

84-
private Reader(ThreadBuffer buffer, String name) throws IOException {
85-
this.threadBuffer = buffer;
86-
int start = this.findMetadataStart(name);
83+
private Reader(BufferHolder bufferHolder, String name) throws IOException {
84+
this.bufferHolder = bufferHolder;
8785

88-
Decoder metadataDecoder = new Decoder(this.threadBuffer, start);
86+
ByteBuffer buffer = this.bufferHolder.get();
87+
int start = this.findMetadataStart(buffer, name);
88+
89+
Decoder metadataDecoder = new Decoder(buffer, start);
8990
this.metadata = new Metadata(metadataDecoder.decode(start).getNode());
90-
this.decoder = new Decoder(this.threadBuffer,
91-
this.metadata.searchTreeSize + DATA_SECTION_SEPARATOR_SIZE);
9291
}
9392

9493
/**
@@ -101,27 +100,28 @@ private Reader(ThreadBuffer buffer, String name) throws IOException {
101100
* if a file I/O error occurs.
102101
*/
103102
public JsonNode get(InetAddress ipAddress) throws IOException {
104-
int pointer = this.findAddressInTree(ipAddress);
103+
ByteBuffer buffer = this.bufferHolder.get();
104+
int pointer = this.findAddressInTree(buffer, ipAddress);
105105
if (pointer == 0) {
106106
return null;
107107
}
108-
return this.resolveDataPointer(pointer);
108+
return this.resolveDataPointer(buffer, pointer);
109109
}
110110

111-
private int findAddressInTree(InetAddress address)
111+
private int findAddressInTree(ByteBuffer buffer, InetAddress address)
112112
throws InvalidDatabaseException {
113113
byte[] rawAddress = address.getAddress();
114114

115115
int bitLength = rawAddress.length * 8;
116-
int record = this.startNode(bitLength);
116+
int record = this.startNode(buffer, bitLength);
117117

118118
for (int i = 0; i < bitLength; i++) {
119119
if (record >= this.metadata.nodeCount) {
120120
break;
121121
}
122122
int b = 0xFF & rawAddress[i / 8];
123123
int bit = 1 & (b >> 7 - (i % 8));
124-
record = this.readNode(record, bit);
124+
record = this.readNode(buffer, record, bit);
125125
}
126126
if (record == this.metadata.nodeCount) {
127127
// record is empty
@@ -133,18 +133,18 @@ record = this.readNode(record, bit);
133133
throw new InvalidDatabaseException("Something bad happened");
134134
}
135135

136-
private int startNode(int bitLength) throws InvalidDatabaseException {
136+
private int startNode(ByteBuffer buffer, int bitLength) throws InvalidDatabaseException {
137137
// Check if we are looking up an IPv4 address in an IPv6 tree. If this
138138
// is the case, we can skip over the first 96 nodes.
139139
if (this.metadata.ipVersion == 6 && bitLength == 32) {
140-
return this.ipV4StartNode();
140+
return this.ipV4StartNode(buffer);
141141
}
142142
// The first node of the tree is always node 0, at the beginning of the
143143
// value
144144
return 0;
145145
}
146146

147-
private int ipV4StartNode() throws InvalidDatabaseException {
147+
private int ipV4StartNode(ByteBuffer buffer) throws InvalidDatabaseException {
148148
// This is a defensive check. There is no reason to call this when you
149149
// have an IPv4 tree.
150150
if (this.metadata.ipVersion == 4) {
@@ -156,15 +156,14 @@ private int ipV4StartNode() throws InvalidDatabaseException {
156156
}
157157
int node = 0;
158158
for (int i = 0; i < 96 && node < this.metadata.nodeCount; i++) {
159-
node = this.readNode(node, 0);
159+
node = this.readNode(buffer, node, 0);
160160
}
161161
this.ipV4Start = node;
162162
return node;
163163
}
164164

165-
private int readNode(int nodeNumber, int index)
165+
private int readNode(ByteBuffer buffer, int nodeNumber, int index)
166166
throws InvalidDatabaseException {
167-
ByteBuffer buffer = this.threadBuffer.get();
168167
int baseOffset = nodeNumber * this.metadata.nodeByteSize;
169168

170169
switch (this.metadata.recordSize) {
@@ -190,19 +189,21 @@ private int readNode(int nodeNumber, int index)
190189
}
191190
}
192191

193-
private JsonNode resolveDataPointer(int pointer) throws IOException {
192+
private JsonNode resolveDataPointer(ByteBuffer buffer, int pointer) throws IOException {
194193
int resolved = (pointer - this.metadata.nodeCount)
195194
+ this.metadata.searchTreeSize;
196195

197-
if (resolved >= this.threadBuffer.get().capacity()) {
196+
if (resolved >= buffer.capacity()) {
198197
throw new InvalidDatabaseException(
199198
"The MaxMind DB file's search tree is corrupt: "
200199
+ "contains pointer larger than the database.");
201200
}
202201

203202
// We only want the data from the decoder, not the offset where it was
204203
// found.
205-
return this.decoder.decode(resolved).getNode();
204+
Decoder decoder = new Decoder(buffer,
205+
this.metadata.searchTreeSize + DATA_SECTION_SEPARATOR_SIZE);
206+
return decoder.decode(resolved).getNode();
206207
}
207208

208209
/*
@@ -213,9 +214,8 @@ private JsonNode resolveDataPointer(int pointer) throws IOException {
213214
* are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever
214215
* an issue, but I suspect it won't be.
215216
*/
216-
private int findMetadataStart(String databaseName)
217+
private int findMetadataStart(ByteBuffer buffer, String databaseName)
217218
throws InvalidDatabaseException {
218-
ByteBuffer buffer = this.threadBuffer.get();
219219
int fileSize = buffer.capacity();
220220

221221
FILE: for (int i = 0; i < fileSize - METADATA_START_MARKER.length + 1; i++) {
@@ -245,6 +245,6 @@ Metadata getMetadata() {
245245
*/
246246
@Override
247247
public void close() throws IOException {
248-
this.threadBuffer.close();
248+
this.bufferHolder.close();
249249
}
250250
}

src/test/java/com/maxmind/db/DecoderTest.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@
2222
import com.fasterxml.jackson.databind.node.ArrayNode;
2323
import com.fasterxml.jackson.databind.node.FloatNode;
2424
import com.fasterxml.jackson.databind.node.ObjectNode;
25-
import com.maxmind.db.Decoder;
26-
import com.maxmind.db.InvalidDatabaseException;
27-
import com.maxmind.db.ThreadBuffer;
2825

2926
@SuppressWarnings({ "boxing", "static-method" })
3027
public class DecoderTest {
@@ -426,7 +423,7 @@ static <T> void testTypeDecoding(Decoder.Type type, Map<T, byte[]> tests)
426423
MappedByteBuffer mmap = fc.map(MapMode.READ_ONLY, 0, fc.size());
427424
try {
428425

429-
Decoder decoder = new Decoder(new ThreadBuffer(mmap), 0);
426+
Decoder decoder = new Decoder(mmap, 0);
430427
decoder.POINTER_TEST_HACK = true;
431428

432429
// XXX - this could be streamlined

src/test/java/com/maxmind/db/PointerTest.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010

1111
import com.fasterxml.jackson.databind.ObjectMapper;
1212
import com.fasterxml.jackson.databind.node.ObjectNode;
13-
import com.maxmind.db.Decoder;
14-
import com.maxmind.db.InvalidDatabaseException;
15-
import com.maxmind.db.ThreadBuffer;
1613
import com.maxmind.db.Reader.FileMode;
1714

1815
public class PointerTest {
@@ -22,9 +19,9 @@ public void testWithPointers() throws InvalidDatabaseException,
2219
IOException, URISyntaxException {
2320
File file = new File(PointerTest.class.getResource(
2421
"/maxmind-db/test-data/maps-with-pointers.raw").toURI());
25-
ThreadBuffer ptf = new ThreadBuffer(file, FileMode.MEMORY);
22+
BufferHolder ptf = new BufferHolder(file, FileMode.MEMORY);
2623
try {
27-
Decoder decoder = new Decoder(ptf, 0);
24+
Decoder decoder = new Decoder(ptf.get(), 0);
2825

2926
ObjectMapper om = new ObjectMapper();
3027

0 commit comments

Comments
 (0)