Repository navigation
[DurableTask.ServiceBus] Fix blob container listing and purge for hub names with dashes and culture-dependent container names - #1424
Conversation
…ent container names
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
🟡 Changes recommended
Purging a hub can delete another hub’s overflow containers when their naming prefixes overlap.
1 open issue
What changed in this PR
This PR fixes Service Bus overflow blob naming and expiration handling for dashed hub names and culture-dependent formatting, with regression tests and small test utilities cleanups.
Changes:
- Parse expiration suffixes from the end and use invariant date formatting and lowercasing.
- Tighten suffix validation and add regression tests for dashed hubs, cultures, and invalid characters.
- Return
-1for missing queues and replace fake test credentials with a token credential stub.
| File | Description |
|---|---|
| test/DurableTask.ServiceBus.Tests/WaitForOrchestrationTests.cs | Replace the fake connection string with a credential stub. |
| test/DurableTask.ServiceBus.Tests/NoOpTokenCredential.cs | Add a credential stub for offline tests. |
| test/DurableTask.ServiceBus.Tests/BlobStorageClientHelperTest.cs | Add naming, expiration, and suffix validation tests. |
| src/DurableTask.ServiceBus/Tracking/BlobStorageClientHelper.cs | Fix dashed-name parsing, invariant formatting, and suffix validation. |
| src/DurableTask.ServiceBus/Tracking/BlobStorageClient.cs | Delegate lowercasing to the invariant helper. |
| src/DurableTask.ServiceBus/ServiceBusOrchestrationService.cs | Handle missing queues in delivery-count lookup. |
Review effort: Balanced
Bernd Verst (berndverst)
left a comment
There was a problem hiding this comment.
Requesting changes for two upgrade-compatibility regressions involving containers written by the previous culture-dependent formatter. Newly enabling expiry for dashed hub names can delete payloads whose actual dates are newer than the purge cutoff, while Gregorian-only ownership validation prevents complete hub deletion for some legitimate legacy containers.
Both cases reproduce when comparing the base helper with this HEAD. The 30 targeted net8.0 blob-helper and wait-for-orchestration tests pass, but do not cover these legacy-calendar cases. Storage-backed integration tests and net48 were not run.
…urge-invariant-culture-name
There was a problem hiding this comment.
🟡 Changes recommended
The description, upgrade notes, and stated test coverage need to clarify that eight-digit legacy suffixes are accepted and their containers can be deleted even when the suffix is not a valid Gregorian date.
1 open issue
Review effort: Balanced


Summary
Fixes how
DurableTask.ServiceBusnames, lists and purges overflow blob containers, so that:Also includes two small test-related cleanups.
Fixes
Expired containers were never purged for hub names with dashes
Overflow containers are named
{hubName}-dtfx-{type}-{date}.BlobStorageClientHelper.IsContainerExpiredrequired exactly 4 dash-separated segments, so every container of a hub likemy-hubwas skipped.PurgeOrchestrationHistoryAsyncnever deleted its overflow blobs, and storage grew without limit.The check now reads the fixed segments from the end and requires the
dtfxmarker, so any hub name works. A container is expired only if its date:yyyyMMdddate (invariant culture);Containers whose dates fail these checks are skipped and a warning is traced.
Container listing included other hubs' containers
BlobStorageClient.ListContainersAsyncfiltered by the{hubName}-dtfxprefix only. Because hub names can contain dashes, this returned:alpha, it includedalpha-dtfx-backup-dtfx-session-20150516, which belongs to hubalpha-dtfx-backup;alpha-dtfx-session-backup.Deleting a hub (
DeleteBlobStoreContainersAsync) therefore also deleted them, including another hub's overflow messages and session state.ListContainersAsyncnow returns only containers owned by this hub (new internalBlobStorageClientHelper.IsContainerOwnedByHub):-{type}-{date}must equal this hub's{hubName}-dtfxprefix;Ownership deliberately does not require a valid Gregorian date, so that containers written by earlier versions remain visible to full-store deletion (see below). Any 6–8 digit suffix, e.g.
alpha-dtfx-session-20150230, is therefore owned by the hub and deleted byDeleteBlobStoreContainersAsync, but never purged by date.Both
DeleteExpiredContainersAsyncandDeleteBlobStoreContainersAsyncuse this listing.Container names depended on the host culture
th-TH2558xxxxfor 2015. Dates are now written withCultureInfo.InvariantCulture. Blob reads and writes are not affected, because the blob key carries the exact container name.ToLower(). Ontr-TR,Ibecomes a dotlessı, which is not valid in a container name. Now usesToLowerInvariant(), and a redundantToLower()inBlobStorageClientis removed.Legacy calendar dates
The invariant formatter only changes newly generated names. Containers created by earlier versions, and the message keys that still reference them, keep the old culture-dependent date. Examples for a message fired on 2026-11-06:
ar-SA(Um Al Qura)14480525fa-IR(Persian)14050815th-TH(Thai Buddhist)25691106ja-JP+JapaneseCalendar081106zh-TW+TaiwanCalendar1151106Japanese and Taiwan calendars write the era year with as few as 2 digits, hence the 6–8 digit ownership rule. Some Thai dates are not valid Gregorian dates, e.g.
25670229for 2024-02-29.To avoid deleting payloads that may still be referenced:
DeleteBlobStoreContainersAsync/AzureStorageBlobStore.DeleteStoreAsyncremoves them, as the previous prefix-based deletion did.Container name suffix validation accepted invalid characters
The regex
@"^[a-z0-9\\-]+$"also matched a backslash (\\in a verbatim string is a literal backslash), and$matched before a trailing\n. It is now^[a-z0-9-]+\z, which accepts only lowercase letters, digits and dashes. It is built once as a static field.Cleanups
ServiceBusOrchestrationService.GetHubQueueMaxDeliveryCountsAsync(internal, used only by tests):Single→SingleOrDefault, so a missing queue returns the intended-1instead of throwing.WaitForOrchestrationTestsno longer uses a fake connection string containingSharedAccessKey=..., which could trigger secret scanners. It now uses a new sharedNoOpTokenCredentialtest helper with the host-name +TokenCredentialconstructor.Behaviour changes / upgrade notes
BlobStorageClient.ListContainersAsync(public) no longer returns:{hubName}-dtfx-{type}-{date}with a 6–8 digit date. The framework never creates these, but any created manually are no longer listed or deleted with the hub.TaskHubClient.PurgeOrchestrationHistoryAsync. For hubs whose names contain dashes, the first purge after upgrading deletes the backlog of the hub's own overflow containers older than the threshold, which may be a large one-off batch.AzureTableInstanceStoreneeds a dash-free hub name; that is unchanged.Tests
BlobStorageClientHelperTest, offline:IsContainerExpiredHubNameWithDashesTestIsContainerExpiredIgnoresLegacyNonGregorianContainersTest: suffixes generated with the old formatter underar-SA,fa-IR,th-TH(including25670229),JapaneseCalendarandTaiwanCalendar(including the pre-2011 6-digit form) are owned by the hub and never expire; the 2000-01-01 boundary.ContainerNamesAreCultureInvariantTest(th-TH,tr-TR)ParseKeyRejectsInvalidSuffixCharactersTest(backslash, trailing newline)IsContainerOwnedByHubTest: overlapping and dashed hubs, empty type, non-digit suffixes, 5- and 9-digit suffixes are rejected; non-Gregorian 6–8 digit suffixes are accepted.BlobStorageClientTest, storage-backed:TestDeleteExpiredContainersPreservesOverlappingHub: with hubsalphaandalpha-dtfx-backupin one account, purgingalphadeletes only its own expired container; the other hub's containers remain and its session blob is still readable.TestDeleteBlobStoreContainersPreservesOverlappingHub:alphalists only its own containers, and deleting the hub keeps the other hub's containers.TestDeleteBlobStoreContainersIgnoresMalformedContainers:{prefix}-session-backupand{prefix}-session-201505160are neither listed nor deleted with the hub.TestDeleteExpiredContainersPreservesLegacyNonGregorianContainers:ar-SA, Japanese and Taiwan containers survive a purge that deletes a Gregorian expired container; the legacy payload stays readable.TestDeleteBlobStoreContainersDeletesLegacyNonGregorianContainers: payloads inth-TH(25670229),ar-SA, Japanese (081106) and Taiwan (1151106) containers are readable, listed, and deleted with the hub.BlobStorageClientHelperTestBlobStorageClientTest(run against Azurite)ServiceBusUtilsTestsWaitForOrchestrationTests