Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Commit

Permalink
Make AllowSynchronousIO true by default
Browse files Browse the repository at this point in the history
  • Loading branch information
halter73 committed Jun 29, 2017
1 parent a284771 commit 4a93cd6
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ public class KestrelServerOptions
/// <summary>
/// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
/// </summary>
public bool AllowSynchronousIO { get; set; }
/// <remarks>
/// Defaults to true.
/// </remarks>
public bool AllowSynchronousIO { get; set; } = true;

/// <summary>
/// Enables the Listen options callback to resolve and use services registered by the application during startup.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public async Task FlushAsyncThrows()
}

[Fact]
public async Task SynchronousReadsThrowByDefault()
public async Task SynchronousReadsThrowIfDisallowedByIHttpBodyControlFeature()
{
var allowSynchronousIO = false;
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
Expand All @@ -128,6 +128,9 @@ public async Task SynchronousReadsThrowByDefault()
var ioEx = Assert.Throws<InvalidOperationException>(() => stream.Read(new byte[1], 0, 1));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx.Message);

var ioEx2 = Assert.Throws<InvalidOperationException>(() => stream.CopyTo(Stream.Null));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx2.Message);

allowSynchronousIO = true;
Assert.Equal(0, stream.Read(new byte[1], 0, 1));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public void StopAcceptingWritesCausesWriteToThrowObjectDisposedException()
}

[Fact]
public async Task SynchronousWritesThrowByDefault()
public async Task SynchronousWritesThrowIfDisallowedByIHttpBodyControlFeature()
{
var allowSynchronousIO = false;
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,13 @@ public void NoDelayDefaultsToTrue()
Assert.True(o1.ListenOptions[0].NoDelay);
Assert.False(o1.ListenOptions[1].NoDelay);
}

[Fact]
public void AllowSynchronousIODefaultsToTrue()
{
var options = new KestrelServerOptions();

Assert.True(options.AllowSynchronousIO);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1480,33 +1480,52 @@ await connection.ReceiveForcedEnd(
}

[Fact]
public async Task SynchronousReadsThrowByDefault()
public async Task SynchronousReadsAllowedByDefault()
{
using (var server = new TestServer(context =>
var firstRequest = true;

using (var server = new TestServer(async context =>
{
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(new byte[1], 0, 1));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.True(bodyControlFeature.AllowSynchronousIO);

var ioEx2 = Assert.Throws<InvalidOperationException>(() => context.Request.Body.CopyTo(Stream.Null));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx2.Message);
var buffer = new byte[6];
var offset = 0;

var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.False(bodyControlFeature.AllowSynchronousIO);
// The request body is 5 bytes long. The 6th byte (buffer[5]) is only used for writing the response body.
buffer[5] = (byte)(firstRequest ? '1' : '2');

bodyControlFeature.AllowSynchronousIO = true;
if (firstRequest)
{
while (offset < 5)
{
offset += context.Request.Body.Read(buffer, offset, 5 - offset);
}

// Read now no longer throws.
var buffer = new byte[5];
var offset = 0;
while (offset < 5)
firstRequest = false;
}
else
{
offset += context.Request.Body.Read(buffer, offset, 5 - offset);
bodyControlFeature.AllowSynchronousIO = false;

// Synchronous reads now throw.
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(new byte[1], 0, 1));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx.Message);

var ioEx2 = Assert.Throws<InvalidOperationException>(() => context.Request.Body.CopyTo(Stream.Null));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx2.Message);

while (offset < 5)
{
offset += await context.Request.Body.ReadAsync(buffer, offset, 5 - offset);
}
}

Assert.Equal(0, context.Request.Body.Read(new byte[1], 0, 1));
Assert.Equal("Hello", Encoding.ASCII.GetString(buffer));
Assert.Equal(0, await context.Request.Body.ReadAsync(new byte[1], 0, 1));
Assert.Equal("Hello", Encoding.ASCII.GetString(buffer, 0, 5));

return Task.CompletedTask;
context.Response.ContentLength = 6;
await context.Response.Body.WriteAsync(buffer, 0, 6);
}))
{
using (var connection = server.CreateConnection())
Expand All @@ -1516,38 +1535,54 @@ await connection.Send(
"Host:",
"Content-Length: 5",
"",
"HelloPOST / HTTP/1.1",
"Host:",
"Content-Length: 5",
"",
"Hello");
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 0",
"Content-Length: 6",
"",
"");
"Hello1HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"",
"Hello2");
}
}
}

[Fact]
public async Task SynchronousReadsCanBeEnabledGlobally()
public async Task SynchronousReadsCanBeDisallowedGlobally()
{
var testContext = new TestServiceContext
{
ServerOptions = { AllowSynchronousIO = true }
ServerOptions = { AllowSynchronousIO = false }
};

using (var server = new TestServer(context =>
using (var server = new TestServer(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.False(bodyControlFeature.AllowSynchronousIO);

// Synchronous reads now throw.
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(new byte[1], 0, 1));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx.Message);

var ioEx2 = Assert.Throws<InvalidOperationException>(() => context.Request.Body.CopyTo(Stream.Null));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx2.Message);

var buffer = new byte[5];
var offset = 0;
while (offset < 5)
{
offset += context.Request.Body.Read(buffer, offset, 5 - offset);
offset += await context.Request.Body.ReadAsync(buffer, offset, 5 - offset);
}

Assert.Equal(0, context.Request.Body.Read(new byte[1], 0, 1));
Assert.Equal(0, await context.Request.Body.ReadAsync(new byte[1], 0, 1));
Assert.Equal("Hello", Encoding.ASCII.GetString(buffer));

return Task.CompletedTask;
}, testContext))
{
using (var connection = server.CreateConnection())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2332,60 +2332,75 @@ await connection.ReceiveEnd(


[Fact]
public async Task SynchronousWritesThrowByDefault()
public async Task SynchronousWritesAllowedByDefault()
{
using (var server = new TestServer(context =>
{
var helloBuffer = Encoding.ASCII.GetBytes("Hello");
context.Response.ContentLength = 5;

var ioEx = Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(helloBuffer, 0, 5));
Assert.Equal("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
var firstRequest = true;

using (var server = new TestServer(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.False(bodyControlFeature.AllowSynchronousIO);
Assert.True(bodyControlFeature.AllowSynchronousIO);

bodyControlFeature.AllowSynchronousIO = true;
context.Response.ContentLength = 6;

// Write now now longer throws.
context.Response.Body.Write(helloBuffer, 0, 5);
if (firstRequest)
{
context.Response.Body.Write(Encoding.ASCII.GetBytes("Hello1"), 0, 6);
firstRequest = false;
}
else
{
bodyControlFeature.AllowSynchronousIO = false;

return Task.CompletedTask;
// Synchronous writes now throw.
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(Encoding.ASCII.GetBytes("What!?"), 0, 6));
Assert.Equal("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.", ioEx.Message);

await context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello2"), 0, 6);
}
}))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
await connection.SendEmptyGet();
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"",
"");
"Hello1");

await connection.SendEmptyGet();
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 5",
"Content-Length: 6",
"",
"Hello");
"Hello2");
}
}
}

[Fact]
public async Task SynchronousWritesCanBeEnabledGlobally()
public async Task SynchronousWritesCanBeDisallowedGlobally()
{
var testContext = new TestServiceContext
{
ServerOptions = { AllowSynchronousIO = true }
ServerOptions = { AllowSynchronousIO = false }
};

using (var server = new TestServer(context =>
{
var helloBuffer = Encoding.ASCII.GetBytes("Hello");
context.Response.ContentLength = 5;
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.False(bodyControlFeature.AllowSynchronousIO);

context.Response.Body.Write(helloBuffer, 0, 5);
context.Response.ContentLength = 6;

return Task.CompletedTask;
// Synchronous writes now throw.
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(Encoding.ASCII.GetBytes("What!?"), 0, 6));
Assert.Equal("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.", ioEx.Message);

return context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello!"), 0, 6);
}, testContext))
{
using (var connection = server.CreateConnection())
Expand All @@ -2398,9 +2413,9 @@ await connection.Send(
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 5",
"Content-Length: 6",
"",
"Hello");
"Hello!");
}
}
}
Expand Down

0 comments on commit 4a93cd6

Please sign in to comment.