diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index d76b28b48f..494d47c4f2 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -41,6 +41,9 @@ Project: jackson-databind #2486: Builder Deserialization with JsonCreator Value vs Array (reported by Ville K) +#2755: `StdSubtypeResolver` is not thread safe (possibly due to copy + not being made with `ObjectMapper.copy()`) + (reported by tjwilson90@github) 2.11.0 (26-Apr-2020) diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index cc4263e4a2..50cfcaf005 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -526,7 +526,7 @@ public boolean useForType(JavaType t) * (should very quickly converge to zero after startup), let's * explicitly define a low concurrency setting. *

- * Since version 1.5, these may are either "raw" deserializers (when + * These may are either "raw" deserializers (when * no type information is needed for base type), or type-wrapped * deserializers (if it is needed) */ @@ -573,7 +573,7 @@ protected ObjectMapper(ObjectMapper src) { _jsonFactory = src._jsonFactory.copy(); _jsonFactory.setCodec(this); - _subtypeResolver = src._subtypeResolver; + _subtypeResolver = src._subtypeResolver.copy(); _typeFactory = src._typeFactory; _injectableValues = src._injectableValues; _configOverrides = src._configOverrides.copy(); diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/SubtypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/SubtypeResolver.java index 0f6c50b6ee..206eb9a66b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/SubtypeResolver.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/SubtypeResolver.java @@ -14,9 +14,25 @@ */ public abstract class SubtypeResolver { + /** + * Method called by {@code ObjectMapper.copy()} to make sure that + * {@link SubtypeResolver} instances used by two independent mappers + * can not cause thread-safety issues: if resolver is immutable, it + * may return {@code this}, but if not, it should create a copy with + * same configuration and return that instead. + * + * @return Either new instance with same configuration as this one (if + * instances are mutable), or this instance (if immutable) + * + * @since 2.12 + */ + public SubtypeResolver copy() { + return this; + } + /* /********************************************************** - /* Methods for registering external subtype definitions + /* Methods for registering external subtype definitions (init/config) /********************************************************** */ @@ -36,7 +52,7 @@ public abstract class SubtypeResolver /* /********************************************************** - /* Subtype resolution + /* Subtype resolution (public API) /********************************************************** */ diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java index 3d2a3dcd26..7807c85b7e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java @@ -23,6 +23,19 @@ public class StdSubtypeResolver public StdSubtypeResolver() { } + // @since 2.12 + protected StdSubtypeResolver(StdSubtypeResolver src) { + LinkedHashSet reg = src._registeredSubtypes; + _registeredSubtypes = (reg == null) ? null + : new LinkedHashSet<>(reg); + } + + // @since 2.12 + @Override + public SubtypeResolver copy() { + return new StdSubtypeResolver(this); + } + /* /********************************************************** /* Subtype registration diff --git a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java index 96989bf2fc..3baf1e8450 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java @@ -133,9 +133,11 @@ public void testCopy() throws Exception assertFalse(m2.isEnabled(DeserializationFeature.UNWRAP_ROOT_VALUE)); // // Also, underlying JsonFactory instances should be distinct - assertNotSame(m.getFactory(), m2.getFactory()); + // [databind#2755]: also need to copy this: + assertNotSame(m.getSubtypeResolver(), m2.getSubtypeResolver()); + // [databind#122]: Need to ensure mix-ins are not shared assertEquals(0, m.getSerializationConfig().mixInCount()); assertEquals(0, m2.getSerializationConfig().mixInCount());