Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Conversation

@ahsonkhan
Copy link

@ahsonkhan ahsonkhan commented Apr 17, 2019

The first commit (i.e. a19d268 - Add an in-box array-backed IBufferWriter), fixes https://github.com/dotnet/corefx/issues/34894.
Related PR: #35094

That commit can be reviewed independently of the Utf8JsonWriter changes but since there was a strong dependency between these components, I submitted them together.

The remaining commits update the Utf8JsonWriter design based on recent usability and reliability feedback: https://gist.github.com/ahsonkhan/f6f30656717548212693e5eaa49cece5

API Review Feedback TL;DR:

  1. We should aim to provide built-in stream support and simplify its usage even more than proposed.
  2. The Utf8JsonWriter should implement IDisposable and IAsyncDisposable (on latest platform).
  3. BytesWritten should be renamed to BytesPending
  4. Auto-flush on dispose and remove isFinalBlock bool on flush (the user would need to validate correctness themselves).
  5. Add Reset() and Reset(...) overloads that allow re-using the Utf8JsonWriter instance.
  6. Remove Clear() and clear the memory field on flush to avoid forcing the caller to reset after flushing.
  7. Consider adding leaveOpen/dispose bools on ctor to close/dispose the stream on Utf8JsonWriter.Dispose (?).
  8. Keep the JsonWriterOptions a struct. It cannot be modified once passed in to the constructor.
  9. Internally create an ArrayBufferWriter to have the same code path for both Stream and IBufferWriter modes.

Left TODO:

This change also fixes https://github.com/dotnet/corefx/issues/35182

Note: Fixed sized IBW without slack capacity are generally not supported since the writer tends to eagerly ask for space and an exact, fixed-sized IBW would fail the GetSpan(minimumSize) contract.

cc @davidfowl, @KrzysztofCwalina, @stephentoub, @steveharter, @bartonjs, @layomia, @JeremyKuhne, @GrabYourPitchforks, @BrennanConroy, @rynowak, @pranavkm, @pakrym, @terrajobst, @glennc, @joshfree, @JamesNK

@ahsonkhan ahsonkhan changed the title Redesign utf8 json writer Re-design Utf8JsonWriter as a regular class rather than a ref struct. Apr 17, 2019
@ahsonkhan ahsonkhan added this to the 3.0 milestone Apr 17, 2019
#else
public
#endif
sealed class ArrayBufferWriter<T> : IBufferWriter<T>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add where T:Struct to avoid mixing objects and memory.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to match the IBufferWriter<T> constraint here (i.e. none). If there is strong opposition, we should discuss both outside of this PR in an API review.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not artificially constrain it, unless there's good reason to.

Copy link
Contributor

@steveharter steveharter Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling GetMemory() would be a bit odd with an array of objects. I'm assuming we don't do anything unsafe with these object pointers like mem copying (in the middle of a gc).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling GetMemory() would be a bit odd with an array of objects.

Why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scenario is a bit edge case IMO - when would you pass a writeable buffer of objects? There's an indirection in memory (element->object) and "ownership" now becomes a bit more ambiguous - i.e. what if the objects implement IDispose -- who's going to call that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's no different than storing objects into an array; when do you pass arrays of things, like strings? You also can have the exact same ownership question with structs, which might themselves have resources that have lifetime, or wrap any number of reference types. Limiting it is just an artificial restriction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that limiting isn't a good thing if we don't need to.

Since Memory has lifetime semantics (don't hold onto it after your method returns etc) it is not the same as an array.

Consider a buffer type that is only for data (or primitives like char or byte - not objects) and in that case there could be some memory clear and copy-like efficiencies regarding clearing after use and shuffling unprocessed elements to the beginning when making room for more data. Originally I was thinking we may be doing some of these operations here which would make it unsafe for objects...

Copy link
Author

@ahsonkhan ahsonkhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this is a large, time sensitive change, and to stir up some more targeted review, I have highlighted/commented on a few things that I would like feedback on (or at least a second set of eyes).

/// Represents a heap-based, array-backed output sink into which <typeparam name="T"/> data can be written.
/// </summary>
#if USE_ABW_INTERNALLY
internal
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this pattern acceptable to use? If not, I am open to suggestions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do this in other places as well, but I think it would be better to invert the condition, such that the type is only public if you explicitly set a compilation constant... otherwise, if a project includes this file, by default it's going to be exposing a new type publicly.

if (initialCapacity <= 0)
throw new ArgumentException(nameof(initialCapacity));

_buffer = new T[initialCapacity];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to bound initialCapacity to MinimumBufferSize from the bottom or is this fine?


if (sizeHint == 0)
{
sizeHint = MinimumBufferSize;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any feedback/concerns on this growth strategy? Any call to GetSpan/GetMemory will return at least 256 bytes, if sizeHint is not specified.

This has consequences when used with an ArrayBufferWriter ctor that has initialCapacity.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was contemplating this when looking earlier. I'd say that sizeHint of 0 should imply sizeHint of 1 (simply "not empty" but avoids growing if it can)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had considered this, but in practice, most workloads would suffer from this since they can't make forward progress with 1 byte. It effectively makes calling GetSpan without an argument useless. I am not sure returning 1 is better. If there are no strong opinions here, I would let scenario-driven user feedback guide the change, and leave it as is for now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in practice, most workloads would suffer from this since they can't make forward progress with 1 byte. It effectively makes calling GetSpan without an argument useless.

I dunno, once the regrow happens it'll double in size, so you'll end up with one call between 1 and 256, then the "oh, I need more than that..." would get a bigger buffer.

I would let scenario-driven user feedback guide the change, and leave it as is for now.

Also seems valid, but if the caller really doesn't have any notion of a hint then it seems like any size is acceptable. Otherwise passing in min/max/accurate seems like the best bet.

}

if (flushThreshold >= 0 && writer.BytesWritten > flushThreshold)
if (flushThreshold >= 0 && writer.BytesPending > flushThreshold)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that writer.BytesWritten previously showcased the total written (i.e. BytesCommitted + whats still pending to be flushed). BytesPending now only returns the pending data. Not sure if the semantics were broken before, or now (given that we previously were creating a new Utf8JsonWriter every time, but now are not, which likely resolves the issue).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BytesPending seems right. Basically we want to call the writer before we fill the buffer too much and force it to grow if the next write pushes it over the edge (can still happen, but shouldn't be common).

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void FlushHelper()
{
_output.Advance(BytesPending);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the stream mode, should I use the concrete type _arrayBufferWriter.Advance() rather than going through an interface call like this?

Copy link
Member

@stephentoub stephentoub Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have this helper at all? It's a single line in a method (with a non-descriptive name) marked AggressiveInlining.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Artifact of merging two concepts/terminology. With stream/IBW mode, we have "advance IBW" and we have "write/flush to stream", which effectively have the same semantics as far as the JsonWriter is concerned so I normalized it. This just helped me make sense of what operations I am doing while reading the source (normalizes to call Flush... for both cases).

This goes back to my original question of should I call _output.Advance in the stream case or call _arrayBufferWriter.Advance?

Do you think I should remove it regardless?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest you remove it and then at the call sites instead do whatever is the more efficient call.

GrowAndEnsure(minimumSize);
alreadyWritten = 0;
}
int sizeHint = Math.Max(DefaultGrowthSize, requiredSize);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a reasonable growth-scheme?

[Theory]
[InlineData(false)]
[InlineData(true)]
public static void WriteNumberAsPropertyWithLargeName(bool indented)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improves some test coverage.

Assert.Equal(expectedString, actualString);
}

private static async Task WriteLargeToStreamHelper(Stream stream, JsonWriterOptions options)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test captures the expected usage pattern for most users.

jsonUtf8.Flush();

// Explicitly skipping flushing here
var invalidUtf8 = new byte[2] { 0xc3, 0x28 };
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test captures the current semantics for writing comments that contain invalid data. Let me know if its fine as is.

@ahsonkhan
Copy link
Author

ahsonkhan commented Apr 17, 2019

@mairaw, @rpetrusha - please feel free to review/provide feedback on some of the xml comments (particularly for newer APIs like Reset and the change from ref struct to class). This has implications on the recent docs change which will need to be updated: dotnet/dotnet-api-docs#1880

Your feedback/insights here would be much appreciated :)

@steveharter
Copy link
Contributor

@ahson it's not clear to me if we can skip the escaping logic. Previously the serializer would ensure the property name is already escaped and skip the expensive check.

I believe we discussed creating a new "resolved string" type that could be leveraged to avoid the perf hit.

@ahsonkhan
Copy link
Author

ahsonkhan commented Apr 17, 2019

it's not clear to me if we can skip the escaping logic. Previously the serializer would ensure the property name is already escaped and skip the expensive check.

I believe we discussed creating a new "resolved string" type that could be leveraged to avoid the perf hit.

That's TBD atm. I will submit a proposal for this outside this PR and provide that functionality. Is there anything in the serializer code (in master) that used bool escape: false? I didn't see any.


