-
-
Notifications
You must be signed in to change notification settings - Fork 136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cache record names to avoid hitting class loader #219
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,8 @@ | |
|
||
public abstract class AvroSchemaHelper | ||
{ | ||
private static final Map<String, String> SCHEMA_NAME_CACHE = new HashMap<>(); | ||
|
||
/** | ||
* Dedicated mapper for handling default values (String <-> JsonNode <-> Object) | ||
* | ||
|
@@ -332,32 +334,54 @@ public static String getTypeId(Schema schema) { | |
*/ | ||
public static String getFullName(Schema schema) { | ||
switch (schema.getType()) { | ||
case RECORD: | ||
case ENUM: | ||
case FIXED: | ||
String namespace = schema.getNamespace(); | ||
String name = schema.getName(); | ||
if (namespace == null) { | ||
return schema.getName(); | ||
} | ||
if (namespace.endsWith("$")) { | ||
return namespace + name; | ||
} | ||
StringBuilder sb = new StringBuilder(1 + namespace.length() + name.length()); | ||
// 28-Feb-2020: [dataformats-binary#195] somewhat complicated logic of trying | ||
// to support differences between avro-lib 1.8 and 1.9... | ||
// Check if this is a nested class | ||
String nestedClassName = sb.append(namespace).append('$').append(name).toString(); | ||
try { | ||
Class.forName(nestedClassName); | ||
return nestedClassName; | ||
} catch (ClassNotFoundException e) { | ||
// Could not find a nested class, must be a regular class | ||
sb.setLength(0); | ||
return sb.append(namespace).append('.').append(name).toString(); | ||
} | ||
case RECORD: | ||
case ENUM: | ||
case FIXED: | ||
String namespace = schema.getNamespace(); | ||
String name = schema.getName(); | ||
String key = namespace + "." + name; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just double checked, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi there @baharclerode! Thank you for additional comments -- and yes, I realize that Avro's choice of "interesting" ways of dealing with stuff sort of forces our hand here. One other related question: if we had a setting to select between "1.8-and-earlier" vs "1.9-and-later" modes, would it be possible to use different logic, avoiding Class lookup or at least alleviate it? |
||
String schemaName = SCHEMA_NAME_CACHE.get(key); | ||
|
||
if (schemaName == null) { | ||
schemaName = resolveFullName(schema); | ||
SCHEMA_NAME_CACHE.put(key, schemaName); | ||
} | ||
|
||
return schemaName; | ||
|
||
default: | ||
return schema.getType().getName(); | ||
return schema.getType().getName(); | ||
} | ||
} | ||
|
||
private static String resolveFullName(Schema schema) { | ||
String namespace = schema.getNamespace(); | ||
String name = schema.getName(); | ||
|
||
if (namespace == null) { | ||
return schema.getName(); | ||
} | ||
|
||
if (namespace.endsWith("$")) { | ||
return namespace + name; | ||
} | ||
|
||
StringBuilder sb = new StringBuilder(1 + namespace.length() + name.length()); | ||
|
||
// 28-Feb-2020: [dataformats-binary#195] somewhat complicated logic of trying | ||
// to support differences between avro-lib 1.8 and 1.9... | ||
// Check if this is a nested class | ||
String nestedClassName = sb.append(namespace).append('$').append(name).toString(); | ||
|
||
try { | ||
Class.forName(nestedClassName); | ||
|
||
return nestedClassName; | ||
} catch (ClassNotFoundException e) { | ||
// Could not find a nested class, must be a regular class | ||
sb.setLength(0); | ||
|
||
return sb.append(namespace).append('.').append(name).toString(); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some concerns about this.
Since it is
static
it is per-ClassLoader, and ideally nothing in Jackson would use that. If at all possible this should be tied to something else, probablyAvroFactory
(orAvroMapper
if practical).Second, all caches should be bound (not with unlimited size). This is easy to solve by just using
with whatever size settings (default, max size) makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also another significant problem is that access is not synchronized: since this will get called from multiple threads, and it is not read-only, access must be made thread-safe.
LRUMap
usesConcurrentHashMap
which solves that issue.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question from a Java non-expert: will the call
Class.forName(nestedClassName)
(called in the method that resolves the name) use the default class loader? If so, no matter how we cache the names, it will still be broken.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would use the default class loader of... well. It's probably the context class loader (alternative being class loader that loaded given class; usually these are the same but not always).