Skip to content

Commit 5ad4b49

Browse files
authored
Merge pull request #4839 from Particular/john/remove_header
Remove header instead
2 parents 85928c9 + c9419f9 commit 5ad4b49

File tree

5 files changed

+243
-2
lines changed

5 files changed

+243
-2
lines changed

src/ServiceControl.AcceptanceTesting/HttpExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public static async Task<ManyResult<T>> TryGetMany<T>(this IAcceptanceTestInfras
4747
return ManyResult<T>.Empty;
4848
}
4949

50-
return ManyResult<T>.New(true, response);
50+
return ManyResult<T>.New(true, response.Where(m => condition(m)).ToList());
5151
}
5252

5353
public static async Task<HttpStatusCode> Patch<T>(this IAcceptanceTestInfrastructureProvider provider, string url, T payload = null) where T : class
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace ServiceControl.MultiInstance.AcceptanceTests.Recoverability;
2+
3+
using System.Threading.Tasks;
4+
using AcceptanceTesting;
5+
using MessageFailures;
6+
using MessageFailures.Api;
7+
using TestSupport;
8+
9+
abstract class WhenRetrying : AcceptanceTest
10+
{
11+
protected Task<SingleResult<FailedMessage>> GetFailedMessage(string uniqueMessageId, string instance, FailedMessageStatus expectedStatus)
12+
{
13+
if (uniqueMessageId == null)
14+
{
15+
return Task.FromResult(SingleResult<FailedMessage>.Empty);
16+
}
17+
18+
return this.TryGet<FailedMessage>($"/api/errors/{uniqueMessageId}", f => f.Status == expectedStatus, instance);
19+
}
20+
21+
protected Task<ManyResult<FailedMessageView>> GetAllFailedMessage(string instance, FailedMessageStatus expectedStatus) => this.TryGetMany<FailedMessageView>("/api/errors", f => f.Status == expectedStatus, instance);
22+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
namespace ServiceControl.MultiInstance.AcceptanceTests.Recoverability
2+
{
3+
using System;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using AcceptanceTesting;
7+
using AcceptanceTesting.EndpointTemplates;
8+
using MessageFailures;
9+
using NServiceBus;
10+
using NServiceBus.AcceptanceTesting;
11+
using NServiceBus.Settings;
12+
using NUnit.Framework;
13+
using ServiceControl.Infrastructure;
14+
using TestSupport;
15+
16+
class WhenRetryingSameMessageMultipleTimes : WhenRetrying
17+
{
18+
public enum RetryType
19+
{
20+
NoEdit,
21+
Edit
22+
}
23+
24+
[TestCase(new[] { RetryType.NoEdit, RetryType.NoEdit, RetryType.Edit })]
25+
[TestCase(new[] { RetryType.Edit, RetryType.NoEdit, RetryType.Edit })]
26+
[TestCase(new[] { RetryType.NoEdit, RetryType.Edit, RetryType.NoEdit })]
27+
[TestCase(new[] { RetryType.Edit, RetryType.Edit, RetryType.NoEdit })]
28+
public async Task WithMixOfRetryTypes(RetryType[] retryTypes)
29+
{
30+
CustomServiceControlPrimarySettings = s => { s.AllowMessageEditing = true; };
31+
32+
await Define<MyContext>()
33+
.WithEndpoint<FailureEndpoint>(b =>
34+
b.When(bus => bus.SendLocal(new MyMessage())).DoNotFailOnErrorMessages())
35+
.Done(async c =>
36+
{
37+
if (c.RetryCount >= retryTypes.Length) // Are all retries done?
38+
{
39+
return !(await GetAllFailedMessage(ServiceControlInstanceName, FailedMessageStatus.Unresolved))
40+
.HasResult; // Should return true if all failed messages are no longer unresolved
41+
}
42+
43+
if (retryTypes[c.RetryCount] == RetryType.Edit)
44+
{
45+
var results = await GetAllFailedMessage(ServiceControlInstanceName,
46+
FailedMessageStatus.Unresolved);
47+
if (!results.HasResult)
48+
{
49+
return false; // No failed messages yet
50+
}
51+
52+
var result = results.Items.Single();
53+
54+
c.MessageId = result.MessageId;
55+
}
56+
57+
var failedMessage = await GetFailedMessage(c.UniqueMessageId, ServiceControlInstanceName, FailedMessageStatus.Unresolved);
58+
if (!failedMessage.HasResult)
59+
{
60+
return false; // No failed message yet
61+
}
62+
63+
if (retryTypes[c.RetryCount] == RetryType.Edit)
64+
{
65+
await this.Post<object>($"/api/edit/{failedMessage.Item.UniqueMessageId}",
66+
new
67+
{
68+
MessageBody = $"{{ \"Name\": \"Hello {c.RetryCount}\" }}",
69+
MessageHeaders = failedMessage.Item.ProcessingAttempts[^1].Headers
70+
}, null,
71+
ServiceControlInstanceName);
72+
}
73+
else
74+
{
75+
await this.Post<object>($"/api/errors/{failedMessage.Item.UniqueMessageId}/retry", null, null,
76+
ServiceControlInstanceName);
77+
}
78+
79+
c.RetryCount++;
80+
81+
return false;
82+
83+
})
84+
.Run(TimeSpan.FromMinutes(2));
85+
}
86+
87+
class FailureEndpoint : EndpointConfigurationBuilder
88+
{
89+
public FailureEndpoint() => EndpointSetup<DefaultServerWithoutAudit>(c => { c.NoRetries(); });
90+
91+
public class MyMessageHandler(MyContext testContext, IReadOnlySettings settings)
92+
: IHandleMessages<MyMessage>
93+
{
94+
public Task Handle(MyMessage message, IMessageHandlerContext context)
95+
{
96+
testContext.MessageId = context.MessageId.Replace(@"\", "-");
97+
testContext.EndpointNameOfReceivingEndpoint = settings.EndpointName();
98+
99+
if (testContext.RetryCount < 3)
100+
{
101+
Console.Out.WriteLine("Throwing exception");
102+
throw new Exception("Simulated exception");
103+
}
104+
105+
Console.Out.WriteLine("Handling message");
106+
107+
return Task.CompletedTask;
108+
}
109+
}
110+
}
111+
112+
class MyMessage : ICommand
113+
{
114+
public string Name { get; set; }
115+
}
116+
117+
class MyContext : ScenarioContext
118+
{
119+
public string MessageId { get; set; }
120+
121+
public string EndpointNameOfReceivingEndpoint { get; set; }
122+
123+
public string UniqueMessageId => DeterministicGuid.MakeId(MessageId, EndpointNameOfReceivingEndpoint).ToString();
124+
public int RetryCount { get; set; }
125+
}
126+
}
127+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
namespace ServiceControl.MultiInstance.AcceptanceTests.Recoverability;
2+
3+
using System;
4+
using System.Threading.Tasks;
5+
using AcceptanceTesting;
6+
using AcceptanceTesting.EndpointTemplates;
7+
using MessageFailures;
8+
using NServiceBus;
9+
using NServiceBus.AcceptanceTesting;
10+
using NServiceBus.Settings;
11+
using NUnit.Framework;
12+
using TestSupport;
13+
using ServiceControl.Infrastructure;
14+
15+
class WhenRetryingWithEdit : WhenRetrying
16+
{
17+
[Test]
18+
public async Task ShouldCreateNewMessageAndResolveEditedMessage()
19+
{
20+
CustomServiceControlPrimarySettings = s => { s.AllowMessageEditing = true; };
21+
22+
await Define<MyContext>()
23+
.WithEndpoint<FailureEndpoint>(b =>
24+
b.When(bus => bus.SendLocal(new MyMessage { Password = "Bad password!" })).DoNotFailOnErrorMessages())
25+
.Done(async c =>
26+
{
27+
if (!c.ErrorRetried)
28+
{
29+
var failedMessage = await GetFailedMessage(c.UniqueMessageId, ServiceControlInstanceName,
30+
FailedMessageStatus.Unresolved);
31+
if (!failedMessage.HasResult)
32+
{
33+
return false; // No failed message yet
34+
}
35+
36+
await this.Post<object>($"/api/edit/{failedMessage.Item.UniqueMessageId}",
37+
new
38+
{
39+
MessageBody = "{ \"Password\": \"VerySecretPassword\" }",
40+
MessageHeaders = failedMessage.Item.ProcessingAttempts[^1].Headers
41+
}, null,
42+
ServiceControlInstanceName);
43+
c.ErrorRetried = true;
44+
45+
return false;
46+
}
47+
48+
var failedResolvedMessage = await GetFailedMessage(c.UniqueMessageId, ServiceControlInstanceName, FailedMessageStatus.Resolved);
49+
50+
return failedResolvedMessage.HasResult; // If there is a result it means the message was resolved
51+
})
52+
.Run(TimeSpan.FromMinutes(2));
53+
}
54+
55+
class FailureEndpoint : EndpointConfigurationBuilder
56+
{
57+
public FailureEndpoint() => EndpointSetup<DefaultServerWithoutAudit>(c => { c.NoRetries(); });
58+
59+
public class MyMessageHandler(MyContext testContext, IReadOnlySettings settings) : IHandleMessages<MyMessage>
60+
{
61+
public Task Handle(MyMessage message, IMessageHandlerContext context)
62+
{
63+
if (message.Password == "VerySecretPassword")
64+
{
65+
Console.Out.WriteLine("Handling message");
66+
return Task.CompletedTask;
67+
}
68+
69+
testContext.MessageId = context.MessageId.Replace(@"\", "-");
70+
testContext.EndpointNameOfReceivingEndpoint = settings.EndpointName();
71+
72+
Console.Out.WriteLine("Throwing exception");
73+
throw new Exception("Simulated exception");
74+
}
75+
}
76+
}
77+
78+
class MyMessage : ICommand
79+
{
80+
public string Password { get; set; }
81+
}
82+
83+
class MyContext : ScenarioContext
84+
{
85+
public string MessageId { get; set; }
86+
87+
public string EndpointNameOfReceivingEndpoint { get; set; }
88+
89+
public string UniqueMessageId => DeterministicGuid.MakeId(MessageId, EndpointNameOfReceivingEndpoint).ToString();
90+
public bool ErrorRetried { get; set; }
91+
}
92+
}

src/ServiceControl/Recoverability/Editing/EditHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public async Task Handle(EditAndSend message, IMessageHandlerContext context)
6767
var outgoingMessage = BuildMessage(message);
6868
// mark the new message with a link to the original message id
6969
outgoingMessage.Headers.Add("ServiceControl.EditOf", message.FailedMessageId);
70-
outgoingMessage.Headers["ServiceControl.Retry.AcknowledgementQueue"] = "";
70+
outgoingMessage.Headers.Remove("ServiceControl.Retry.AcknowledgementQueue");
7171
var address = ApplyRedirect(attempt.FailureDetails.AddressOfFailingEndpoint, redirects);
7272

7373
if (outgoingMessage.Headers.TryGetValue("ServiceControl.RetryTo", out var retryTo))

0 commit comments

Comments
 (0)