From 6a8a09983e22e5e8fb529a8f32af969e7f7f2e7d Mon Sep 17 00:00:00 2001 From: Syrone Wong Date: Fri, 5 Aug 2016 11:05:16 +0800 Subject: [PATCH] Merge fixes from @GangZhuo, close #478 (#638) Squashed commit of the following: commit e280c2385c234f68e3b653dbd9efc26ed9df002f Merge: 579039f 87aa9eb Author: Gang Zhuo Date: Wed Mar 16 04:02:27 2016 -0400 Merge remote-tracking branch 'origin/master' commit 579039fe40d2ac66b97fd9cbfe6bda695d4e92ec Author: Gang Zhuo Date: Fri Mar 4 21:45:23 2016 +0800 log unhandle exception commit 8d863f1d5f484bf636a0780f5e69d944b1810ae2 Author: Gang Zhuo Date: Fri Mar 4 21:44:54 2016 +0800 fix Availability Statistics commit b3ce1c698a7518e76f521c411ca0d4240fc2f34a Author: Gang Zhuo Date: Fri Mar 4 21:07:54 2016 +0800 tiny refactor commit 0f7d39e27ef524bfa2d75034066bb074c91f91e6 Merge: bd7078a b01aced Author: Gang Zhuo Date: Fri Mar 4 20:02:58 2016 +0800 Merge remote-tracking branch 'origin/master' commit bd7078aa4f1198c5e94dc4b89b44632b42ed1982 Author: Gang Zhuo Date: Fri Mar 4 20:01:58 2016 +0800 tiny refactor commit 49adb95b6ec957a3a162399b846aca9ad1b65fdc Author: Gang Zhuo Date: Fri Mar 4 03:44:26 2016 -0500 log more Ping information for debug commit 2aa0620ddda3cb98e0db741529debd3a58b1e9c3 Author: Gang Zhuo Date: Fri Mar 4 03:04:10 2016 -0500 reduce 3rd packages [solve conflicts and squash to make history clean] Signed-off-by: Syrone Wong --- .../Controller/Service/AvailabilityStatistics.cs | 332 ++++++++++++++------- .../Controller/Service/PolipoRunner.cs | 43 ++- .../Controller/ShadowsocksController.cs | 7 +- shadowsocks-csharp/Data/cn.txt | 2 + shadowsocks-csharp/Program.cs | 15 + shadowsocks-csharp/packages.config | 5 - shadowsocks-csharp/shadowsocks-csharp.csproj | 34 --- test/packages.config | 8 - test/test.csproj | 30 -- 9 files changed, 261 insertions(+), 215 deletions(-) delete mode 100644 test/packages.config diff --git a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs index af039b18..a43c4780 100644 --- a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs +++ b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs @@ -107,67 +107,30 @@ namespace Shadowsocks.Controller var bytes = inbound - lastInbound; _lastInboundCounter[id] = inbound; var inboundSpeed = GetSpeedInKiBPerSecond(bytes, _monitorInterval.TotalSeconds); - _inboundSpeedRecords.GetOrAdd(id, new List {inboundSpeed}).Add(inboundSpeed); + _inboundSpeedRecords.GetOrAdd(id, (k) => + { + List records = new List(); + records.Add(inboundSpeed); + return records; + }); var lastOutbound = _lastOutboundCounter[id]; var outbound = _outboundCounter[id]; bytes = outbound - lastOutbound; _lastOutboundCounter[id] = outbound; var outboundSpeed = GetSpeedInKiBPerSecond(bytes, _monitorInterval.TotalSeconds); - _outboundSpeedRecords.GetOrAdd(id, new List {outboundSpeed}).Add(outboundSpeed); + _outboundSpeedRecords.GetOrAdd(id, (k) => + { + List records = new List(); + records.Add(outboundSpeed); + return records; + }); Logging.Debug( $"{id}: current/max inbound {inboundSpeed}/{_inboundSpeedRecords[id].Max()} KiB/s, current/max outbound {outboundSpeed}/{_outboundSpeedRecords[id].Max()} KiB/s"); } } - private async Task ICMPTest(Server server) - { - Logging.Debug("Ping " + server.FriendlyName()); - if (server.server == "") return null; - var result = new ICMPResult(server); - try - { - var IP = - Dns.GetHostAddresses(server.server) - .First( - ip => - ip.AddressFamily == AddressFamily.InterNetwork || - ip.AddressFamily == AddressFamily.InterNetworkV6); - var ping = new Ping(); - - foreach (var _ in Enumerable.Range(0, Repeat)) - { - try - { - var reply = await ping.SendTaskAsync(IP, TimeoutMilliseconds); - if (reply.Status.Equals(IPStatus.Success)) - { - result.RoundtripTime.Add((int?) reply.RoundtripTime); - } - else - { - result.RoundtripTime.Add(null); - } - - //Do ICMPTest in a random frequency - Thread.Sleep(TimeoutMilliseconds + new Random().Next()%TimeoutMilliseconds); - } - catch (Exception e) - { - Logging.Error($"An exception occured while eveluating {server.FriendlyName()}"); - Logging.LogUsefulException(e); - } - } - } - catch (Exception e) - { - Logging.Error($"An exception occured while eveluating {server.FriendlyName()}"); - Logging.LogUsefulException(e); - } - return result; - } - private void Reset() { _inboundSpeedRecords.Clear(); @@ -178,15 +141,14 @@ namespace Shadowsocks.Controller private void Run(object _) { UpdateRecords(); - Save(); Reset(); - FilterRawStatistics(); } - private async void UpdateRecords() + private void UpdateRecords() { var records = new Dictionary(); - + UpdateRecordsState state = new UpdateRecordsState(); + state.counter = _controller.GetCurrentConfiguration().configs.Count; foreach (var server in _controller.GetCurrentConfiguration().configs) { var id = server.Identifier(); @@ -202,43 +164,80 @@ namespace Shadowsocks.Controller records[id] = record; else records.Add(id, record); + if (Config.Ping) + { + MyPing ping = new MyPing(server, Repeat); + ping.Completed += ping_Completed; + ping.Start(new PingState { state = state, record = record }); + } + else if (!record.IsEmptyData()) + { + AppendRecord(id, record); + } } - if (Config.Ping) + if (!Config.Ping) { - var icmpResults = await TaskEx.WhenAll(_controller.GetCurrentConfiguration().configs.Select(ICMPTest)); - foreach (var result in icmpResults.Where(result => result != null)) - { - records[result.Server.Identifier()].SetResponse(result.RoundtripTime); - } + Save(); + FilterRawStatistics(); } + } - foreach (var kv in records.Where(kv => !kv.Value.IsEmptyData())) + private void ping_Completed(object sender, MyPing.CompletedEventArgs e) + { + PingState pingState = (PingState)e.UserState; + UpdateRecordsState state = pingState.state; + Server server = e.Server; + StatisticsRecord record = pingState.record; + record.SetResponse(e.RoundtripTime); + if (!record.IsEmptyData()) { - AppendRecord(kv.Key, kv.Value); + AppendRecord(server.Identifier(), record); + } + Logging.Debug($"Ping {server.FriendlyName()} {e.RoundtripTime.Count} times, {(100 - record.PackageLoss * 100)}% packages loss, min {record.MinResponse} ms, max {record.MaxResponse} ms, avg {record.AverageResponse} ms"); + if (Interlocked.Decrement(ref state.counter) == 0) + { + Save(); + FilterRawStatistics(); } } private void AppendRecord(string serverIdentifier, StatisticsRecord record) { - List records; - if (!RawStatistics.TryGetValue(serverIdentifier, out records)) + try + { + List records; + lock (RawStatistics) + { + if (!RawStatistics.TryGetValue(serverIdentifier, out records)) + { + records = new List(); + RawStatistics[serverIdentifier] = records; + } + } + records.Add(record); + } + catch (Exception e) { - records = new List(); + Logging.LogUsefulException(e); } - records.Add(record); - RawStatistics[serverIdentifier] = records; } private void Save() { + Logging.Debug($"save statistics to {AvailabilityStatisticsFile}"); if (RawStatistics.Count == 0) { return; } try { - var content = JsonConvert.SerializeObject(RawStatistics, Formatting.None); + string content; +#if DEBUG + content = JsonConvert.SerializeObject(RawStatistics, Formatting.Indented); +#else + content = JsonConvert.SerializeObject(RawStatistics, Formatting.None); +#endif File.WriteAllText(AvailabilityStatisticsFile, content); } catch (IOException e) @@ -258,17 +257,25 @@ namespace Shadowsocks.Controller private void FilterRawStatistics() { - if (RawStatistics == null) return; - if (FilteredStatistics == null) + try { - FilteredStatistics = new Statistics(); - } + Logging.Debug("filter raw statistics"); + if (RawStatistics == null) return; + if (FilteredStatistics == null) + { + FilteredStatistics = new Statistics(); + } - foreach (var serverAndRecords in RawStatistics) + foreach (var serverAndRecords in RawStatistics) + { + var server = serverAndRecords.Key; + var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord); + FilteredStatistics[server] = filteredRecords; + } + } + catch (Exception e) { - var server = serverAndRecords.Key; - var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord); - FilteredStatistics[server] = filteredRecords; + Logging.LogUsefulException(e); } } @@ -298,21 +305,10 @@ namespace Shadowsocks.Controller private static int GetSpeedInKiBPerSecond(long bytes, double seconds) { - var result = (int) (bytes/seconds)/1024; + var result = (int)(bytes / seconds) / 1024; return result; } - private class ICMPResult - { - internal readonly List RoundtripTime = new List(); - internal readonly Server Server; - - internal ICMPResult(Server server) - { - Server = server; - } - } - public void Dispose() { _recorder.Dispose(); @@ -321,44 +317,158 @@ namespace Shadowsocks.Controller public void UpdateLatency(Server server, int latency) { - List records; - _latencyRecords.TryGetValue(server.Identifier(), out records); - if (records == null) + _latencyRecords.GetOrAdd(server.Identifier(), (k) => { - records = new List(); - } - records.Add(latency); - _latencyRecords[server.Identifier()] = records; + List records = new List(); + records.Add(latency); + return records; + }); } public void UpdateInboundCounter(Server server, long n) { - long count; - if (_inboundCounter.TryGetValue(server.Identifier(), out count)) + _inboundCounter.AddOrUpdate(server.Identifier(), (k) => { - count += n; - } - else - { - count = n; - _lastInboundCounter[server.Identifier()] = 0; - } - _inboundCounter[server.Identifier()] = count; + _lastInboundCounter.GetOrAdd(server.Identifier(), 0); + return n; + }, (k, v) => (v + n)); } public void UpdateOutboundCounter(Server server, long n) { - long count; - if (_outboundCounter.TryGetValue(server.Identifier(), out count)) + _outboundCounter.AddOrUpdate(server.Identifier(), (k) => + { + _lastOutboundCounter.GetOrAdd(server.Identifier(), 0); + return n; + }, (k, v) => (v + n)); + } + + class UpdateRecordsState + { + public int counter; + } + + class PingState + { + public UpdateRecordsState state; + public StatisticsRecord record; + } + + class MyPing + { + //arguments for ICMP tests + public const int TimeoutMilliseconds = 500; + + public EventHandler Completed; + private Server server; + + private int repeat; + private IPAddress ip; + private Ping ping; + private List RoundtripTime; + + public MyPing(Server server, int repeat) { - count += n; + this.server = server; + this.repeat = repeat; + RoundtripTime = new List(repeat); + ping = new Ping(); + ping.PingCompleted += Ping_PingCompleted; + } + + public void Start(object userstate) + { + if (server.server == "") + { + FireCompleted(new Exception("Invalid Server"), userstate); + return; + } + new Task(() => ICMPTest(0, userstate)).Start(); } - else + + private void ICMPTest(int delay, object userstate) { - count = n; - _lastOutboundCounter[server.Identifier()] = 0; + try + { + Logging.Debug($"Ping {server.FriendlyName()}"); + if (ip == null) + { + ip = Dns.GetHostAddresses(server.server) + .First( + ip => + ip.AddressFamily == AddressFamily.InterNetwork || + ip.AddressFamily == AddressFamily.InterNetworkV6); + } + repeat--; + if (delay > 0) + Thread.Sleep(delay); + ping.SendAsync(ip, TimeoutMilliseconds, userstate); + } + catch (Exception e) + { + Logging.Error($"An exception occured while eveluating {server.FriendlyName()}"); + Logging.LogUsefulException(e); + FireCompleted(e, userstate); + } + } + + private void Ping_PingCompleted(object sender, PingCompletedEventArgs e) + { + try + { + if (e.Reply.Status == IPStatus.Success) + { + Logging.Debug($"Ping {server.FriendlyName()} {e.Reply.RoundtripTime} ms"); + RoundtripTime.Add((int?)e.Reply.RoundtripTime); + } + else + { + Logging.Debug($"Ping {server.FriendlyName()} timeout"); + RoundtripTime.Add(null); + } + TestNext(e.UserState); + } + catch (Exception ex) + { + Logging.Error($"An exception occured while eveluating {server.FriendlyName()}"); + Logging.LogUsefulException(ex); + FireCompleted(ex, e.UserState); + } + } + + private void TestNext(object userstate) + { + if (repeat > 0) + { + //Do ICMPTest in a random frequency + int delay = TimeoutMilliseconds + new Random().Next() % TimeoutMilliseconds; + new Task(() => ICMPTest(delay, userstate)).Start(); + } + else + { + FireCompleted(null, userstate); + } + } + + private void FireCompleted(Exception error, object userstate) + { + Completed?.Invoke(this, new CompletedEventArgs + { + Error = error, + Server = server, + RoundtripTime = RoundtripTime, + UserState = userstate + }); + } + + public class CompletedEventArgs : EventArgs + { + public Exception Error; + public Server Server; + public List RoundtripTime; + public object UserState; } - _outboundCounter[server.Identifier()] = count; } + } } diff --git a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs index 1c0efcdd..a8b1e2d2 100644 --- a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs +++ b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs @@ -47,20 +47,7 @@ namespace Shadowsocks.Controller Process[] existingPolipo = Process.GetProcessesByName("ss_privoxy"); foreach (Process p in existingPolipo) { - try - { - p.CloseMainWindow(); - p.WaitForExit(100); - if (!p.HasExited) - { - p.Kill(); - p.WaitForExit(); - } - } - catch (Exception e) - { - Logging.LogUsefulException(e); - } + KillProcess(p); } string polipoConfig = Resources.privoxy_conf; _runningPort = this.GetFreePort(); @@ -86,20 +73,30 @@ namespace Shadowsocks.Controller { if (_process != null) { - try - { - _process.Kill(); - _process.WaitForExit(); - } - catch (Exception e) - { - Logging.LogUsefulException(e); - } + KillProcess(_process); _process = null; } RefreshTrayArea(); } + private static void KillProcess(Process p) + { + try + { + p.CloseMainWindow(); + p.WaitForExit(100); + if (!p.HasExited) + { + p.Kill(); + p.WaitForExit(); + } + } + catch (Exception e) + { + Logging.LogUsefulException(e); + } + } + private int GetFreePort() { int defaultPort = 8123; diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index b6f64ed5..8023f51f 100755 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -5,7 +5,6 @@ using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; -using System.Threading.Tasks; using Newtonsoft.Json; using Shadowsocks.Controller.Strategy; @@ -335,7 +334,7 @@ namespace Shadowsocks.Controller { if (_config.availabilityStatistics) { - new Task(() => availabilityStatistics.UpdateLatency(server, (int)latency.TotalMilliseconds)).Start(); + availabilityStatistics.UpdateLatency(server, (int)latency.TotalMilliseconds); } } @@ -344,7 +343,7 @@ namespace Shadowsocks.Controller Interlocked.Add(ref inboundCounter, n); if (_config.availabilityStatistics) { - new Task(() => availabilityStatistics.UpdateInboundCounter(server, n)).Start(); + availabilityStatistics.UpdateInboundCounter(server, n); } } @@ -353,7 +352,7 @@ namespace Shadowsocks.Controller Interlocked.Add(ref outboundCounter, n); if (_config.availabilityStatistics) { - new Task(() => availabilityStatistics.UpdateOutboundCounter(server, n)).Start(); + availabilityStatistics.UpdateOutboundCounter(server, n); } } diff --git a/shadowsocks-csharp/Data/cn.txt b/shadowsocks-csharp/Data/cn.txt index a1211fb4..30f51f20 100644 --- a/shadowsocks-csharp/Data/cn.txt +++ b/shadowsocks-csharp/Data/cn.txt @@ -101,3 +101,5 @@ Failed to decode QRCode=无法解析二维码 Failed to update registry=无法修改注册表 System Proxy On: =系统代理已启用: Running: Port {0}=正在运行:端口 {0} +Unexpect error, shadowsocks will be exit. Please report to=非预期错误,Shadowsocks将退出。请提交此错误到 + \ No newline at end of file diff --git a/shadowsocks-csharp/Program.cs b/shadowsocks-csharp/Program.cs index 977ebffd..f277de6f 100755 --- a/shadowsocks-csharp/Program.cs +++ b/shadowsocks-csharp/Program.cs @@ -21,6 +21,7 @@ namespace Shadowsocks Utils.ReleaseMemory(true); using (Mutex mutex = new Mutex(false, "Global\\Shadowsocks_" + Application.StartupPath.GetHashCode())) { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); @@ -53,5 +54,19 @@ namespace Shadowsocks Application.Run(); } } + + private static int exited = 0; + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + if (Interlocked.Increment(ref exited) == 1) + { + Logging.Error(e.ExceptionObject?.ToString()); + MessageBox.Show(I18N.GetString("Unexpect error, shadowsocks will be exit. Please report to") + + " https://github.com/shadowsocks/shadowsocks-windows/issues " + + Environment.NewLine + (e.ExceptionObject?.ToString()), + "Shadowsocks Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + Application.Exit(); + } + } } } diff --git a/shadowsocks-csharp/packages.config b/shadowsocks-csharp/packages.config index 6f3efda8..b80520d3 100644 --- a/shadowsocks-csharp/packages.config +++ b/shadowsocks-csharp/packages.config @@ -3,11 +3,6 @@ - - - - - \ No newline at end of file diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 15046bb9..2bd9fa88 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -66,18 +66,6 @@ - - 3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll - True - - - 3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll - True - - - 3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll - True - 3rd\Newtonsoft.Json.8.0.2\lib\net40\Newtonsoft.Json.dll @@ -88,27 +76,7 @@ - - 3rd\Microsoft.Bcl.1.1.10\lib\net40\System.IO.dll - True - - - 3rd\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll - True - - - 3rd\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll - True - - - 3rd\Microsoft.Bcl.1.1.10\lib\net40\System.Runtime.dll - True - - - 3rd\Microsoft.Bcl.1.1.10\lib\net40\System.Threading.Tasks.dll - True - @@ -332,10 +300,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - diff --git a/test/packages.config b/test/packages.config deleted file mode 100644 index 7d90c6e3..00000000 --- a/test/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/test/test.csproj b/test/test.csproj index 777a10b8..d1c3fa2e 100755 --- a/test/test.csproj +++ b/test/test.csproj @@ -35,29 +35,9 @@ - - ..\shadowsocks-csharp\3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll - True - - - ..\shadowsocks-csharp\3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll - True - - - ..\shadowsocks-csharp\3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll - True - - - ..\shadowsocks-csharp\3rd\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll - True - - - ..\shadowsocks-csharp\3rd\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll - True - @@ -82,9 +62,6 @@ shadowsocks-csharp - - - @@ -105,13 +82,6 @@ - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - -