Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 change: 1 addition & 0 deletions src/libraries/System.Net.Mail/src/System.Net.Mail.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<Compile Include="System\Net\Mail\Attachment.cs" />
<Compile Include="System\Net\Mail\AttachmentCollection.cs" />
<Compile Include="System\Net\BufferedReadStream.cs" />
<Compile Include="System\Net\Mail\ReadWriteAdapter.cs" />
<Compile Include="System\Net\Mail\LinkedResource.cs" />
<Compile Include="System\Net\Mail\LinkedResourceCollection.cs" />
<Compile Include="System\Net\Mail\DomainLiteralReader.cs" />
Expand Down
7 changes: 7 additions & 0 deletions src/libraries/System.Net.Mail/src/System/Net/BufferBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ internal void Append(byte value)
_buffer[_offset++] = value;
}

internal void Append(ReadOnlyMemory<byte> value)
{
EnsureBuffer(value.Length);
value.Span.CopyTo(_buffer.AsSpan(_offset));
_offset += value.Length;
}

internal void Append(ReadOnlySpan<byte> value)
{
EnsureBuffer(value.Length);
Expand Down
27 changes: 18 additions & 9 deletions src/libraries/System.Net.Mail/src/System/Net/Mail/MailMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@

using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Mime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net.Mail
{
Expand Down Expand Up @@ -429,22 +432,28 @@ private void SetContent(bool allowUnicode)
}
}

internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode)
internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode, CancellationToken cancellationToken = default)
{
SetContent(allowUnicode);
_message.Send(writer, sendEnvelope, allowUnicode);
Task task = SendAsync<SyncReadWriteAdapter>(writer, sendEnvelope, allowUnicode, cancellationToken);
Debug.Assert(task.IsCompleted, "SendAsync should be completed synchronously.");
task.GetAwaiter().GetResult();
}

Comment thread
rzikm marked this conversation as resolved.
internal IAsyncResult BeginSend(BaseWriter writer, bool allowUnicode,
AsyncCallback? callback, object? state)
internal IAsyncResult BeginSend(BaseWriter writer, bool sendEnvelope, bool allowUnicode, AsyncCallback callback, object? state)
{
SetContent(allowUnicode);
return _message.BeginSend(writer, allowUnicode, callback, state);
return TaskToAsyncResult.Begin(SendAsync<AsyncReadWriteAdapter>(writer, sendEnvelope, allowUnicode), callback, state);
}

internal static void EndSend(IAsyncResult asyncResult)
{
TaskToAsyncResult.End(asyncResult);
}

internal void EndSend(IAsyncResult asyncResult)
internal async Task SendAsync<TIOAdapter>(BaseWriter writer, bool sendEnvelope, bool allowUnicode, CancellationToken cancellationToken = default)
where TIOAdapter : IReadWriteAdapter
{
_message.EndSend(asyncResult);
SetContent(allowUnicode);
await _message.SendAsync<TIOAdapter>(writer, sendEnvelope, allowUnicode, cancellationToken).ConfigureAwait(false);
}

internal string BuildDeliveryStatusNotificationString()
Expand Down
99 changes: 9 additions & 90 deletions src/libraries/System.Net.Mail/src/System/Net/Mail/MailPriority.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Net.Mime;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net.Mail
{
Expand Down Expand Up @@ -139,7 +141,9 @@ internal string? Subject
// extract the encoding from =?encoding?BorQ?blablalba?=
inputEncoding = MimeBasePart.DecodeEncoding(value);
}
catch (ArgumentException) { };
catch (ArgumentException)
{
}

if (inputEncoding != null && value != null)
{
Expand Down Expand Up @@ -240,94 +244,8 @@ internal MimeBasePart? Content

#region Sending

internal void EmptySendCallback(IAsyncResult result)
{
Exception? e = null;

if (result.CompletedSynchronously)
{
return;
}

EmptySendContext context = (EmptySendContext)result.AsyncState!;
try
{
BaseWriter.EndGetContentStream(result).Close();
}
catch (Exception ex)
{
e = ex;
}
context._result.InvokeCallback(e);
}

internal sealed class EmptySendContext
{
internal EmptySendContext(BaseWriter writer, LazyAsyncResult result)
{
_writer = writer;
_result = result;
}

internal LazyAsyncResult _result;
internal BaseWriter _writer;
}

internal IAsyncResult BeginSend(BaseWriter writer, bool allowUnicode,
AsyncCallback? callback, object? state)
{
PrepareHeaders(allowUnicode);
writer.WriteHeaders(Headers, allowUnicode);

if (Content != null)
{
return Content.BeginSend(writer, callback, allowUnicode, state);
}
else
{
LazyAsyncResult result = new LazyAsyncResult(this, state, callback);
IAsyncResult newResult = writer.BeginGetContentStream(EmptySendCallback, new EmptySendContext(writer, result));
if (newResult.CompletedSynchronously)
{
BaseWriter.EndGetContentStream(newResult).Close();
result.InvokeCallback();
}
return result;
}
}

internal void EndSend(IAsyncResult asyncResult)
{
ArgumentNullException.ThrowIfNull(asyncResult);

if (Content != null)
{
Content.EndSend(asyncResult);
}
else
{
LazyAsyncResult? castedAsyncResult = asyncResult as LazyAsyncResult;

if (castedAsyncResult == null || castedAsyncResult.AsyncObject != this)
{
throw new ArgumentException(SR.net_io_invalidasyncresult);
}

if (castedAsyncResult.EndCalled)
{
throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, nameof(EndSend)));
}

castedAsyncResult.InternalWaitForCompletion();
castedAsyncResult.EndCalled = true;
if (castedAsyncResult.Result is Exception e)
{
ExceptionDispatchInfo.Throw(e);
}
}
}

internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode)
internal async Task SendAsync<TIOAdapter>(BaseWriter writer, bool sendEnvelope, bool allowUnicode, CancellationToken cancellationToken = default)
where TIOAdapter : IReadWriteAdapter
{
if (sendEnvelope)
{
Expand All @@ -340,10 +258,11 @@ internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode)

if (Content != null)
{
Content.Send(writer, allowUnicode);
await Content.SendAsync<TIOAdapter>(writer, allowUnicode, cancellationToken).ConfigureAwait(false);
}
else
{
// No content to write, just close the stream
writer.GetContentStream().Close();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal override void WriteHeaders(NameValueCollection headers, bool allowUnico
internal override void Close()
{
_bufferBuilder.Append("\r\n"u8);
Flush(null);
Flush();
_stream.Close();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net.Mail
{
internal interface IReadWriteAdapter
{
static abstract ValueTask<int> ReadAsync(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken);
static abstract ValueTask<int> ReadAtLeastAsync(Stream stream, Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken);
static abstract ValueTask WriteAsync(Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken);
static abstract Task FlushAsync(Stream stream, CancellationToken cancellationToken);
static abstract Task WaitAsync(TaskCompletionSource<bool> waiter);
}

internal readonly struct AsyncReadWriteAdapter : IReadWriteAdapter
{
public static ValueTask<int> ReadAsync(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken) =>
stream.ReadAsync(buffer, cancellationToken);

public static ValueTask<int> ReadAtLeastAsync(Stream stream, Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) =>
stream.ReadAtLeastAsync(buffer, minimumBytes, throwOnEndOfStream, cancellationToken);

public static ValueTask WriteAsync(Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken) =>
stream.WriteAsync(buffer, cancellationToken);

public static Task FlushAsync(Stream stream, CancellationToken cancellationToken) => stream.FlushAsync(cancellationToken);

public static Task WaitAsync(TaskCompletionSource<bool> waiter) => waiter.Task;
}

internal readonly struct SyncReadWriteAdapter : IReadWriteAdapter
{
public static ValueTask<int> ReadAsync(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken) =>
new ValueTask<int>(stream.Read(buffer.Span));

public static ValueTask<int> ReadAtLeastAsync(Stream stream, Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) =>
new ValueTask<int>(stream.ReadAtLeast(buffer.Span, minimumBytes, throwOnEndOfStream));

public static ValueTask WriteAsync(Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
{
stream.Write(buffer.Span);
return default;
}

public static Task FlushAsync(Stream stream, CancellationToken cancellationToken)
{
stream.Flush();
return Task.CompletedTask;
}

public static Task WaitAsync(TaskCompletionSource<bool> waiter)
{
waiter.Task.GetAwaiter().GetResult();
return Task.CompletedTask;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return waiter.Task; ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of the SyncReadWriteAdapter is that it always returns a completed task, so that calling code can be fully synchronous (and can assert that task.IsCompleted). This file was copied from SslStream implementation, where it is used to deduplicate sync/async logic (in fact, it was the inspiration for this refactoring).

Followup PR removes this copy of the ReadWriteAdapters and shares the same file.

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ private void SendMessageCallback(IAsyncResult result)
{
try
{
_message!.EndSend(result);
MailMessage.EndSend(result);
// If some recipients failed but not others, throw AFTER sending the message.
Complete(_failedRecipientException, result.AsyncState!);
}
Expand Down Expand Up @@ -929,8 +929,7 @@ private void SendMailCallback(IAsyncResult result)
}
else
{
_message!.BeginSend(_writer,
IsUnicodeSupported(), new AsyncCallback(SendMessageCallback), result.AsyncState!);
_message!.BeginSend(_writer, DeliveryMethod != SmtpDeliveryMethod.Network, IsUnicodeSupported(), new AsyncCallback(SendMessageCallback), result.AsyncState!);
}
}
catch (Exception e)
Expand Down
Loading