Skip to content

Commit f1158bb

Browse files
committed
Fixes
1 parent 8991b52 commit f1158bb

File tree

7 files changed

+71
-17
lines changed

7 files changed

+71
-17
lines changed

build/dotnet8-compat/global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"sdk": {
3-
"version": "8.0.415"
3+
"version": "8.0.416"
44
}
55
}

src/Microsoft.Health.Fhir.Core.UnitTests/Features/Search/Registry/SearchParameterCacheRefreshBackgroundServiceTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ public async Task OnRefreshTimer_WhenCacheIsStale_ShouldCallGetAndApplySearchPar
203203
await _service.HandleAsync(new SearchParametersInitializedNotification(), CancellationToken.None);
204204

205205
// Wait for the timer to fire at least once and allow async operations to complete
206-
await Task.Delay(200);
206+
await Task.Delay(500);
207207

208208
// Assert - use at least 1 call since timer might fire multiple times in test environment
209209
await _searchParameterStatusManager.Received().EnsureCacheFreshnessAsync(Arg.Any<CancellationToken>());
@@ -224,7 +224,7 @@ public async Task OnRefreshTimer_WhenCacheIsFresh_ShouldNotCallGetAndApplySearch
224224
await _service.HandleAsync(new SearchParametersInitializedNotification(), CancellationToken.None);
225225

226226
// Wait for the timer to fire at least once and allow async operations to complete
227-
await Task.Delay(200);
227+
await Task.Delay(500);
228228

229229
// Assert - verify the timer is working and EnsureCacheFreshnessAsync was called
230230
await _searchParameterStatusManager.Received().EnsureCacheFreshnessAsync(Arg.Any<CancellationToken>());

src/Microsoft.Health.Fhir.Core/Features/Search/Registry/SearchParameterCacheRefreshBackgroundService.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ public class SearchParameterCacheRefreshBackgroundService : BackgroundService, I
3030
private readonly TimeSpan _refreshInterval;
3131
private readonly Timer _refreshTimer;
3232
private readonly SemaphoreSlim _refreshSemaphore;
33-
private bool _isInitialized;
33+
private volatile bool _isInitialized;
34+
private volatile bool _serviceStopped;
35+
private volatile bool _startAsyncCalled;
3436
private CancellationToken _stoppingToken;
3537

3638
public SearchParameterCacheRefreshBackgroundService(
@@ -57,9 +59,17 @@ public SearchParameterCacheRefreshBackgroundService(
5759
_refreshSemaphore = new SemaphoreSlim(1, 1);
5860
}
5961

62+
public override Task StartAsync(CancellationToken cancellationToken)
63+
{
64+
// Set flag and token before calling base.StartAsync
65+
// This ensures HandleAsync can check if the service was started and if it was cancelled
66+
_startAsyncCalled = true;
67+
_stoppingToken = cancellationToken;
68+
return base.StartAsync(cancellationToken);
69+
}
70+
6071
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
6172
{
62-
_stoppingToken = stoppingToken;
6373
_logger.LogInformation("SearchParameterCacheRefreshBackgroundService starting...");
6474

6575
try
@@ -92,6 +102,8 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
92102
}
93103
finally
94104
{
105+
_serviceStopped = true;
106+
95107
// Stop the timer when ExecuteAsync completes to prevent it from continuing to fire
96108
// after the service has stopped and potentially after service provider disposal
97109
_refreshTimer?.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
@@ -173,8 +185,11 @@ public async Task HandleAsync(SearchParametersInitializedNotification notificati
173185

174186
_isInitialized = true;
175187

176-
// Only start the timer if the service hasn't been cancelled
177-
if (!_stoppingToken.IsCancellationRequested)
188+
// Only start the timer if:
189+
// 1. Service hasn't been stopped, AND
190+
// 2. Either StartAsync was never called OR it was called but not cancelled
191+
// This prevents starting the timer when StartAsync was called with a cancelled token
192+
if (!_serviceStopped && (!_startAsyncCalled || !_stoppingToken.IsCancellationRequested))
178193
{
179194
// Start the timer now that search parameters are initialized
180195
_refreshTimer.Change(TimeSpan.Zero, _refreshInterval);

src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosContainerProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ public Task StartAsync(CancellationToken cancellationToken)
177177
// The result is ignored and will be awaited in EnsureInitialized(). Exceptions are logged within CosmosClientInitializer.
178178
_initializationOperation.EnsureInitialized()
179179
.AsTask()
180-
.ContinueWith(_ => _mediator.PublishAsync(new StorageInitializedNotification(), CancellationToken.None), TaskScheduler.Default);
180+
.ContinueWith(_ => _mediator.PublishAsync(new StorageInitializedNotification(), CancellationToken.None), TaskScheduler.Default)
181+
.Unwrap();
181182
#pragma warning restore CS4014
182183

183184
return Task.CompletedTask;

src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Search/SearchParameters/SearchParameterBehaviorTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public async Task GivenAnUpsertResourceRequest_WhenSearchParameterDoesNotExist_T
172172
var response = new UpsertResourceResponse(new SaveOutcome(new RawResourceElement(wrapper), SaveOutcomeType.Created));
173173

174174
var behavior = new CreateOrUpdateSearchParameterBehavior<UpsertResourceRequest, UpsertResourceResponse>(_searchParameterOperations, _fhirDataStore);
175-
await behavior.Handle(request, async (ct) => await Task.Run(() => response), CancellationToken.None);
175+
await behavior.HandleAsync(request, async () => await Task.Run(() => response), CancellationToken.None);
176176

177177
// Should call AddSearchParameterAsync since the resource doesn't exist
178178
await _searchParameterOperations.Received().AddSearchParameterAsync(Arg.Any<ITypedElement>(), Arg.Any<CancellationToken>());
@@ -199,7 +199,7 @@ public async Task GivenAnUpsertResourceRequest_WhenSearchParameterExists_ThenUpd
199199
var response = new UpsertResourceResponse(new SaveOutcome(new RawResourceElement(newWrapper), SaveOutcomeType.Updated));
200200

201201
var behavior = new CreateOrUpdateSearchParameterBehavior<UpsertResourceRequest, UpsertResourceResponse>(_searchParameterOperations, _fhirDataStore);
202-
await behavior.Handle(request, async (ct) => await Task.Run(() => response), CancellationToken.None);
202+
await behavior.HandleAsync(request, async () => await Task.Run(() => response), CancellationToken.None);
203203

204204
// Should call UpdateSearchParameterAsync since the resource exists
205205
await _searchParameterOperations.DidNotReceive().AddSearchParameterAsync(Arg.Any<ITypedElement>(), Arg.Any<CancellationToken>());

src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Validation/ValidateCapabilityPreProcessorTests.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// -------------------------------------------------------------------------------------------------
55

66
using System;
7+
using System.Net.Http;
78
using System.Threading;
89
using Hl7.Fhir.ElementModel;
910
using Hl7.Fhir.Model;
@@ -48,8 +49,10 @@ public async Task GivenARequest_WhenValidatingCapability_ThenAllValidationRulesS
4849
var preProcessor = new ValidateCapabilityPreProcessor<GetResourceRequest, GetResourceResponse>(_conformanceProvider);
4950

5051
var getResourceRequest = new GetResourceRequest("Observation", Guid.NewGuid().ToString(), bundleResourceContext: null);
52+
var resource = Samples.GetDefaultObservation().UpdateId("observation1");
53+
var mockResponse = new GetResourceResponse(CreateRawResourceElement(resource));
5154

52-
await preProcessor.HandleAsync(getResourceRequest, null, CancellationToken.None);
55+
await preProcessor.HandleAsync(getResourceRequest, () => Task.FromResult(mockResponse), CancellationToken.None);
5356
}
5457

5558
[Theory]
@@ -67,5 +70,20 @@ await Assert.ThrowsAsync<MethodNotAllowedException>(
6770
() => Task.FromResult(new DeleteResourceResponse(new ResourceKey("Observation", "test-id"))),
6871
CancellationToken.None));
6972
}
73+
74+
private static RawResourceElement CreateRawResourceElement(ResourceElement resource)
75+
{
76+
var rawResource = new RawResource("data", FhirResourceFormat.Json, isMetaSet: true);
77+
var wrapper = new ResourceWrapper(
78+
resource,
79+
rawResource,
80+
new ResourceRequest(HttpMethod.Post, "http://fhir"),
81+
deleted: false,
82+
searchIndices: null,
83+
compartmentIndices: null,
84+
lastModifiedClaims: null);
85+
86+
return new RawResourceElement(wrapper);
87+
}
7088
}
7189
}

src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Validation/ValidateRequestPreProcessorTests.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
// -------------------------------------------------------------------------------------------------
55

6+
using System.Net.Http;
67
using System.Threading;
78
using System.Threading.Tasks;
89
using FluentValidation;
910
using FluentValidation.Results;
1011
using Medino;
12+
using Microsoft.Health.Fhir.Core.Extensions;
1113
using Microsoft.Health.Fhir.Core.Features.Persistence;
1214
using Microsoft.Health.Fhir.Core.Features.Validation;
1315
using Microsoft.Health.Fhir.Core.Messages.Upsert;
@@ -31,8 +33,9 @@ public async Task GivenARequest_WhenValidatingThatType_ThenAllValidationRulesSho
3133

3234
var validators = new[] { mockValidator1, mockValidator2 };
3335
var upsertValidationHandler = new ValidateRequestPreProcessor<UpsertResourceRequest, UpsertResourceResponse>(validators);
34-
var upsertResourceRequest = new UpsertResourceRequest(Samples.GetDefaultObservation());
35-
var mockResponse = new UpsertResourceResponse(new SaveOutcome(null, SaveOutcomeType.Created));
36+
var resource = Samples.GetDefaultObservation().UpdateId("observation1");
37+
var upsertResourceRequest = new UpsertResourceRequest(resource);
38+
var mockResponse = new UpsertResourceResponse(new SaveOutcome(CreateRawResourceElement(resource), SaveOutcomeType.Created));
3639

3740
await upsertValidationHandler.HandleAsync(
3841
upsertResourceRequest,
@@ -55,7 +58,8 @@ public async Task GivenARequest_WhenValidatingThatTypeWithFailingRule_ThenAValid
5558

5659
var validators = new[] { mockValidator1, mockValidator2 };
5760
var upsertValidationHandler = new ValidateRequestPreProcessor<UpsertResourceRequest, UpsertResourceResponse>(validators);
58-
var upsertResourceRequest = new UpsertResourceRequest(Samples.GetDefaultObservation());
61+
var resource = Samples.GetDefaultObservation().UpdateId("observation1");
62+
var upsertResourceRequest = new UpsertResourceRequest(resource);
5963

6064
var validationError = Task.FromResult(new ValidationResult(new[] { new ValidationFailure("Id", "Id should not be null") }));
6165

@@ -68,7 +72,7 @@ public async Task GivenARequest_WhenValidatingThatTypeWithFailingRule_ThenAValid
6872
await Assert.ThrowsAsync<ResourceNotValidException>(
6973
async () => await upsertValidationHandler.HandleAsync(
7074
upsertResourceRequest,
71-
() => Task.FromResult(new UpsertResourceResponse(new SaveOutcome(null, SaveOutcomeType.Created))),
75+
() => Task.FromResult(new UpsertResourceResponse(new SaveOutcome(CreateRawResourceElement(resource), SaveOutcomeType.Created))),
7276
CancellationToken.None));
7377
}
7478

@@ -79,7 +83,8 @@ public async Task GivenARequest_WhenReturningFhirValidationFailure_ThenOperation
7983

8084
var validators = new[] { mockValidator };
8185
var upsertValidationHandler = new ValidateRequestPreProcessor<UpsertResourceRequest, UpsertResourceResponse>(validators);
82-
var upsertResourceRequest = new UpsertResourceRequest(Samples.GetDefaultObservation());
86+
var resource = Samples.GetDefaultObservation().UpdateId("observation1");
87+
var upsertResourceRequest = new UpsertResourceRequest(resource);
8388

8489
var operationOutcomeIssue = new OperationOutcomeIssue(
8590
OperationOutcomeConstants.IssueSeverity.Error,
@@ -98,10 +103,25 @@ public async Task GivenARequest_WhenReturningFhirValidationFailure_ThenOperation
98103
var exception = await Assert.ThrowsAsync<ResourceNotValidException>(
99104
async () => await upsertValidationHandler.HandleAsync(
100105
upsertResourceRequest,
101-
() => Task.FromResult(new UpsertResourceResponse(new SaveOutcome(null, SaveOutcomeType.Created))),
106+
() => Task.FromResult(new UpsertResourceResponse(new SaveOutcome(CreateRawResourceElement(resource), SaveOutcomeType.Created))),
102107
CancellationToken.None));
103108

104109
Assert.Contains(operationOutcomeIssue, exception.Issues);
105110
}
111+
112+
private static RawResourceElement CreateRawResourceElement(ResourceElement resource)
113+
{
114+
var rawResource = new RawResource("data", FhirResourceFormat.Json, isMetaSet: true);
115+
var wrapper = new ResourceWrapper(
116+
resource,
117+
rawResource,
118+
new ResourceRequest(HttpMethod.Post, "http://fhir"),
119+
deleted: false,
120+
searchIndices: null,
121+
compartmentIndices: null,
122+
lastModifiedClaims: null);
123+
124+
return new RawResourceElement(wrapper);
125+
}
106126
}
107127
}

0 commit comments

Comments
 (0)