Skip to content

Commit 2df3d85

Browse files
Fix race condition inside DataCollectionAttachmentManager
1 parent 94d6b89 commit 2df3d85

File tree

13 files changed

+187
-41
lines changed

13 files changed

+187
-41
lines changed

eng/Version.Details.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Dependencies>
33
<ProductDependencies>
4-
<Dependency Name="Microsoft.Internal.CodeCoverage" Version="17.2.1-beta.22077.2">
4+
<Dependency Name="Microsoft.Internal.CodeCoverage" Version="17.2.1-beta.22078.4">
55
<Uri>https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage</Uri>
6-
<Sha>9b6dfa3dbcf061a958c11e93ad0a2604f913c1de</Sha>
6+
<Sha>e4acfc45e9c659643141bb7818fbd5de6587e196</Sha>
77
</Dependency>
88
<Dependency Name="Microsoft.Diagnostics.NETCore.Client" Version="0.2.0-preview.21508.1">
99
<Uri>https://github.com/dotnet/diagnostics</Uri>

eng/Versions.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
JS etc.) is risky. This can break setup authoring and cause runtime failures in workloads
8383
where Rolsyn is not installed. -->
8484
<MicrosoftCodeAnalysisVersion>3.8.0-3.20427.2</MicrosoftCodeAnalysisVersion>
85-
<MicrosoftInternalCodeCoverageVersion>17.2.1-beta.22077.2</MicrosoftInternalCodeCoverageVersion>
85+
<MicrosoftInternalCodeCoverageVersion>17.2.1-beta.22078.4</MicrosoftInternalCodeCoverageVersion>
8686
<MicrosoftDiagnosticsNETCoreClientVersion>0.2.0-preview.21508.1</MicrosoftDiagnosticsNETCoreClientVersion>
8787
<MicrosoftSourceBuildIntermediatediagnosticsVersion>5.0.0-preview.21508.1</MicrosoftSourceBuildIntermediatediagnosticsVersion>
8888
</PropertyGroup>

src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionAttachmentManager.cs

+27-13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollector;
55

66
using System;
7+
using System.Collections.Concurrent;
78
using System.Collections.Generic;
89
using System.ComponentModel;
910
using System.Diagnostics;
@@ -14,18 +15,31 @@ namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollector;
1415
using System.Threading.Tasks;
1516

1617
using Interfaces;
17-
using ObjectModel;
18+
1819
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
1920
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
2021
using Microsoft.VisualStudio.TestPlatform.Utilities;
2122
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
2223

