Skip to content
This repository was archived by the owner on Jul 30, 2024. It is now read-only.

Commit df60067

Browse files
authored
Create new scope for each initialization batch (#529)
Split up the initialization phase into multiple scopes. Each phase uses its own scope, and each batch within each phase uses its own scope (as there's a 30 second sleep time between initialization batches).
1 parent b01d02f commit df60067

3 files changed

Lines changed: 106 additions & 51 deletions

File tree

src/NuGet.Services.Revalidate/Initialization/InitializationManager.cs

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Threading.Tasks;
8+
using Microsoft.Extensions.DependencyInjection;
89
using Microsoft.Extensions.Logging;
910
using NuGet.Services.Validation;
1011
using NuGet.Versioning;
@@ -18,19 +19,22 @@ public class InitializationManager
1819
private readonly IRevalidationJobStateService _jobState;
1920
private readonly IPackageRevalidationStateService _packageState;
2021
private readonly IPackageFinder _packageFinder;
22+
private readonly IServiceScopeFactory _scopeFactory;
2123
private readonly InitializationConfiguration _config;
2224
private readonly ILogger<InitializationManager> _logger;
2325

2426
public InitializationManager(
2527
IRevalidationJobStateService jobState,
2628
IPackageRevalidationStateService packageState,
2729
IPackageFinder packageFinder,
30+
IServiceScopeFactory scopeFactory,
2831
InitializationConfiguration config,
2932
ILogger<InitializationManager> logger)
3033
{
3134
_jobState = jobState ?? throw new ArgumentNullException(nameof(jobState));
3235
_packageState = packageState ?? throw new ArgumentNullException(nameof(packageState));
3336
_packageFinder = packageFinder ?? throw new ArgumentNullException(nameof(packageFinder));
37+
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
3438
_config = config ?? throw new ArgumentNullException(nameof(config));
3539
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
3640
}
@@ -122,59 +126,83 @@ private async Task ClearPackageRevalidationStateAsync()
122126

123127
private async Task InitializePackageSetAsync(string setName, HashSet<int> packageRegistrationKeys)
124128
{
125-
var packageInformations = await _packageFinder.FindPackageRegistrationInformationAsync(setName, packageRegistrationKeys);
129+
using (var scope = _scopeFactory.CreateScope())
130+
{
131+
var scopedPackageFinder = scope.ServiceProvider.GetRequiredService<IPackageFinder>();
132+
var scopedJobState = scope.ServiceProvider.GetRequiredService<IRevalidationJobStateService>();
133+
var scopedScopeFactory = scope.ServiceProvider.GetRequiredService<IServiceScopeFactory>();
126134

127-
var chunks = packageInformations
128-
.OrderByDescending(p => p.Downloads)
129-
.WeightedBatch(BatchSize, p => p.Versions);
135+
var packageInformations = await scopedPackageFinder.FindPackageRegistrationInformationAsync(setName, packageRegistrationKeys);
136+
var chunks = packageInformations
137+
.OrderByDescending(p => p.Downloads)
138+
.WeightedBatch(BatchSize, p => p.Versions);
130139

131-
for (var chunkIndex = 0; chunkIndex < chunks.Count; chunkIndex++)
132-
{
133-
while (await _jobState.IsKillswitchActiveAsync())
140+
for (var chunkIndex = 0; chunkIndex < chunks.Count; chunkIndex++)
134141
{
135-
_logger.LogInformation(
136-
"Delaying initialization of chunk {Chunk} of {Chunks} for package set {SetName} due to active killswitch",
137-
chunkIndex + 1,
138-
chunks.Count,
139-
setName);
142+
while (await scopedJobState.IsKillswitchActiveAsync())
143+
{
144+
_logger.LogInformation(
145+
"Delaying initialization of chunk {Chunk} of {Chunks} for package set {SetName} due to active killswitch",
146+
chunkIndex + 1,
147+
chunks.Count,
148+
setName);
140149

141-
await Task.Delay(_config.SleepDurationBetweenBatches);
150+
await Task.Delay(_config.SleepDurationBetweenBatches);
151+
}
152+
153+
await InitializePackageSetChunkAsync(setName, chunks, chunkIndex, scopedScopeFactory, _logger);
154+
155+
// Sleep if this is not the last chunk to prevent overloading the database.
156+
if (chunkIndex < chunks.Count - 1)
157+
{
158+
_logger.LogInformation(
159+
"Sleeping for {SleepDuration} before initializing the next chunk...",
160+
_config.SleepDurationBetweenBatches);
161+
162+
await Task.Delay(_config.SleepDurationBetweenBatches);
163+
}
142164
}
143165

144-
_logger.LogInformation(
145-
"Initializing chunk {Chunk} of {Chunks} for package set {SetName}...",
146-
chunkIndex + 1,
147-
chunks.Count,
148-
setName);
166+
_logger.LogInformation("Finished initializing package set {SetName}", setName);
167+
}
168+
}
169+
170+
private static async Task InitializePackageSetChunkAsync(
171+
string setName,
172+
List<List<PackageRegistrationInformation>> chunks,
173+
int chunkIndex,
174+
IServiceScopeFactory scopeFactory,
175+
ILogger<InitializationManager> logger)
176+
{
177+
logger.LogInformation(
178+
"Initializing chunk {Chunk} of {Chunks} for package set {SetName}...",
179+
chunkIndex + 1,
180+
chunks.Count,
181+
setName);
182+
183+
using (var scope = scopeFactory.CreateScope())
184+
{
185+
var scopedPackageState = scope.ServiceProvider.GetRequiredService<IPackageRevalidationStateService>();
186+
var scopedPackageFinder = scope.ServiceProvider.GetRequiredService<IPackageFinder>();
149187

150188
var chunk = chunks[chunkIndex];
151-
var versions = _packageFinder.FindAppropriateVersions(chunk);
189+
var versions = scopedPackageFinder.FindAppropriateVersions(chunk);
152190

153-
await InitializeRevalidationsAsync(chunk, versions);
191+
await InitializeRevalidationsAsync(chunk, versions, scopedPackageState, logger);
154192

155-
_logger.LogInformation(
193+
logger.LogInformation(
156194
"Initialized chunk {Chunk} of {Chunks} for package set {SetName}",
157195
chunkIndex + 1,
158196
chunks.Count,
159197
setName);
160-
161-
// Sleep if this is not the last chunk to prevent overloading the database.
162-
if (chunkIndex < chunks.Count - 1)
163-
{
164-
_logger.LogInformation(
165-
"Sleeping for {SleepDuration} before initializing the next chunk...",
166-
_config.SleepDurationBetweenBatches);
167-
168-
await Task.Delay(_config.SleepDurationBetweenBatches);
169-
}
170198
}
171-
172-
_logger.LogInformation("Finished initializing package set {SetName}", setName);
173199
}
174200

175-
private async Task InitializeRevalidationsAsync(
201+
private static async Task InitializeRevalidationsAsync(
176202
List<PackageRegistrationInformation> packageRegistrations,
177-
Dictionary<int, List<NuGetVersion>> versions)
203+
Dictionary<int, List<NuGetVersion>> versions,
204+
IPackageRevalidationStateService packageState,
205+
ILogger<InitializationManager> logger)
178206
{
179207
var revalidations = new List<PackageRevalidation>();
180208

@@ -184,7 +212,7 @@ private async Task InitializeRevalidationsAsync(
184212

185213
if (!versions.ContainsKey(packageRegistration.Key) || versions[packageRegistration.Key].Count == 0)
186214
{
187-
_logger.LogWarning("Could not find any versions of package {PackageId} to revalidate", packageId);
215+
logger.LogWarning("Could not find any versions of package {PackageId} to revalidate", packageId);
188216

189217
continue;
190218
}
@@ -205,7 +233,7 @@ private async Task InitializeRevalidationsAsync(
205233
}
206234
}
207235

208-
await _packageState.AddPackageRevalidationsAsync(revalidations);
236+
await packageState.AddPackageRevalidationsAsync(revalidations);
209237
}
210238
}
211239
}

src/NuGet.Services.Revalidate/Services/PackageRevalidationStateService.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public PackageRevalidationStateService(IValidationEntitiesContext context, ILogg
2525

2626
public async Task AddPackageRevalidationsAsync(IReadOnlyList<PackageRevalidation> revalidations)
2727
{
28+
_logger.LogDebug("Persisting package revalidations to database...");
29+
2830
var validationContext = _context as ValidationEntitiesContext;
2931

3032
if (validationContext != null)
@@ -38,13 +40,19 @@ public async Task AddPackageRevalidationsAsync(IReadOnlyList<PackageRevalidation
3840
_context.PackageRevalidations.Add(revalidation);
3941
}
4042

43+
_logger.LogDebug("Saving the validation context...");
44+
4145
await _context.SaveChangesAsync();
4246

47+
_logger.LogDebug("Finished saving the validation context...");
48+
4349
if (validationContext != null)
4450
{
4551
validationContext.Configuration.AutoDetectChangesEnabled = true;
4652
validationContext.Configuration.ValidateOnSaveEnabled = true;
4753
}
54+
55+
_logger.LogDebug("Finished persisting package revalidations to database...");
4856
}
4957

5058
public async Task<int> RemovePackageRevalidationsAsync(int max)

tests/NuGet.Services.Revalidate.Tests/Initializer/InitializationManagerFacts.cs

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Threading.Tasks;
8+
using Microsoft.Extensions.DependencyInjection;
89
using Microsoft.Extensions.Logging;
910
using Moq;
1011
using NuGet.Services.Validation;
@@ -22,7 +23,7 @@ public class TheInitializeAsyncMethod : FactsBase
2223
public async Task ThrowsIfAlreadyInitialized()
2324
{
2425
// Arrange
25-
_settings.Setup(s => s.IsInitializedAsync()).ReturnsAsync(true);
26+
_jobState.Setup(s => s.IsInitializedAsync()).ReturnsAsync(true);
2627

2728
// Act & Assert
2829
var e = await Assert.ThrowsAsync<InvalidOperationException>(() => _target.InitializeAsync());
@@ -223,39 +224,43 @@ public async Task PartitionsPackagesIntoBatchesOf1000OrLessVersions(int[] packag
223224
_packageState.Verify(
224225
s => s.AddPackageRevalidationsAsync(It.IsAny<IReadOnlyList<PackageRevalidation>>()),
225226
Times.Exactly(expectedBatches));
227+
228+
// A scope should be created for each package set. Also, a scope should be created
229+
// for each batch.
230+
_scopeFactory.Verify(f => f.CreateScope(), Times.Exactly(4 + expectedBatches));
226231
}
227232

228233
public static IEnumerable<object[]> PartitionsPackagesIntoBatchesOf1000OrLessVersionsData()
229234
{
230235
yield return new object[]
231236
{
232237
new[] { 1001 },
233-
1
238+
1,
234239
};
235240

236241
yield return new object[]
237242
{
238243
new[] { 1, 1001, 1 },
239-
3
244+
3,
240245
};
241246

242247
// Should be batched into two batches of 501 items.
243248
yield return new object[]
244249
{
245250
new[] { 1, 500, 500, 1 },
246-
2
251+
2,
247252
};
248253

249254
yield return new object[]
250255
{
251256
new[] { 500, 500 },
252-
1
257+
1,
253258
};
254259

255260
yield return new object[]
256261
{
257262
Enumerable.Repeat(1, 1000).ToArray(),
258-
1
263+
1,
259264
};
260265
}
261266

@@ -277,15 +282,15 @@ public async Task MarksAsInitializedAfterAddingRevalidations()
277282
.Callback(() => addRevalidationOrder = order++)
278283
.Returns(Task.CompletedTask);
279284

280-
_settings
285+
_jobState
281286
.Setup(s => s.MarkAsInitializedAsync())
282287
.Callback(() => markAsInitializedOrder = order++)
283288
.Returns(Task.CompletedTask);
284289

285290
// Act & Assert
286291
await _target.InitializeAsync();
287292

288-
_settings.Verify(s => s.MarkAsInitializedAsync(), Times.Once);
293+
_jobState.Verify(s => s.MarkAsInitializedAsync(), Times.Once);
289294

290295
Assert.True(markAsInitializedOrder > addRevalidationOrder);
291296
}
@@ -396,7 +401,7 @@ public class TheVerifyAsyncMethod : FactsBase
396401
[Fact]
397402
public async Task ThrowsIfNotInitialized()
398403
{
399-
_settings.Setup(s => s.IsInitializedAsync()).ReturnsAsync(false);
404+
_jobState.Setup(s => s.IsInitializedAsync()).ReturnsAsync(false);
400405

401406
var e = await Assert.ThrowsAsync<Exception>(() => _target.VerifyInitializationAsync());
402407

@@ -406,7 +411,7 @@ public async Task ThrowsIfNotInitialized()
406411
[Fact]
407412
public async Task ThrowsIfAppropriatePackageCountDoesNotMatchRevalidationCount()
408413
{
409-
_settings.Setup(s => s.IsInitializedAsync()).ReturnsAsync(true);
414+
_jobState.Setup(s => s.IsInitializedAsync()).ReturnsAsync(true);
410415
_packageFinder.Setup(f => f.AppropriatePackageCount()).Returns(100);
411416
_packageState.Setup(s => s.PackageRevalidationCountAsync()).ReturnsAsync(50);
412417

@@ -418,7 +423,7 @@ public async Task ThrowsIfAppropriatePackageCountDoesNotMatchRevalidationCount()
418423
[Fact]
419424
public async Task DoesNotThrowIfCountsMatch()
420425
{
421-
_settings.Setup(s => s.IsInitializedAsync()).ReturnsAsync(true);
426+
_jobState.Setup(s => s.IsInitializedAsync()).ReturnsAsync(true);
422427
_packageFinder.Setup(f => f.AppropriatePackageCount()).Returns(100);
423428
_packageState.Setup(s => s.PackageRevalidationCountAsync()).ReturnsAsync(100);
424429

@@ -428,25 +433,39 @@ public async Task DoesNotThrowIfCountsMatch()
428433

429434
public class FactsBase
430435
{
431-
public readonly Mock<IRevalidationJobStateService> _settings;
436+
public readonly Mock<IRevalidationJobStateService> _jobState;
432437
public readonly Mock<IPackageRevalidationStateService> _packageState;
433438
public readonly Mock<IPackageFinder> _packageFinder;
439+
public readonly Mock<IServiceScopeFactory> _scopeFactory;
434440

435441
public readonly InitializationConfiguration _config;
436442
public readonly InitializationManager _target;
437443

438444
public FactsBase()
439445
{
440-
_settings = new Mock<IRevalidationJobStateService>();
446+
_jobState = new Mock<IRevalidationJobStateService>();
441447
_packageState = new Mock<IPackageRevalidationStateService>();
442448
_packageFinder = new Mock<IPackageFinder>();
449+
_scopeFactory = new Mock<IServiceScopeFactory>();
450+
451+
var scope = new Mock<IServiceScope>();
452+
var serviceProvider = new Mock<IServiceProvider>();
453+
454+
serviceProvider.Setup(p => p.GetService(typeof(IRevalidationJobStateService))).Returns(_jobState.Object);
455+
serviceProvider.Setup(p => p.GetService(typeof(IPackageRevalidationStateService))).Returns(_packageState.Object);
456+
serviceProvider.Setup(p => p.GetService(typeof(IPackageFinder))).Returns(_packageFinder.Object);
457+
serviceProvider.Setup(p => p.GetService(typeof(IServiceScopeFactory))).Returns(_scopeFactory.Object);
458+
459+
scope.Setup(s => s.ServiceProvider).Returns(serviceProvider.Object);
460+
_scopeFactory.Setup(s => s.CreateScope()).Returns(scope.Object);
443461

444462
_config = new InitializationConfiguration();
445463

446464
_target = new InitializationManager(
447-
_settings.Object,
465+
_jobState.Object,
448466
_packageState.Object,
449467
_packageFinder.Object,
468+
_scopeFactory.Object,
450469
_config,
451470
Mock.Of<ILogger<InitializationManager>>());
452471
}

0 commit comments

Comments
 (0)