-
-
Notifications
You must be signed in to change notification settings - Fork 952
Add async support to SftpClient and SftpFileStream #819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
ca1a59f
5f74049
d5c80f5
d578af7
c37a884
ed8e789
a33cab8
2235756
6fb5be2
b7fd333
85839e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"CurrentProjectSetting": null | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
IgorMilavec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"ExpandedNodes": [ | ||
"", | ||
"\\src", | ||
"\\src\\Renci.SshNet", | ||
"\\src\\Renci.SshNet\\Sftp" | ||
], | ||
"SelectedNode": "\\src\\Renci.SshNet.VS2019.sln", | ||
"PreviewInSolutionExplorer": false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,10 +2,13 @@ | |
using System.Net; | ||
using System.Net.Sockets; | ||
|
||
#if FEATURE_TAP | ||
using System.Threading.Tasks; | ||
#endif | ||
|
||
#if FEATURE_DNS_SYNC | ||
#elif FEATURE_DNS_APM | ||
using Renci.SshNet.Common; | ||
#elif FEATURE_DNS_TAP | ||
#elif FEATURE_DEVICEINFORMATION_APM | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
@@ -42,8 +45,6 @@ public static IPAddress[] GetHostAddresses(string hostNameOrAddress) | |
if (!asyncResult.AsyncWaitHandle.WaitOne(Session.InfiniteTimeSpan)) | ||
throw new SshOperationTimeoutException("Timeout resolving host name."); | ||
return Dns.EndGetHostAddresses(asyncResult); | ||
#elif FEATURE_DNS_TAP | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be reverted, if not we'll use the IPAddress.TryParse(hostNameOrAddress, out address) implementation for .NET Standard 1.3 and UAP 10. It's not like we're always using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ups, I was too quick to remove and missed that the fallback was just IPAddress.TryParse. I have reverted the change, but I propose to remove FEATURE_DNS_TAP in the future from this method as we're using Sync/Async, which could cause problems. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. These conditional compilation symbols are a necessary evil. The less we have to maintain, the better. |
||
return Dns.GetHostAddressesAsync(hostNameOrAddress).GetAwaiter().GetResult(); | ||
#else | ||
IPAddress address; | ||
if (IPAddress.TryParse(hostNameOrAddress, out address)) | ||
|
@@ -87,5 +88,23 @@ public static IPAddress[] GetHostAddresses(string hostNameOrAddress) | |
#endif // FEATURE_DEVICEINFORMATION_APM | ||
#endif | ||
} | ||
|
||
#if FEATURE_TAP | ||
/// <summary> | ||
/// Returns the Internet Protocol (IP) addresses for the specified host. | ||
/// </summary> | ||
/// <param name="hostNameOrAddress">The host name or IP address to resolve</param> | ||
/// <returns> | ||
/// A task with result of an array of type <see cref="IPAddress"/> that holds the IP addresses for the host that | ||
/// is specified by the <paramref name="hostNameOrAddress"/> parameter. | ||
/// </returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="hostNameOrAddress"/> is <c>null</c>.</exception> | ||
/// <exception cref="SocketException">An error is encountered when resolving <paramref name="hostNameOrAddress"/>.</exception> | ||
public static Task<IPAddress[]> GetHostAddressesAsync(string hostNameOrAddress) | ||
IgorMilavec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
return Dns.GetHostAddressesAsync(hostNameOrAddress); | ||
} | ||
#endif | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
#if FEATURE_TAP | ||
using System; | ||
using System.Net; | ||
using System.Net.Sockets; | ||
using System.Runtime.CompilerServices; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Renci.SshNet.Abstractions | ||
{ | ||
// Async helpers based on https://devblogs.microsoft.com/pfxteam/awaiting-socket-operations/ | ||
|
||
internal static class SocketExtensions | ||
{ | ||
sealed class SocketAsyncEventArgsAwaitable : SocketAsyncEventArgs, INotifyCompletion | ||
{ | ||
private readonly static Action SENTINEL = () => { }; | ||
|
||
private bool isCancelled; | ||
private Action continuationAction; | ||
|
||
public SocketAsyncEventArgsAwaitable() | ||
{ | ||
Completed += delegate { SetCompleted(); }; | ||
} | ||
|
||
public SocketAsyncEventArgsAwaitable ExecuteAsync(Func<SocketAsyncEventArgs, bool> func) | ||
{ | ||
if (!func(this)) | ||
{ | ||
SetCompleted(); | ||
} | ||
return this; | ||
} | ||
|
||
public void SetCompleted() | ||
{ | ||
IsCompleted = true; | ||
var continuation = continuationAction ?? Interlocked.CompareExchange(ref continuationAction, SENTINEL, null); | ||
if (continuation != null) | ||
{ | ||
continuation(); | ||
} | ||
} | ||
|
||
public void SetCancelled() | ||
{ | ||
isCancelled = true; | ||
SetCompleted(); | ||
} | ||
|
||
public SocketAsyncEventArgsAwaitable GetAwaiter() { return this; } | ||
|
||
public bool IsCompleted { get; private set; } | ||
|
||
void INotifyCompletion.OnCompleted(Action continuation) | ||
{ | ||
if (continuationAction == SENTINEL || Interlocked.CompareExchange(ref continuationAction, continuation, null) == SENTINEL) | ||
{ | ||
// We have already completed; run continuation asynchronously | ||
Task.Run(continuation); | ||
} | ||
} | ||
|
||
public void GetResult() | ||
{ | ||
if (isCancelled) | ||
{ | ||
throw new TaskCanceledException(); | ||
} | ||
else if (IsCompleted) | ||
{ | ||
if (SocketError != SocketError.Success) | ||
{ | ||
throw new SocketException((int)SocketError); | ||
} | ||
} | ||
else | ||
{ | ||
// We don't support sync/async | ||
throw new InvalidOperationException("The asynchronous operation has not yet completed."); | ||
} | ||
} | ||
} | ||
|
||
public static async Task ConnectAsync(this Socket socket, IPEndPoint remoteEndpoint, CancellationToken cancellationToken) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
using (var args = new SocketAsyncEventArgsAwaitable()) | ||
{ | ||
args.RemoteEndPoint = remoteEndpoint; | ||
|
||
using (cancellationToken.Register(o => ((SocketAsyncEventArgsAwaitable)o).SetCancelled(), args, false)) | ||
{ | ||
await args.ExecuteAsync(socket.ConnectAsync); | ||
} | ||
} | ||
} | ||
|
||
public static async Task<int> ReceiveAsync(this Socket socket, byte[] buffer, int offset, int length, CancellationToken cancellationToken) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
using (var args = new SocketAsyncEventArgsAwaitable()) | ||
{ | ||
args.SetBuffer(buffer, offset, length); | ||
|
||
using (cancellationToken.Register(o => ((SocketAsyncEventArgsAwaitable)o).SetCancelled(), args, false)) | ||
{ | ||
await args.ExecuteAsync(socket.ReceiveAsync); | ||
} | ||
|
||
return args.BytesTransferred; | ||
} | ||
} | ||
} | ||
} | ||
#endif |
Uh oh!
There was an error while loading. Please reload this page.