Skip to content
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

Consider adding Socket Send/ReceiveAsync overloads that elide SocketFlags argument #43934

Closed
geoffkizer opened this issue Oct 28, 2020 · 20 comments · Fixed by #57634
Closed

Consider adding Socket Send/ReceiveAsync overloads that elide SocketFlags argument #43934

geoffkizer opened this issue Oct 28, 2020 · 20 comments · Fixed by #57634
Labels
api-approved API was approved in API review, it can be implemented area-System.Net.Sockets help wanted [up-for-grabs] Good issue for external contributors
Milestone

Comments

@geoffkizer
Copy link
Contributor

geoffkizer commented Oct 28, 2020

Background and Motivation

The sync methods for Send, Receive, SendTo, and ReceiveFrom all have overloads that don't take a SocketFlags argument. This argument is rarely used.

The Task-based async methods don't have these overloads. We actually approved overloads without SocketFlags for SendTo and ReceiveFrom in #938, but these haven't been implemented yet, and it doesn't really make sense to implement them just for SendTo and ReceiveFrom.

Proposed API

Additions to existing overloads

// Existing overloads in comments
public static class SocketTaskExtensions
{
//  public static Task<int> ReceiveAsync(this Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags);
    public static Task<int> ReceiveAsync(this Socket socket, ArraySegment<byte> buffer);
    
//  public static Task<int> ReceiveAsync(this Socket socket, IList<ArraySegment<byte>> buffers, SocketFlags socketFlags);
    public static Task<int> ReceiveAsync(this Socket socket, IList<ArraySegment<byte>> buffers);
    
//  public static ValueTask<int> ReceiveAsync(this Socket socket, Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default(CancellationToken));
    public static ValueTask<int> ReceiveAsync(this Socket socket, Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken));
    
//  public static Task<SocketReceiveFromResult> ReceiveFromAsync(this Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint);
    public static Task<SocketReceiveFromResult> ReceiveFromAsync(this Socket socket, ArraySegment<byte> buffer, EndPoint remoteEndPoint);
    
//  public static Task<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(this Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint);
    public static Task<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(this Socket socket, ArraySegment<byte> buffer, EndPoint remoteEndPoint);
    
//  public static Task<int> SendAsync(this Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags);
    public static Task<int> SendAsync(this Socket socket, ArraySegment<byte> buffer);
    
//  public static Task<int> SendAsync(this Socket socket, IList<ArraySegment<byte>> buffers, SocketFlags socketFlags);
    public static Task<int> SendAsync(this Socket socket, IList<ArraySegment<byte>> buffers);
    
//  public static ValueTask<int> SendAsync(this Socket socket, ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default(CancellationToken));
    public static ValueTask<int> SendAsync(this Socket socket, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken));
    
//  public static Task<int> SendToAsync(this Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP);
    public static Task<int> SendToAsync(this Socket socket, ArraySegment<byte> buffer, EndPoint remoteEP);
}

Additions to #938

// #938 overloads in comments
public static class SocketTaskExtensions
{
//  public static ValueTask<int> SendToAsync(this Socket socket, ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default);
    public static ValueTask<int> SendToAsync(this Socket socket, ReadOnlyMemory<byte> buffer, EndPoint remoteEP, CancellationToken cancellationToken = default);

//  public static ValueTask<SocketReceiveFromResult> ReceiveFromAsync(this Socket socket, Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default);
    public static ValueTask<SocketReceiveFromResult> ReceiveFromAsync(this Socket socket, Memory<byte> buffer, EndPoint remoteEP, CancellationToken cancellationToken = default);

//  public static ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(this Socket socket, Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default);
    public static ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(this Socket socket, Memory<byte> buffer, EndPoint remoteEP, CancellationToken cancellationToken = default);
}

Consider: addition to #43933

This is not an async API but probably worth to add.

