diff --git a/src/core/IronPython/Runtime/CommonDictionaryStorage.cs b/src/core/IronPython/Runtime/CommonDictionaryStorage.cs index 91a85dc83..45f5f9c06 100644 --- a/src/core/IronPython/Runtime/CommonDictionaryStorage.cs +++ b/src/core/IronPython/Runtime/CommonDictionaryStorage.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using System.Threading; using IronPython.Runtime.Operations; @@ -33,7 +34,8 @@ namespace IronPython.Runtime { /// the buckets and then calls a static helper function to do the read from the bucket /// array to ensure that readers are not seeing multiple bucket arrays. /// - internal sealed class CommonDictionaryStorage : DictionaryStorage { + [Serializable] + internal sealed class CommonDictionaryStorage : DictionaryStorage, ISerializable, IDeserializationCallback { private int[] _indices; private List _buckets; private int _count; @@ -117,6 +119,16 @@ private CommonDictionaryStorage(int[] indices, List buckets, int count, _eqFunc = eqFunc; } +#if FEATURE_SERIALIZATION + private CommonDictionaryStorage(SerializationInfo info, StreamingContext context) { + // Remember the serialization info, we'll deserialize when we get the callback. This + // enables special types like DBNull.Value to successfully be deserialized inside of us. We + // store the serialization info in a special bucket so we don't have an extra field just for + // serialization. + _buckets = new List { new Bucket(null, info, 0) }; + } +#endif + public override void Add(ref DictionaryStorage storage, object key, object value) => Add(key, value); @@ -651,5 +663,31 @@ private static bool TupleEquals(object o1, object o2) private static bool GenericEquals(object o1, object o2) => PythonOps.EqualRetBool(o1, o2); #endregion + +#if FEATURE_SERIALIZATION + #region ISerializable Members + + public void GetObjectData(SerializationInfo info, StreamingContext context) { + info.AddValue("buckets", GetItems()); + } + + void IDeserializationCallback.OnDeserialization(object sender) { + if (_indices is not null) { + // we've received multiple OnDeserialization callbacks, only + // deserialize after the 1st one + return; + } + + var info = (SerializationInfo)_buckets[0].Value; + _buckets.Clear(); + + var buckets = (List>)info.GetValue("buckets", typeof(List>)); + foreach (KeyValuePair kvp in buckets) { + AddNoLock(kvp.Key, kvp.Value); + } + } + + #endregion +#endif } } diff --git a/tests/suite/test_ordered_dict_stdlib.py b/tests/suite/test_ordered_dict_stdlib.py index cf6e4b74d..1e51e76c1 100644 --- a/tests/suite/test_ordered_dict_stdlib.py +++ b/tests/suite/test_ordered_dict_stdlib.py @@ -30,6 +30,7 @@ def load_tests(loader, standard_tests, pattern): ] skip_tests = [ + test.test_ordered_dict.CPythonBuiltinDictTests('test_highly_nested_subclass'), # intermittent AssertionError - GC related? test.test_ordered_dict.CPythonOrderedDictSubclassTests('test_highly_nested_subclass'), # intermittent AssertionError - GC related? test.test_ordered_dict.CPythonOrderedDictTests('test_highly_nested_subclass'), # intermittent AssertionError - GC related? test.test_ordered_dict.PurePythonOrderedDictSubclassTests('test_highly_nested_subclass'), # intermittent AssertionError - GC related?