/// Encapsulates all cached metadata referenced by the current <see cref="JsonSerializerOptions" /> instance.
/// Context can be shared across multiple equivalent options instances.
/// </summary>
+ internal CachingContext CacheContext
+ {
+ get
+ {
+ Debug.Assert(IsReadOnly);
+ return _cachingContext ??= TrackedCachingContexts.GetOrCreate(this);
+ }
+ }
+
private CachingContext? _cachingContext;
// Simple LRU cache for the public (de)serialize entry points that avoid some lookups in _cachingContext.
if (IsReadOnly)
{
- typeInfo = GetCachingContext()?.GetOrAddJsonTypeInfo(type);
+ typeInfo = CacheContext.GetOrAddTypeInfo(type);
if (ensureConfigured)
{
typeInfo?.EnsureConfigured();
_objectTypeInfo = null;
}
- private CachingContext? GetCachingContext()
- {
- Debug.Assert(IsReadOnly);
-
- return _cachingContext ??= TrackedCachingContexts.GetOrCreate(this);
- }
-
/// <summary>
/// Stores and manages all reflection caches for one or more <see cref="JsonSerializerOptions"/> instances.
/// NB the type encapsulates the original options instance and only consults that one when building new types;
// If changing please ensure that src/ILLink.Descriptors.LibraryBuild.xml is up-to-date.
public int Count => _jsonTypeInfoCache.Count;
- public JsonTypeInfo? GetOrAddJsonTypeInfo(Type type) =>
+ public JsonTypeInfo? GetOrAddTypeInfo(Type type) =>
#if NETCOREAPP
_jsonTypeInfoCache.GetOrAdd(type, static (type, options) => options.GetTypeInfoNoCaching(type), Options);
#else
_jsonTypeInfoCache.GetOrAdd(type, _jsonTypeInfoFactory);
#endif
- public bool TryGetJsonTypeInfo(Type type, [NotNullWhen(true)] out JsonTypeInfo? typeInfo) => _jsonTypeInfoCache.TryGetValue(type, out typeInfo);
+ public bool TryGetJsonTypeInfo(Type type, [NotNullWhen(true)] out JsonTypeInfo? typeInfo)
+ => _jsonTypeInfoCache.TryGetValue(type, out typeInfo);
public void Clear()
{
get => _ignoreCondition;
set
{
- Debug.Assert(!_isConfigured);
+ Debug.Assert(!IsConfigured);
ConfigureIgnoreCondition(value);
_ignoreCondition = value;
}
}
}
- internal bool IsConfigured => _isConfigured;
- private volatile bool _isConfigured;
-
- internal void EnsureConfigured()
- {
- if (_isConfigured)
- {
- return;
- }
-
- Configure();
-
- _isConfigured = true;
- }
+ internal bool IsConfigured { get; private set; }
internal void Configure()
{
Debug.Assert(ParentTypeInfo != null);
+ Debug.Assert(!IsConfigured);
if (IsIgnored)
{
}
else
{
- _jsonTypeInfo ??= Options.GetTypeInfoInternal(PropertyType, ensureConfigured: false);
+ _jsonTypeInfo ??= Options.GetTypeInfoInternal(PropertyType);
+ _jsonTypeInfo.EnsureConfigured();
+
DetermineEffectiveConverter(_jsonTypeInfo);
DetermineNumberHandlingForProperty();
DetermineSerializationCapabilities();
Debug.Assert(!IgnoreNullTokensOnRead);
}
+
+ IsConfigured = true;
}
private protected abstract void DetermineEffectiveConverter(JsonTypeInfo jsonTypeInfo);
sb.AppendLine($"{ind}{{");
sb.AppendLine($"{ind} Name: {Name},");
sb.AppendLine($"{ind} NameAsUtf8.Length: {(NameAsUtf8Bytes?.Length ?? -1)},");
- sb.AppendLine($"{ind} IsConfigured: {_isConfigured},");
+ sb.AppendLine($"{ind} IsConfigured: {IsConfigured},");
sb.AppendLine($"{ind} IsIgnored: {IsIgnored},");
sb.AppendLine($"{ind} CanSerialize: {CanSerialize},");
sb.AppendLine($"{ind} CanDeserialize: {CanDeserialize},");
get
{
Debug.Assert(IsConfigured);
- Debug.Assert(_jsonTypeInfo != null);
- _jsonTypeInfo.EnsureConfigured();
+ Debug.Assert(_jsonTypeInfo?.IsConfigured == true);
return _jsonTypeInfo;
}
set
{
get
{
- Debug.Assert(_isConfigured);
+ Debug.Assert(IsConfigured);
Debug.Assert(IsRequired);
return _index;
}
set
{
- Debug.Assert(!_isConfigured);
+ Debug.Assert(!IsConfigured);
_index = value;
}
}
internal PolymorphicTypeResolver? PolymorphicTypeResolver { get; private set; }
- // If enumerable or dictionary, the JsonTypeInfo for the element type.
- private JsonTypeInfo? _elementTypeInfo;
-
// Flag indicating that JsonTypeInfo<T>.SerializeHandler is populated and is compatible with the associated Options instance.
internal bool CanUseSerializeHandler { get; private protected set; }
}
}
+ internal Type? ElementType { get; }
+ internal Type? KeyType { get; }
+
/// <summary>
/// Return the JsonTypeInfo for the element type, or null if the type is not an enumerable or dictionary.
/// </summary>
- /// <remarks>
- /// This should not be called during warm-up (initial creation of JsonTypeInfos) to avoid recursive behavior
- /// which could result in a StackOverflowException.
- /// </remarks>
internal JsonTypeInfo? ElementTypeInfo
{
get
{
- if (_elementTypeInfo == null)
- {
- if (ElementType != null)
- {
- // GetOrAddJsonTypeInfo already ensures JsonTypeInfo is configured
- // also see comment on JsonPropertyInfo.JsonTypeInfo
- _elementTypeInfo = Options.GetTypeInfoInternal(ElementType);
- }
- }
- else
- {
- _elementTypeInfo.EnsureConfigured();
- }
-
+ Debug.Assert(IsConfigured);
return _elementTypeInfo;
}
set
{
- // Set by JsonMetadataServices.
- Debug.Assert(_elementTypeInfo == null);
+ Debug.Assert(!IsReadOnly);
+ Debug.Assert(value is null || value.Type == ElementType);
_elementTypeInfo = value;
}
}
- internal Type? ElementType { get; }
-
- // If dictionary, the JsonTypeInfo for the key type.
- private JsonTypeInfo? _keyTypeInfo;
-
/// <summary>
/// Return the JsonTypeInfo for the key type, or null if the type is not a dictionary.
/// </summary>
- /// <remarks>
- /// This should not be called during warm-up (initial creation of JsonTypeInfos) to avoid recursive behavior
- /// which could result in a StackOverflowException.
- /// </remarks>
internal JsonTypeInfo? KeyTypeInfo
{
get
{
- if (_keyTypeInfo == null)
- {
- if (KeyType != null)
- {
- Debug.Assert(Kind == JsonTypeInfoKind.Dictionary);
-
- // GetOrAddJsonTypeInfo already ensures JsonTypeInfo is configured
- // also see comment on JsonPropertyInfo.JsonTypeInfo
- _keyTypeInfo = Options.GetTypeInfoInternal(KeyType);
- }
- }
- else
- {
- _keyTypeInfo.EnsureConfigured();
- }
-
+ Debug.Assert(IsConfigured);
return _keyTypeInfo;
}
set
{
- // Set by JsonMetadataServices.
- Debug.Assert(_keyTypeInfo == null);
+ Debug.Assert(!IsReadOnly);
+ Debug.Assert(value is null || value.Type == KeyType);
_keyTypeInfo = value;
}
}
- internal Type? KeyType { get; }
+ private JsonTypeInfo? _elementTypeInfo;
+ private JsonTypeInfo? _keyTypeInfo;
/// <summary>
/// Gets the <see cref="JsonSerializerOptions"/> value associated with the current <see cref="JsonTypeInfo" /> instance.
}
}
- private volatile bool _isConfigured;
- private readonly object _configureLock = new object();
- private ExceptionDispatchInfo? _cachedConfigureError;
+ internal bool IsConfigured => _configurationState == ConfigurationState.Configured;
+ private volatile ConfigurationState _configurationState;
+ private enum ConfigurationState : byte { NotConfigured = 0, Configuring = 1, Configured = 2 };
- internal bool IsConfigured => _isConfigured;
+ private ExceptionDispatchInfo? _cachedConfigureError;
internal void EnsureConfigured()
{
- Debug.Assert(!Monitor.IsEntered(_configureLock), "recursive locking detected.");
-
- if (!_isConfigured)
- ConfigureLocked();
+ if (!IsConfigured)
+ ConfigureSynchronized();
- void ConfigureLocked()
+ void ConfigureSynchronized()
{
+ Options.MakeReadOnly();
+ MakeReadOnly();
+
_cachedConfigureError?.Throw();
- lock (_configureLock)
+ lock (Options.CacheContext)
{
- if (_isConfigured)
+ if (_configurationState != ConfigurationState.NotConfigured)
+ {
+ // The value of _configurationState is either
+ // 'Configuring': recursive instance configured by this thread or
+ // 'Configured' : instance already configured by another thread.
+ // We can safely yield the configuration operation in both cases.
return;
+ }
_cachedConfigureError?.Throw();
try
{
+ _configurationState = ConfigurationState.Configuring;
Configure();
-
- IsReadOnly = true;
- _isConfigured = true;
+ _configurationState = ConfigurationState.Configured;
}
catch (Exception e)
{
_cachedConfigureError = ExceptionDispatchInfo.Capture(e);
+ _configurationState = ConfigurationState.NotConfigured;
throw;
}
}
}
}
- internal void Configure()
+ private void Configure()
{
- Debug.Assert(Monitor.IsEntered(_configureLock), "Configure called directly, use EnsureConfigured which locks this method");
-
- if (!Options.IsReadOnly)
- {
- Options.MakeReadOnly();
- }
-
- PropertyInfoForTypeInfo.EnsureConfigured();
+ Debug.Assert(Monitor.IsEntered(Options.CacheContext), "Configure called directly, use EnsureConfigured which synchronizes access to this method");
+ Debug.Assert(Options.IsReadOnly);
+ Debug.Assert(IsReadOnly);
+ PropertyInfoForTypeInfo.Configure();
CanUseSerializeHandler &= Options.CanUseFastPathSerializationLogic;
- Debug.Assert(PropertyInfoForTypeInfo.EffectiveConverter.ConverterStrategy == Converter.ConverterStrategy,
- $"ConverterStrategy from PropertyInfoForTypeInfo.EffectiveConverter.ConverterStrategy ({PropertyInfoForTypeInfo.EffectiveConverter.ConverterStrategy}) does not match converter's ({Converter.ConverterStrategy})");
if (Kind == JsonTypeInfoKind.Object)
{
ConfigureProperties();
}
}
+ if (ElementType != null)
+ {
+ _elementTypeInfo ??= Options.GetTypeInfoInternal(ElementType);
+ _elementTypeInfo.EnsureConfigured();
+ }
+
+ if (KeyType != null)
+ {
+ _keyTypeInfo ??= Options.GetTypeInfoInternal(KeyType);
+ _keyTypeInfo.EnsureConfigured();
+ }
+
if (PolymorphismOptions != null)
{
PolymorphicTypeResolver = new PolymorphicTypeResolver(this);
}
}
- property.EnsureConfigured();
+ property.Configure();
}
NumberOfRequiredProperties = numberOfRequiredProperties;