public void Start() { var Success = false; try { IsRunningValue.Update ( b => { if (b) { throw new InvalidOperationException(); } if (BindingsValue.Length == 0) { throw new Exception("NoValidBinding"); } ListeningTaskTokenSource = new CancellationTokenSource(); var ListeningTaskToken = ListeningTaskTokenSource.Token; Action <UdpSession> Purify = StoppingSession => { SessionSets.DoAction ( ss => { if (ss.Sessions.Contains(StoppingSession)) { ss.Sessions.Remove(StoppingSession); var IpAddress = StoppingSession.RemoteEndPoint.Address; var isi = ss.IpSessions[IpAddress]; if (isi.Authenticated.Contains(StoppingSession)) { isi.Authenticated.Remove(StoppingSession); } isi.Count -= 1; if (isi.Count == 0) { ss.IpSessions.Remove(IpAddress); } var SessionId = StoppingSession.SessionId; ss.SessionIdToSession.Remove(SessionId); } } ); StoppingSession.Dispose(); }; Action <AcceptingInfo> Accept = a => { var ep = a.RemoteEndPoint; UdpSession s = null; try { var Buffer = a.ReadBuffer; if (Buffer.Length < 12) { return; } var SessionId = Buffer[0] | ((Int32)(Buffer[1]) << 8) | ((Int32)(Buffer[2]) << 16) | ((Int32)(Buffer[3]) << 24); var Flag = Buffer[4] | ((Int32)(Buffer[5]) << 8); var Index = Buffer[6] | ((Int32)(Buffer[7]) << 8); var Verification = Buffer[8] | ((Int32)(Buffer[9]) << 8) | ((Int32)(Buffer[10]) << 16) | ((Int32)(Buffer[11]) << 24); Buffer[8] = 0; Buffer[9] = 0; Buffer[10] = 0; Buffer[11] = 0; if (ServerContext.EnableLogTransport) { //按Flag中是否包含AUX分别生成日志 if ((Flag & 8) != 0) { ServerContext.RaiseSessionLog(new SessionLogEntry { Token = SessionId.ToString("X8"), RemoteEndPoint = ep, Time = DateTime.UtcNow, Type = "UdpTransport", Name = "ReceiveAux", Message = "AckIndex: " + Index.ToInvariantString() + " Length: " + Buffer.Length.ToInvariantString() }); } else { ServerContext.RaiseSessionLog(new SessionLogEntry { Token = SessionId.ToString("X8"), RemoteEndPoint = ep, Time = DateTime.UtcNow, Type = "UdpTransport", Name = "Receive", Message = "Index: " + Index.ToInvariantString() + " Length: " + Buffer.Length.ToInvariantString() }); } } //如果Flag中不包含ENC,则验证CRC32 if ((Flag & 2) == 0) { if (Cryptography.CRC32(Buffer) != Verification) { if (ServerContext.EnableLogTransport) { ServerContext.RaiseSessionLog(new SessionLogEntry { Token = SessionId.ToString("X8"), RemoteEndPoint = ep, Time = DateTime.UtcNow, Type = "UdpTransport", Name = "Receive", Message = "Index: " + Index.ToInvariantString() + " CRC32Failed" }); } return; } } //如果Flag中包含INI,则初始化 if ((Flag & 4) != 0) { if ((Flag & 1) != 0) { return; } if ((Flag & 2) != 0) { return; } if ((Flag & 8) != 0) { return; } var Offset = 12; s = new UdpSession(this, a.Socket, ep, VirtualTransportServerFactory, QueueUserWorkItem); SessionId = s.SessionId; if (MaxConnectionsValue.HasValue && (SessionSets.Check(ss => ss.Sessions.Count) >= MaxConnectionsValue.Value)) { PurifyConsumer.DoOne(); } if (MaxConnectionsValue.HasValue && (SessionSets.Check(ss => ss.Sessions.Count) >= MaxConnectionsValue.Value)) { try { s.Start(); OnMaxConnectionsExceeded(s); } finally { s.Dispose(); } return; } if (MaxConnectionsPerIPValue.HasValue && (SessionSets.Check(ss => ss.IpSessions.ContainsKey(ep.Address) ? ss.IpSessions[ep.Address].Count : 0) >= MaxConnectionsPerIPValue.Value)) { try { s.Start(); OnMaxConnectionsPerIPExceeded(s); } finally { PurifyConsumer.Push(s); } return; } if (MaxUnauthenticatedPerIPValue.HasValue && (SessionSets.Check(ss => ss.IpSessions.ContainsKey(ep.Address) ? (ss.IpSessions[ep.Address].Count - ss.IpSessions[ep.Address].Authenticated.Count) : 0) >= MaxUnauthenticatedPerIPValue.Value)) { try { s.Start(); OnMaxConnectionsPerIPExceeded(s); } finally { PurifyConsumer.Push(s); } return; } SessionSets.DoAction ( ss => { ss.Sessions.Add(s); if (ss.IpSessions.ContainsKey(ep.Address)) { ss.IpSessions[ep.Address].Count += 1; } else { var isi = new IpSessionInfo(); isi.Count += 1; ss.IpSessions.Add(ep.Address, isi); } while ((SessionId == 0) || ss.SessionIdToSession.ContainsKey(SessionId)) { s = new UdpSession(this, a.Socket, ep, VirtualTransportServerFactory, QueueUserWorkItem); SessionId = s.SessionId; } ss.SessionIdToSession.Add(SessionId, s); } ); s.Start(); s.PrePush(() => { if (!s.Push(ep, Index, null, Buffer, Offset, Buffer.Length - Offset)) { PurifyConsumer.Push(s); } }); } else { var Close = false; SessionSets.DoAction ( ss => { if (!ss.SessionIdToSession.ContainsKey(SessionId)) { Close = true; return; } s = ss.SessionIdToSession[SessionId]; } ); if (Close) { return; } s.PrePush(() => { var IsEncrypted = (Flag & 2) != 0; var NextSecureContext = s.NextSecureContext; var SecureContext = s.SecureContext; if ((SecureContext == null) && (NextSecureContext != null)) { s.SecureContext = NextSecureContext; s.NextSecureContext = null; SecureContext = NextSecureContext; NextSecureContext = null; } if ((SecureContext != null) != IsEncrypted) { return; } if (IsEncrypted) { var Key = SecureContext.ClientToken.Concat(Cryptography.SHA256(Buffer.Skip(4).Take(4))); var HMACBytes = Cryptography.HMACSHA256Simple(Key, Buffer).Take(4).ToArray(); var HMAC = HMACBytes[0] | ((Int32)(HMACBytes[1]) << 8) | ((Int32)(HMACBytes[2]) << 16) | ((Int32)(HMACBytes[3]) << 24); if (HMAC != Verification) { if (ServerContext.EnableLogTransport) { ServerContext.RaiseSessionLog(new SessionLogEntry { Token = SessionId.ToString("X8"), RemoteEndPoint = ep, Time = DateTime.UtcNow, Type = "UdpTransport", Name = "Receive", Message = "Index: " + Index.ToInvariantString() + " HMACFailed" }); } return; } } var Offset = 12; int[] Indices = null; if ((Flag & 1) != 0) { if (Buffer.Length < 14) { return; } var NumIndex = Buffer[Offset] | ((Int32)(Buffer[Offset + 1]) << 8); if (Buffer.Length < 14 + NumIndex * 2) { return; } if (NumIndex > UdpSession.WritingWindowSize) //若Index数量较大,则丢弃包 { return; } Offset += 2; Indices = new int[NumIndex]; for (int k = 0; k < NumIndex; k += 1) { Indices[k] = Buffer[Offset + k * 2] | ((Int32)(Buffer[Offset + k * 2 + 1]) << 8); } Offset += NumIndex * 2; } //如果Flag中包含AUX,则判断 if ((Flag & 8) != 0) { if (Indices == null) { return; } if (Indices.Length < 1) { return; } if (Index != Indices[0]) { return; } if (Offset != Buffer.Length) { return; } } var PreviousRemoteEndPoint = s.RemoteEndPoint; if (!PreviousRemoteEndPoint.Equals(ep)) { SessionSets.DoAction ( ss => { var Authenticated = false; { var PreviousIpAddress = PreviousRemoteEndPoint.Address; var isi = ss.IpSessions[PreviousIpAddress]; if (isi.Authenticated.Contains(s)) { isi.Authenticated.Remove(s); Authenticated = true; } isi.Count -= 1; if (isi.Count == 0) { ss.IpSessions.Remove(PreviousIpAddress); } } { IpSessionInfo isi; if (ss.IpSessions.ContainsKey(ep.Address)) { isi = ss.IpSessions[ep.Address]; isi.Count += 1; } else { isi = new IpSessionInfo(); isi.Count += 1; ss.IpSessions.Add(ep.Address, isi); } if (Authenticated) { isi.Authenticated.Add(s); } } s.RemoteEndPoint = ep; } ); } if ((Flag & 8) != 0) { if (!s.PushAux(ep, Indices)) { PurifyConsumer.Push(s); } } else { if (!s.Push(ep, Index, Indices, Buffer, Offset, Buffer.Length - Offset)) { PurifyConsumer.Push(s); } } }); } } catch (Exception ex) { if (ServerContext.EnableLogSystem) { ServerContext.RaiseSessionLog(new SessionLogEntry { Token = "", RemoteEndPoint = ep, Time = DateTime.UtcNow, Type = "Sys", Name = "Exception", Message = ExceptionInfo.GetExceptionInfo(ex) }); } if (s != null) { PurifyConsumer.Push(s); } } }; AcceptConsumer = new AsyncConsumer <AcceptingInfo>(QueueUserWorkItem, a => { Accept(a); return(true); }, int.MaxValue); var Exceptions = new List <Exception>(); var Bindings = new List <IPEndPoint>(); //将所有默认地址换为实际的所有接口地址 foreach (var Binding in BindingsValue) { if (IPAddress.Equals(Binding.Address, IPAddress.Any) || IPAddress.Equals(Binding.Address, IPAddress.IPv6Any)) { foreach (var ni in NetworkInterface.GetAllNetworkInterfaces()) { foreach (var a in ni.GetIPProperties().UnicastAddresses) { if (a.Address.AddressFamily == Binding.Address.AddressFamily) { Bindings.Add(new IPEndPoint(a.Address, Binding.Port)); } } } } else { Bindings.Add(Binding); } } foreach (var Binding in Bindings) { Func <Socket> CreateSocket = () => { var s = new Socket(Binding.AddressFamily, SocketType.Dgram, ProtocolType.Udp); //在Windows下关闭SIO_UDP_CONNRESET报告,防止接受数据出错 //http://support.microsoft.com/kb/263823/en-us if (System.Environment.OSVersion.Platform == PlatformID.Win32NT) { uint IOC_IN = 0x80000000; uint IOC_VENDOR = 0x18000000; uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12; s.IOControl(unchecked ((int)(SIO_UDP_CONNRESET)), new byte[] { Convert.ToByte(false) }, null); } return(s); }; var Socket = CreateSocket(); try { Socket.Bind(Binding); } catch (SocketException ex) { Exceptions.Add(ex); continue; } var BindingInfo = new BindingInfo(); BindingInfo.EndPoint = Binding; BindingInfo.Socket = new LockedVariable <Socket>(Socket); Func <SocketAsyncEventArgs, Boolean> Completed = args => { try { if (ListeningTaskToken.IsCancellationRequested) { return(false); } if (args.SocketError == SocketError.Success) { var Count = args.BytesTransferred; var ReadBuffer = new Byte[Count]; Array.Copy(BindingInfo.ReadBuffer, ReadBuffer, Count); var a = new AcceptingInfo { Socket = BindingInfo.Socket.Check(s => s), ReadBuffer = ReadBuffer, RemoteEndPoint = (IPEndPoint)(args.RemoteEndPoint) }; AcceptConsumer.Push(a); } else { BindingInfo.Socket.Update ( OriginalSocket => { try { OriginalSocket.Dispose(); } catch (Exception) { } var NewSocket = CreateSocket(); NewSocket.Bind(Binding); return(NewSocket); } ); } } finally { args.Dispose(); } BindingInfo.Start(); return(true); }; BindingInfo.ListenConsumer = new AsyncConsumer <SocketAsyncEventArgs>(QueueUserWorkItem, Completed, 1); BindingInfo.Start = () => { var EventArgs = new SocketAsyncEventArgs(); EventArgs.RemoteEndPoint = new IPEndPoint(Binding.Address.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Any : IPAddress.Any, 0); EventArgs.SetBuffer(BindingInfo.ReadBuffer, 0, BindingInfo.ReadBuffer.Length); var bs = BindingInfo.Socket.Check(s => s); EventArgs.Completed += (o, args) => BindingInfo.ListenConsumer.Push(args); try { if (!bs.ReceiveFromAsync(EventArgs)) { BindingInfo.ListenConsumer.Push(EventArgs); } } catch (ObjectDisposedException) { } }; BindingInfos.Add(BindingInfo); } if (BindingInfos.Count == 0) { throw new AggregateException(Exceptions); } PurifyConsumer = new AsyncConsumer <UdpSession>(PurifierQueueUserWorkItem, s => { Purify(s); return(true); }, int.MaxValue); if (UnauthenticatedSessionIdleTimeoutValue.HasValue || SessionIdleTimeoutValue.HasValue) { var TimePeriod = TimeSpan.FromSeconds(Math.Max(TimeoutCheckPeriodValue, 1)); LastActiveTimeCheckTimer = new Timer(state => { if (UnauthenticatedSessionIdleTimeoutValue.HasValue) { var CheckTime = DateTime.UtcNow.AddIntSeconds(-UnauthenticatedSessionIdleTimeoutValue.Value); SessionSets.DoAction ( ss => { foreach (var s in ss.Sessions) { var IpAddress = s.RemoteEndPoint.Address; var isi = ss.IpSessions[IpAddress]; if (!isi.Authenticated.Contains(s)) { if (s.LastActiveTime < CheckTime) { PurifyConsumer.Push(s); } } } } ); } if (SessionIdleTimeoutValue.HasValue) { var CheckTime = DateTime.UtcNow.AddIntSeconds(-SessionIdleTimeoutValue.Value); SessionSets.DoAction ( ss => { foreach (var s in ss.Sessions) { var IpAddress = s.RemoteEndPoint.Address; var isi = ss.IpSessions[IpAddress]; if (isi.Authenticated.Contains(s)) { if (s.LastActiveTime < CheckTime) { PurifyConsumer.Push(s); } } } } ); } }, null, TimePeriod, TimePeriod); } foreach (var BindingInfo in BindingInfos) { BindingInfo.Start(); } Success = true; return(true); } ); } finally { if (!Success) { Stop(); } } }
public void Start() { var Success = false; try { IsRunningValue.Update ( b => { if (b) { throw new InvalidOperationException(); } if (BindingsValue.Length == 0) { throw new Exception("NoValidBinding"); } ListeningTaskTokenSource = new CancellationTokenSource(); AcceptingTaskTokenSource = new CancellationTokenSource(); AcceptingTaskNotifier = new AutoResetEvent(false); PurifieringTaskTokenSource = new CancellationTokenSource(); PurifieringTaskNotifier = new AutoResetEvent(false); var ListeningTaskToken = ListeningTaskTokenSource.Token; var AcceptingTaskToken = AcceptingTaskTokenSource.Token; var PurifieringTaskToken = PurifieringTaskTokenSource.Token; var Exceptions = new List <Exception>(); foreach (var Binding in BindingsValue) { var Socket = new Socket(Binding.AddressFamily, SocketType.Stream, ProtocolType.Tcp); try { Socket.Bind(Binding); } catch (SocketException ex) { Exceptions.Add(ex); continue; } Socket.Listen(MaxConnectionsValue.HasValue ? (MaxConnectionsValue.Value + 1) : 128); var BindingInfo = new BindingInfo { Socket = new LockedVariable <Socket>(Socket), Task = null }; var Task = new Task ( () => { try { while (true) { if (ListeningTaskToken.IsCancellationRequested) { return; } try { var a = BindingInfo.Socket.Check(s => s).Accept(); AcceptedSockets.Add(a); AcceptingTaskNotifier.Set(); } catch (SocketException) { if (ListeningTaskToken.IsCancellationRequested) { return; } BindingInfo.Socket.Update ( OriginalSocket => { try { OriginalSocket.Close(); } catch (Exception) { } try { OriginalSocket.Dispose(); } catch (Exception) { } var NewSocket = new Socket(Binding.AddressFamily, SocketType.Stream, ProtocolType.Tcp); NewSocket.Bind(Binding); NewSocket.Listen(MaxConnectionsValue.HasValue ? (MaxConnectionsValue.Value + 1) : 128); Socket = NewSocket; return(NewSocket); } ); } } } catch (ObjectDisposedException) { } }, TaskCreationOptions.LongRunning ); BindingInfo.Task = Task; BindingInfos.Add(Binding, BindingInfo); } if (BindingInfos.Count == 0) { throw new AggregateException(Exceptions); } AcceptingTask = new Task ( () => { while (true) { if (AcceptingTaskToken.IsCancellationRequested) { return; } AcceptingTaskNotifier.WaitOne(); while (true) { Socket a; if (!AcceptedSockets.TryTake(out a)) { break; } var s = new TSession() { Server = (TServer)this, RemoteEndPoint = (IPEndPoint)(a.RemoteEndPoint) }; if (SessionIdleTimeoutValue.HasValue) { a.ReceiveTimeout = SessionIdleTimeoutValue.Value * 1000; } s.SetSocket(a); if (MaxConnectionsValue.HasValue && (Sessions.Check(ss => ss.Count) >= MaxConnectionsValue.Value)) { try { s.Start(); if (MaxConnectionsExceeded != null) { MaxConnectionsExceeded(s); } } finally { s.Stop(); s.Dispose(); } continue; } IPEndPoint e = (IPEndPoint)(a.RemoteEndPoint); if (MaxConnectionsPerIPValue.HasValue && (IpSessions.Check(iss => iss.ContainsKey(e.Address) ? iss[e.Address] : 0) >= MaxConnectionsPerIPValue.Value)) { try { s.Start(); if (MaxConnectionsPerIPExceeded != null) { MaxConnectionsPerIPExceeded(s); } } finally { s.Stop(); s.Dispose(); } continue; } Sessions.DoAction ( ss => { ss.Add(s); } ); IpSessions.DoAction ( iss => { if (iss.ContainsKey(e.Address)) { iss[e.Address] += 1; } else { iss.Add(e.Address, 1); } } ); s.Start(); } } }, AcceptingTaskToken, TaskCreationOptions.LongRunning ); PurifieringTask = new Task ( () => { while (true) { if (PurifieringTaskToken.IsCancellationRequested) { return; } PurifieringTaskNotifier.WaitOne(); TSession StoppingSession; while (StoppingSessions.TryTake(out StoppingSession)) { var Removed = false; Sessions.DoAction ( ss => { if (ss.Contains(StoppingSession)) { ss.Remove(StoppingSession); Removed = true; } } ); if (Removed) { var IpAddress = StoppingSession.RemoteEndPoint.Address; IpSessions.DoAction ( iss => { if (iss.ContainsKey(IpAddress)) { iss[IpAddress] -= 1; if (iss[IpAddress] == 0) { iss.Remove(IpAddress); } } } ); } StoppingSession.Stop(); StoppingSession.Dispose(); } } }, PurifieringTaskToken, TaskCreationOptions.LongRunning ); AcceptingTask.Start(); PurifieringTask.Start(); foreach (var BindingInfo in BindingInfos.Values) { BindingInfo.Task.Start(); } Success = true; return(true); } ); } finally { if (!Success) { Stop(); } } }
public void Start() { var Success = false; try { IsRunningValue.Update ( b => { if (b) { throw new InvalidOperationException(); } if (BindingsValue.Length == 0) { throw new Exception("NoValidBinding"); } ListeningTaskTokenSource = new CancellationTokenSource(); var ListeningTaskToken = ListeningTaskTokenSource.Token; Action <TcpSession> Purify = StoppingSession => { SessionSets.DoAction ( ss => { if (ss.Sessions.Contains(StoppingSession)) { ss.Sessions.Remove(StoppingSession); var IpAddress = StoppingSession.RemoteEndPoint.Address; var isi = ss.IpSessions[IpAddress]; if (isi.Authenticated.Contains(StoppingSession)) { isi.Authenticated.Remove(StoppingSession); } isi.Count -= 1; if (isi.Count == 0) { ss.IpSessions.Remove(IpAddress); } } } ); StoppingSession.Dispose(); }; Action <Socket> Accept = a => { IPEndPoint ep; try { ep = (IPEndPoint)(a.RemoteEndPoint); } catch { a.Dispose(); return; } var s = new TcpSession(this, new StreamedAsyncSocket(a, UnauthenticatedSessionIdleTimeoutValue, QueueUserWorkItem), ep, VirtualTransportServerFactory, QueueUserWorkItem); if (MaxConnectionsValue.HasValue && (SessionSets.Check(ss => ss.Sessions.Count) >= MaxConnectionsValue.Value)) { PurifyConsumer.DoOne(); } if (MaxConnectionsValue.HasValue && (SessionSets.Check(ss => ss.Sessions.Count) >= MaxConnectionsValue.Value)) { try { s.Start(); OnMaxConnectionsExceeded(s); } finally { s.Dispose(); } return; } if (MaxConnectionsPerIPValue.HasValue && (SessionSets.Check(ss => ss.IpSessions.ContainsKey(ep.Address) ? ss.IpSessions[ep.Address].Count : 0) >= MaxConnectionsPerIPValue.Value)) { try { s.Start(); OnMaxConnectionsPerIPExceeded(s); } finally { PurifyConsumer.Push(s); } return; } if (MaxUnauthenticatedPerIPValue.HasValue && (SessionSets.Check(ss => ss.IpSessions.ContainsKey(ep.Address) ? ss.IpSessions[ep.Address].Count : 0) >= MaxUnauthenticatedPerIPValue.Value)) { try { s.Start(); OnMaxConnectionsPerIPExceeded(s); } finally { PurifyConsumer.Push(s); } return; } SessionSets.DoAction ( ss => { ss.Sessions.Add(s); if (ss.IpSessions.ContainsKey(ep.Address)) { ss.IpSessions[ep.Address].Count += 1; } else { var isi = new IpSessionInfo(); isi.Count += 1; ss.IpSessions.Add(ep.Address, isi); } } ); s.Start(); }; AcceptConsumer = new AsyncConsumer <Socket>(QueueUserWorkItem, a => { Accept(a); return(true); }, int.MaxValue); var Exceptions = new List <Exception>(); foreach (var Binding in BindingsValue) { Func <Socket> CreateSocket = () => { var s = new Socket(Binding.AddressFamily, SocketType.Stream, ProtocolType.Tcp); return(s); }; var Socket = CreateSocket(); try { Socket.Bind(Binding); } catch (SocketException ex) { Exceptions.Add(ex); continue; } Socket.Listen(MaxConnectionsValue.HasValue ? (MaxConnectionsValue.Value + 1) : 128); var BindingInfo = new BindingInfo(); BindingInfo.EndPoint = Binding; BindingInfo.Socket = new LockedVariable <Socket>(Socket); Func <SocketAsyncEventArgs, Boolean> Completed = args => { try { if (ListeningTaskToken.IsCancellationRequested) { return(false); } if (args.SocketError == SocketError.Success) { var a = args.AcceptSocket; AcceptConsumer.Push(a); } else { BindingInfo.Socket.Update ( OriginalSocket => { try { OriginalSocket.Dispose(); } catch (Exception) { } var NewSocket = CreateSocket(); NewSocket.Bind(Binding); NewSocket.Listen(MaxConnectionsValue.HasValue ? (MaxConnectionsValue.Value + 1) : 128); return(NewSocket); } ); } } finally { args.Dispose(); } BindingInfo.Start(); return(true); }; BindingInfo.ListenConsumer = new AsyncConsumer <SocketAsyncEventArgs>(QueueUserWorkItem, Completed, 1); BindingInfo.Start = () => { var EventArgs = new SocketAsyncEventArgs(); var bs = BindingInfo.Socket.Check(s => s); EventArgs.Completed += (o, args) => { if (ListeningTaskToken.IsCancellationRequested) { return; } BindingInfo.ListenConsumer.Push(args); }; try { if (!bs.AcceptAsync(EventArgs)) { BindingInfo.ListenConsumer.Push(EventArgs); } } catch (ObjectDisposedException) { } }; BindingInfos.Add(BindingInfo); } if (BindingInfos.Count == 0) { throw new AggregateException(Exceptions); } PurifyConsumer = new AsyncConsumer <TcpSession>(PurifierQueueUserWorkItem, s => { Purify(s); return(true); }, int.MaxValue); foreach (var BindingInfo in BindingInfos) { BindingInfo.Start(); } Success = true; return(true); } ); } finally { if (!Success) { Stop(); } } }