Skip to content

Commit afabbcc

Browse files
author
benoithudson
committed
Bug 1250: support for easier serialization
Adds MaybeSerialize<T> which handles the reload nicely. Shows how to use it in FieldObject; it's very minimal changes to FieldObject to add this support. This is simpler than the proof of concept code.
1 parent 581a19a commit afabbcc

File tree

3 files changed

+136
-89
lines changed

3 files changed

+136
-89
lines changed

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
<Compile Include="interfaces.cs" />
117117
<Compile Include="interop.cs" />
118118
<Compile Include="iterator.cs" />
119+
<Compile Include="maybeserialize.cs" />
119120
<Compile Include="managedtype.cs" />
120121
<Compile Include="metatype.cs" />
121122
<Compile Include="methodbinder.cs" />

src/runtime/fieldobject.cs

Lines changed: 3 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -12,97 +12,11 @@ namespace Python.Runtime
1212
[Serializable]
1313
internal class FieldObject : ExtensionType
1414
{
15-
[Serializable]
16-
private struct SerializedFieldInfo : ISerializable
17-
{
18-
// The field if we can find it. Otherwise null.
19-
private FieldInfo m_info;
20-
21-
// The name of the field if the field is missing. Otherwise null.
22-
private string m_name;
23-
24-
public SerializedFieldInfo(FieldInfo info)
25-
{
26-
if (info == null)
27-
{
28-
throw new System.ArgumentNullException("null FieldInfo");
29-
}
30-
m_info = info;
31-
m_name = null;
32-
}
33-
34-
public FieldInfo Value
35-
{
36-
get
37-
{
38-
if (m_info == null)
39-
{
40-
throw new SerializationException($".NET field {m_name} was renamed or removed during domain reload");
41-
}
42-
return m_info;
43-
}
44-
}
45-
46-
public string Name
47-
{
48-
get
49-
{
50-
if (m_info == null)
51-
{
52-
return $"(missing {m_name})";
53-
}
54-
else
55-
{
56-
return m_info.Name;
57-
}
58-
}
59-
}
60-
61-
public void GetObjectData(SerializationInfo info, StreamingContext context)
62-
{
63-
if (m_info == null)
64-
{
65-
info.AddValue("n", m_name);
66-
}
67-
else
68-
{
69-
// Serialize in a silly way. TODO optimize.
70-
var formatter = new BinaryFormatter();
71-
using (var ms = new MemoryStream())
72-
{
73-
formatter.Serialize(ms, m_info);
74-
info.AddValue("i", ms.ToArray());
75-
}
76-
77-
// Also save the name in case the info doesn't deserialize
78-
info.AddValue("n", m_info.ToString());
79-
}
80-
}
81-
82-
private SerializedFieldInfo(SerializationInfo info, StreamingContext context)
83-
{
84-
try
85-
{
86-
var serialized = (byte[])info.GetValue("i", typeof(byte[]));
87-
var formatter = new BinaryFormatter();
88-
using (var ms = new MemoryStream(serialized))
89-
{
90-
m_info = (FieldInfo)formatter.Deserialize(ms);
91-
}
92-
}
93-
catch (SerializationException _)
94-
{
95-
m_info = null;
96-
}
97-
m_name = (m_info != null) ? null : info.GetString("n");
98-
}
99-
}
100-
101-
private SerializedFieldInfo m_info;
15+
private MaybeSerialize<FieldInfo> m_info;
10216

10317
public FieldObject(FieldInfo info)
10418
{
105-
m_info = new SerializedFieldInfo(info);
19+
m_info = new MaybeSerialize<FieldInfo>(info);
10620
}
10721

10822
/// <summary>
@@ -260,7 +174,7 @@ int tp_descr_set(IntPtr ob, IntPtr val)
260174
public static IntPtr tp_repr(IntPtr ob)
261175
{
262176
var self = (FieldObject)GetManagedObject(ob);
263-
return Runtime.PyString_FromString($"<field '{self.m_info.Name}'>");
177+
return Runtime.PyString_FromString($"<field '{self.m_info}'>");
264178
}
265179
}
266180
}

