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

Concurrency issue with fixed fields #56581

Merged
merged 5 commits into from
Oct 1, 2021
Merged

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Sep 21, 2021

Fixes #53865

The issue is that we're collecting synthesized nested types into concurrent queues but adding them in a non-deterministic order.
The two code paths below compete to add those nested types and include tasks/threads when not under a debugger (tests turn off concurrency when debugged).
The result was that those nested types didn't always appear in the same order in metadata.

   at Microsoft.CodeAnalysis.CSharp.Symbols.SourceFixedFieldSymbol.FixedImplementationType(PEModuleBuilder emitModule)
   at Microsoft.CodeAnalysis.CSharp.MethodCompiler.CompileNamedType(NamedTypeSymbol containingType)
   at Microsoft.CodeAnalysis.CSharp.MethodCompiler.VisitNamedType(NamedTypeSymbol symbol, TypeCompilationState arg)
   at Microsoft.CodeAnalysis.CSharp.Symbols.NamedTypeSymbol.Accept[TArgument,TResult](CSharpSymbolVisitor`2 visitor, TArgument argument)
   at Microsoft.CodeAnalysis.CSharp.MethodCompiler.CompileNamespace(NamespaceSymbol symbol)
   at Microsoft.CodeAnalysis.CSharp.MethodCompiler.CompileMethodBodies(CSharpCompilation compilation, PEModuleBuilder moduleBeingBuiltOpt, Boolean emittingPdb, Boolean emitTestCoverageData, Boolean hasDeclarationErrors, Boolean emitMethodBodies, BindingDiagnosticBag diagnostics, Predicate`1 filterOpt, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.CompileMethods(CommonPEModuleBuilder moduleBuilder, Boolean emittingPdb, Boolean emitMetadataOnly, Boolean emitTestCoverageData, DiagnosticBag diagnostics, Predicate`1 filterOpt, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream metadataPEStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, RebuildData rebuildData, CompilationTestData testData, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, Stream metadataPEStream, RebuildData rebuildData, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, Stream metadataPEStream, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.UnitTests.Emit.DeterministicTests.TODO2()
   at Microsoft.CodeAnalysis.CSharp.Symbols.SourceFixedFieldSymbol.FixedImplementationType(PEModuleBuilder emitModule)
   at Microsoft.CodeAnalysis.CSharp.Symbols.FieldSymbolAdapter.Microsoft.Cci.IFieldReference.GetType(EmitContext context)
   at Microsoft.CodeAnalysis.CodeGen.ReferenceDependencyWalker.VisitFieldReference(IFieldReference fieldReference, EmitContext context)
   at Microsoft.CodeAnalysis.CodeGen.ReferenceDependencyWalker.VisitReference(IReference reference, EmitContext context)
   at Microsoft.CodeAnalysis.Emit.CommonPEModuleBuilder.GetFakeSymbolTokenForIL(IReference symbol, SyntaxNode syntaxNode, DiagnosticBag diagnostics)
   at Microsoft.CodeAnalysis.CodeGen.ILBuilder.EmitToken(IReference value, SyntaxNode syntaxNode, DiagnosticBag diagnostics, Boolean encodeAsRawToken)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitSymbolToken(FieldSymbol symbol, SyntaxNode syntaxNode)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitInstanceFieldAddress(BoundFieldAccess fieldAccess, AddressKind addressKind)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitFieldAddress(BoundFieldAccess fieldAccess, AddressKind addressKind)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitAddress(BoundExpression expression, AddressKind addressKind)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitAddressOfExpression(BoundAddressOfOperator expression, Boolean used)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitExpressionCore(BoundExpression expression, Boolean used)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitExpression(BoundExpression expression, Boolean used)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitAssignmentPreamble(BoundAssignmentOperator assignmentOperator)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitAssignmentExpression(BoundAssignmentOperator assignmentOperator, UseKind useKind)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitExpressionCore(BoundExpression expression, Boolean used)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitExpressionCoreWithStackGuard(BoundExpression expression, Boolean used)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitExpression(BoundExpression expression, Boolean used)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitStatement(BoundStatement statement)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitStatementAndCountInstructions(BoundStatement statement)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitSequencePointStatement(BoundSequencePoint node)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitStatement(BoundStatement statement)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitStatements(ImmutableArray`1 statements)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitBlock(BoundBlock block)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitStatement(BoundStatement statement)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitStatementList(BoundStatementList list)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.EmitStatement(BoundStatement statement)
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.GenerateImpl()
   at Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.Generate(Boolean& hasStackalloc)
   at Microsoft.CodeAnalysis.CSharp.MethodCompiler.GenerateMethodBody(PEModuleBuilder moduleBuilder, MethodSymbol method, Int32 methodOrdinal, BoundStatement block, ImmutableArray`1 lambdaDebugInfo, ImmutableArray`1 closureDebugInfo, StateMachineTypeSymbol stateMachineTypeOpt, VariableSlotAllocator variableSlotAllocatorOpt, BindingDiagnosticBag diagnostics, DebugDocumentProvider debugDocumentProvider, ImportChain importChainOpt, Boolean emittingPdb, Boolean emitTestCoverageData, ImmutableArray`1 dynamicAnalysisSpans, AsyncForwardEntryPoint entryPointOpt)
   at Microsoft.CodeAnalysis.CSharp.MethodCompiler.CompileMethod(MethodSymbol methodSymbol, Int32 methodOrdinal, ProcessedFieldInitializers& processedInitializers, SynthesizedSubmissionFields previousSubmissionFields, TypeCompilationState compilationState)
   at Microsoft.CodeAnalysis.CSharp.MethodCompiler.CompileNamedType(NamedTypeSymbol containingType)
   at Microsoft.CodeAnalysis.CSharp.MethodCompiler.VisitNamedType(NamedTypeSymbol symbol, TypeCompilationState arg)
   at Microsoft.CodeAnalysis.CSharp.Symbols.NamedTypeSymbol.Accept[TArgument,TResult](CSharpSymbolVisitor`2 visitor, TArgument argument)
   at Microsoft.CodeAnalysis.CSharp.MethodCompiler.CompileNamespace(NamespaceSymbol symbol)
   at Microsoft.CodeAnalysis.CSharp.MethodCompiler.CompileMethodBodies(CSharpCompilation compilation, PEModuleBuilder moduleBeingBuiltOpt, Boolean emittingPdb, Boolean emitTestCoverageData, Boolean hasDeclarationErrors, Boolean emitMethodBodies, BindingDiagnosticBag diagnostics, Predicate`1 filterOpt, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.CompileMethods(CommonPEModuleBuilder moduleBuilder, Boolean emittingPdb, Boolean emitMetadataOnly, Boolean emitTestCoverageData, DiagnosticBag diagnostics, Predicate`1 filterOpt, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream metadataPEStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, RebuildData rebuildData, CompilationTestData testData, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, Stream metadataPEStream, RebuildData rebuildData, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, Stream metadataPEStream, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.UnitTests.Emit.DeterministicTests.TODO2()

@jcouv jcouv self-assigned this Sep 21, 2021
@@ -1417,17 +1417,6 @@ public NamedTypeSymbol SetFixedImplementationType(SourceMemberFieldSymbol field)
}
}

internal NamedTypeSymbol GetFixedImplementationType(FieldSymbol field)
Copy link
Member Author

Choose a reason for hiding this comment

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

📝 This code was not referenced.

@jcouv jcouv added this to the 17.0 milestone Sep 21, 2021
@jcouv jcouv changed the base branch from main to release/dev17.0 September 28, 2021 00:13
@dotnet dotnet deleted a comment from azure-pipelines bot Sep 28, 2021
@jcouv jcouv closed this Sep 28, 2021
@jcouv jcouv reopened this Sep 28, 2021
@jcouv jcouv closed this Sep 28, 2021
@jcouv jcouv reopened this Sep 28, 2021
@jcouv jcouv force-pushed the fixed-bug branch 5 times, most recently from 58ac696 to dab8420 Compare September 29, 2021 19:10
.WithOverflowChecks(false)
.WithModuleName(assemblyName);

for (int j = 0; j < 10; j++)
Copy link
Member Author

@jcouv jcouv Sep 29, 2021

Choose a reason for hiding this comment

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

📝 I've tested with as many as 100 loops and max=100, but found this was a sufficient number to repro well

@@ -763,7 +763,7 @@ public ImmutableArray<ISymbolInternal> GetAllMembers()

if (NestedTypes != null)
{
foreach (var type in NestedTypes)
foreach (var type in NestedTypes.OrderBy(t => t.Name, StringComparer.Ordinal))
Copy link
Member

@cston cston Sep 29, 2021

Choose a reason for hiding this comment

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

NestedTypes.OrderBy(t => t.Name, StringComparer.Ordinal)

Consider adding an IEnumerable<Cci.INestedTypeDefinition> NestedTypesOrdered() method to this type, for use here and below. #Resolved

<forward declaringType=""{ EscapeForXML(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName) }+&lt;&gt;c"" methodName=""&lt;{ EscapeForXML(WellKnownMemberNames.TopLevelStatementsEntryPointMethodName) }&gt;b__0_0"" />
<using>
<namespace usingCount=""2"" />
</using>
Copy link
Member

@cston cston Sep 29, 2021

Choose a reason for hiding this comment

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

Is this change because namespace imports are now attached to a different method, because of re-ordered types? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this information about usings is order dependent. One method gets it and the following ones refer to the first method instead of repeating it.

@jcouv jcouv requested a review from a team September 29, 2021 23:01
@333fred
Copy link
Member

333fred commented Sep 30, 2021

        public ConcurrentQueue<Cci.INestedTypeDefinition> NestedTypes;

Can we make this private and add a public AddNestedType for additions, so that we know there are no reads of the values in this property?


In reply to: 930640897


Refers to: src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs:731 in a15e86b. [](commit_id = a15e86b, deletion_comment = False)

byte[] result = null;

var sourceBuilder = ArrayBuilder<string>.GetInstance();
int max = 20;
Copy link
Member

@333fred 333fred Sep 30, 2021

Choose a reason for hiding this comment

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

Nit: const #Resolved

@333fred
Copy link
Member

333fred commented Sep 30, 2021

Done review pass (commit 2)

@jcouv jcouv requested a review from 333fred September 30, 2021 00:40
@jcouv
Copy link
Member Author

jcouv commented Sep 30, 2021

@333fred Please take another look. Thanks

// Nested types may be queued from concurrent threads, but we need to emit them
// in a deterministic order.
internal IEnumerable<Cci.INestedTypeDefinition> DeterministicNestedTypes
=> NestedTypes?.OrderBy(t => t.Name, StringComparer.Ordinal);
Copy link
Member

Choose a reason for hiding this comment

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

what if you have multiple nested types with the same name, but different arity?

Copy link
Member

Choose a reason for hiding this comment

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

why not emit them in source-order (taking into account partials as well of course).

Copy link
Member Author

Choose a reason for hiding this comment

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

These nested types are synthesized. Not directly from source. I don't think we synthesize nested types with different arities. Could add this in 17.1 for extra safety (future-proofing)...
As for the order itself, I don't think there's a reason to prefer one specific order to another, as long as it's deterministic.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we synthesize nested types with different arities

Can you add a contract call to that effect hten? if that assumption ever changes we want to update this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea. I'll create a 17.1 PR to add assertion. Thanks

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, since this PR isn't merged yet, I'll add the assertion here. Still have time for tomorrow's deadline

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

LGTM (commit 3)

@jcouv jcouv enabled auto-merge (squash) September 30, 2021 18:03
@jcouv jcouv disabled auto-merge September 30, 2021 18:19
Debug.Assert(arities.All(a => a == arities.First()));
}
}
#endif
Copy link
Member

@cston cston Sep 30, 2021

Choose a reason for hiding this comment

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

Can the assert be simplified (or perhaps documented)?

And should we simply assert that there are no duplicate names?

Perhaps:

Debug.Assert(NestedTypes is null ||
    NestedTypes.OrderBy(t => t.Name, StringComparer.Ordinal).Distinct());

Copy link
Member Author

Choose a reason for hiding this comment

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

Distinct returns an enumerable...
But we can indeed compare counts.

@jcouv jcouv enabled auto-merge (squash) September 30, 2021 23:57
@jcouv jcouv merged commit 9c798fd into dotnet:release/dev17.0 Oct 1, 2021
@jcouv jcouv deleted the fixed-bug branch October 1, 2021 04:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Non deterministic build result for structure.
4 participants