Skip to content

Commit 7363a6c

Browse files
authored
Fix DelegationRule to work after receiver restarts (#40967)
1 parent bc1c2ad commit 7363a6c

File tree

7 files changed

+101
-44
lines changed

7 files changed

+101
-44
lines changed

src/Servers/HttpSys/HttpSysServer.slnf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
"projects": [
55
"src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj",
66
"src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
7+
"src\\FileProviders\\Embedded\\src\\Microsoft.Extensions.FileProviders.Embedded.csproj",
78
"src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
89
"src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
910
"src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
11+
"src\\Hosting\\Server.IntegrationTesting\\src\\Microsoft.AspNetCore.Server.IntegrationTesting.csproj",
1012
"src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj",
1113
"src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj",
1214
"src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",

src/Servers/HttpSys/src/DelegationRule.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,19 @@ namespace Microsoft.AspNetCore.Server.HttpSys
1313
public class DelegationRule : IDisposable
1414
{
1515
private readonly ILogger _logger;
16-
private readonly UrlGroup _urlGroup;
1716
private readonly UrlGroup _sourceQueueUrlGroup;
1817
private bool _disposed;
18+
1919
/// <summary>
2020
/// The name of the Http.Sys request queue
2121
/// </summary>
2222
public string QueueName { get; }
23+
2324
/// <summary>
2425
/// The URL of the Http.Sys Url Prefix
2526
/// </summary>
2627
public string UrlPrefix { get; }
28+
2729
internal RequestQueue Queue { get; }
2830

2931
internal DelegationRule(UrlGroup sourceQueueUrlGroup, string queueName, string urlPrefix, ILogger logger)
@@ -32,8 +34,7 @@ internal DelegationRule(UrlGroup sourceQueueUrlGroup, string queueName, string u
3234
_logger = logger;
3335
QueueName = queueName;
3436
UrlPrefix = urlPrefix;
35-
Queue = new RequestQueue(queueName, UrlPrefix, _logger, receiver: true);
36-
_urlGroup = Queue.UrlGroup;
37+
Queue = new RequestQueue(queueName, _logger);
3738
}
3839

3940
/// <inheritdoc />
@@ -51,7 +52,6 @@ public void Dispose()
5152
_sourceQueueUrlGroup.UnSetDelegationProperty(Queue, throwOnError: false);
5253
}
5354
catch (ObjectDisposedException) { /* Server may have been shutdown */ }
54-
_urlGroup.Dispose();
5555
Queue.Dispose();
5656
}
5757
}

src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,16 @@ internal class RequestQueue
1919
private readonly ILogger _logger;
2020
private bool _disposed;
2121

22-
internal RequestQueue(string requestQueueName, string urlPrefix, ILogger logger, bool receiver)
23-
: this(urlGroup: null!, requestQueueName, RequestQueueMode.Attach, logger, receiver)
22+
internal RequestQueue(string requestQueueName, ILogger logger)
23+
: this(urlGroup: null, requestQueueName, RequestQueueMode.Attach, logger, receiver: true)
2424
{
25-
try
26-
{
27-
UrlGroup = new UrlGroup(this, UrlPrefix.Create(urlPrefix), logger);
28-
}
29-
catch
30-
{
31-
Dispose();
32-
throw;
33-
}
3425
}
3526

3627
internal RequestQueue(UrlGroup urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger)
3728
: this(urlGroup, requestQueueName, mode, logger, false)
3829
{ }
3930

