Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Cross framework serialization finish up #20870

Merged
merged 32 commits into from
Jun 14, 2017

Conversation

ViktorHofer
Copy link
Member

@ViktorHofer ViktorHofer commented Jun 9, 2017

This PR is intended to be the last piece to make serialization work across Desktop and Core.

TODO

Status

Read from left to right. E.g.: Core deserializes to Core

Core Desktop
Core x x
Desktop x x

@ViktorHofer ViktorHofer added * NO MERGE * The PR is not ready for merge yet (see discussion for detailed reasons) area-Serialization labels Jun 9, 2017
@ViktorHofer ViktorHofer self-assigned this Jun 9, 2017
@ViktorHofer ViktorHofer force-pushed the SerilalizationFinishUp branch from 735a398 to bb170d7 Compare June 9, 2017 15:04
@ViktorHofer ViktorHofer changed the title [WIP] Cross framework serialization finishing up [WIP] Cross framework serialization finish up Jun 9, 2017
@justinvp
Copy link
Contributor

justinvp commented Jun 9, 2017

CookieContainer data storage (Hashtable vs Dictionary)

Note that the nested CookieContainer.PathList type is stored in the CookieContainer's Hashtable/Dictionary... In the full framework, PathList uses the non-generic SortedList, but in CoreFX it uses the generic SortedList<T>.

@ViktorHofer
Copy link
Member Author

Thanks Justin. I already know that. We will discuss how to handle CookieContainer and all involved types later today and will keep you updated here.

@ViktorHofer ViktorHofer force-pushed the SerilalizationFinishUp branch 3 times, most recently from a532ba1 to a0fc21a Compare June 9, 2017 17:53
Copy link
Member

@danmoseley danmoseley left a comment

Choose a reason for hiding this comment

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

LGTM

@ViktorHofer
Copy link
Member Author

ViktorHofer commented Jun 9, 2017

@lmolkova Do you need Activity to be interchangeable between Core and Desktop or is it enough to be serializable from Core to Core?

@lmolkova
Copy link

lmolkova commented Jun 9, 2017

I don't know how much context I miss.

I assume your question is related to cross AppDomain/process calls and presence of the Serializable attribute int the Activity. Note that Activity is serializable on NET 4.5 only.

The whole Activity is not intended to be passed outside of the process, only its certain properties should be propagated along with the request. E.g. if an application makes HTTP call, some properties of the current Activity are passed in HTTP headers. So some protocol should decide how Activity properties are serialized into a particular transport.

So Activity should not be interchangeable at all.

May be @vancem could also comment on that if I missed anything.

@ViktorHofer
Copy link
Member Author

ViktorHofer commented Jun 9, 2017

EDIT: the list was wrong :(

@jkotas
Copy link
Member

jkotas commented Jun 9, 2017

custom equality check

What is it for?

@ViktorHofer
Copy link
Member Author

ViktorHofer commented Jun 9, 2017

@lmolkova Thanks for the clarification. That means we don't need to put a TypeForward attribute on it and test roundtripping it from netfx to core and back.

@ViktorHofer
Copy link
Member Author

ViktorHofer commented Jun 9, 2017

@jkotas to make sure that types are correctly serializing and deserializing from/to different platforms. Example given DataSet and DataTable don't override object Equals nor implement IEquatable. Therefore we need to manually check the cloned and original objects serializable fields for equality.

Some of these types are covered by xunit Assert.Equal (I think it also tests items in collections which implement IEnumerable/IList). But some of them are definitely not covered like specialized collections.

@krwq
Copy link
Member

krwq commented Jun 9, 2017

@ViktorHofer should we implement those in the product code?

@jkotas
Copy link
Member

jkotas commented Jun 9, 2017

Example given DataSet and DataTable don't override object Equals nor implement IEquatable

Why is it a problem? When you serialize/deserialize object on full .NET Framework, Equals for master and clone returns true for some types and returns false for other types. That's by design. I do not think we need to be fixing it.

@krwq
Copy link
Member

krwq commented Jun 9, 2017

@jkotas - I believe this was meant for tests only so that we are able to asses if the type is cross-round-tripping (netfx->corefx) correctly

@jkotas
Copy link
Member

jkotas commented Jun 9, 2017

I think I understand it now - it is just about making the test coverage better, not about fixing the product.

@ViktorHofer
Copy link
Member Author

@ViktorHofer should we implement those in the product code?

If you want to go through API reviews for each type, then go for it. As this probably would take a lot of time and arguing and we want to test this till next week, I would not suggest it ;)

