Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
de244ff
initial implementation
Mar 19, 2026
8f6bf9d
fixing the compilation errors
Mar 24, 2026
ea5a36f
addressing copilot comments
Mar 24, 2026
3ab0859
Merge branch 'main' into stevosyan/add-poison-message-handling
sophiatev Mar 24, 2026
4c7665d
fixing the error in the logger where I was incorrectly calling Discar…
Mar 24, 2026
3bd1dc9
moved the max dispatch count from IOrchestrationService to dispatch p…
Mar 25, 2026
2e3c7c2
updated the implementations to remove all exception-throwing in the c…
Apr 1, 2026
eecc077
comment updates
Apr 1, 2026
f0fc35d
fixed a typo, added an argument range check for the max dispatch count
Apr 1, 2026
ea7a67f
Apply suggestion from @cgillum
sophiatev Jun 2, 2026
c976817
Apply suggestions from code review
sophiatev Jun 2, 2026
559bb4d
redid the implementation to follow an interface format
Jun 3, 2026
68dbde9
mroe cleanup and PR comments
Jun 3, 2026
b9a875d
updated to wait for the async calls
Jun 3, 2026
8a77b40
Merge branch 'main' into stevosyan/add-poison-message-handling
Jun 3, 2026
c7d5803
fixing a bug related to the results count and something incorrect i h…
Jun 4, 2026
b091049
some more updates, adding a private 0 arg constructor to the rewind e…
Jun 10, 2026
868b856
Potential fix for pull request finding 'Nested 'if' statements can be…
sophiatev Jun 10, 2026
d13c7b3
Potential fix for pull request finding 'Nested 'if' statements can be…
sophiatev Jun 10, 2026
85ae250
updating the iteration logic in the entity dispatcher
Jun 10, 2026
7cbb931
addressing the first round of PR comments
Jun 12, 2026
756d592
missed changing one place from error to warning
Jun 12, 2026
d54925a
removed the IsPoisonMessage method in favor of MaxDispatchCount
Jun 12, 2026
2657e8d
updated variable names to be more descriptive
Jun 12, 2026
1fb9930
Potential fix for pull request finding 'Constant condition'
sophiatev Jun 19, 2026
7f5f923
Potential fix for pull request finding 'Constant condition'
sophiatev Jun 19, 2026
3607f36
Potential fix for pull request finding 'Constant condition'
sophiatev Jun 19, 2026
2f4490f
Potential fix for pull request finding 'Constant condition'
sophiatev Jun 19, 2026
1318aa6
remove another unnecessary null check
Jun 19, 2026
dff3c25
remove more unnecessary null checks
Jun 19, 2026
573e66c
Merge branch 'main' into stevosyan/add-poison-message-handling
Jul 13, 2026
30194ec
azure storage implementation done
Jul 15, 2026
a96de44
slight update to avoid looking for specific strings
Jul 15, 2026
3cc0261
added one more invalid work item test
Jul 16, 2026
41daecc
fixed bug in adding failure results for poison messages
Jul 16, 2026
21090e9
tiny test updates
Jul 16, 2026
ac6bf9e
fixing a bug related to storing poison messages for lock release mess…
Jul 17, 2026
9307a81
a few more stylistic things
Jul 17, 2026
d092621
fixed the tests
Jul 17, 2026
b279375
Potential fix for pull request finding 'Useless assignment to local v…
sophiatev Jul 17, 2026
ea0bdd4
Apply suggestions from code review
sophiatev Jul 17, 2026
0aff766
some more updates to the dispatchers based on copilot comments
Jul 18, 2026
630a6e3
slight update to only return a result if the entity request is not a …
Jul 20, 2026
e61360b
new queue implementation
Jul 22, 2026
0415720
addressing copilot comments
Jul 22, 2026
93c8053
Apply suggestions from code review
sophiatev Jul 22, 2026
7e1e6d5
slight updates
Jul 22, 2026
8885ae2
Merge branch 'stevosyan/add-poison-message-handling' of https://githu…
Jul 22, 2026
e39d294
added a comment
Jul 22, 2026
fe7902d
reverted some useful but unreltaed changes, added a sanitize blob nam…
Jul 22, 2026
29ba654
addressing PR comments
Jul 22, 2026
93d852f
settings updates, more blob name sanitation, moving to just one blob …
Jul 23, 2026
8746fcf
addressing copilot comments
Jul 23, 2026
74e531a
Potential fix for pull request finding
sophiatev Jul 23, 2026
738dc46
more copilot comments
Jul 23, 2026
9b6906a
tiny setting update to move the default dequeue count to 5000
Jul 24, 2026
e1d12bb
missed a comma
Jul 24, 2026
d577d4c
latest copilot comments
Jul 24, 2026
41092ac
hopefully the last tiny update to update the default task event ID to…
Jul 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,019 changes: 1,019 additions & 0 deletions Test/DurableTask.AzureStorage.Tests/PoisonMessageHandlingTests.cs

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/DurableTask.AzureStorage/AnalyticsEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public void DuplicateMessageDetected(
ExtensionVersion);
}

[Event(EventIds.PoisonMessageDetected, Level = EventLevel.Warning, Version = 3)]
[Event(EventIds.PoisonMessageDetected, Level = EventLevel.Warning, Version = 4)]
public void PoisonMessageDetected(
string Account,
string TaskHub,
Expand All @@ -288,6 +288,7 @@ public void PoisonMessageDetected(
string ExecutionId,
string PartitionId,
long DequeueCount,
string BlobName,
string AppName,
string ExtensionVersion)
{
Expand All @@ -302,6 +303,7 @@ public void PoisonMessageDetected(
ExecutionId ?? string.Empty,
PartitionId,
DequeueCount,
BlobName,
AppName,
ExtensionVersion);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ namespace DurableTask.AzureStorage
{
using System;
using System.Runtime.Serialization;
using Azure.Data.Tables;
using DurableTask.AzureStorage.Logging;
using DurableTask.AzureStorage.Partitioning;
Comment thread
sophiatev marked this conversation as resolved.
using DurableTask.Core;
Expand Down Expand Up @@ -311,5 +310,40 @@ internal LogHelper Logger
/// table must now be read before processing every orchestration work item to obtain the latest etag.
/// </remarks>
public bool UseInstanceTableEtag { get; set; } = false;

/// <summary>
/// Gets or sets a flag whether poison message storage is enabled.
/// </summary>
/// <remarks>
/// <para>
/// If enabled, orchestration, entity, and activity messages that have been dequeued for processing
/// more than <see cref="MaxDequeueCount"/> times will be moved to poison message storage in Azure Blob
/// Storage and deleted from their source queue.
/// This may leave orchestrations permanently as <see cref="OrchestrationStatus.Running"/> (or any other
/// non-terminal state) if the message(s) necessary for them make to progress are deemed "poisoned" and deleted.
/// </para>
/// </remarks>
public bool IsPoisonMessageStorageEnabled { get; set; } = true;

/// <summary>
/// Gets or sets the number of times a message is dequeued for processing before it is considered "poisoned",
/// moved to poison message storage, and deleted from the source queue. The default value is 5,000, chosen with
/// the intent that only very pathological cases will be handled automatically.
/// </summary>
/// <remarks>
/// This setting is applicable when <see cref="IsPoisonMessageStorageEnabled"/> is set to <c>true</c>.
/// </remarks>
public long MaxDequeueCount { get; set; } = 5000;

/// <summary>
/// Gets or sets the Azure Blob Storage container name suffix to use for poison message storage.
/// A container will be created with this suffix in the format "{taskhubname}-{suffix}".
/// The container name must adhere to the Azure Blob Storage container naming rules:
/// https://learn.microsoft.com/en-us/rest/api/storageservices/Naming-and-Referencing-Containers--Blobs--and-Metadata#directory-names
/// In particular, this means the total length of the name must not exceed 63 characters, the name can only contain lowercase letters,
/// numbers, and hyphens, must start and end with a letter or number, and cannot contain consecutive hyphens.
/// If not specified, the default value "poison-messages" will be used.
/// </summary>
public string PoisonMessageStorageContainerNameSuffix { get; set; } = "poison-messages";
Comment thread
sophiatev marked this conversation as resolved.
}
}
26 changes: 19 additions & 7 deletions src/DurableTask.AzureStorage/Logging/LogEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,8 @@ public PoisonMessageDetected(
string instanceId,
string executionId,
string partitionId,
long dequeueCount)
long dequeueCount,
string blobName)
{
this.Account = account;
this.TaskHub = taskHub;
Expand All @@ -677,6 +678,7 @@ public PoisonMessageDetected(
this.ExecutionId = executionId;
this.PartitionId = partitionId;
this.DequeueCount = dequeueCount;
this.BlobName = blobName;
}

[StructuredLogField]
Expand Down Expand Up @@ -706,18 +708,27 @@ public PoisonMessageDetected(
[StructuredLogField]
public long DequeueCount { get; }

[StructuredLogField]
public string BlobName { get; }

public override EventId EventId => new EventId(
EventIds.PoisonMessageDetected,
nameof(EventIds.PoisonMessageDetected));

public override LogLevel Level => LogLevel.Warning;

protected override string CreateLogMessage() => string.Format(
"{0}: Message {1} with ID {2} has been dequeued {3} times and is now considered poison",
this.InstanceId,
GetEventDescription(this.EventType, this.TaskEventId),
this.MessageId,
this.DequeueCount);
protected override string CreateLogMessage()
{
string message = string.Format(
"{0}: Message {1} with ID {2} has been dequeued {3} times and is now considered poison, stored in blob {4}",
this.InstanceId,
GetEventDescription(this.EventType, this.TaskEventId),
this.MessageId,
this.DequeueCount,
this.BlobName);

return message;
}

void IEventSourceEvent.WriteEventSource() => AnalyticsEventSource.Log.PoisonMessageDetected(
this.Account,
Expand All @@ -729,6 +740,7 @@ void IEventSourceEvent.WriteEventSource() => AnalyticsEventSource.Log.PoisonMess
this.ExecutionId,
this.PartitionId,
this.DequeueCount,
this.BlobName,
Utils.AppName,
Utils.ExtensionVersion);
}
Expand Down
6 changes: 4 additions & 2 deletions src/DurableTask.AzureStorage/Logging/LogHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ internal void PoisonMessageDetected(
string instanceId,
string executionId,
string partitionId,
long dequeueCount)
long dequeueCount,
string blobName)
{
var logEvent = new LogEvents.PoisonMessageDetected(
account,
Expand All @@ -247,7 +248,8 @@ internal void PoisonMessageDetected(
instanceId,
executionId,
partitionId,
dequeueCount);
dequeueCount,
blobName);
this.WriteStructuredLog(logEvent);
}

Expand Down
19 changes: 19 additions & 0 deletions src/DurableTask.AzureStorage/Messaging/ControlQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,31 @@ await batch.ParallelForEachAsync(async delegate (QueueMessage queueMessage)
0 /* TaskEventId */,
e.ToString());

if (await this.CheckForAndHandlePoisonMessageAsync(
blobNamePrefix: "instance-messages/",
queueMessage,
linkedCts.Token))
{
return;
}

// Abandon the message so we can try it again later.
// Note: We will fetch the message again from the queue before retrying, so no need to read the receipt
_ = await this.AbandonMessageAsync(queueMessage);
return;
}

if (await this.CheckForAndHandlePoisonMessageAsync(
blobNamePrefix: "instance-messages/",
queueMessage,
linkedCts.Token,
messageData.TaskMessage.OrchestrationInstance,
messageData.TaskMessage.Event.EventType.ToString(),
Utils.GetTaskEventId(messageData.TaskMessage.Event)))
{
return;
}

// Check to see whether we've already dequeued this message.
if (!this.stats.PendingOrchestratorMessages.TryAdd(queueMessage.MessageId, 1))
{
Expand Down
Loading
Loading