namespace System.Buffers.Tests
{
public static partial class ArrayBufferWriterTests_String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize we want to support this on non-primitive types; string is normally immutable as well.

Copy link
Author

@ahsonkhan ahsonkhan Apr 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize we want to support this on non-primitive types

This is a question on the interface, tbh:

public interface IBufferWriter<T>

The test using string is just to verify that T : reference type works fine. As you mentioned, strings are immutable, so I don't expect anyone to create an ABW<string>.

I can remove this test all together.

It doesn't have any constraints on T. Maybe it should be T: struct or T: unmanaged.

cc @davidfowl, @stephentoub

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also cc @jkotas, if you have any thoughts on the issue.

Copy link
Member

@jkotas jkotas Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IBufferWriter shipped without any constraints. Do we consider it to be a mistake?

Since IBufferWriter does not have any constraints, ArrayBufferWriter should not have any constraints either for consistency.

@steveharter
Copy link
Contributor

it's not clear to me if we can skip the escaping logic. Previously the serializer would ensure the property name is already escaped and skip the expensive check.
I believe we discussed creating a new "resolved string" type that could be leveraged to avoid the perf hit.

That's TBD atm. I will submit a proposal for this outside this PR and provide that functionality. Is there anything in the serializer code (in master) that used bool escape: false? I didn't see any.

Not currently. However all of the write method in the serializer need to be changed to support pre-escaping the property name (pass false) for performance. I was planning to address this when we add the data type extensibility (needs tests).

<Target Name="GetSourcesToPackage" BeforeTargets="ExpandProjectReferences">
<ItemGroup>
<_ProjectsToBuild Include="../src/System.Text.Json.csproj" UndefineProperties="Configuration" />
<_ProjectsToBuild Include="../src/System.Text.Json.csproj" UndefineProperties="Configuration" AdditionalProperties="TargetGroup=netstandard"/>
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joperezr, please review the packaging change :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a comment regarding the define constant that you declare for netstandard, since you probably don't want to require a constant to be defined in order for the source package to work. Other than that, this particular change on the pkgproj looks good to me.

@ahsonkhan
Copy link
Author

ahsonkhan commented Apr 18, 2019

If there is no other critical feedback, I would like to merge this in to give upstream repositories time to react accordingly (at least to the API breaking changes). Other changes, like code refactoring, etc., can be made in a subsequent PR.

@ahsonkhan ahsonkhan merged commit 1f9b84a into dotnet:master Apr 18, 2019
@ahsonkhan ahsonkhan deleted the RedesignUtf8JsonWriter branch April 18, 2019 06:58
private T[] _buffer;
private int _index;

private const int MinimumBufferSize = 256;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't actually MinimumBufferSize, at least not how it's currently used. A better name for the current usage would be DefaultInitialBufferSize, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was thinking the same thing. The only concern is that I use this to define the minimum size GetMemory/GetSpan can return as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only concern is that I use this to define the minimum size GetMemory/GetSpan can return as well.

It isn't the minimum, though. It's just the value that you pretend sizeHint was if it's 0. (GetSpan(1) is allowed to return a span of Length 1)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I meant minimum in the "default" case, but you brought up a useful clarification.

/// </summary>
public ArrayBufferWriter()
{
_buffer = new T[MinimumBufferSize];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't we start with Array.Empty<T>()? Then if it's not actually written to, we don't allocate, and if it is written to, the first allocation will be based on the supplied sizeHint. If a typical usage is creating one of these and then passing it off to something that uses a sizeHint larger than 256, this is going to unnecessarily allocate a throw-away 256-byte buffer.

Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint);
}

private static void ThrowInvalidOperationException(int capacity)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Seems like this should be named ThrowAdvancedTooFarException, or something like that.

/// <summary>
/// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow.
/// </summary>
public int FreeCapacity => _buffer.Length - _index;
Copy link
Member

@stephentoub stephentoub Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this? Someone can just do Capacity - WrittenCount.

Also, WrittenCount is an uncommon name. How about Position?

Copy link
Author

@ahsonkhan ahsonkhan Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The serializer uses this but you are right it is primarily a convenience thing. Do you think its exposing too much or do you just think it is unnecessary and not worth the benefit it provides?

Regarding the naming/API changes like this, how do you feel about discussing it all up in a follow up review (post preview 5)? I prefer not to make these changes in PR (in isolation). WrittenCount fits nicer with WrittenMemory/Span (especially when passed in as "offset/count" to stream.write), imo, but I am not super attached to the name.


namespace System.Buffers.Tests
{
public static partial class ArrayBufferWriterTests_Char
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we duplicating all of these tests for different data types? Wouldn't it be better to just have a generic base test type parameterized on T, and then have multiple derived types that provide T==char, T==string, etc.? This is a ton of what appears to be largely duplicated code.

Copy link
Author

@ahsonkhan ahsonkhan Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, definitely. The only difference is that I have stream-based tests for T=byte, but that can be extracted out.

I, generally, don't mind code duplication in tests (for clarity/debugging failed tests), but in this case I agree it goes too far.

<value>Serializer options cannot be changed once serialization or deserialization has occurred.</value>
</data>
<data name="StreamNotWritable" xml:space="preserve">
<value>Stream was not writable.</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: was => is ?

Copy link
Author

@ahsonkhan ahsonkhan Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#if BUILDING_INBOX_LIBRARY
Span<char> temp = MemoryMarshal.CreateSpan<char>(ref value, 1);
Span<char> temp = MemoryMarshal.CreateSpan(ref value, 1);
writer.WriteStringValue(temp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you don't actually need a temp here; it can be a single line to parallel the other else case, e.g.

writer.WriteStringValue(
#if BUILDING_INBOX_LIBRARY
    MemoryMarshal.CreateSpan(ref value, 1)
#else
    value.ToString()
#endif
    );

or something like that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to keep changes outside of the writer minimal, but that would work.

However, your suggestion does make it a bit harder to read (but VS coloring would help).

cc @steveharter

{
if (_rentedBuffer == null)
ThrowHelper.ThrowObjectDisposedException(nameof(ArrayBufferWriter<T>));
ThrowHelper.ThrowObjectDisposedException(nameof(PooledBufferWriter<T>));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When can this exception actually occur? And if it can occur and can leak out to callers, since "This is an implementation detail", do we really want the internal type's name in the exception message?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was copy/pasted from the existing API that was going to be public. Given this is totally internal now, we don't need such checks/exceptions (and can be Debug.Asserts). We should be using it correctly, internally.

{
throw ThrowHelper.GetInvalidOperationException_CallFlushFirst(_buffered);
_arrayBufferWriter.Clear();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: _arrayBufferWriter?.Clear();

_tokenType = state._tokenType;
_writerOptions = state._writerOptions;
_bitStack = state._bitStack;
_arrayBufferWriter = default;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we clear it if we're just going to drop it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if we must clear it for some reason (we don't do this in most other situations where we just let the GC clean up), this line should be moved into the if block. It should also use null instead of default.

Debug.Assert(_arrayBufferWriter.WrittenSpan.Length == _arrayBufferWriter.WrittenCount);
_stream.Write(_arrayBufferWriter.WrittenSpan.ToArray(), 0, _arrayBufferWriter.WrittenCount);
#endif
_arrayBufferWriter.Clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We end up clearing the writer, which means zero'ing all bytes written, every time we flush. That seems unnecessary. Shouldn't we be able to just reset the position to 0 and not clear at all? When we're done using the buffer writer we'll just drop it and let the GC clean up.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

Copy link
Author

@ahsonkhan ahsonkhan Apr 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have a "reset" method on the ArrayBufferWriter that just resets the position 0. Should we make the WrittenCount property settable, or just change Clear() to Reset() which doesn't clear?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have a "reset" method on the ArrayBufferWriter that just resets the position 0. Should we make the WrittenCount property settable, or just change Clear() to Reset() which doesn't clear?

That would be my preference, with it also renamed to Position 😉

<!-- BUILDING_INBOX_LIBRARY is only false when building for netstandard to validate that the sources are netstandard compatible. -->
<!-- This is meant to help with producing a source package and not to ship a netstandard compatible binary. -->
<DefineConstants Condition="'$(TargetsNETStandard)' != 'true'">$(DefineConstants);BUILDING_INBOX_LIBRARY</DefineConstants>
<DefineConstants Condition="'$(TargetsNETStandard)' == 'true'">$(DefineConstants);USE_ABW_INTERNALLY</DefineConstants>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is required for NetStandard, keep in mind that the source package won't have this constant defined, so consumers of the source package will get the netstandard sources, but won't have this constant defined.

Copy link
Author

@ahsonkhan ahsonkhan Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. It doesn't have any concerns here since all we are doing is going from public to internal (which the source package does anyway). But, I will invert the condition. @stephentoub provided the same feedback here: #36961 (comment)

picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…dotnet/corefx#36961)

* Add an in-box array-backed IBufferWriter<T>

* Update Utf8JsonWriter ref and main writer file.

* Fix JsonWriter WriteValue APIs.

* Use Environment.NewLine static and invert some stream conditions.

* Update JsonWriter properties and fix serializer build breaks.

* Update JsonWriter unit tests.

* Add xml comments, clean up, and improve test coverage.

* Update JsonDocument and JsonSerializer to react to JsonWriter changes.

* Normalize the reference assembly.

* Do not escape/validate comments and update issue number.

* Do not escape comments and validate they don't contain embedded
delimiter

* Remove dead code and update issue number in comments.

* Throw InvalidOEx instead of ArgEx if IBW doesn't give requested memory.

* Fix test build breaks for netfx.

* Remove dead code and fix source packaging.

* Address PR feedback.

* Disable writing floats test on windows

* 8 digit floats don't work well on older TFMs. Reduce to 7.


Commit migrated from dotnet/corefx@1f9b84a
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

7 participants