You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
OneYaml.Compat.YamlDotNet tells an alias budget from a depth limit.AliasExpansionLimitExceededException derives from MaximumRecursionLevelReachedException and is thrown where reading or writing expanded aliases passes the alias budget. Depth and size limits still throw the base type, and a catch (MaximumRecursionLevelReachedException) still catches both. MaximumRecursionLevelReachedException is no longer sealed so that this is possible.
Changed
A run of replacements on a large document no longer rewrites the offsets of everything behind each edit.ApplyPatch and ApplyMergePatch edit one state in place, and each edit that kept the number of nodes moved the offsets of every later node: about 0.1 ms per edit at 100,000 keys, whatever the edit. On documents of 4,096 nodes or more, from the second edit of a patch on, that move is now noted per chunk of 64 nodes and carried out when a chunk is next read, so a run of edits costs what the edits touch. Measured, not a bound: parse, apply and write of 10,000 replacements on a mapping of 100,000 keys takes 0.15 s (was 1.05 s) and of 100,000 replacements 1.6 s (was 10.6 s); a 100-operation patch on an 8 MB document of records takes about 95 ms (was about 145 ms), mostly parsing. Output bytes, errors, positions and the stack a deep document needs are unchanged, and so is the memory a parsed document keeps. What still grows with the document: an edit that adds or removes nodes (add, remove, a collection value) moves the node array behind it, and an edit that changes the length of the text moves the text behind it; the comment and property tables are walked in full.
A loop of single edits no longer copies the whole document for each edit. (Breaking in one rare case: a document whose restore after a failed edit ran out of memory now refuses further use with InvalidOperationException; parse the text again to continue.) Set, Remove, the YamlNode indexers and the other single edits used to build a full copy of the document's state and swap it in, so each edit cost the size of the document. They now edit the document's own state in place. If an edit fails, the document is left as it was: most failures are found before anything changes, and an edit that fails after it has begun changing the text parses the old text again, so only that error path pays for it. The one exception is running out of memory while the old text is parsed again: the error you get is the edit's own, and the document then refuses further use with an InvalidOperationException (Dispose still works). Any other failure during that step would be a defect in the library and is not hidden. Output bytes, errors, positions and live views are unchanged. Measured, not a bound (parse and write included): 1,000 edits on a mapping of 100,000 keys take 0.09 s through the indexer (was 0.23 s) and 0.18 s through Set (was 0.24 s); 10,000 take 0.8 s (was 2.3 s); 1,000 edits on the 8 MB records file take 0.9 s (was 4.7 s) and 10,000 take 8.2 s (was 46 s); setting one field on every item of a 1 MB records file takes 0.35 s (was 0.7 s). One Set on a parsed document is 1.0 to 2.6 times as fast on the benchmark files and allocates about 120 bytes less; parsing, reading and writing back are as fast as before and allocate the same. What still grows with the document: an edit moves the offsets of the nodes behind it, an edit that adds or removes nodes moves the node array, an edit that changes the length of the text moves the text behind it, and the comment and property tables are walked in full. ApplyPatch and ApplyMergePatch hold some of that move back across a run of edits and still do, so a patch of many operations stays faster than the same edits made one call at a time.
A key index lost to a race is built again. When two readers raced to index two large mappings, one could leave its table in the slot of the other, and the other mapping stayed unindexed until an edit. The slot now counts as free.
Fixed
A text reader is no longer read to its end before the input limit applies.YamlSerializer.Deserialize and DeserializeAll over a TextReader read the whole reader and only then compared its size with MaxInputBytes, so a very large or endless reader filled memory first, even under YamlLimits.Untrusted. They now read in chunks, count the UTF-8 size as they go and stop with the usual Limit error as soon as the text is over the limit, after reading at most one chunk (4,096 characters) beyond it. A text exactly at the limit still reads, and a leading byte order mark is not counted, as for a string. When no input size limit is set (MaxInputBytes left at its default, as in YamlLimits.Default and YamlLimits.None), the reader is read in one call as before.
Compat readers over a TextReader apply the input limit as the core does.YamlStream.Load(TextReader, limits) and the other YamlDotNet readers stopped reading at MaxInputBytes characters and parsed what they had, so a text with a byte order mark that was one character over the limit lost its tail without an error; it is now a Limit error. The SharpYaml Deserialize overloads over a TextReader refused a text of exactly the limit when it began with a byte order mark, which a string of the same text is allowed; they now accept it. Both use the core's chunked reader.
OneYaml.Compat.YamlDotNet writes Int128, UInt128 and Half as numbers and reads them back. They were written as {} (as YamlDotNet does), which lost the value. A Half too large for the type is an error rather than infinity.
Breaking: OneYaml.Compat.YamlDotNet reports a repeated key read from a parser as a YamlException.YamlStream.Load(IParser) threw ArgumentException; it now throws the library's YamlException, positioned at the repeated key, as YamlDotNet itself does. Code that caught ArgumentException around Load(IParser) should catch YamlException instead.
A key in extension data now beats the same key merged into the same merge source. With MergeKeys on, a mapping read through a merge key (<<: *base) whose own << brought in x and which also gave x itself kept the merged value when the type has an extension data member; the mapping's own x now wins, wherever its << stands, as it already did for members and dictionaries. The first source still wins among several, and the keys of the mapping that is read itself still beat every source.
OneYaml.Compat.YamlDotNet no longer reads the error text to tell an alias budget from a depth limit. The code that refuses the expansion marks the core error, so rewording a message cannot change which exception is thrown.