Skip to content

Commit 39d4da5

Browse files
authored
🐛 Do not share the broker data dictionary between usage reports (#5983)
The usage report used the broker data dictionary from the data store as its environment data, and then added its own keys to it. When no broker data is stored, the EF Core and RavenDB data stores return one static default instance. All reports in the process then write into the same dictionary. Two reports that are generated at the same time can show values of the other report. This made When_reporting_the_environment fail at random. The acceptance tests run in parallel, so the report of one test could contain the redirect count, the report mask count or the heartbeat values of another test. - The report copies the broker data into a new dictionary. - The EF Core and RavenDB data stores return a new default instance.
1 parent ff995c9 commit 39d4da5

4 files changed

Lines changed: 37 additions & 5 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace Particular.LicensingComponent.UnitTests;
2+
3+
using System.Collections.Generic;
4+
using System.Threading.Tasks;
5+
using NUnit.Framework;
6+
using Particular.LicensingComponent.Contracts;
7+
using Particular.LicensingComponent.UnitTests.Infrastructure;
8+
9+
/// <summary>
10+
/// The report adds its environment data to the broker data. The data store can return a shared
11+
/// instance, so the report must not change the broker data that the store holds. Otherwise two
12+
/// reports that are generated at the same time write into the same dictionary.
13+
/// </summary>
14+
[TestFixture]
15+
class ThroughputCollector_Report_BrokerMetadata_Tests : ThroughputCollectorTestFixture
16+
{
17+
[Test]
18+
public async Task Should_not_change_the_stored_broker_data()
19+
{
20+
await DataStore.SaveBrokerMetadata(new BrokerMetadata("scope", new Dictionary<string, string> { ["Broker.Key"] = "value" }));
21+
22+
var report = await ThroughputCollector.GenerateThroughputReport("1.2.3", null);
23+
24+
var stored = await DataStore.GetBrokerMetadata();
25+
26+
using (Assert.EnterMultipleScope())
27+
{
28+
Assert.That(report.ReportData.EnvironmentInformation.EnvironmentData, Does.ContainKey("Broker.Key"), "The report includes the broker data");
29+
Assert.That(report.ReportData.EnvironmentInformation.EnvironmentData, Does.ContainKey(EnvironmentDataType.ServicePulseVersion.ToString()));
30+
Assert.That(stored.Data.Keys, Is.EquivalentTo(new[] { "Broker.Key" }), "The report must not add its keys to the stored broker data");
31+
}
32+
}
33+
}

‎src/Particular.LicensingComponent/ThroughputCollector.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ public async Task<SignedReport> GenerateThroughputReport(string spVersion, DateT
168168
IgnoredQueues = [.. ignoredQueueNames],
169169
Queues = [.. queueThroughputs],
170170
TotalQueues = queueThroughputs.Count,
171-
EnvironmentInformation = new EnvironmentInformation { AuditServicesData = new AuditServicesData(auditServiceMetadata.Versions, auditServiceMetadata.Transports), EnvironmentData = brokerMetaData.Data }
171+
// A copy, because the report adds its own keys. The data store can return a shared instance.
172+
EnvironmentInformation = new EnvironmentInformation { AuditServicesData = new AuditServicesData(auditServiceMetadata.Versions, auditServiceMetadata.Transports), EnvironmentData = new Dictionary<string, string>(brokerMetaData.Data) }
172173
};
173174

174175
var auditThroughput = queueThroughputs.SelectMany(w => w.DailyThroughputFromAudit);

‎src/ServiceControl.Persistence.EFCore/Implementation/LicensingDataStore.cs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class LicensingDataStore(IServiceScopeFactory scopeFactory, TimeProvider timePro
1212
{
1313
static readonly string PlatformEndpointIndicator = EndpointIndicator.PlatformEndpoint.ToString();
1414
static readonly AuditServiceMetadata DefaultAuditServiceMetadata = new([], []);
15-
static readonly BrokerMetadata DefaultBrokerMetadata = new(null, []);
1615

1716
public Task<IEnumerable<Endpoint>> GetAllEndpoints(bool includePlatformEndpoints, CancellationToken cancellationToken = default) =>
1817
ExecuteWithDbContext(async (context, token) =>
@@ -291,7 +290,7 @@ Task<bool> IsThereThroughput(int days, ThroughputSource? throughputSource, bool
291290

292291
public Task<BrokerMetadata> GetBrokerMetadata(CancellationToken cancellationToken = default) =>
293292
ExecuteWithDbContext(async (context, token) =>
294-
await context.GetSetting<BrokerMetadata>(SettingKeys.BrokerMetadata, token) ?? DefaultBrokerMetadata, cancellationToken);
293+
await context.GetSetting<BrokerMetadata>(SettingKeys.BrokerMetadata, token) ?? new BrokerMetadata(null, []), cancellationToken);
295294

296295
public Task SaveBrokerMetadata(BrokerMetadata brokerMetadata, CancellationToken cancellationToken = default) =>
297296
ExecuteWithDbContext((context, token) => context.StoreSetting(SettingKeys.BrokerMetadata, brokerMetadata, token), cancellationToken);

‎src/ServiceControl.Persistence.RavenDB/Throughput/LicensingDataStore.cs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ class LicensingDataStore(
2727
const string LicencedEndpointDetailsDocumentId = "LicensedEndpointDetails";
2828

2929
static readonly AuditServiceMetadata DefaultAuditServiceMetadata = new([], []);
30-
static readonly BrokerMetadata DefaultBrokerMetadata = new(null, []);
3130
static readonly ReportConfigurationDocument DefaultReportConfiguration = new();
3231

3332
public async Task<IEnumerable<Endpoint>> GetAllEndpoints(bool includePlatformEndpoints, CancellationToken cancellationToken = default)
@@ -289,7 +288,7 @@ public async Task<BrokerMetadata> GetBrokerMetadata(CancellationToken cancellati
289288
var store = await storeProvider.GetDocumentStore(cancellationToken);
290289
using IAsyncDocumentSession session = store.OpenAsyncSession(databaseConfiguration.Name);
291290

292-
return await session.LoadAsync<BrokerMetadata>(BrokerMetadataDocumentId, cancellationToken) ?? DefaultBrokerMetadata;
291+
return await session.LoadAsync<BrokerMetadata>(BrokerMetadataDocumentId, cancellationToken) ?? new BrokerMetadata(null, []);
293292
}
294293

295294
public async Task SaveBrokerMetadata(BrokerMetadata brokerMetadata, CancellationToken cancellationToken = default)

0 commit comments

Comments
 (0)