src/runtime/maybeserialize.cs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
using System;
2+
using System.Reflection;
3+
using System.Runtime.Serialization;
4+
using System.Runtime.Serialization.Formatters.Binary;
5+
using System.IO;
6+
7+
namespace Python.Runtime
8+
{
9+
/// <summary>
10+
/// A MaybeSerialize&lt;T&gt; delays errors from serialization and
11+
/// deserialization until the item is used.
12+
///
13+
/// Python for .NET uses this in the C# reloading architecture.
14+
/// If e.g. a class member was renamed when reloading, references to the
15+
/// old field will be invalid, but the rest of the system will still work.
16+
/// Code that tries to use the old field will receive an exception.
17+
///
18+
/// Assumption: the item being wrapped by MaybeSerialize will never be null.
19+
/// </summary>
20+
[Serializable]
21+
internal struct MaybeSerialize<T> : ISerializable where T : class
22+
{
23+
/// <summary>
24+
/// The item being wrapped.
25+
///
26+
/// If this is null, that means we failed to serialize or deserialize it.
27+
/// </summary>
28+
private T m_item;
29+
30+
/// <summary>
31+
/// A string useful for debugging the error.
32+
///
33+
/// This is null if m_item deserialized properly.
34+
/// Otherwise, it will be derived off of m_item.ToString() when we
35+
/// serialized.
36+
/// </summary>
37+
private string m_name;
38+
39+
/// <summary>
40+
/// Store an item in such a way that it can be deserialized.
41+
///
42+
/// It must not be null.
43+
/// </summary>
44+
public MaybeSerialize(T item)
45+
{
46+
if (item == null)
47+
{
48+
throw new System.ArgumentNullException("Trying to store a null");
49+
}
50+
m_item = item;
51+
m_name = null;
52+
}
53+
54+
/// <summary>
55+
/// Get the underlying deserialized value, or throw an exception
56+
/// if deserialiation failed.
57+
/// </summary>
58+
public T Value
59+
{
60+
get
61+
{
62+
if (m_item == null)
63+
{
64+
throw new SerializationException($"The .NET object underlying {m_name} no longer exists");
65+
}
66+
return m_item;
67+
}
68+
}
69+
70+
/// <summary>
71+
/// Get a printable name.
72+
/// </summary>
73+
public string ToString()
74+
{
75+
if (m_item == null)
76+
{
77+
return $"(missing {m_name})";
78+
}
79+
else
80+
{
81+
return m_item.ToString();
82+
}
83+
}
84+
85+
/// <summary>
86+
/// Implements ISerializable
87+
/// </summary>
88+
public void GetObjectData(SerializationInfo info, StreamingContext context)
89+
{
90+
if (m_item == null)
91+
{
92+
// Save the name; this failed to reload in a previous
93+
// generation but we still need to remember what it was.
94+
info.AddValue("n", m_name);
95+
}
96+
else
97+
{
98+
// Try to save the item. If it fails, too bad.
99+
try
100+
{
101+
info.AddValue("i", m_item);
102+
}
103+
catch(SerializationException _)
104+
{
105+
}
106+
107+
// Also save the name in case the item doesn't deserialize
108+
info.AddValue("n", m_item.ToString());
109+
}
110+
}
111+
112+
/// <summary>
113+
/// Implements ISerializable
114+
/// </summary>
115+
private MaybeSerialize(SerializationInfo info, StreamingContext context)
116+
{
117+
try
118+
{
119+
// Try to deserialize the item. It might fail, or it might
120+
// have already failed so there just isn't an "i" to find.
121+
m_item = (T)info.GetValue("i", typeof(T));
122+
m_name = null;
123+
}
124+
catch (SerializationException _)
125+
{
126+
// Getting the item failed, so get the name.
127+
m_item = null;
128+
m_name = info.GetString("n");
129+
}
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)