24+
using ObjectModel;
25+
2326
/// <summary>
2427
/// Manages file transfer from data collector to test runner service.
28+
///
29+
/// Events are handled sequentially so it's not possible have parallel AddAttachment/GetAttachments for the same DataCollectionContext.
30+
/// DataCollectionContext can be a session context(session start/end) or a test case context(test case start/end).
31+
///
32+
/// We have two type of events that will fire a collection of files "session end" and "test case end".
33+
/// File are sent and copied/moved in parallel using async tasks, for these reason we need to use an async structure ConcurrentDictionary
34+
/// to be able to handle parallel test case start/end events(if host run tests in parallel).
35+
///
36+
/// We could avoid to use ConcurrentDictionary for the list of the attachment sets of a specific DataCollectionContext, but
37+
/// we don't know how the user will implement the datacollector and they could send file out of events(wrong usage, no more expected sequential access AddAttachment->GetAttachments),
38+
/// so we prefer protect every collection. This not means that outcome will be "always correct"(file attached in a correct way) but at least we avoid exceptions.
2539
/// </summary>
2640
internal class DataCollectionAttachmentManager : IDataCollectionAttachmentManager
2741
{
28-
private static readonly object AttachmentTaskLock = new();
42+
private readonly object _attachmentTaskLock = new();
2943

3044
#region Fields
3145

@@ -42,7 +56,7 @@ internal class DataCollectionAttachmentManager : IDataCollectionAttachmentManage
4256
/// <summary>
4357
/// Attachment transfer tasks associated with a given datacollection context.
4458
/// </summary>
45-
private readonly Dictionary<DataCollectionContext, List<Task>> _attachmentTasks;
59+
private readonly ConcurrentDictionary<DataCollectionContext, List<Task>> _attachmentTasks;
4660

4761
/// <summary>
4862
/// Use to cancel attachment transfers if test run is canceled.
@@ -74,8 +88,8 @@ protected DataCollectionAttachmentManager(IFileHelper fileHelper)
7488
{
7589
_fileHelper = fileHelper;
7690
_cancellationTokenSource = new CancellationTokenSource();
77-
_attachmentTasks = new Dictionary<DataCollectionContext, List<Task>>();
78-
AttachmentSets = new Dictionary<DataCollectionContext, Dictionary<Uri, AttachmentSet>>();
91+
_attachmentTasks = new ConcurrentDictionary<DataCollectionContext, List<Task>>();
92+
AttachmentSets = new ConcurrentDictionary<DataCollectionContext, ConcurrentDictionary<Uri, AttachmentSet>>();
7993
}
8094

8195
#endregion
@@ -90,7 +104,7 @@ protected DataCollectionAttachmentManager(IFileHelper fileHelper)
90104
/// <summary>
91105
/// Gets the attachment sets for the given datacollection context.
92106
/// </summary>
93-
internal Dictionary<DataCollectionContext, Dictionary<Uri, AttachmentSet>> AttachmentSets
107+
internal ConcurrentDictionary<DataCollectionContext, ConcurrentDictionary<Uri, AttachmentSet>> AttachmentSets
94108
{
95109
get; private set;
96110
}
@@ -155,8 +169,8 @@ public List<AttachmentSet> GetAttachments(DataCollectionContext dataCollectionCo
155169
if (AttachmentSets.TryGetValue(dataCollectionContext, out var uriAttachmentSetMap))
156170
{
157171
attachments = uriAttachmentSetMap.Values.ToList();
158-
_attachmentTasks.Remove(dataCollectionContext);
159-
AttachmentSets.Remove(dataCollectionContext);
172+
_attachmentTasks.TryRemove(dataCollectionContext, out _);
173+
AttachmentSets.TryRemove(dataCollectionContext, out _);
160174
}
161175

162176
return attachments;
@@ -180,14 +194,14 @@ public void AddAttachment(FileTransferInformation fileTransferInfo, AsyncComplet
180194

181195
if (!AttachmentSets.ContainsKey(fileTransferInfo.Context))
182196
{
183-
var uriAttachmentSetMap = new Dictionary<Uri, AttachmentSet>();
184-
AttachmentSets.Add(fileTransferInfo.Context, uriAttachmentSetMap);
185-
_attachmentTasks.Add(fileTransferInfo.Context, new List<Task>());
197+
var uriAttachmentSetMap = new ConcurrentDictionary<Uri, AttachmentSet>();
198+
AttachmentSets.TryAdd(fileTransferInfo.Context, uriAttachmentSetMap);
199+
_attachmentTasks.TryAdd(fileTransferInfo.Context, new List<Task>());
186200
}
187201

188202
if (!AttachmentSets[fileTransferInfo.Context].ContainsKey(uri))
189203
{
190-
AttachmentSets[fileTransferInfo.Context].Add(uri, new AttachmentSet(uri, friendlyName));
204+
AttachmentSets[fileTransferInfo.Context].TryAdd(uri, new AttachmentSet(uri, friendlyName));
191205
}
192206

193207
AddNewFileTransfer(fileTransferInfo, sendFileCompletedCallback, uri, friendlyName);
@@ -327,7 +341,7 @@ private void AddNewFileTransfer(FileTransferInformation fileTransferInfo, AsyncC
327341
{
328342
if (t.Exception == null)
329343
{
330-
lock (AttachmentTaskLock)
344+
lock (_attachmentTaskLock)
331345
{
332346
AttachmentSets[fileTransferInfo.Context][uri].Attachments.Add(UriDataAttachment.CreateFrom(localFilePath, fileTransferInfo.Description));
333347
}

src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionManager.cs

+18
Original file line numberDiff line numberDiff line change
@@ -768,10 +768,28 @@ private void RemoveDataCollectors(IReadOnlyCollection<DataCollectorInformation>
768768

769769
private void LogAttachments(List<AttachmentSet> attachmentSets)
770770
{
771+
if (attachmentSets is null)
772+
{
773+
EqtTrace.Error("DataCollectionManager.LogAttachments: Unexpected null attachmentSets.");
774+
return;
775+
}
776+
771777
foreach (var entry in attachmentSets)
772778
{
779+
if (entry is null)
780+
{
781+
EqtTrace.Error("DataCollectionManager.LogAttachments: Unexpected null entry inside attachmentSets.");
782+
continue;
783+
}
784+
773785
foreach (var file in entry.Attachments)
774786
{
787+
if (file is null)
788+
{
789+
EqtTrace.Error("DataCollectionManager.LogAttachments: Unexpected null file inside entry attachments.");
790+
continue;
791+
}
792+
775793
EqtTrace.Verbose(
776794
"Test Attachment Description: Collector:'{0}' Uri:'{1}' Description:'{2}' Uri:'{3}' ",
777795
entry.DisplayName,

src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ protected DataCollectionRequestHandler(IMessageSink messageSink, IRequestData re
7676
new SocketCommunicationManager(),
7777
messageSink,
7878
DataCollectionManager.Create(messageSink, requestData),
79-
new DataCollectionTestCaseEventHandler(),
79+
new DataCollectionTestCaseEventHandler(messageSink),
8080
JsonDataSerializer.Instance,
8181
new FileHelper(),
8282
requestData)
@@ -162,7 +162,7 @@ public static DataCollectionRequestHandler Create(
162162
communicationManager,
163163
messageSink,
164164
DataCollectionManager.Create(messageSink, requestData),
165-
new DataCollectionTestCaseEventHandler(),
165+
new DataCollectionTestCaseEventHandler(messageSink),
166166
JsonDataSerializer.Instance,
167167
new FileHelper(),
168168
requestData);
@@ -362,7 +362,7 @@ private void HandleBeforeTestRunStart(Message message)
362362
}
363363
catch (Exception e)
364364
{
365-
EqtTrace.Error("DataCollectionRequestHandler.HandleBeforeTestRunStart : Error occurred during initialization of TestHost : {0}", e);
365+
EqtTrace.Error("DataCollectionRequestHandler.HandleBeforeTestRunStart : Error occurred during test case events handling: {0}.", e);
366366
}
367367
},
368368
_cancellationTokenSource.Token);

src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs

+45-9
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@
33

44
namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection;
55

6+
using System;
7+
using System.Collections.ObjectModel;
68
using System.Net;
79

810
using Common.DataCollector;
11+
912
using Microsoft.VisualStudio.TestPlatform.Common.DataCollector.Interfaces;
1013
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces;
11-
using ObjectModel;
1214
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
1315
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
16+
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
17+
18+
using ObjectModel;
1419

1520
/// <summary>
1621
/// The test case data collection request handler.
@@ -20,26 +25,27 @@ internal class DataCollectionTestCaseEventHandler : IDataCollectionTestCaseEvent
2025
private readonly ICommunicationManager _communicationManager;
2126
private readonly IDataCollectionManager _dataCollectionManager;
2227
private readonly IDataSerializer _dataSerializer;
28+
private readonly IMessageSink _messageSink;
2329

2430
/// <summary>
2531
/// Initializes a new instance of the <see cref="DataCollectionTestCaseEventHandler"/> class.
2632
/// </summary>
27-
internal DataCollectionTestCaseEventHandler()
28-
: this(new SocketCommunicationManager(), DataCollectionManager.Instance, JsonDataSerializer.Instance)
29-
{
30-
}
33+
internal DataCollectionTestCaseEventHandler(IMessageSink messageSink)
34+
: this(messageSink, new SocketCommunicationManager(), DataCollectionManager.Instance, JsonDataSerializer.Instance)
35+
{ }
3136

3237
/// <summary>
3338
/// Initializes a new instance of the <see cref="DataCollectionTestCaseEventHandler"/> class.
3439
/// </summary>
3540
/// <param name="communicationManager">Communication manager implementation.</param>
3641
/// <param name="dataCollectionManager">Data collection manager implementation.</param>
3742
/// <param name="dataSerializer">Serializer for serialization and deserialization of the messages.</param>
38-
internal DataCollectionTestCaseEventHandler(ICommunicationManager communicationManager, IDataCollectionManager dataCollectionManager, IDataSerializer dataSerializer)
43+
internal DataCollectionTestCaseEventHandler(IMessageSink messageSink, ICommunicationManager communicationManager, IDataCollectionManager dataCollectionManager, IDataSerializer dataSerializer)
3944
{
4045
_communicationManager = communicationManager;
4146
_dataCollectionManager = dataCollectionManager;
4247
_dataSerializer = dataSerializer;
48+
_messageSink = messageSink;
4349
}
4450

4551
/// <inheritdoc />
@@ -79,7 +85,17 @@ public void ProcessRequests()
7985
}
8086

8187
var testCaseStartEventArgs = _dataSerializer.DeserializePayload<TestCaseStartEventArgs>(message);
82-
_dataCollectionManager.TestCaseStarted(testCaseStartEventArgs);
88+
89+
try
90+
{
91+
_dataCollectionManager.TestCaseStarted(testCaseStartEventArgs);
92+
}
93+
catch (Exception ex)
94+
{
95+
_messageSink.SendMessage(new DataCollectionMessageEventArgs(TestMessageLevel.Error, $"Error occurred during TestCaseStarted event handling: {ex}"));
96+
EqtTrace.Error($"DataCollectionTestCaseEventHandler.ProcessRequests: Error occurred during TestCaseStarted event handling: {ex}");
97+
}
98+
8399
_communicationManager.SendMessage(MessageType.DataCollectionTestStartAck);
84100

85101
if (EqtTrace.IsInfoEnabled)
@@ -96,7 +112,19 @@ public void ProcessRequests()
96112
}
97113

98114
var testCaseEndEventArgs = _dataSerializer.DeserializePayload<TestCaseEndEventArgs>(message);
99-
var attachmentSets = _dataCollectionManager.TestCaseEnded(testCaseEndEventArgs);
115+
116+
Collection<AttachmentSet> attachmentSets;
117+
try
118+
{
119+
attachmentSets = _dataCollectionManager.TestCaseEnded(testCaseEndEventArgs);
120+
}
121+
catch (Exception ex)
122+
{
123+
_messageSink.SendMessage(new DataCollectionMessageEventArgs(TestMessageLevel.Error, $"Error occurred during DataCollectionTestEnd event handling: {ex}"));
124+
EqtTrace.Error($"DataCollectionTestCaseEventHandler.ProcessRequests: Error occurred during DataCollectionTestEnd event handling: {ex}");
125+
attachmentSets = new Collection<AttachmentSet>();
126+
}
127+
100128
_communicationManager.SendMessage(MessageType.DataCollectionTestEndResult, attachmentSets);
101129

102130
if (EqtTrace.IsInfoEnabled)
@@ -114,7 +142,15 @@ public void ProcessRequests()
114142
EqtTrace.Info("DataCollectionTestCaseEventHandler: Test session ended");
115143
}
116144

117-
Close();
145+
try
146+
{
147+
Close();
148+
}
149+
catch (Exception ex)
150+
{
151+
_messageSink.SendMessage(new DataCollectionMessageEventArgs(TestMessageLevel.Error, $"Error occurred during SessionEnd event handling: {ex}"));
152+
EqtTrace.Error($"DataCollectionTestCaseEventHandler.ProcessRequests: Error occurred during SessionEnd event handling: {ex}");
153+
}
118154

119155
break;
120156

src/Microsoft.TestPlatform.ObjectModel/Architecture.cs

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ public enum Architecture
1111
ARM,
1212
AnyCPU,
1313
ARM64,
14+
S390x
1415
}

src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter.TestPlatformFormatExcept
106106
Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter.TestPlatformFormatException.TestPlatformFormatException(string message, string filterValue) -> void
107107
Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter.TestPlatformFormatException.TestPlatformFormatException(string message, System.Exception innerException) -> void
108108
Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture
109+
Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture.S390x = 6 -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture
109110
Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture.ARM64 = 5 -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture
110111
Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture.AnyCPU = 4 -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture
111112
Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture.ARM = 3 -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture
@@ -887,4 +888,4 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.InvokedDataCollector.HasAttachme
887888
Microsoft.VisualStudio.TestPlatform.ObjectModel.InvokedDataCollector.Uri.get -> System.Uri
888889
override Microsoft.VisualStudio.TestPlatform.ObjectModel.InvokedDataCollector.Equals(object obj) -> bool
889890
override Microsoft.VisualStudio.TestPlatform.ObjectModel.InvokedDataCollector.GetHashCode() -> int
890-
override Microsoft.VisualStudio.TestPlatform.ObjectModel.InvokedDataCollector.ToString() -> string
891+
override Microsoft.VisualStudio.TestPlatform.ObjectModel.InvokedDataCollector.ToString() -> string

src/vstest.console/CommandLine/AssemblyMetadataProvider.cs

+17-3
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,31 @@ private Architecture GetArchitectureFromAssemblyMetadata(string path)
103103
switch (peReader.PEHeaders.CoffHeader.Machine)
104104
{
105105
case Machine.Amd64:
106-
return Architecture.X64;
107106
case Machine.IA64:
108107
return Architecture.X64;
109108
case Machine.Arm64:
110109
return Architecture.ARM64;
111110
case Machine.Arm:
112111
return Architecture.ARM;
113112
case Machine.I386:
114-
return Architecture.X86;
113+
// We can distinguish AnyCPU only from the set of CorFlags.Requires32Bit, but in case of Ready
114+
// to Run image that flag is not "updated" and ignored. So we check if the module is IL only or not.
115+
// If it's not IL only it means that is a R2R (Ready to Run) and we're already in the correct architecture x86.
116+
// In all other cases the architecture will end inside the correct switch branch.
117+
var corflags = peReader.PEHeaders.CorHeader.Flags;
118+
if ((corflags & CorFlags.Requires32Bit) != 0 || (corflags & CorFlags.ILOnly) == 0)
119+
{
120+
return Architecture.X86;
121+
}
122+
else
123+
{
124+
return Architecture.AnyCPU;
125+
}
115126
default:
116-
break;
127+
{
128+
EqtTrace.Error($"AssemblyMetadataProvider.GetArchitecture: Unhandled architecture '{peReader.PEHeaders.CoffHeader.Machine}'.");
129+
break;
130+
}
117131
}
118132
}
119133

0 commit comments

Comments
 (0)