// #43933 overload in comments
public class Socket
{
//  public int ReceiveMessageFrom(Span<byte> buffer, ref SocketFlags socketFlags, ref EndPoint remoteEP, out IPPacketInformation ipPacketInformation);
    public int ReceiveMessageFrom(Span<byte> buffer, ref EndPoint remoteEP, out IPPacketInformation ipPacketInformation);
}

@scalablecory @wfurt @antonfirsov @stephentoub Thoughts?

@geoffkizer geoffkizer added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Net.Sockets labels Oct 28, 2020
@ghost
Copy link

ghost commented Oct 28, 2020

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Oct 28, 2020
@antonfirsov
Copy link
Member

This argument is rarely used.

Agreed, this will simplify call sites and improve readability. I think we should go for an API proposal. Is it just an overlook that these overloads were missing from the original proposal for SendAsync/ReceiveAsync, or was there a practical reason?

@geoffkizer
Copy link
Contributor Author

I don't think there was a practical reason, aside from just keeping the API surface minimal.

@scalablecory
Copy link
Contributor

This seems reasonable to me, especially considering we have already approved UDP APIs with these overloads.

@karelz karelz removed the untriaged New issue has not been triaged by the area owner label Oct 29, 2020
@karelz
Copy link
Member

karelz commented Oct 29, 2020

Triage: Let's add them from consistency reasons.
Should be simple once the API go through API approval.

@karelz karelz added this to the 6.0.0 milestone Oct 29, 2020
@karelz karelz added the help wanted [up-for-grabs] Good issue for external contributors label Oct 29, 2020
@geoffkizer
Copy link
Contributor Author

@antonfirsov Do you want to put together the actual API proposal?

@antonfirsov
Copy link
Member

antonfirsov commented Nov 5, 2020

@geoffkizer edited the opening post, hope all overloads are included.

@geoffkizer
Copy link
Contributor Author

The new overloads for SendToAsync, ReceiveFromAsync, and ReceiveMessageFromAsync in #33418 aren't included above. I think we should include them for completeness, even though they have technically been approved already.

@scalablecory
Copy link
Contributor

Yeah. Also, we should make sure to merge this API back into #33418 once approved.

@antonfirsov
Copy link
Member

Ok, will add tomorrow, thanks for checking!

@antonfirsov
Copy link
Member

antonfirsov commented Nov 6, 2020

Done, PTAL

@geoffkizer
Copy link
Contributor Author

Looks good

@antonfirsov
Copy link
Member

According to #43901, we should move the proposed methods to the Socket class. Will update the proposal later.

@gfoidl
Copy link
Member

gfoidl commented Jan 27, 2021

Is it possible to use default arguments (socketFlags = SocketFlags.None) here?
This shouldn't be a breaking change, as the overload with socketFlags is already there.

For most overloads this could be used. Some of them have the socketFlags in a position where this wouldn't work, so the new extra overloads is needed. But where possible I prefer the default argument.

@geoffkizer
Copy link
Contributor Author

My understanding is that isn't possible to do, though I'm not sure why. @terrajobst would know.

@karelz karelz added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels May 13, 2021
@karelz karelz modified the milestones: 6.0.0, Future May 13, 2021
@geoffkizer
Copy link
Contributor Author

I do wonder if we should wait a bit here until we figure out scatter/gather APIs. If we have new and improved scatter/gather APIs, we should only add overloads for those.

@bartonjs
Copy link
Member

bartonjs commented Aug 17, 2021

Video

  • We discussed defaulting the parameter where possible, and landed on overloading for symmetry with existing things.
  • We moved everything from the extensions class to the main class as instance methods
  • Please double check that on ReceiveMessageFrom it's not important that the ref SocketFlags is not used, since that could have been passing data back to the caller.
  • Also, let's paint the current extensions type as EB-Never
namespace System.Net.Sockets
{
    // Existing overloads in comments
    partial class Socket
    {
    //  public Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags);
        public Task<int> ReceiveAsync(ArraySegment<byte> buffer);
        
    //  public Task<int> ReceiveAsync(IList<ArraySegment<byte>> buffers, SocketFlags socketFlags);
        public Task<int> ReceiveAsync(IList<ArraySegment<byte>> buffers);
        
    //  public ValueTask<int> ReceiveAsync(Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default(CancellationToken));
        public ValueTask<int> ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken));
        
    //  public Task<SocketReceiveFromResult> ReceiveFromAsync(ArraySegment<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint);
        public Task<SocketReceiveFromResult> ReceiveFromAsync(ArraySegment<byte> buffer, EndPoint remoteEndPoint);
        
    //  public Task<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(ArraySegment<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint);
        public Task<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(ArraySegment<byte> buffer, EndPoint remoteEndPoint);
        
    //  public Task<int> SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags);
        public Task<int> SendAsync(ArraySegment<byte> buffer);
        
    //  public Task<int> SendAsync(IList<ArraySegment<byte>> buffers, SocketFlags socketFlags);
        public Task<int> SendAsync(IList<ArraySegment<byte>> buffers);
        
    //  public ValueTask<int> SendAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default(CancellationToken));
        public ValueTask<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken));
        
    //  public Task<int> SendToAsync(ArraySegment<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP);
        public Task<int> SendToAsync(ArraySegment<byte> buffer, EndPoint remoteEP);

    //  public ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default);
        public ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, EndPoint remoteEP, CancellationToken cancellationToken = default);

    //  public ValueTask<SocketReceiveFromResult> ReceiveFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default);
        public ValueTask<SocketReceiveFromResult> ReceiveFromAsync(Memory<byte> buffer, EndPoint remoteEP, CancellationToken cancellationToken = default);

    //  public ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default);
        public ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, EndPoint remoteEP, CancellationToken cancellationToken = default);


/*** DOUBLE CHECK THAT THIS ONE IS OK, SINCE IT IS NO LONGER "RETURNING" DATA ***/

    //  public int ReceiveMessageFrom(Span<byte> buffer, ref SocketFlags socketFlags, ref EndPoint remoteEP, out IPPacketInformation ipPacketInformation);
        public int ReceiveMessageFrom(Span<byte> buffer, ref EndPoint remoteEP, out IPPacketInformation ipPacketInformation);
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    partial class SocketTaskExtensions
    {
    }
}

@bartonjs bartonjs removed the api-ready-for-review API is ready for review, it is NOT ready for implementation label Aug 17, 2021
@bartonjs bartonjs added the api-approved API was approved in API review, it can be implemented label Aug 17, 2021
@hrrrrustic
Copy link
Contributor

hrrrrustic commented Aug 17, 2021

// public ValueTask<int> ReceiveAsync(Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default(CancellationToken));
(Third approved method)

There is no = default at existing method

public ValueTask<int> ReceiveAsync(Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken) =>
ReceiveAsync(buffer, socketFlags, fromNetworkStream: false, cancellationToken);

Should the new overload have = default? Is it safe to update the old one if needed?

@hrrrrustic
Copy link
Contributor

Same for 8th method

public ValueTask<int> SendAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken)

@antonfirsov
Copy link
Member

As long as it's there in the reference assembly declarations, it should work fine:

public System.Threading.Tasks.ValueTask<int> ReceiveAsync(System.Memory<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }

However we should have probably assigned the defaults in the implementation declarations too for clarity and consistency.

i3arnon added a commit to i3arnon/runtime that referenced this issue Aug 18, 2021
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 18, 2021
antonfirsov pushed a commit that referenced this issue Sep 14, 2021
* Elide SocketFlags in Socket API

Fix #43934

* Fix SocketAsyncEventArgs tests

* Cosmetics

* Elide SocketFlags in tests

* Comments
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Sep 14, 2021
@karelz karelz modified the milestones: Future, 7.0.0 Sep 22, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Nov 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Net.Sockets help wanted [up-for-grabs] Good issue for external contributors
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants