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

CoreFx #16274 Implementation - New overloads of LazyInitializer.EnsureInitialized for reference types#9831

Merged
stephentoub merged 1 commit into
dotnet:masterfrom
WinCPP:LazyInitializerNewOverloads
Mar 1, 2017
Merged

CoreFx #16274 Implementation - New overloads of LazyInitializer.EnsureInitialized for reference types#9831
stephentoub merged 1 commit into
dotnet:masterfrom
WinCPP:LazyInitializerNewOverloads

Conversation

@WinCPP
Copy link
Copy Markdown

@WinCPP WinCPP commented Feb 27, 2017

This PR is for the implementation of new overloads of LazyInitializer.EnsureInitialized for reference types. Please refer to https://github.com/dotnet/corefx/issues/16274 for more information.

@stephentoub @terrajobst @karelz Kindly review the changes.

/// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing
/// <paramref name="target"/>. If <paramref name="syncLock"/> is null, a new object will be instantiated.</param>
/// <returns>The initialized value of type <typeparamref name="T"/>.</returns>
public static T EnsureInitialized<T>(ref T target, ref object syncLock) where T : class
Copy link
Copy Markdown
Member

@stephentoub stephentoub Feb 27, 2017

Choose a reason for hiding this comment

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

I don't believe this API was discussed or approved, was it? Wasn't it just the one that takes a delegate?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It was discussed but not concluded (here)... that is why I left the comment above (here). Request you to consider the same as it makes the API symmetric...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@WinCPP any API change has to be first concluded and approved - it is best done in issues. PRs are bad place to do API review discussions. Please revert it for now and restart the discussion in the issue (or create a new one).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

removing...

Copy link
Copy Markdown
Author

@WinCPP WinCPP Feb 27, 2017

Choose a reason for hiding this comment

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

@karelz our comments crossed... yes I understand it is not a good idea. I am removing the API. Build and test is in progress and will push the same.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@WinCPP perfect, thank you!

if (Volatile.Read(ref target) == null)
{
target = valueFactory();
if (target == null)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should be: Volatile.Write(ref target, valueFactory());

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Changing this. It will force the write of target.

@WinCPP
Copy link
Copy Markdown
Author

WinCPP commented Feb 27, 2017

@stephentoub , the changes are in. Sorry.

}

return EnsureInitializedCore<T>(ref target, ref syncLock, valueFactory);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: I realize you're just copying the style used above; that's fine. We could subsequently shorten / clean this up as:

public static T EnsureInitialized<T>(ref T target, ref object syncLock, Func<T> valueFactory) where T : class =>
    Volatile.Read(ref target) ??
    EnsureInitializedCore<T>(ref target, ref syncLock, valueFactory);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done!

slock = newLock;
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this code is now duplicated in two different methods, could you separate it out into a helper? e.g.

private static object EnsureLockInitialized(ref object syncLock) =>
    syncLock ??
    Interlocked.CompareExchange(ref syncLock, new object(), null) ??
    syncLock;

which you could then use here and in the other function as:

lock (EnsureLockInitialized(ref syncLock))
{
    ...
}

?

Copy link
Copy Markdown
Author

@WinCPP WinCPP Feb 28, 2017

Choose a reason for hiding this comment

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

Yup. Makes sense. Actually out of habit, I was into the groove of 'don't touch something which already works; follow the style'...

private static object EnsureLockInitialized(ref object syncLock) =>
    syncLock ??
    Interlocked.CompareExchange(ref syncLock, new object(), null) ??
    syncLock;

Quick Qestion: The '?? syncLock' towards end of statement made me scratch my head a bit... typo, not intentional... right...? Really bad question... treated it like Interlocked.Increment which returns the incremented value...

@WinCPP WinCPP force-pushed the LazyInitializerNewOverloads branch from a8d9a1a to 283f386 Compare February 28, 2017 16:28
@WinCPP
Copy link
Copy Markdown
Author

WinCPP commented Feb 28, 2017

@stephentoub the changes are ready...


// Lazily initialize the lock if necessary and, then double check if initialization is still required.
lock (EnsureLockInitialized(ref slock))
{
Copy link
Copy Markdown
Member

@stephentoub stephentoub Feb 28, 2017

Choose a reason for hiding this comment

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

This is not correct. It needs to be:

lock (EnsureLockInitialized(ref syncLock))

By initializing the local, racing threads will all end up creating and using their own slock object. You can delete the slock local.


// Lazily initialize the lock if necessary and, then double check if initialization is still required.
lock (EnsureLockInitialized(ref slock))
{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same problem. This needs to be ref syncLock, not ref slock. You can delete the local slock.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, agreed. This is because of oversight. My test cases ran successfully because I added one for non-null syncLock to the new LazyInitializer.EnsureInitialized API and was happy with its success. I will add another test case for scenario that the user sends in a null syncLock which would have set off the race condition.

/// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null,
/// a new object will be instantiated.</param>
/// <returns>Initialized lock object.</returns>
private static object EnsureLockInitialized(ref object syncLock) => syncLock ?? Interlocked.CompareExchange(ref syncLock, new object(), null) ?? syncLock;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: please split this across multiple lines to make it easier to read.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done!

types

LazyInitializer.EnsureInitialized overload for reference types that does
not take boolean for tracking initialization status of the reference
type object. Null check on the object parameter is sufficient to whether
or not it has been initialized.
@WinCPP WinCPP force-pushed the LazyInitializerNewOverloads branch from 283f386 to f6533e1 Compare March 1, 2017 03:25
Copy link
Copy Markdown
Member

@stephentoub stephentoub left a comment

Choose a reason for hiding this comment

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

Thanks for adding this.

@stephentoub
Copy link
Copy Markdown
Member

@jkotas, does the mscorlib.cs ref still need to be updated to include new APIs, or is that now defunct?

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Mar 1, 2017

mscorlib.cs ref is defunct. We can delete it once the CoreCLR tests are fixed to consume live corefx - they are still stuck on corefx before the corefx build changes.

@stephentoub
Copy link
Copy Markdown
Member

Thanks.

@stephentoub stephentoub merged commit 38b0df9 into dotnet:master Mar 1, 2017
@WinCPP WinCPP deleted the LazyInitializerNewOverloads branch March 2, 2017 16:39
@karelz karelz modified the milestone: 2.0.0 Aug 28, 2017
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…Overloads

CoreFx dotnet/coreclr#16274 Implementation - New overloads of LazyInitializer.EnsureInitialized for reference types

Commit migrated from dotnet/coreclr@38b0df9
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants