Skip to content

Fix IOException in MSSQL pipeline by disposing config file watchers#3243

Open
aaronburtle wants to merge 9 commits intomainfrom
dev/aaronburtle/MSSQLIntegrationTestPipelineCleanupFix
Open

Fix IOException in MSSQL pipeline by disposing config file watchers#3243
aaronburtle wants to merge 9 commits intomainfrom
dev/aaronburtle/MSSQLIntegrationTestPipelineCleanupFix

Conversation

@aaronburtle
Copy link
Contributor

@aaronburtle aaronburtle commented Mar 12, 2026

Why make this change?

Closes #2992

Several configuration tests create a TestServer that sets up a ConfigFileWatcher to support hot-reload. When the file watcher detects a change, its callback reads the file, holding a read handle. Neither ConfigFileWatcher nor FileSystemRuntimeConfigLoader implemented IDisposable, so when the TestServer is disposed, the DI container could not clean up the file watcher. The watcher remained active, and if its callback was mid-read when CleanupAfterEachTest called File.Delete, the delete failed with an IOException

What is this change?

ConfigFileWatcher now implements IDisposable. It stops raising events, unsubscribes the Changed handler, and disposes the underlying IFileSystemWatcher.

FileSystemRuntimeConfigLoader now implements IDisposable. It disposes the owned ConfigFileWatcher. As a DI singleton, it is now automatically disposed when the TestServer shuts down.

CleanupAfterEachTest retries with exponential backoff. As a safety net, File.Delete is retried up to 3 times on IOException (2s, 4s, 8s delays), matching the existing retry pattern used elsewhere.

How was this tested?

Run against the pipeline. Several pipeline runs were initiated to add certainty that the fix is working, since this issue is intermittent.

https://dev.azure.com/sqldab/Data%20API%20builder%20Dependency%20Packages/_build/results?buildId=18957&view=results

https://dev.azure.com/sqldab/Data%20API%20builder%20Dependency%20Packages/_build/results?buildId=18958&view=results

https://dev.azure.com/sqldab/Data%20API%20builder%20Dependency%20Packages/_build/results?buildId=18952&view=results

https://dev.azure.com/sqldab/Data%20API%20builder%20Dependency%20Packages/_build/results?buildId=18959&view=results

https://dev.azure.com/sqldab/Data%20API%20builder%20Dependency%20Packages/_build/results?buildId=18960&view=results

@aaronburtle
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses intermittent MSSQL pipeline test failures caused by lingering config file watchers holding file handles during test cleanup, leading to IOException on File.Delete. It introduces explicit disposal of config file watcher resources and adds a retry-based safety net in test cleanup.

Changes:

  • Implement IDisposable on ConfigFileWatcher to stop events, unsubscribe handlers, and dispose the underlying watcher.
  • Implement IDisposable on FileSystemRuntimeConfigLoader so DI can dispose the owned ConfigFileWatcher when the test server shuts down.
  • Add exponential backoff retries around test config file deletion in CleanupAfterEachTest.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/Service.Tests/Configuration/ConfigurationTests.cs Adds retry/backoff around deleting the custom config file during test cleanup to reduce flakiness from transient file locks.
src/Config/FileSystemRuntimeConfigLoader.cs Makes the loader disposable so the DI container can clean up the hot-reload watcher on shutdown.
src/Config/ConfigFileWatcher.cs Makes the watcher disposable to stop notifications and release OS resources/file handles.

aaronburtle and others added 3 commits March 12, 2026 19:32
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@aaronburtle
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

Copy link
Contributor

@souvikghosh04 souvikghosh04 left a comment

Choose a reason for hiding this comment

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

Approved with a follow-up comment

@@ -710,9 +710,25 @@ type Moon {
[TestCleanup]
public void CleanupAfterEachTest()
Copy link
Contributor

Choose a reason for hiding this comment

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

The true auto-dispose (as mentioned in PR description) of FileSystemRuntimeConfigLoader when TestServer shuts down can be done with using block. tests in ConfigurationTests.cs create TestServer without using. If the server isn't explicitly disposed, the DI container's IDisposable cleanup may never run and the file watcher could be undisposed. retry in CleanupAfterEachTest covers this as a safety net but the more correct fix would be ensuring those TestServer instances are disposed. This could be a follow-up PR if not possible in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, the 20 or so TestServer instances that don't have using won't trigger the auto-dispose, Ill follow up on this in a future PR to keep the scope of this one from growing, and the CleanupAfterEachTest should cover us in the meantime.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@aaronburtle,
If we dont add using in this same change, the PR's title is not serving its purpose. It is not disposing the file watchers. It is only implementing the dispose function.

I think all those FileSystemRuntimeConfigLoader should be actually disposed as well in this same PR.

@souvikghosh04
Copy link
Contributor

/azp run

@souvikghosh04 souvikghosh04 enabled auto-merge (squash) March 13, 2026 04:59
@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@aaronburtle
Copy link
Contributor Author

/azp run

… of github.com:Azure/data-api-builder into dev/aaronburtle/MSSQLIntegrationTestPipelineCleanupFix
@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@aaronburtle
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@aaronburtle
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@@ -710,9 +710,25 @@ type Moon {
[TestCleanup]
public void CleanupAfterEachTest()
Copy link
Collaborator

Choose a reason for hiding this comment

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

@aaronburtle,
If we dont add using in this same change, the PR's title is not serving its purpose. It is not disposing the file watchers. It is only implementing the dispose function.

I think all those FileSystemRuntimeConfigLoader should be actually disposed as well in this same PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Fix failing MS SQL Integration tests in the pipeline

4 participants