From a3589624f9b9211673f036b2c7f4e180e6c94c95 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Tue, 5 Nov 2024 11:21:52 -0800 Subject: [PATCH] Breaking Changes for FastSerialization (#2121) * - Rename SerializationConfiguration --> SerializationSettings. - Require an instance of SerializationSettings for serializers and deserializers. * Require a SerializationSettings property for both IStreamReader and IStreamWriter to allow writers to directly create readers. * Refactor SerializationSettings to clone on mutation. This ensures that usres can't change the set of allowed types after it is set and handed to the deserialization code. * Refactor FastSerialization factories and add known types. Remove the ability for FastSerialization to create arbitrary types that are specified in serialized files. Instead, force users to register all types before or during serialization. Types can be registered via calls to Deserializer.RegisterType and Deserializer.RegisterFactory. Users can also implement Deserializer.OnUnregisteredType to handle unregistered types encountered during deserialization. It is incumbent on the implementor to not just blindly call Type.GetType in OnUnregisteredType, and instead only create known types. * Add tests and remove RegisterType(string) because users will need to provide enough context to find the right assembly. * Move StreamReaderAlignment into SerializationSettings. * Undo RID change to HeapDump.csproj. * Rename SetXXX --> WithXXX since it doesn't change the current instance. * Simplify based on feedback. --- src/FastSerialization/FastSerialization.cs | 161 +++++++++++------- .../MemoryMappedFileStreamReader.cs | 18 +- .../SegmentedMemoryStreamReader.cs | 16 +- .../SegmentedMemoryStreamWriter.cs | 14 +- src/FastSerialization/StreamReaderWriter.cs | 60 ++++--- src/HeapDump/GCHeapDump.cs | 6 +- src/HeapDump/GCHeapDumper.cs | 4 +- src/MemoryGraph/MemoryGraph.cs | 3 +- src/MemoryGraph/graph.cs | 10 +- .../EventPipe/EventPipeEventSource.cs | 4 +- .../Parsing/EventPipeParsing.cs | 2 +- .../Serialization/FastSerializerTests.cs | 55 +++++- src/TraceEvent/TraceLog.cs | 21 +-- 13 files changed, 235 insertions(+), 139 deletions(-) diff --git a/src/FastSerialization/FastSerialization.cs b/src/FastSerialization/FastSerialization.cs index 9c883f19d..bcc2e4c5e 100644 --- a/src/FastSerialization/FastSerialization.cs +++ b/src/FastSerialization/FastSerialization.cs @@ -98,14 +98,41 @@ enum StreamReaderAlignment : int }; /// - /// These settings apply to use of Serializer and Deserializer specifically. + /// Settings used by Serializer and Deserializer. /// #if FASTSERIALIZATION_PUBLIC public #endif - sealed class SerializationConfiguration + sealed class SerializationSettings { - public StreamLabelWidth StreamLabelWidth { get; set; } = StreamLabelWidth.EightBytes; + internal StreamLabelWidth StreamLabelWidth { get; } + + internal StreamReaderAlignment StreamReaderAlignment { get; } + + public static SerializationSettings Default { get; } = new SerializationSettings( + StreamLabelWidth.EightBytes, + StreamReaderAlignment.EightBytes); + + public SerializationSettings WithStreamLabelWidth(StreamLabelWidth width) + { + return new SerializationSettings( + width, + StreamReaderAlignment); + } + + public SerializationSettings WithStreamReaderAlignment(StreamReaderAlignment alignment) + { + return new SerializationSettings( + StreamLabelWidth, + alignment); + } + + private SerializationSettings( + StreamLabelWidth streamLabelWidth, StreamReaderAlignment streamReaderAlignment) + { + StreamLabelWidth = streamLabelWidth; + StreamReaderAlignment = streamReaderAlignment; + } } /// @@ -184,6 +211,11 @@ interface IStreamWriter : IDisposable /// IStreamReader.GotoSuffixLabel for more) /// void WriteSuffixLabel(StreamLabel value); + + /// + /// The settings associated with this writer. + /// + SerializationSettings Settings { get; } } @@ -253,6 +285,11 @@ interface IStreamReader : IDisposable /// and then seeking to that position. /// void GotoSuffixLabel(); + + /// + /// The settings associated with this reader. + /// + SerializationSettings Settings { get; } } #if !DOTNET_V35 @@ -516,7 +553,7 @@ sealed class Serializer : IDisposable /// The destination file. /// The object to serialize. /// Optional sharing mode for the destination file. Defaults to . - public Serializer(string filePath, IFastSerializable entryObject, FileShare share = FileShare.Read) : this(new IOStreamStreamWriter(filePath, share: share), entryObject) { } + public Serializer(string filePath, IFastSerializable entryObject, FileShare share = FileShare.Read) : this(new IOStreamStreamWriter(filePath, settings: SerializationSettings.Default, share: share), entryObject) { } /// /// Create a serializer that writes to a . The serializer @@ -533,7 +570,7 @@ public Serializer(Stream outputStream, IFastSerializable entryObject) /// closes. /// public Serializer(Stream outputStream, IFastSerializable entryObject, bool leaveOpen) - : this(new IOStreamStreamWriter(outputStream, leaveOpen: leaveOpen), entryObject) + : this(new IOStreamStreamWriter(outputStream, SerializationSettings.Default, leaveOpen: leaveOpen), entryObject) { } @@ -1035,18 +1072,18 @@ sealed class Deserializer : IDisposable /// /// Create a Deserializer that reads its data from a given file /// - public Deserializer(string filePath, SerializationConfiguration config = null) + public Deserializer(string filePath, SerializationSettings settings) { - IOStreamStreamReader reader = new IOStreamStreamReader(filePath, config); + IOStreamStreamReader reader = new IOStreamStreamReader(filePath, settings); Initialize(reader, filePath); } /// /// Create a Deserializer that reads its data from a given System.IO.Stream. The stream will be closed when the Deserializer is done with it. /// - public Deserializer(Stream inputStream, string streamName, SerializationConfiguration config = null) + public Deserializer(Stream inputStream, string streamName, SerializationSettings settings) { - IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, config: config); + IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, settings: settings); Initialize(reader, streamName); } @@ -1055,9 +1092,9 @@ public Deserializer(Stream inputStream, string streamName, SerializationConfigur /// parameter determines whether the deserializer will close the stream when it /// closes. /// - public Deserializer(Stream inputStream, string streamName, bool leaveOpen, SerializationConfiguration config = null) + public Deserializer(Stream inputStream, string streamName, bool leaveOpen, SerializationSettings settings) { - IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, leaveOpen: leaveOpen, config: config); + IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, leaveOpen: leaveOpen, settings: settings); Initialize(reader, streamName); } @@ -1610,38 +1647,61 @@ public StreamLabel ResolveForwardReference(ForwardReference reference, bool pres public String Name { get; private set; } /// - /// If set this function is set, then it is called whenever a type name from the serialization - /// data is encountered. It is your you then need to look that up. If it is not present - /// it uses Type.GetType(string) which only checks the current assembly and mscorlib. + /// Called when Deserializer encounters a type that is not registered, allowing the implementation + /// to return a factory delegate to be cached and used for subsequent encounters. /// - public Func TypeResolver { get; set; } + public Func> OnUnregisteredType { get; set; } /// - /// For every IFastSerializable object being deserialized, the Deserializer needs to create 'empty' objects - /// that 'FromStream' is invoked on. The Deserializer gets these 'empty' objects by calling a 'factory' - /// delegate for that type. Thus all types being deserialized must have a factory. + /// Registers a creation factory for a type. /// - /// RegisterFactory registers such a factory for particular 'type'. + /// When the Deserializerencounters a serialized type, it will look for a registered factory or type registration + /// so that it knows how to construct an empty instance of the type that can be filled. All non-primitive types + /// must either be registered by calling RegisterFactory or RegisterType. /// public void RegisterFactory(Type type, Func factory) { factories[type.FullName] = factory; } + + /// + /// Registers a creation factory for a type name. + /// + /// When the Deserializerencounters a serialized type, it will look for a registered factory or type registration + /// so that it knows how to construct an empty instance of the type that can be filled. All non-primitive types + /// must either be registered by calling RegisterFactory or RegisterType. + /// public void RegisterFactory(string typeName, Func factory) { factories[typeName] = factory; } /// - /// For every IFastSerializable object being deserialized, the Deserializer needs to create 'empty' objects - /// that 'FromStream' is invoked on. The Deserializer gets these 'empty' objects by calling a 'factory' - /// delegate for that type. Thus all types being deserialized must have a factory. + /// Registers a type that can be created by instantiating the parameterless constructor. /// - /// RegisterDefaultFactory registers a factory that is passed a type parameter and returns a new IFastSerialable object. + /// When the Deserializerencounters a serialized type, it will look for a registered factory or type registration + /// so that it knows how to construct an empty instance of the type that can be filled. All non-primitive types + /// must either be registered by calling RegisterFactory or RegisterType. /// - public void RegisterDefaultFactory(Func defaultFactory) + public void RegisterType(Type type) + { + RegisterFactory(type, () => + { + return CreateDefault(type); + }); + } + + private static IFastSerializable CreateDefault(Type type) { - this.defaultFactory = defaultFactory; + try + { + return (IFastSerializable)Activator.CreateInstance(type); + } + catch (MissingMethodException) + { + throw new SerializationException( + $"Unable to create an object of type {type.FullName}. It must either have a parameterless constructor or have been registered with the deserializer via RegisterFactory."); + } } // For FromStream method bodies, reading tagged values (for post V1 field additions) @@ -1964,49 +2024,31 @@ private IFastSerializable ReadObjectDefinition(Tags tag, StreamLabel objectLabel internal Func GetFactory(string fullName) { - Func ret; - if (factories.TryGetValue(fullName, out ret)) + // Check for a registered factory. + Func factory = null; + if (factories.TryGetValue(fullName, out factory)) { - return ret; + return factory; } - Type type; - if (TypeResolver != null) - { - type = TypeResolver(fullName); - } - else + // If there is not a registered factory, give the implementation of IFastSerializable an opportunity to create a factory. + if (OnUnregisteredType != null) { - type = Type.GetType(fullName); + factory = OnUnregisteredType(fullName); + if (factory != null) + { + // Save the factory for future encounters of this type name. + RegisterFactory(fullName, factory); + } } - if (type == null) + if (factory == null) { - throw new TypeLoadException("Could not find type " + fullName); + throw new TypeLoadException( + $"Could not create an instance of type {fullName}. The type must be registered with the deserializer via a call to RegisterFactory or RegisterType."); } - return delegate - { - // If we have a default factory, use it. - if (defaultFactory != null) - { - IFastSerializable instance = defaultFactory(type); - if (instance != null) - { - return instance; - } - } - // Factory of last resort. - try - { - return (IFastSerializable)Activator.CreateInstance(type); - } - catch (MissingMethodException) - { - throw new SerializationException("Failure deserializing " + type.FullName + - ".\r\nIt must either have a parameterless constructor or been registered with the serializer."); - } - }; + return factory; } private void FindEndTag(SerializationType type, IFastSerializable objectBeingDeserialized) @@ -2144,7 +2186,6 @@ private Tags ReadTag() /// internal bool deferForwardReferences; private Dictionary> factories; - private Func defaultFactory; #endregion }; diff --git a/src/FastSerialization/MemoryMappedFileStreamReader.cs b/src/FastSerialization/MemoryMappedFileStreamReader.cs index a7b14aa6c..653f5dee3 100644 --- a/src/FastSerialization/MemoryMappedFileStreamReader.cs +++ b/src/FastSerialization/MemoryMappedFileStreamReader.cs @@ -29,17 +29,19 @@ public class MemoryMappedFileStreamReader : IStreamReader private long _capacity; private long _offset; - public MemoryMappedFileStreamReader(string mapName, long length) - : this(MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.Read), length, leaveOpen: false) + public MemoryMappedFileStreamReader(string mapName, long length, SerializationSettings settings) + : this(MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.Read), length, leaveOpen: false, settings) { } - public MemoryMappedFileStreamReader(MemoryMappedFile file, long length, bool leaveOpen) + public MemoryMappedFileStreamReader(MemoryMappedFile file, long length, bool leaveOpen, SerializationSettings settings) { _file = file; _fileLength = length; _leaveOpen = leaveOpen; + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + if (IntPtr.Size == 4) { _capacity = Math.Min(_fileLength, BlockCopyCapacity); @@ -53,11 +55,17 @@ public MemoryMappedFileStreamReader(MemoryMappedFile file, long length, bool lea _viewAddress = _view.SafeMemoryMappedViewHandle.DangerousGetHandle(); } - public static MemoryMappedFileStreamReader CreateFromFile(string path) + public static MemoryMappedFileStreamReader CreateFromFile(string path, SerializationSettings settings) { long capacity = new FileInfo(path).Length; MemoryMappedFile file = MemoryMappedFile.CreateFromFile(path, FileMode.Open, Guid.NewGuid().ToString("N"), capacity, MemoryMappedFileAccess.Read); - return new MemoryMappedFileStreamReader(file, capacity, leaveOpen: false); + return new MemoryMappedFileStreamReader(file, capacity, leaveOpen: false, settings); + } + + public SerializationSettings Settings + { + get; + private set; } public DeferedStreamLabel Current diff --git a/src/FastSerialization/SegmentedMemoryStreamReader.cs b/src/FastSerialization/SegmentedMemoryStreamReader.cs index 1cb94d244..cb15f6f00 100644 --- a/src/FastSerialization/SegmentedMemoryStreamReader.cs +++ b/src/FastSerialization/SegmentedMemoryStreamReader.cs @@ -12,15 +12,15 @@ public class SegmentedMemoryStreamReader /// /// Create a IStreamReader (reads binary data) from a given byte buffer /// - public SegmentedMemoryStreamReader(SegmentedList data, SerializationConfiguration config = null) : this(data, 0, data.Count, config) { } + public SegmentedMemoryStreamReader(SegmentedList data, SerializationSettings settings) : this(data, 0, data.Count, settings) { } /// /// Create a IStreamReader (reads binary data) from a given subregion of a byte buffer /// - public SegmentedMemoryStreamReader(SegmentedList data, long start, long length, SerializationConfiguration config = null) + public SegmentedMemoryStreamReader(SegmentedList data, long start, long length, SerializationSettings settings) { - SerializationConfiguration = config ?? new SerializationConfiguration(); + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); - if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes) + if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes) { readLabel = () => { @@ -157,7 +157,7 @@ public virtual void Goto(StreamLabel label) { Debug.Assert(label != StreamLabel.Invalid); - if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes) + if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes) { Debug.Assert((long)label <= int.MaxValue); position = (uint)label; @@ -174,7 +174,7 @@ public virtual StreamLabel Current { get { - if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes) + if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes) { return (StreamLabel)(uint)position; } @@ -206,9 +206,9 @@ public void Dispose() protected virtual void Dispose(bool disposing) { } /// - /// Returns the SerializationConfiguration for this stream reader. + /// Returns the SerializationSettings for this stream reader. /// - internal SerializationConfiguration SerializationConfiguration { get; private set; } + internal SerializationSettings Settings { get; private set; } #endregion #region private diff --git a/src/FastSerialization/SegmentedMemoryStreamWriter.cs b/src/FastSerialization/SegmentedMemoryStreamWriter.cs index e97783bad..7c82bf0e4 100644 --- a/src/FastSerialization/SegmentedMemoryStreamWriter.cs +++ b/src/FastSerialization/SegmentedMemoryStreamWriter.cs @@ -7,12 +7,12 @@ namespace FastSerialization { public class SegmentedMemoryStreamWriter { - public SegmentedMemoryStreamWriter(SerializationConfiguration config = null) : this(64, config) { } - public SegmentedMemoryStreamWriter(long initialSize, SerializationConfiguration config = null) + public SegmentedMemoryStreamWriter(SerializationSettings settings) : this(64, settings) { } + public SegmentedMemoryStreamWriter(long initialSize, SerializationSettings settings) { - SerializationConfiguration = config ?? new SerializationConfiguration(); + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); - if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes) + if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes) { writeLabel = (value) => { @@ -100,14 +100,14 @@ public void WriteToStream(Stream outputStream) public SegmentedMemoryStreamReader GetReader() { var readerBytes = bytes; - return new SegmentedMemoryStreamReader(readerBytes, 0, readerBytes.Count, SerializationConfiguration); + return new SegmentedMemoryStreamReader(readerBytes, 0, readerBytes.Count, Settings); } public void Dispose() { } /// - /// Returns the SerializationConfiguration for this stream writer. + /// Returns the SerializerSettings for this stream writer. /// - internal SerializationConfiguration SerializationConfiguration { get; private set; } + internal SerializationSettings Settings { get; private set; } #region private protected virtual void MakeSpace() diff --git a/src/FastSerialization/StreamReaderWriter.cs b/src/FastSerialization/StreamReaderWriter.cs index 2aa7efea6..5c3b8b376 100644 --- a/src/FastSerialization/StreamReaderWriter.cs +++ b/src/FastSerialization/StreamReaderWriter.cs @@ -22,18 +22,19 @@ class MemoryStreamReader : IStreamReader /// /// Create a IStreamReader (reads binary data) from a given byte buffer /// - public MemoryStreamReader(byte[] data) : this(data, 0, data.Length) { } + public MemoryStreamReader(byte[] data, SerializationSettings settings) : this(data, 0, data.Length, settings) { } /// /// Create a IStreamReader (reads binary data) from a given subregion of a byte buffer. /// - public MemoryStreamReader(byte[] data, int start, int length, SerializationConfiguration config = null) + public MemoryStreamReader(byte[] data, int start, int length, SerializationSettings settings) { bytes = data; position = start; endPosition = length; - SerializationConfiguration = config != null ? config : new SerializationConfiguration(); - if(SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes) + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + + if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes) { readLabel = () => { @@ -56,9 +57,14 @@ public MemoryStreamReader(byte[] data, int start, int length, SerializationConfi /// public virtual long Length { get { return endPosition; } } public virtual bool HasLength { get { return true; } } - public SerializationConfiguration SerializationConfiguration { get; private set; } #region implemenation of IStreamReader + + /// + /// The settings associated with this reader. + /// + public SerializationSettings Settings { get; private set; } + public virtual void Read(byte[] data, int offset, int length) { if (length > endPosition - position) @@ -244,12 +250,11 @@ class MemoryStreamWriter : IStreamWriter /// /// Call 'GetBytes' call to get the raw array. Only the first 'Length' bytes are valid /// - public MemoryStreamWriter(int initialSize = 64, SerializationConfiguration config = null) + public MemoryStreamWriter(SerializationSettings settings, int initialSize = 64) { - bytes = new byte[initialSize]; - SerializationConfiguration = config != null ? config : new SerializationConfiguration(); + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); - if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes) + if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes) { writeLabel = (value) => { @@ -265,9 +270,14 @@ public MemoryStreamWriter(int initialSize = 64, SerializationConfiguration confi Write((long)value); }; } + + bytes = new byte[initialSize]; } - public SerializationConfiguration SerializationConfiguration { get; set; } + /// + /// The settings associated with this writer. + /// + public SerializationSettings Settings { get; private set; } /// /// Returns a IStreamReader that will read the written bytes. You cannot write additional bytes to the stream after making this call. @@ -281,7 +291,7 @@ public virtual MemoryStreamReader GetReader() readerBytes = new byte[endPosition]; Array.Copy(bytes, readerBytes, endPosition); } - return new MemoryStreamReader(readerBytes, 0, endPosition); + return new MemoryStreamReader(readerBytes, 0, endPosition, Settings); } /// @@ -647,17 +657,17 @@ class IOStreamStreamReader : MemoryStreamReader, IDisposable /// Create a new IOStreamStreamReader from the given file. /// /// - public IOStreamStreamReader(string fileName, SerializationConfiguration config = null) - : this(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete), config: config) { } + public IOStreamStreamReader(string fileName, SerializationSettings settings) + : this(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete), settings: settings) { } /// /// Create a new IOStreamStreamReader from the given System.IO.Stream. Optionally you can specify the size of the read buffer /// The stream will be closed by the IOStreamStreamReader when it is closed. /// - public IOStreamStreamReader(Stream inputStream, int bufferSize = defaultBufferSize, bool leaveOpen = false, SerializationConfiguration config = null, StreamReaderAlignment alignment = StreamReaderAlignment.EightBytes) - : base(new byte[bufferSize + (int)alignment], 0, 0, config) + public IOStreamStreamReader(Stream inputStream, SerializationSettings settings, int bufferSize = defaultBufferSize, bool leaveOpen = false) + : base(new byte[bufferSize + (int)settings.StreamReaderAlignment], 0, 0, settings) { - align = (int)alignment; + align = (int)settings.StreamReaderAlignment; Debug.Assert(bufferSize % align == 0); this.inputStream = inputStream; this.leaveOpen = leaveOpen; @@ -907,17 +917,17 @@ sealed unsafe class PinnedStreamReader : IOStreamStreamReader /// /// Create a new PinnedStreamReader that gets its data from a given file. You can optionally set the size of the read buffer. /// - public PinnedStreamReader(string fileName, int bufferSize = defaultBufferSize, SerializationConfiguration config = null) + public PinnedStreamReader(string fileName, SerializationSettings settings, int bufferSize = defaultBufferSize) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read, - FileShare.Read | FileShare.Delete), bufferSize, config) + FileShare.Read | FileShare.Delete), settings, bufferSize) { } /// /// Create a new PinnedStreamReader that gets its data from a given System.IO.Stream. You can optionally set the size of the read buffer. /// The stream will be closed by the PinnedStreamReader when it is closed. /// - public PinnedStreamReader(Stream inputStream, int bufferSize = defaultBufferSize, SerializationConfiguration config = null, StreamReaderAlignment alignment = StreamReaderAlignment.EightBytes) - : base(inputStream, bufferSize, config: config, alignment: alignment) + public PinnedStreamReader(Stream inputStream, SerializationSettings settings, int bufferSize = defaultBufferSize) + : base(inputStream, settings, bufferSize) { // Pin the array pinningHandle = System.Runtime.InteropServices.GCHandle.Alloc(bytes, System.Runtime.InteropServices.GCHandleType.Pinned); @@ -934,7 +944,7 @@ public PinnedStreamReader(Stream inputStream, int bufferSize = defaultBufferSize /// public PinnedStreamReader Clone() { - PinnedStreamReader ret = new PinnedStreamReader(inputStream, bytes.Length - align); + PinnedStreamReader ret = new PinnedStreamReader(inputStream, Settings, bytes.Length - align); return ret; } @@ -1004,13 +1014,13 @@ class IOStreamStreamWriter : MemoryStreamWriter, IDisposable /// Create a IOStreamStreamWriter that writes its data to a given file that it creates /// /// - public IOStreamStreamWriter(string fileName, SerializationConfiguration config = null, FileShare share = FileShare.Read) : this(new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, share), config: config) { } + public IOStreamStreamWriter(string fileName, SerializationSettings settings, FileShare share = FileShare.Read) : this(new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, share), settings: settings) { } /// /// Create a IOStreamStreamWriter that writes its data to a System.IO.Stream /// - public IOStreamStreamWriter(Stream outputStream, int bufferSize = defaultBufferSize + sizeof(long), bool leaveOpen = false, SerializationConfiguration config = null) - : base(bufferSize, config) + public IOStreamStreamWriter(Stream outputStream, SerializationSettings settings, int bufferSize = defaultBufferSize + sizeof(long), bool leaveOpen = false) + : base(settings, bufferSize) { this.outputStream = outputStream; this.leaveOpen = leaveOpen; @@ -1059,7 +1069,7 @@ public override long Length public override StreamLabel GetLabel() { long len = Length; - if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes && len != (uint)len) + if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes && len != (uint)len) { throw new NotSupportedException("Streams larger than 4 GB. You need to use /MaxEventCount to limit the size."); } diff --git a/src/HeapDump/GCHeapDump.cs b/src/HeapDump/GCHeapDump.cs index 9c9405d03..dee163f58 100644 --- a/src/HeapDump/GCHeapDump.cs +++ b/src/HeapDump/GCHeapDump.cs @@ -17,11 +17,11 @@ public class GCHeapDump : IFastSerializable, IFastSerializableVersion { public GCHeapDump(string inputFileName) : - this(new Deserializer(inputFileName, new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.FourBytes })) + this(new Deserializer(inputFileName, SerializationSettings.Default.WithStreamLabelWidth(StreamLabelWidth.FourBytes))) { } public GCHeapDump(Stream inputStream, string streamName) : - this(new Deserializer(inputStream, streamName, new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.FourBytes })) + this(new Deserializer(inputStream, streamName, SerializationSettings.Default.WithStreamLabelWidth(StreamLabelWidth.FourBytes))) { } /// @@ -192,7 +192,7 @@ public static Dictionary GetProcessesWithGCHeaps() private void Write(string outputFileName) { Debug.Assert(MemoryGraph != null); - var serializer = new Serializer(new IOStreamStreamWriter(outputFileName, config: new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.FourBytes }), this); + var serializer = new Serializer(new IOStreamStreamWriter(outputFileName, settings: SerializationSettings.Default.WithStreamLabelWidth(StreamLabelWidth.FourBytes)), this); serializer.Close(); } diff --git a/src/HeapDump/GCHeapDumper.cs b/src/HeapDump/GCHeapDumper.cs index 5aa32c957..cb2468d0b 100644 --- a/src/HeapDump/GCHeapDumper.cs +++ b/src/HeapDump/GCHeapDumper.cs @@ -1256,7 +1256,7 @@ private void WriteData(bool logLiveStats) if (m_outputFileName != null) { m_log.WriteLine("{0,5:f1}s: Started Writing to file.", m_sw.Elapsed.TotalSeconds); - var serializer = new Serializer(new IOStreamStreamWriter(m_outputFileName, config: new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.FourBytes }), m_gcHeapDump); + var serializer = new Serializer(new IOStreamStreamWriter(m_outputFileName, settings: SerializationSettings.Default.WithStreamLabelWidth(StreamLabelWidth.FourBytes)), m_gcHeapDump); serializer.Close(); m_log.WriteLine("Actual file size = {0:f3}MB", new FileInfo(m_outputFileName).Length / 1000000.0); @@ -1265,7 +1265,7 @@ private void WriteData(bool logLiveStats) if (m_outputStream != null) { m_log.WriteLine("{0,5:f1}s: Started Writing to stream.", m_sw.Elapsed.TotalSeconds); - var serializer = new Serializer(new IOStreamStreamWriter(m_outputStream, config: new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.FourBytes }), m_gcHeapDump); + var serializer = new Serializer(new IOStreamStreamWriter(m_outputStream, settings: SerializationSettings.Default.WithStreamLabelWidth(StreamLabelWidth.FourBytes)), m_gcHeapDump); serializer.Close(); } diff --git a/src/MemoryGraph/MemoryGraph.cs b/src/MemoryGraph/MemoryGraph.cs index 1560c6acb..86933e349 100644 --- a/src/MemoryGraph/MemoryGraph.cs +++ b/src/MemoryGraph/MemoryGraph.cs @@ -31,8 +31,7 @@ public void WriteAsBinaryFile(string outputFileName) } public static MemoryGraph ReadFromBinaryFile(string inputFileName) { - Deserializer deserializer = new Deserializer(inputFileName); - deserializer.TypeResolver = typeName => System.Type.GetType(typeName); // resolve types in this assembly (and mscorlib) + Deserializer deserializer = new Deserializer(inputFileName, SerializationSettings.Default); deserializer.RegisterFactory(typeof(MemoryGraph), delegate () { return new MemoryGraph(1); }); deserializer.RegisterFactory(typeof(Graphs.Module), delegate () { return new Graphs.Module(0); }); return (MemoryGraph)deserializer.GetEntryObject(); diff --git a/src/MemoryGraph/graph.cs b/src/MemoryGraph/graph.cs index 1d7d63d62..180aa778a 100644 --- a/src/MemoryGraph/graph.cs +++ b/src/MemoryGraph/graph.cs @@ -465,8 +465,9 @@ private void ClearWorker() RootIndex = NodeIndex.Invalid; if (m_writer == null) { - m_writer = new SegmentedMemoryStreamWriter(m_expectedNodeCount * 8, - m_isVeryLargeGraph ? new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.EightBytes } : null); + SerializationSettings settings = SerializationSettings.Default + .WithStreamLabelWidth(m_isVeryLargeGraph ? StreamLabelWidth.EightBytes : StreamLabelWidth.FourBytes); + m_writer = new SegmentedMemoryStreamWriter(m_expectedNodeCount * 8, settings); } m_totalSize = 0; @@ -594,8 +595,9 @@ public void FromStream(Deserializer deserializer) // Read in the Blob stream. // TODO be lazy about reading in the blobs. int blobCount = deserializer.ReadInt(); - SegmentedMemoryStreamWriter writer = new SegmentedMemoryStreamWriter(blobCount, - m_isVeryLargeGraph ? new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.EightBytes } : null); + SerializationSettings settings = SerializationSettings.Default + .WithStreamLabelWidth(m_isVeryLargeGraph ? StreamLabelWidth.EightBytes : StreamLabelWidth.FourBytes); + SegmentedMemoryStreamWriter writer = new SegmentedMemoryStreamWriter(blobCount, settings); while (8 <= blobCount) { diff --git a/src/TraceEvent/EventPipe/EventPipeEventSource.cs b/src/TraceEvent/EventPipe/EventPipeEventSource.cs index f0aaae012..e197c078a 100644 --- a/src/TraceEvent/EventPipe/EventPipeEventSource.cs +++ b/src/TraceEvent/EventPipe/EventPipeEventSource.cs @@ -28,12 +28,12 @@ namespace Microsoft.Diagnostics.Tracing /// public unsafe class EventPipeEventSource : TraceEventDispatcher, IFastSerializable, IFastSerializableVersion { - public EventPipeEventSource(string fileName) : this(new PinnedStreamReader(fileName, 0x20000, new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.FourBytes }), fileName, false) + public EventPipeEventSource(string fileName) : this(new PinnedStreamReader(fileName, SerializationSettings.Default.WithStreamLabelWidth(StreamLabelWidth.FourBytes), 0x20000), fileName, false) { } public EventPipeEventSource(Stream stream) - : this(new PinnedStreamReader(stream, alignment: StreamReaderAlignment.OneByte, config: new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.FourBytes }), "stream", true) + : this(new PinnedStreamReader(stream, settings: SerializationSettings.Default.WithStreamLabelWidth(StreamLabelWidth.FourBytes).WithStreamReaderAlignment(StreamReaderAlignment.OneByte)), "stream", true) { } diff --git a/src/TraceEvent/TraceEvent.Tests/Parsing/EventPipeParsing.cs b/src/TraceEvent/TraceEvent.Tests/Parsing/EventPipeParsing.cs index 066c9476d..3eba0be43 100644 --- a/src/TraceEvent/TraceEvent.Tests/Parsing/EventPipeParsing.cs +++ b/src/TraceEvent/TraceEvent.Tests/Parsing/EventPipeParsing.cs @@ -327,7 +327,7 @@ public void V4EventPipeFileHasProcNumbers() [Fact] public void GotoWorksForPositionsGreaterThanAlignment() { - using (var reader = new PinnedStreamReader(new MockHugeStream((long)uint.MaxValue + 5_000_000), bufferSize: 0x4000 /* 16KB */, alignment: StreamReaderAlignment.EightBytes)) + using (var reader = new PinnedStreamReader(new MockHugeStream((long)uint.MaxValue + 5_000_000), settings: SerializationSettings.Default.WithStreamReaderAlignment(StreamReaderAlignment.EightBytes), bufferSize: 0x4000 /* 16KB */)) { reader.Goto((StreamLabel)0x148); var buf = new byte[100_000]; diff --git a/src/TraceEvent/TraceEvent.Tests/Serialization/FastSerializerTests.cs b/src/TraceEvent/TraceEvent.Tests/Serialization/FastSerializerTests.cs index a7d3ffa29..75798e0d8 100644 --- a/src/TraceEvent/TraceEvent.Tests/Serialization/FastSerializerTests.cs +++ b/src/TraceEvent/TraceEvent.Tests/Serialization/FastSerializerTests.cs @@ -23,7 +23,7 @@ public void ParseEightByteStreamLabel() writer.Write((long)0xf1234567); ms.Position = 0; - Deserializer d = new Deserializer(new PinnedStreamReader(ms, config: new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.EightBytes }), "name"); + Deserializer d = new Deserializer(new PinnedStreamReader(ms, settings: SerializationSettings.Default.WithStreamLabelWidth(StreamLabelWidth.EightBytes)), "name"); Assert.Equal((StreamLabel)0, d.ReadLabel()); Assert.Equal((StreamLabel)19, d.ReadLabel()); Assert.Equal((StreamLabel)1_000_000, d.ReadLabel()); @@ -42,7 +42,7 @@ public void ParseFourByteStreamLabel() writer.Write(0xf1234567); ms.Position = 0; - Deserializer d = new Deserializer(new PinnedStreamReader(ms, config: new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.FourBytes }), "name"); + Deserializer d = new Deserializer(new PinnedStreamReader(ms, settings: SerializationSettings.Default.WithStreamLabelWidth(StreamLabelWidth.FourBytes)), "name"); Assert.Equal((StreamLabel)0, d.ReadLabel()); Assert.Equal((StreamLabel)19, d.ReadLabel()); Assert.Equal((StreamLabel)1_000_000, d.ReadLabel()); @@ -58,12 +58,15 @@ private void WriteString(BinaryWriter writer, string val) [Fact] public void WriteAndParseFourByteStreamLabel() { + SerializationSettings settings = SerializationSettings.Default + .WithStreamLabelWidth(StreamLabelWidth.FourBytes); + SampleSerializableType sample = new SampleSerializableType(SampleSerializableType.ConstantValue); MemoryStream ms = new MemoryStream(); - Serializer s = new Serializer(new IOStreamStreamWriter(ms, leaveOpen:true, config: new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.FourBytes }), sample); + Serializer s = new Serializer(new IOStreamStreamWriter(ms, settings, leaveOpen: true), sample); s.Dispose(); - Deserializer d = new Deserializer(new PinnedStreamReader(ms, config: new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.FourBytes }), "name"); + Deserializer d = new Deserializer(new PinnedStreamReader(ms, settings), "name"); d.RegisterFactory(typeof(SampleSerializableType), () => new SampleSerializableType(0)); SampleSerializableType serializable = (SampleSerializableType)d.ReadObject(); Assert.Equal(SampleSerializableType.ConstantValue, serializable.BeforeValue); @@ -73,23 +76,63 @@ public void WriteAndParseFourByteStreamLabel() [Fact] public void WriteAndParseEightByteStreamLabel() { + SerializationSettings settings = SerializationSettings.Default + .WithStreamLabelWidth(StreamLabelWidth.EightBytes); + SampleSerializableType sample = new SampleSerializableType(SampleSerializableType.ConstantValue); MemoryStream ms = new MemoryStream(); - Serializer s = new Serializer(new IOStreamStreamWriter(ms, leaveOpen: true, config: new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.EightBytes }), sample); + Serializer s = new Serializer(new IOStreamStreamWriter(ms, settings,leaveOpen: true), sample); s.Dispose(); - Deserializer d = new Deserializer(new PinnedStreamReader(ms, config: new SerializationConfiguration() { StreamLabelWidth = StreamLabelWidth.EightBytes }), "name"); + Deserializer d = new Deserializer(new PinnedStreamReader(ms, settings), "name"); d.RegisterFactory(typeof(SampleSerializableType), () => new SampleSerializableType(0)); SampleSerializableType serializable = (SampleSerializableType)d.ReadObject(); Assert.Equal(SampleSerializableType.ConstantValue, serializable.BeforeValue); Assert.Equal(SampleSerializableType.ConstantValue, serializable.AfterValue); } + + [Fact] + public void FailToDeserializeUnregisteredType() + { + SerializationSettings settings = SerializationSettings.Default; + + SampleSerializableType sample = new SampleSerializableType(SampleSerializableType.ConstantValue); + MemoryStream ms = new MemoryStream(); + Serializer s = new Serializer(new IOStreamStreamWriter(ms, settings, leaveOpen: true), sample); + s.Dispose(); + + Deserializer d = new Deserializer(new PinnedStreamReader(ms, settings), "name"); + Assert.Throws(() => d.ReadObject()); + } + + [Fact] + public void SuccessfullyDeserializeRegisteredType() + { + SerializationSettings settings = SerializationSettings.Default + .WithStreamLabelWidth(StreamLabelWidth.EightBytes); + + SampleSerializableType sample = new SampleSerializableType(SampleSerializableType.ConstantValue); + MemoryStream ms = new MemoryStream(); + Serializer s = new Serializer(new IOStreamStreamWriter(ms, settings, leaveOpen: true), sample); + s.Dispose(); + + Deserializer d = new Deserializer(new PinnedStreamReader(ms, settings), "name"); + d.RegisterType(typeof(SampleSerializableType)); + SampleSerializableType serializable = (SampleSerializableType)d.ReadObject(); + Assert.Equal(SampleSerializableType.ConstantValue, serializable.BeforeValue); + Assert.Equal(SampleSerializableType.ConstantValue, serializable.AfterValue); + } } public sealed class SampleSerializableType : IFastSerializable { public const int ConstantValue = 42; + public SampleSerializableType() + : this(0) + { + } + public SampleSerializableType(int value) { BeforeValue = value; diff --git a/src/TraceEvent/TraceLog.cs b/src/TraceEvent/TraceLog.cs index 690a6f6e9..2d62b6c2b 100644 --- a/src/TraceEvent/TraceLog.cs +++ b/src/TraceEvent/TraceLog.cs @@ -3580,8 +3580,7 @@ private unsafe void InitializeFromFile(string etlxFilePath) Debug.Assert(sizeof(TraceEventNativeMethods.EVENT_HEADER) == 0x50 && sizeof(TraceEventNativeMethods.ETW_BUFFER_CONTEXT) == 4); // As of TraceLog version 74, all StreamLabels are 64-bit. See IFastSerializableVersion for details. - Deserializer deserializer = new Deserializer(new PinnedStreamReader(etlxFilePath, 0x10000), etlxFilePath); - deserializer.TypeResolver = typeName => System.Type.GetType(typeName); // resolve types in this assembly (and mscorlib) + Deserializer deserializer = new Deserializer(new PinnedStreamReader(etlxFilePath, SerializationSettings.Default, 0x10000), etlxFilePath); // when the deserializer needs a TraceLog we return the current instance. We also assert that // we only do this once. @@ -3616,18 +3615,12 @@ private unsafe void InitializeFromFile(string etlxFilePath) return new DynamicTraceEventData(null, 0, 0, null, Guid.Empty, 0, null, Guid.Empty, null); }); - // when the serializer needs any TraceEventParser class, we assume that its constructor - // takes an argument of type TraceEventSource and that you can pass null to make an - // 'empty' parser to fill in with FromStream. - deserializer.RegisterDefaultFactory(delegate (Type typeToMake) - { - if (typeToMake.GetTypeInfo().IsSubclassOf(typeof(TraceEventParser))) - { - return (IFastSerializable)Activator.CreateInstance(typeToMake, new object[] { null }); - } - - return null; - }); + deserializer.RegisterType(typeof(KernelTraceEventParserState)); + deserializer.RegisterType(typeof(KernelToUserDriveMapping)); + deserializer.RegisterType(typeof(DynamicTraceEventParserState)); + deserializer.RegisterType(typeof(ExternalTraceEventParserState)); + deserializer.RegisterType(typeof(ClrTraceEventParserState)); + deserializer.RegisterType(typeof(TraceCodeAddresses.ILToNativeMap)); IFastSerializable entry = deserializer.GetEntryObject();