Skip to content

XmlSerializer requires next tag after EndElement to be present in Stream #47371

Description

@slavanap

Description

XmlSerializer by default requires next tag after EndElement to be present in the Stream to fully deserialize a message. This makes XmlSerializer unapplicable to deserialize directly from NetworkStream.

Configuration

.NET 3.1 or 5; x64

Other information

Demo project:
msg2 is expected to be read successfully because it was fully written.

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;

namespace ConsoleApp1 {

    [XmlType(AnonymousType = true)]
    [XmlRoot(ElementName = "msg")]
    public class MessageRoot {
        [XmlAttribute("code")]
        public string Code { get; set; }
        public override string ToString() => $"msg(code: {Code})";
    }

    class Program {
        const int TestPort = 40001;

        static TcpListener _listener;
        static void WriterThread() {
            // dummy writer: write message and block indefinitely
            using var client = _listener.AcceptTcpClient();
            var stream = new StreamMonitor(client.GetStream(), "SERVER");
            TextWriter writer = new StreamWriter(stream, Encoding.ASCII);
            writer.Write(@"<msg code=""1""></msg><msg code=""2""></msg>");
            writer.Flush();
            Thread.Sleep(TimeSpan.FromHours(1));
        }

        static void Main() {
            _listener = new TcpListener(IPAddress.Loopback, TestPort);
            _listener.Start(1);
            var writerThread = new Thread(WriterThread);
            writerThread.Start();

            // this thread will read
            var client = new TcpClient(IPAddress.Loopback.ToString(), TestPort);
            var stream = new StreamMonitor(client.GetStream(), "CLIENT");

            var reader = XmlReader.Create(stream, new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment });
            var serializer = new XmlSerializer(typeof(MessageRoot), string.Empty);

            var msg1 = serializer.Deserialize(reader);
            Console.WriteLine($"We've read message: {msg1}");
            var msg2 = serializer.Deserialize(reader);     // <<< Hangs here.
            Console.WriteLine($"We've read message: {msg2}");

            Console.WriteLine("Success!");
        }
    }

    #region Utility
    class StreamMonitor : Stream {
        readonly Stream _origin;
        readonly string _name;
        public StreamMonitor(Stream origin, string name) {
            _origin = origin;
            _name = name;
        }

        public override bool CanRead => _origin.CanRead;
        public override bool CanSeek => _origin.CanSeek;
        public override bool CanWrite => _origin.CanWrite;
        public override long Length => _origin.Length;
        public override long Position { get => _origin.Position; set => _origin.Position = value; }
        public override void Flush() => _origin.Flush();
        public override long Seek(long offset, SeekOrigin origin) => _origin.Seek(offset, origin);
        public override void SetLength(long value) => _origin.SetLength(value);

        public override int Read(byte[] buffer, int offset, int count) {
            Console.WriteLine($"[{_name} tries to read]");
            var ret = _origin.Read(buffer, offset, count);
            Console.WriteLine($"[{_name} reads {ret} bytes] {new string(Encoding.UTF8.GetChars(buffer, offset, ret))}");
            return ret;
        }

        public override void Write(byte[] buffer, int offset, int count) {
            Console.WriteLine($"[{_name} writes {count} bytes] {new string(Encoding.UTF8.GetChars(buffer, offset, count))}");
            _origin.Write(buffer, offset, count);
        }
    }
    #endregion

}

Area: src/libraries/System.Private.Xml/src/System/Xml/Serialization

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Priority:3Work that is nice to havearea-Serializationin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions