-
Notifications
You must be signed in to change notification settings - Fork 1k
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
[Async TestKit] Modernize TcpStage and TcpListener #5989
Changes from 3 commits
abbabbf
2fde142
eb250b5
2097983
388333e
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 |
---|---|---|
|
@@ -14,20 +14,20 @@ | |
using Akka.Util.Internal; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace Akka.IO | ||
{ | ||
partial class TcpListener : ActorBase, IRequiresMessageQueue<IUnboundedMessageQueueSemantics> | ||
class TcpListener : ActorBase, IRequiresMessageQueue<IUnboundedMessageQueueSemantics> | ||
{ | ||
private readonly TcpExt _tcp; | ||
private readonly IActorRef _bindCommander; | ||
private readonly Tcp.Bind _bind; | ||
private readonly Socket _socket; | ||
private Tcp.Bind _bind; | ||
private Socket _socket; | ||
private readonly ILoggingAdapter _log = Context.GetLogger(); | ||
private int _acceptLimit; | ||
private SocketAsyncEventArgs[] _saeas; | ||
|
||
private int acceptLimit; | ||
private bool _binding; | ||
|
||
/// <summary> | ||
/// TBD | ||
|
@@ -40,79 +40,136 @@ partial class TcpListener : ActorBase, IRequiresMessageQueue<IUnboundedMessageQu | |
{ | ||
_tcp = tcp; | ||
_bindCommander = bindCommander; | ||
_bind = bind; | ||
|
||
Context.Watch(bind.Handler); | ||
|
||
_socket = new Socket(_bind.LocalAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { Blocking = false }; | ||
|
||
_acceptLimit = bind.PullMode ? 0 : _tcp.Settings.BatchAcceptLimit; | ||
|
||
try | ||
{ | ||
bind.Options.ForEach(x => x.BeforeServerSocketBind(_socket)); | ||
_socket.Bind(bind.LocalAddress); | ||
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. Good idea, moving this out of the constructor |
||
_socket.Listen(bind.Backlog); | ||
_saeas = Accept(_acceptLimit).ToArray(); | ||
} | ||
catch (Exception e) | ||
{ | ||
_bindCommander.Tell(bind.FailureMessage); | ||
_log.Error(e, "Bind failed for TCP channel on endpoint [{0}]", bind.LocalAddress); | ||
Context.Stop(Self); | ||
} | ||
|
||
bindCommander.Tell(new Tcp.Bound(_socket.LocalEndPoint)); | ||
Become(Initializing()); | ||
Self.Tell(bind); | ||
} | ||
private IEnumerable<SocketAsyncEventArgs> Accept(int limit) | ||
|
||
private Receive Initializing() => message => | ||
{ | ||
for(var i = 0; i < _acceptLimit; i++) | ||
switch (message) | ||
{ | ||
var self = Self; | ||
var saea = new SocketAsyncEventArgs(); | ||
saea.Completed += (s, e) => self.Tell(new SocketEvent(e)); | ||
if (!_socket.AcceptAsync(saea)) | ||
Self.Tell(new SocketEvent(saea)); | ||
yield return saea; | ||
case Tcp.Bind bind: | ||
if (_binding) | ||
{ | ||
_log.Warning("Already trying to bind to TCP channel on endpoint [{0}]", _bind.LocalAddress); | ||
return true; | ||
} | ||
_binding = true; | ||
_bind = bind; | ||
_acceptLimit = bind.PullMode ? 0 : _tcp.Settings.BatchAcceptLimit; | ||
BindAsync().PipeTo(Self); | ||
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. LGTM |
||
return true; | ||
|
||
case Status.Failure fail: | ||
_bindCommander.Tell(_bind.FailureMessage); | ||
_log.Error(fail.Cause, "Bind failed for TCP channel on endpoint [{0}]", _bind.LocalAddress); | ||
Context.Stop(Self); | ||
_binding = false; | ||
return true; | ||
|
||
case Tcp.Bound bound: | ||
Context.Watch(_bind.Handler); | ||
_bindCommander.Tell(bound); | ||
Become(Bound()); | ||
_binding = false; | ||
return true; | ||
|
||
default: | ||
return false; | ||
} | ||
} | ||
}; | ||
|
||
protected override SupervisorStrategy SupervisorStrategy() | ||
{ | ||
return Tcp.ConnectionSupervisorStrategy; | ||
} | ||
|
||
protected override bool Receive(object message) | ||
private Receive Bound() => message => | ||
{ | ||
switch (message) | ||
{ | ||
case SocketEvent evt: | ||
var saea = evt.Args; | ||
if (saea.SocketError == SocketError.Success) | ||
Context.ActorOf(Props.Create<TcpIncomingConnection>(_tcp, saea.AcceptSocket, _bind.Handler, _bind.Options, _bind.PullMode).WithDeploy(Deploy.Local)); | ||
|
||
saea.AcceptSocket = null; | ||
|
||
if (!_socket.AcceptAsync(saea)) | ||
Self.Tell(new SocketEvent(saea)); | ||
return true; | ||
|
||
case Tcp.ResumeAccepting resumeAccepting: | ||
_acceptLimit = resumeAccepting.BatchSize; | ||
// TODO: this is dangerous, previous async args are not disposed and there's no guarantee that they're not still receiving data | ||
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. Previous socket async event args? 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.
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. aren't these SAEA only handling bind events though, which have no buffers? |
||
_saeas = Accept(_acceptLimit).ToArray(); | ||
return true; | ||
|
||
case Tcp.Unbind _: | ||
_log.Debug("Unbinding endpoint {0}", _bind.LocalAddress); | ||
_socket.Dispose(); | ||
Sender.Tell(Tcp.Unbound.Instance); | ||
Become(Unbinding(Sender)); | ||
UnbindAsync().PipeTo(Self); | ||
return true; | ||
|
||
default: | ||
return false; | ||
} | ||
}; | ||
|
||
private Receive Unbinding(IActorRef requester) => message => | ||
{ | ||
switch (message) | ||
{ | ||
case Tcp.Unbound unbound: | ||
requester.Tell(unbound); | ||
_log.Debug("Unbound endpoint {0}, stopping listener", _bind.LocalAddress); | ||
Context.Stop(Self); | ||
return true; | ||
|
||
|
||
case Status.Failure fail: | ||
_log.Error(fail.Cause, "Failed to unbind TCP listener for address [{0}]", _bind.LocalAddress); | ||
Context.Stop(Self); | ||
return true; | ||
|
||
default: | ||
return false; | ||
} | ||
}; | ||
|
||
private IEnumerable<SocketAsyncEventArgs> Accept(int limit) | ||
{ | ||
for(var i = 0; i < limit; i++) | ||
{ | ||
var self = Self; | ||
var saea = new SocketAsyncEventArgs(); | ||
saea.Completed += (s, e) => self.Tell(new SocketEvent(e)); | ||
if (!_socket.AcceptAsync(saea)) | ||
Self.Tell(new SocketEvent(saea)); | ||
yield return saea; | ||
} | ||
} | ||
|
||
private async Task<Tcp.Bound> BindAsync() | ||
{ | ||
_socket = new Socket(_bind.LocalAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { Blocking = false }; | ||
|
||
_bind.Options.ForEach(x => x.BeforeServerSocketBind(_socket)); | ||
_socket.Bind(_bind.LocalAddress); | ||
Aaronontheweb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_socket.Listen(_bind.Backlog); | ||
_saeas = Accept(_acceptLimit).ToArray(); | ||
|
||
return new Tcp.Bound(_socket.LocalEndPoint); | ||
} | ||
|
||
private async Task<Tcp.Unbound> UnbindAsync() | ||
{ | ||
_log.Debug("Unbinding endpoint {0}", _bind.LocalAddress); | ||
_socket.Close(); | ||
return Tcp.Unbound.Instance; | ||
} | ||
|
||
protected override SupervisorStrategy SupervisorStrategy() | ||
{ | ||
return Tcp.ConnectionSupervisorStrategy; | ||
} | ||
|
||
protected override bool Receive(object message) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -122,7 +179,7 @@ protected override void PostStop() | |
{ | ||
try | ||
{ | ||
_socket.Dispose(); | ||
_socket?.Dispose(); | ||
_saeas?.ForEach(x => x.Dispose()); | ||
} | ||
catch (Exception e) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are breaking API changes, but no big deal. Need to be able to do stuff like this in Akka.NET v1.5.