@ViktorHofer
Copy link
Member Author

I think I understand it now - it is just about making the test coverage better, not about fixing the product.

Correct.

@krwq krwq force-pushed the SerilalizationFinishUp branch from 70556d9 to 5cd3774 Compare June 10, 2017 00:18
@ViktorHofer ViktorHofer force-pushed the SerilalizationFinishUp branch 2 times, most recently from bbc4e28 to 5b428ad Compare June 10, 2017 23:03
@@ -810,22 +815,24 @@ private void BuildCookieCollectionFromDomainMatches(Uri uri, bool isSecure, int
PathList pathList;
lock (m_domainTable)
Copy link
Member

Choose a reason for hiding this comment

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

Oops, this should be lock (m_domainTable.SyncRoot)

Copy link
Member

Choose a reason for hiding this comment

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

DEfinitely should fix that.

Copy link
Member

Choose a reason for hiding this comment

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

Same issue on line 866 below the comment // (This is the only place that does domain removal)

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@@ -810,22 +815,24 @@ private void BuildCookieCollectionFromDomainMatches(Uri uri, bool isSecure, int
PathList pathList;
lock (m_domainTable)
{
if (!m_domainTable.TryGetValue(domainAttribute[i], out pathList))
object pathListValue = m_domainTable[domainAttribute[i]];
Copy link
Member

Choose a reason for hiding this comment

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

There is no need for pathListValue you can just assign directly to pathList

Copy link
Member

Choose a reason for hiding this comment

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

Looks like desktop does that.

Copy link
Member Author

Choose a reason for hiding this comment

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

not changing

@@ -950,7 +958,7 @@ internal string GetCookieHeader(Uri uri, out string optCookie2)

string delimiter = string.Empty;

var builder = StringBuilderCache.Acquire();
StringBuilder builder = StringBuilderCache.Acquire();
Copy link
Member

Choose a reason for hiding this comment

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

FYI for ask-mode changes, we try to avoid unnecessary unrelated changes like this. Reasons are (1) distracting from actual diff (2) in general, adds risk. Not worth changing this now, especially because of the volume of other changes...

Copy link
Member Author

Choose a reason for hiding this comment

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

ok

count += cc.Count;
}
}
return count;
}

public CookieCollection this[string s]
public ICollection Values
Copy link
Member

Choose a reason for hiding this comment

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

do we need to add this?

Copy link
Member

Choose a reason for hiding this comment

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

Although I see desktop had it (and with no explicit lock) so I guess it is best to add it as you did.

Copy link
Member Author

Choose a reason for hiding this comment

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

ok


public static PathList Create() => new PathList(new SortedList<string, CookieCollection>(PathListComparer.StaticInstance));
public static PathList Create() => new PathList(SortedList.Synchronized(new SortedList(PathListComparer.StaticInstance)));
Copy link
Member

@danmoseley danmoseley Jun 14, 2017

Choose a reason for hiding this comment

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

I'm not sure why this was .Synchronized before since evidently when we changed to the generic form we concluded that the locks below were enough. Especially since we exposed no way to write to it.

But, it is safest to match desktop here as you have done.

Choose a reason for hiding this comment

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

I'm not sure whether we decided to make synchronized SortedLists serializable. I'd suggest checking.

Copy link
Member Author

Choose a reason for hiding this comment

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

@@ -48,15 +49,16 @@ public sealed class Cookie
private CookieVariant m_cookieVariant = CookieVariant.Plain; // Do not rename (binary serialization)
private bool m_discard = false; // Do not rename (binary serialization)
private string m_domain = string.Empty; // Do not rename (binary serialization)
private bool m_domainImplicit = true; // Do not rename (binary serialization)
private bool m_domain_implicit = true; // Do not rename (binary serialization)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are these variables being renamed to use underscores?

Copy link
Member

Choose a reason for hiding this comment

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

Must match desktop to serialize between.


for (int i = 0; i < ports.Length; ++i)
{
// Skip spaces
if (ports[i] != string.Empty)
{
if (!Int32.TryParse(ports[i], out port))
if (!Int32.TryParse(ports[i], out int port))
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the purpose of this change?

Copy link
Member

Choose a reason for hiding this comment

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

This looks like cleanup, which we can revert.

Copy link
Member Author

Choose a reason for hiding this comment

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

I reverted it

@@ -11,7 +11,7 @@ namespace System.Net
//
// A list of cookies maintained in Sorted order. Only one cookie with matching Name/Domain/Path
[Serializable]
[System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, PublicKeyToken=b77a5c561934e089")]
[System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we adding "Culture=neutral" attributes? Is this different from .NET Framework?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because we need it for BinaryFormatter blob optimization. If you want to know it in more detail just ping me

@@ -100,8 +100,7 @@ public class CookieContainer
new HeaderVariantInfo(HttpKnownHeaderNames.SetCookie2, CookieVariant.Rfc2965)
};

// NOTE: all accesses of _domainTable must be performed with _domainTable locked.
private readonly Dictionary<string, PathList> m_domainTable = new Dictionary<string, PathList>(); // Do not rename (binary serialization)
private readonly Hashtable m_domainTable = new Hashtable(); // Do not rename (binary serialization)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why change to Hashtable?

Copy link
Member

Choose a reason for hiding this comment

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

Because Desktop uses Hashtable and we are using vanilla binary serialization so the types and names must match.

@danmoseley
Copy link
Member

pathList field is defined as PathList which is a struct which doesn't support null checks

Ah yes I didn't see that. Fine.

{
foreach (KeyValuePair<string, PathList> entry in m_domainTable)
foreach (DictionaryEntry entry in m_domainTable)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is going to allocate a boxed object for each entry in the dictionary (see #7539, #9050, #10247). It'd be better to do something like:

// Manual use of IDictionaryEnumerator instead of foreach to avoid DictionaryEntry box allocations.
IDictionaryEnumerator e = m_domainTable.GetEnumerator();
while (e.MoveNext())
{
    if (domain == null)
    {
        tempDomain = (string)e.Key;
        pathList = (PathList)e.Value;
    }
    ...
}

Copy link
Member Author

Choose a reason for hiding this comment

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

We can change that later. Or even better, if you want to help you could spin up a PR which optimizes my recent changes without killing the just introduced serialization support.

}

lock (pathList.SyncRoot)
{
foreach (KeyValuePair<string, CookieCollection> pair in pathList)
foreach (DictionaryEntry entry in pathList)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is going to allocate a boxed object for each entry in the dictionary. I wonder if PathList.GetEnumerator should be changed to return IDictionaryEnumerator instead of IEnumerator, so the allocations can be avoided while iterating through pathList.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can change that later. Or even better, if you want to help you could spin up a PR which optimizes my recent changes without killing the just introduced serialization support.

@@ -1061,14 +1072,15 @@ public object SyncRoot
}

[Serializable]
private sealed class PathListComparer : IComparer<string>
[System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
sealed class PathListComparer : IComparer
Copy link
Contributor

Choose a reason for hiding this comment

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

private

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought this was needed for cross framework serialization. I might be wrong

@@ -983,15 +991,16 @@ public void SetCookies(Uri uri, string cookieHeader)
}

[Serializable]
[System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
internal struct PathList
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that this is going back to being stored in Hashtable (and therefore is going to be boxed anyway), should it be changed back to a class?

Copy link
Member Author

Choose a reason for hiding this comment

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

We can change that later. Or even better, if you want to help you could spin up a PR which optimizes my recent changes without killing the just introduced serialization support.

@@ -737,8 +742,8 @@ internal CookieCollection InternalGetCookies(Uri uri)
int port = uri.Port;
CookieCollection cookies = null;

List<string> domainAttributeMatchAnyCookieVariant = new List<string>();
List<string> domainAttributeMatchOnlyCookieVariantPlain = null;
var domainAttributeMatchAnyCookieVariant = new Collections.Generic.List<string>();

Choose a reason for hiding this comment

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

Using a partial namespace here is a bit strange looking. Why not just leave it as List? If we need namespace qualification, please use the full namespace.

Copy link
Member Author

Choose a reason for hiding this comment

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

The compiler couldn't distinguish between SortedList generic and non-generic. I will add the fully namespace here.

Copy link
Member Author

Choose a reason for hiding this comment

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

done


public static PathList Create() => new PathList(new SortedList<string, CookieCollection>(PathListComparer.StaticInstance));
public static PathList Create() => new PathList(SortedList.Synchronized(new SortedList(PathListComparer.StaticInstance)));

Choose a reason for hiding this comment

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

I'm not sure whether we decided to make synchronized SortedLists serializable. I'd suggest checking.

}
catch (Exception)
{
Console.WriteLine("Error during equality check of type " + obj?.GetType()?.FullName);

Choose a reason for hiding this comment

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

Can't you just add a message to the assert?

BinaryFormatterTests.CheckEquals(@this.Comparer, other.Comparer)))
return false;

return BinaryFormatterTests.CheckSequenceEquals(@this, other);

Choose a reason for hiding this comment

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

I'm not sure ordering is guaranteed in different hash sets

Copy link
Member Author

Choose a reason for hiding this comment

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

@krwq please review this comment

Copy link
Member

Choose a reason for hiding this comment

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

@morganbr @ViktorHofer currently no failures related to that so seems like order is preserved

Choose a reason for hiding this comment

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

@krwq , I expect that ordering may change in the future, such as when #14354 is completed. It would be good if this test wasn't sensitive to that.

@this.IsReadOnly == other.IsReadOnly &&
@this.IsFixedSize == other.IsFixedSize &&
@this.IsSynchronized == other.IsSynchronized &&
BinaryFormatterTests.CheckSequenceEquals(@this.Keys, other.Keys) &&

Choose a reason for hiding this comment

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

Same set ordering issue

other != null &&
BinaryFormatterTests.CheckEquals(@this.Comparer, other.Comparer) &&
@this.Count == other.Count &&
BinaryFormatterTests.CheckSequenceEquals(@this.Keys, other.Keys) &&

Choose a reason for hiding this comment

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

Same set ordering issue

Copy link
Member Author

Choose a reason for hiding this comment

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

@krwq please review this comment

Copy link
Member

Choose a reason for hiding this comment

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

@morganbr @ViktorHofer because we don't have any failures related to that I'd leave as is - I wouldn't fix more than needed in this PR

{
if (!(@this != null &&
other != null &&
BinaryFormatterTests.CheckSequenceEquals(@this.Keys, other.Keys) &&

Choose a reason for hiding this comment

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

Same set ordering issue

Copy link
Member Author

Choose a reason for hiding this comment

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

@krwq please review this comment

if (!(@this != null &&
other != null &&
@this.Count == other.Count &&
BinaryFormatterTests.CheckSequenceEquals(@this.Keys, other.Keys) &&

Choose a reason for hiding this comment

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

Another possible set ordering issue

Copy link
Member Author

Choose a reason for hiding this comment

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

@krwq please review this comment


public static bool IsEqual(this LinkedList<Point> @this, LinkedList<Point> other)
{
if (!(@this != null &&
Copy link
Member

Choose a reason for hiding this comment

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

@krwq many of these IsEqual implementations are missing this I thikn?

            if (@this == null && other == null)
               return true;

Copy link
Member

Choose a reason for hiding this comment

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

Ah, that's handled in CheckSequenceEquals. fine.

Copy link
Member

Choose a reason for hiding this comment

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

I'm only fixing when I understand why the value is null, in many cases it should not be null even though that would be correct to check it in here

@danmoseley
Copy link
Member

All LGTM as of now...

@ViktorHofer ViktorHofer merged commit cdfb6ec into dotnet:master Jun 14, 2017
ViktorHofer added a commit to ViktorHofer/corefx that referenced this pull request Jun 15, 2017
* wip: add stubs for custom isEqual

* Updated blobs, small code refactoring, disabled CookieContainer test

* Fixed netfx471 detection

* add custom equals for graph with cycles

* fix few more comparers

* Merge

* fix few more test cases

* fix few more types, fix GetExtensionMethod to work with parameters with same type name but different generic params

* finish tests (2 comparisons are commented out: search for TODO)

* Adding TypeForward for DictionaryNode

* Updating blobs

* Update Cookie hashes, adding TypeForwards, removing unnecessary equality checks for CookieCollection

* Reenabling SortedDictionary test

* Update blobs

* make cookie.Timestamp to roundtrip correctly

* regenerate netfx and corefx blobs to account for Cookie.Timestamp change

* make regex fix the blob for ObjectWithArray

* fix ObjectWithArray netfx test failure

* Replacing Dictionary with Hashtable for desktop serialization support

* Update blobs

* Fixing a lot of TypeForwards, adding another hashtable test, other fixes

* Adding a few more TypeForwardedFrom attributes

* Updating the coreclr version, disabling CookieContainer test

* Update blobs

* Disabling CompareInfo test --> waiting for coreclr

* generate test cases

* update blobs, comment out not working cases

* fix null equality checks for new test cases and reenable them

* PR feedback addressed

* Fixed a typo...

* Updating used coreclr version, enabling compareinfo test again
ViktorHofer added a commit to ViktorHofer/corefx that referenced this pull request Jun 15, 2017
* wip: add stubs for custom isEqual

* Updated blobs, small code refactoring, disabled CookieContainer test

* Fixed netfx471 detection

* add custom equals for graph with cycles

* fix few more comparers

* Merge

* fix few more test cases

* fix few more types, fix GetExtensionMethod to work with parameters with same type name but different generic params

* finish tests (2 comparisons are commented out: search for TODO)

* Adding TypeForward for DictionaryNode

* Updating blobs

* Update Cookie hashes, adding TypeForwards, removing unnecessary equality checks for CookieCollection

* Reenabling SortedDictionary test

* Update blobs

* make cookie.Timestamp to roundtrip correctly

* regenerate netfx and corefx blobs to account for Cookie.Timestamp change

* make regex fix the blob for ObjectWithArray

* fix ObjectWithArray netfx test failure

* Replacing Dictionary with Hashtable for desktop serialization support

* Update blobs

* Fixing a lot of TypeForwards, adding another hashtable test, other fixes

* Adding a few more TypeForwardedFrom attributes

* Updating the coreclr version, disabling CookieContainer test

* Update blobs

* Disabling CompareInfo test --> waiting for coreclr

* generate test cases

* update blobs, comment out not working cases

* fix null equality checks for new test cases and reenable them

* PR feedback addressed

* Fixed a typo...

* Updating used coreclr version, enabling compareinfo test again
danmoseley pushed a commit to ViktorHofer/corefx that referenced this pull request Jun 17, 2017
* wip: add stubs for custom isEqual

* Updated blobs, small code refactoring, disabled CookieContainer test

* Fixed netfx471 detection

* add custom equals for graph with cycles

* fix few more comparers

* Merge

* fix few more test cases

* fix few more types, fix GetExtensionMethod to work with parameters with same type name but different generic params

* finish tests (2 comparisons are commented out: search for TODO)

* Adding TypeForward for DictionaryNode

* Updating blobs

* Update Cookie hashes, adding TypeForwards, removing unnecessary equality checks for CookieCollection

* Reenabling SortedDictionary test

* Update blobs

* make cookie.Timestamp to roundtrip correctly

* regenerate netfx and corefx blobs to account for Cookie.Timestamp change

* make regex fix the blob for ObjectWithArray

* fix ObjectWithArray netfx test failure

* Replacing Dictionary with Hashtable for desktop serialization support

* Update blobs

* Fixing a lot of TypeForwards, adding another hashtable test, other fixes

* Adding a few more TypeForwardedFrom attributes

* Updating the coreclr version, disabling CookieContainer test

* Update blobs

* Disabling CompareInfo test --> waiting for coreclr

* generate test cases

* update blobs, comment out not working cases

* fix null equality checks for new test cases and reenable them

* PR feedback addressed

* Fixed a typo...

* Updating used coreclr version, enabling compareinfo test again
@ViktorHofer ViktorHofer deleted the SerilalizationFinishUp branch September 13, 2018 20:24
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Serialization * NO MERGE * The PR is not ready for merge yet (see discussion for detailed reasons)
Projects
None yet
Development

Successfully merging this pull request may close these issues.