Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make compiled model and runtime initialization thread-safe #25559

Merged
merged 1 commit into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,24 +164,18 @@ private string CreateModel(
using (mainBuilder.Indent())
{
mainBuilder
.Append("private static ").Append(className).AppendLine(nullable ? "? _instance;" : " _instance;")
.Append("public static IModel Instance")
.AppendLines(@"
.Append("static ").Append(className).Append("()")
.AppendLines(
@"
{
get
{
if (_instance == null)
{
_instance = new " + className + @"();
_instance.Initialize();
_instance.Customize();
}

return _instance;
}
}");

mainBuilder
var model = new " + className + @"();
model.Initialize();
model.Customize();
_instance = model;
}")
.AppendLine()
.Append("private static ").Append(className).AppendLine(nullable ? "? _instance;" : " _instance;")
.AppendLine("public static IModel Instance => _instance;")
.AppendLine()
.AppendLine("partial void Initialize();")
.AppendLine()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ protected override void InitializeModel(IModel model, bool designTime, bool prev
{
if (prevalidation)
{
model.AddRuntimeAnnotation(RelationalAnnotationNames.ModelDependencies, RelationalDependencies.RelationalModelDependencies);
model.SetRuntimeAnnotation(RelationalAnnotationNames.ModelDependencies, RelationalDependencies.RelationalModelDependencies);
}
else
{
Expand Down
70 changes: 34 additions & 36 deletions src/EFCore/Infrastructure/ModelRuntimeInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
/// </summary>
public class ModelRuntimeInitializer : IModelRuntimeInitializer
{
private static readonly object _syncObject = new();

/// <summary>
/// Creates a new <see cref="ModelRuntimeInitializer" /> instance.
/// </summary>
Expand Down Expand Up @@ -56,59 +58,55 @@ public virtual IModel Initialize(
if (model is Model mutableModel
&& !mutableModel.IsReadOnly)
{
model = mutableModel.FinalizeModel();
lock (_syncObject)
{
if (!mutableModel.IsReadOnly)
{
model = mutableModel.FinalizeModel();
}
}
}

if (model.ModelDependencies == null)
{
model = model.GetOrAddRuntimeAnnotationValue(
CoreAnnotationNames.ReadOnlyModel,
static args =>
// Make sure InitializeModel really only gets called once, since it may not be thread safe or idempotent.
lock (_syncObject)
{
if (model.ModelDependencies == null)
{
var (initializer, model, designTime, validationLogger) = args;
model.ModelDependencies = Dependencies.ModelDependencies;

model.ModelDependencies = initializer.Dependencies.ModelDependencies;

initializer.InitializeModel(model, designTime, prevalidation: true);
InitializeModel(model, designTime, prevalidation: true);

if (validationLogger != null
&& model is IConventionModel)
{
initializer.Dependencies.ModelValidator.Validate(model, validationLogger);
}

initializer.InitializeModel(model, designTime, prevalidation: false);

if (!designTime
&& model is Model mutableModel)
{
model = mutableModel.OnModelFinalized();
Dependencies.ModelValidator.Validate(model, validationLogger);
}

return model;
},
(this, model, designTime, validationLogger));

if (designTime)
{
model.RemoveRuntimeAnnotation(CoreAnnotationNames.ReadOnlyModel);
InitializeModel(model, designTime, prevalidation: false);
}
}
}
else if (!designTime)

if (designTime)
{
model = model.GetOrAddRuntimeAnnotationValue(
CoreAnnotationNames.ReadOnlyModel,
static model =>
return model;
}

model = model.GetOrAddRuntimeAnnotationValue(
CoreAnnotationNames.ReadOnlyModel,
static model =>
{
if (model is Model mutableModel)
{
if (model is Model mutableModel)
{
model = mutableModel.OnModelFinalized();
}
// This assumes OnModelFinalized is thread-safe
model = mutableModel.OnModelFinalized();
}

return model!;
},
model);
}
return model!;
},
model);

return model;
}
Expand Down
3 changes: 2 additions & 1 deletion src/EFCore/Metadata/Conventions/IModelFinalizedConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
public interface IModelFinalizedConvention : IConvention
{
/// <summary>
/// Called after a model is finalized and can no longer be mutated.
/// <para> Called after a model is finalized and can no longer be mutated. </para>
/// <para> The implementation must be thread-safe. </para>
/// </summary>
/// <param name="model"> The model. </param>
IModel ProcessModelFinalized(IModel model);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.EmptyContext))]
partial class EmptyContextModel : RuntimeModel
{
private static EmptyContextModel _instance;
public static IModel Instance
static EmptyContextModel()
{
get
{
if (_instance == null)
{
_instance = new EmptyContextModel();
_instance.Initialize();
_instance.Customize();
}

return _instance;
}
var model = new EmptyContextModel();
model.Initialize();
model.Customize();
_instance = model;
}

private static EmptyContextModel _instance;
public static IModel Instance => _instance;

partial void Initialize();

partial void Customize();
Expand Down Expand Up @@ -476,22 +471,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.BigContext))]
partial class BigContextModel : RuntimeModel
{
private static BigContextModel? _instance;
public static IModel Instance
static BigContextModel()
{
get
{
if (_instance == null)
{
_instance = new BigContextModel();
_instance.Initialize();
_instance.Customize();
}

return _instance;
}
var model = new BigContextModel();
model.Initialize();
model.Customize();
_instance = model;
}

private static BigContextModel? _instance;
public static IModel Instance => _instance;

partial void Initialize();

partial void Customize();
Expand Down Expand Up @@ -1853,22 +1843,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.DbFunctionContext))]
partial class DbFunctionContextModel : RuntimeModel
{
private static DbFunctionContextModel _instance;
public static IModel Instance
static DbFunctionContextModel()
{
get
{
if (_instance == null)
{
_instance = new DbFunctionContextModel();
_instance.Initialize();
_instance.Customize();
}

return _instance;
}
var model = new DbFunctionContextModel();
model.Initialize();
model.Customize();
_instance = model;
}

private static DbFunctionContextModel _instance;
public static IModel Instance => _instance;

partial void Initialize();

partial void Customize();
Expand Down Expand Up @@ -2343,22 +2328,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.SequencesContext))]
partial class SequencesContextModel : RuntimeModel
{
private static SequencesContextModel _instance;
public static IModel Instance
static SequencesContextModel()
{
get
{
if (_instance == null)
{
_instance = new SequencesContextModel();
_instance.Initialize();
_instance.Customize();
}

return _instance;
}
var model = new SequencesContextModel();
model.Initialize();
model.Customize();
_instance = model;
}

private static SequencesContextModel _instance;
public static IModel Instance => _instance;

partial void Initialize();

partial void Customize();
Expand Down Expand Up @@ -2551,22 +2531,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.ConstraintsContext))]
partial class ConstraintsContextModel : RuntimeModel
{
private static ConstraintsContextModel _instance;
public static IModel Instance
static ConstraintsContextModel()
{
get
{
if (_instance == null)
{
_instance = new ConstraintsContextModel();
_instance.Initialize();
_instance.Customize();
}

return _instance;
}
var model = new ConstraintsContextModel();
model.Initialize();
model.Customize();
_instance = model;
}

private static ConstraintsContextModel _instance;
public static IModel Instance => _instance;

partial void Initialize();

partial void Customize();
Expand Down Expand Up @@ -2707,22 +2682,17 @@ namespace Microsoft.EntityFrameworkCore.Metadata
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.SqliteContext))]
partial class SqliteContextModel : RuntimeModel
{
private static SqliteContextModel _instance;
public static IModel Instance
static SqliteContextModel()
{
get
{
if (_instance == null)
{
_instance = new SqliteContextModel();
_instance.Initialize();
_instance.Customize();
}

return _instance;
}
var model = new SqliteContextModel();
model.Initialize();
model.Customize();
_instance = model;
}

private static SqliteContextModel _instance;
public static IModel Instance => _instance;

partial void Initialize();

partial void Customize();
Expand Down Expand Up @@ -2887,22 +2857,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.CosmosContext))]
partial class CosmosContextModel : RuntimeModel
{
private static CosmosContextModel _instance;
public static IModel Instance
static CosmosContextModel()
{
get
{
if (_instance == null)
{
_instance = new CosmosContextModel();
_instance.Initialize();
_instance.Customize();
}

return _instance;
}
var model = new CosmosContextModel();
model.Initialize();
model.Customize();
_instance = model;
}

private static CosmosContextModel _instance;
public static IModel Instance => _instance;

partial void Initialize();

partial void Customize();
Expand Down
Loading