Fixed issue 63 where Gson could not operate in a thread-safe manner. Resolved the issue by recreating ObjectNavigatorFactory for each call instead of reusing the same object everywhere. This is needed since ObjectNavigatorFactory had MemoryRefStack that was incorrectly being shared across calls.

This commit is contained in:
Inderjeet Singh 2008-10-20 20:09:42 +00:00
parent 95861175a8
commit 9dfa454f6d
4 changed files with 63 additions and 23 deletions

View File

@ -88,7 +88,8 @@ public final class Gson {
static final Logger logger = Logger.getLogger(Gson.class.getName());
private final ObjectNavigatorFactory navigatorFactory;
private final ExclusionStrategy strategy;
private final FieldNamingStrategy fieldNamingPolicy;
private final MappedObjectConstructor objectConstructor;
private final TypeAdapter typeAdapter;
@ -136,7 +137,7 @@ public final class Gson {
* </ul>
*/
public Gson() {
this(createDefaultObjectNavigatorFactory());
this(createExclusionStrategy(VersionConstants.IGNORE_VERSIONS), DEFAULT_NAMING_POLICY);
}
/**
@ -146,17 +147,19 @@ public final class Gson {
* @param factory the object navigator factory to use when creating a new {@link ObjectNavigator}
* instance
*/
Gson(ObjectNavigatorFactory factory) {
this(factory, createObjectConstructor(DefaultTypeAdapters.DEFAULT_INSTANCE_CREATORS),
Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy) {
this(strategy, fieldNamingPolicy, createObjectConstructor(DefaultTypeAdapters.DEFAULT_INSTANCE_CREATORS),
DEFAULT_TYPE_ADAPTER, DEFAULT_JSON_FORMATTER, false,
DefaultTypeAdapters.DEFAULT_SERIALIZERS, DefaultTypeAdapters.DEFAULT_DESERIALIZERS);
}
Gson(ObjectNavigatorFactory factory, MappedObjectConstructor objectConstructor,
Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy,
MappedObjectConstructor objectConstructor,
TypeAdapter typeAdapter, JsonFormatter formatter, boolean serializeNulls,
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers,
ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers) {
this.navigatorFactory = factory;
this.strategy = strategy;
this.fieldNamingPolicy = fieldNamingPolicy;
this.objectConstructor = objectConstructor;
this.typeAdapter = typeAdapter;
this.formatter = formatter;
@ -174,9 +177,8 @@ public final class Gson {
return objectConstructor;
}
private static ObjectNavigatorFactory createDefaultObjectNavigatorFactory() {
return new ObjectNavigatorFactory(
createExclusionStrategy(VersionConstants.IGNORE_VERSIONS), DEFAULT_NAMING_POLICY);
private ObjectNavigatorFactory createDefaultObjectNavigatorFactory() {
return new ObjectNavigatorFactory(strategy, fieldNamingPolicy);
}
private static ExclusionStrategy createExclusionStrategy(double version) {
@ -268,8 +270,8 @@ public final class Gson {
*/
public void toJson(Object src, Type typeOfSrc, Writer writer) {
if (src != null) {
JsonSerializationContext context =
new JsonSerializationContextDefault(navigatorFactory, serializeNulls, serializers);
JsonSerializationContext context = new JsonSerializationContextDefault(
createDefaultObjectNavigatorFactory(), serializeNulls, serializers);
JsonElement jsonElement = context.serialize(src, typeOfSrc);
//TODO(Joel): instead of navigating the "JsonElement" inside the formatter, do it here.
@ -374,8 +376,8 @@ public final class Gson {
try {
JsonParser parser = new JsonParser(json);
JsonElement root = parser.parse();
JsonDeserializationContext context = new JsonDeserializationContextDefault(navigatorFactory,
deserializers, objectConstructor, typeAdapter);
JsonDeserializationContext context = new JsonDeserializationContextDefault(
createDefaultObjectNavigatorFactory(), deserializers, objectConstructor, typeAdapter);
T target = (T) context.deserialize(root, typeOfT);
return target;
} catch (TokenMgrError e) {

View File

@ -341,8 +341,6 @@ public final class GsonBuilder {
strategies.add(new ExposeAnnotationBasedExclusionStrategy());
}
ExclusionStrategy exclusionStrategy = new DisjunctionExclusionStrategy(strategies);
ObjectNavigatorFactory objectNavigatorFactory =
new ObjectNavigatorFactory(exclusionStrategy, fieldNamingPolicy);
ParameterizedTypeHandlerMap<JsonSerializer<?>> customSerializers = serializers.copyOf();
ParameterizedTypeHandlerMap<JsonDeserializer<?>> customDeserializers = deserializers.copyOf();
@ -357,8 +355,8 @@ public final class GsonBuilder {
customInstanceCreators.registerIfAbsent(DefaultTypeAdapters.DEFAULT_INSTANCE_CREATORS);
MappedObjectConstructor objConstructor = Gson.createObjectConstructor(customInstanceCreators);
Gson gson = new Gson(objectNavigatorFactory, objConstructor, typeAdapter, formatter,
serializeNulls, customSerializers, customDeserializers);
Gson gson = new Gson(exclusionStrategy, fieldNamingPolicy, objConstructor, typeAdapter,
formatter, serializeNulls, customSerializers, customDeserializers);
return gson;
}

View File

@ -52,8 +52,8 @@ public class FunctionalWithInternalDependenciesTest extends TestCase {
}
public void testAnonymousLocalClassesSerialization() {
Gson gson = new Gson(new ObjectNavigatorFactory(new ModifierBasedExclusionStrategy(
true, Modifier.TRANSIENT, Modifier.STATIC), Gson.DEFAULT_NAMING_POLICY));
Gson gson = new Gson(new ModifierBasedExclusionStrategy(
true, Modifier.TRANSIENT, Modifier.STATIC), Gson.DEFAULT_NAMING_POLICY);
assertEquals("{}", gson.toJson(new ClassWithNoFields() {
// empty anonymous class
}));

View File

@ -43,8 +43,8 @@ public class ConcurrencyTest extends TestCase {
* Source-code based on
* http://groups.google.com/group/google-gson/browse_thread/thread/563bb51ee2495081
*/
public void testSingleThread() {
MyObject myObj = new MyObject("hello", "world", 42);
public void testSingleThreadSerialization() {
MyObject myObj = new MyObject();
for (int i = 0; i < 10; i++) {
gson.toJson(myObj);
}
@ -54,7 +54,17 @@ public class ConcurrencyTest extends TestCase {
* Source-code based on
* http://groups.google.com/group/google-gson/browse_thread/thread/563bb51ee2495081
*/
public void testMultiThread() throws InterruptedException {
public void testSingleThreadDeserialization() {
for (int i = 0; i < 10; i++) {
gson.fromJson("{'a':'hello','b':'world','i':1}", MyObject.class);
}
}
/**
* Source-code based on
* http://groups.google.com/group/google-gson/browse_thread/thread/563bb51ee2495081
*/
public void testMultiThreadSerialization() throws InterruptedException {
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch finishedLatch = new CountDownLatch(10);
final AtomicBoolean failed = new AtomicBoolean(false);
@ -66,7 +76,7 @@ public class ConcurrencyTest extends TestCase {
try {
startLatch.await();
for (int i = 0; i < 10; i++) {
String json = gson.toJson(myObj);
gson.toJson(myObj);
}
} catch (Throwable t) {
failed.set(true);
@ -81,6 +91,36 @@ public class ConcurrencyTest extends TestCase {
assertFalse(failed.get());
}
/**
* Source-code based on
* http://groups.google.com/group/google-gson/browse_thread/thread/563bb51ee2495081
*/
public void testMultiThreadDeserialization() throws InterruptedException {
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch finishedLatch = new CountDownLatch(10);
final AtomicBoolean failed = new AtomicBoolean(false);
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int taskCount = 0; taskCount < 10; taskCount++) {
executor.execute(new Runnable() {
public void run() {
try {
startLatch.await();
for (int i = 0; i < 10; i++) {
gson.fromJson("{'a':'hello','b':'world','i':1}", MyObject.class);
}
} catch (Throwable t) {
failed.set(true);
} finally {
finishedLatch.countDown();
}
}
});
}
startLatch.countDown();
finishedLatch.await();
assertFalse(failed.get());
}
private static class MyObject {
private String a;
private String b;