40-
private RequestQueue(UrlGroup urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger, bool receiver)
31+
private RequestQueue(UrlGroup? urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger, bool receiver)
4132
{
4233
_mode = mode;
4334
UrlGroup = urlGroup;
@@ -117,10 +108,15 @@ private RequestQueue(UrlGroup urlGroup, string? requestQueueName, RequestQueueMo
117108
internal SafeHandle Handle { get; }
118109
internal ThreadPoolBoundHandle BoundHandle { get; }
119110

120-
internal UrlGroup UrlGroup { get; }
111+
internal UrlGroup? UrlGroup { get; }
121112

122113
internal unsafe void AttachToUrlGroup()
123114
{
115+
if (UrlGroup == null)
116+
{
117+
throw new NotSupportedException("Can't attach when UrlGroup is null");
118+
}
119+
124120
Debug.Assert(Created);
125121
CheckDisposed();
126122
// Set the association between request queue and url group. After this, requests for registered urls will
@@ -138,6 +134,11 @@ internal unsafe void AttachToUrlGroup()
138134

139135
internal unsafe void DetachFromUrlGroup()
140136
{
137+
if (UrlGroup == null)
138+
{
139+
throw new NotSupportedException("Can't detach when UrlGroup is null");
140+
}
141+
141142
Debug.Assert(Created);
142143
CheckDisposed();
143144
// Break the association between request queue and url group. After this, requests for registered urls

src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,6 @@ internal unsafe UrlGroup(ServerSession serverSession, ILogger logger)
4141
Id = urlGroupId;
4242
}
4343

44-
internal unsafe UrlGroup(RequestQueue requestQueue, UrlPrefix url, ILogger logger)
45-
{
46-
_logger = logger;
47-
48-
ulong urlGroupId = 0;
49-
_created = false;
50-
var statusCode = HttpApi.HttpFindUrlGroupId(
51-
url.FullPrefix, requestQueue.Handle, &urlGroupId);
52-
53-
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
54-
{
55-
throw new HttpSysException((int)statusCode);
56-
}
57-
58-
Debug.Assert(urlGroupId != 0, "Invalid id returned by HttpCreateUrlGroup");
59-
Id = urlGroupId;
60-
}
61-
6244
internal ulong Id { get; private set; }
6345

6446
internal unsafe void SetMaxConnections(long maxConnections)

src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,13 @@ internal unsafe void Delegate(DelegationRule destination)
289289
PropertyInfoLength = (uint)System.Text.Encoding.Unicode.GetByteCount(destination.UrlPrefix)
290290
};
291291

292+
// Passing 0 for delegateUrlGroupId allows http.sys to find the right group for the
293+
// URL passed in via the property above. If we passed in the receiver's URL group id
294+
// instead of 0, then delegation would fail if the receiver restarted.
292295
statusCode = HttpApi.HttpDelegateRequestEx(source.Handle,
293296
destination.Queue.Handle,
294297
Request.RequestId,
295-
destination.Queue.UrlGroup.Id,
298+
delegateUrlGroupId: 0,
296299
propertyInfoSetSize: 1,
297300
&property);
298301
}

src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,23 @@ namespace Microsoft.AspNetCore.Server.HttpSys
1414
internal class ServerDelegationPropertyFeature : IServerDelegationFeature
1515
{
1616
private readonly ILogger _logger;
17-
private readonly RequestQueue _queue;
17+
private readonly UrlGroup _urlGroup;
1818

1919
public ServerDelegationPropertyFeature(RequestQueue queue, ILogger logger)
2020
{
21-
_queue = queue;
21+
if (queue.UrlGroup == null)
22+
{
23+
throw new ArgumentException($"{nameof(queue)}.UrlGroup can't be null");
24+
}
25+
26+
_urlGroup = queue.UrlGroup;
2227
_logger = logger;
2328
}
2429

2530
public DelegationRule CreateDelegationRule(string queueName, string uri)
2631
{
27-
var rule = new DelegationRule(_queue.UrlGroup, queueName, uri, _logger);
28-
_queue.UrlGroup.SetDelegationProperty(rule.Queue);
32+
var rule = new DelegationRule(_urlGroup, queueName, uri, _logger);
33+
_urlGroup.SetDelegationProperty(rule.Queue);
2934
return rule;
3035
}
3136
}

src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.IO;
64
using System.Net.Http;
7-
using System.Threading.Tasks;
5+
using System.Runtime.InteropServices;
86
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.HttpSys.Internal;
98
using Microsoft.AspNetCore.Testing;
10-
using Xunit;
119

1210
namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
1311
{
@@ -198,6 +196,72 @@ public async Task UpdateDelegationRuleTest()
198196
destination?.Dispose();
199197
}
200198

199+
[ConditionalFact]
200+
[DelegateSupportedCondition(true)]
201+
public async Task DelegateAfterReceiverRestart()
202+
{
203+
var queueName = Guid.NewGuid().ToString();
204+
using var receiver = Utilities.CreateHttpServer(out var receiverAddress, async httpContext =>
205+
{
206+
await httpContext.Response.WriteAsync(_expectedResponseString);
207+
},
208+
options =>
209+
{
210+
options.RequestQueueName = queueName;
211+
});
212+
213+
DelegationRule destination = default;
214+
using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, httpContext =>
215+
{
216+
var delegateFeature = httpContext.Features.Get<IHttpSysRequestDelegationFeature>();
217+
delegateFeature.DelegateRequest(destination);
218+
return Task.CompletedTask;
219+
});
220+
221+
var delegationProperty = delegator.Features.Get<IServerDelegationFeature>();
222+
destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress);
223+
224+
var responseString = await SendRequestAsync(delegatorAddress);
225+
Assert.Equal(_expectedResponseString, responseString);
226+
227+
// Stop the receiver
228+
receiver?.Dispose();
229+
230+
// Start the receiver again but this time we need to attach to the existing queue.
231+
// Due to https://github.com/dotnet/aspnetcore/issues/40359, we have to manually
232+
// register URL prefixes and attach the server's queue to them.
233+
using var receiverRestarted = (MessagePump)Utilities.CreateHttpServer(out receiverAddress, async httpContext =>
234+
{
235+
await httpContext.Response.WriteAsync(_expectedResponseString);
236+
},
237+
options =>
238+
{
239+
options.RequestQueueName = queueName;
240+
options.RequestQueueMode = RequestQueueMode.Attach;
241+
options.UrlPrefixes.Clear();
242+
options.UrlPrefixes.Add(receiverAddress);
243+
});
244+
AttachToUrlGroup(receiverRestarted.Listener.RequestQueue);
245+
receiverRestarted.Listener.Options.UrlPrefixes.RegisterAllPrefixes(receiverRestarted.Listener.UrlGroup);
246+
247+
responseString = await SendRequestAsync(delegatorAddress);
248+
Assert.Equal(_expectedResponseString, responseString);
249+
250+
destination?.Dispose();
251+
}
252+
253+
private unsafe void AttachToUrlGroup(RequestQueue requestQueue)
254+
{
255+
var info = new HttpApiTypes.HTTP_BINDING_INFO();
256+
info.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
257+
info.RequestQueueHandle = requestQueue.Handle.DangerousGetHandle();
258+
259+
var infoptr = new IntPtr(&info);
260+
261+
requestQueue.UrlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
262+
infoptr, (uint)Marshal.SizeOf<HttpApiTypes.HTTP_BINDING_INFO>());
263+
}
264+
201265
private async Task<string> SendRequestAsync(string uri)
202266
{
203267
using var client = new HttpClient();

0 commit comments

Comments
 (0)