Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit a786ec7

Browse files
committed
Fix thread management/syncing/closing issues
1 parent dd1f3b1 commit a786ec7

File tree

8 files changed

+238
-114
lines changed

8 files changed

+238
-114
lines changed

ExamplePlugin/ExamplePlugin.csproj

+8-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
1313
<FileAlignment>512</FileAlignment>
1414
<OldToolsVersion>3.5</OldToolsVersion>
15-
<SccProjectName>SAK</SccProjectName>
16-
<SccLocalPath>SAK</SccLocalPath>
17-
<SccAuxPath>SAK</SccAuxPath>
18-
<SccProvider>SAK</SccProvider>
15+
<SccProjectName>
16+
</SccProjectName>
17+
<SccLocalPath>
18+
</SccLocalPath>
19+
<SccAuxPath>
20+
</SccAuxPath>
21+
<SccProvider>
22+
</SccProvider>
1923
<TargetFrameworkProfile />
2024
</PropertyGroup>
2125
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

Toastify/src/Core/Auth/AuthHttpServer.cs

+41-17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Web;
77
using Microsoft.AspNetCore.Hosting;
88
using Toastify.Common;
9+
using Toastify.Threading;
910
using ToastifyAPI.Core.Auth;
1011
using ToastifyWebAuthAPI_Utils = ToastifyAPI.Core.Auth.ToastifyWebAuthAPI.Utils;
1112

@@ -15,6 +16,7 @@ public class AuthHttpServer : IAuthHttpServer, IDisposable
1516
{
1617
private readonly IWebHost webHost;
1718
private readonly NamedPipeServerStream pipe;
19+
private readonly CancellationTokenSource cts;
1820

1921
private Thread receiveThread;
2022

@@ -38,28 +40,32 @@ public AuthHttpServer()
3840
.UseStartup<AuthHttpServerStartup>()
3941
.UseUrls(url)
4042
.Build();
43+
44+
this.cts = new CancellationTokenSource();
4145
}
4246

4347
public async Task Start()
4448
{
45-
await this.webHost.StartAsync().ConfigureAwait(false);
46-
this.receiveThread = new Thread(this.ReceiveThread)
47-
{
48-
Name = $"Toastify_{nameof(AuthHttpServer)}_ReceiveThread_{RuntimeHelpers.GetHashCode(this)}"
49-
};
49+
await this.webHost.StartAsync(this.cts.Token).ConfigureAwait(false);
50+
51+
this.receiveThread = ThreadManager.Instance.CreateThread(this.ReceiveThread);
52+
this.receiveThread.IsBackground = true;
53+
this.receiveThread.Name = $"Toastify_{nameof(AuthHttpServer)}_ReceiveThread_{RuntimeHelpers.GetHashCode(this)}";
5054
this.receiveThread.Start();
5155
}
5256

5357
public Task Stop()
5458
{
55-
return this.webHost.StopAsync();
59+
return this.webHost.StopAsync(this.cts.Token);
5660
}
5761

58-
private void ReceiveThread()
62+
private async void ReceiveThread()
5963
{
6064
try
6165
{
62-
this.pipe.WaitForConnection();
66+
await this.pipe.WaitForConnectionAsync(this.cts.Token).ConfigureAwait(false);
67+
if (this.cts.IsCancellationRequested)
68+
return;
6369

6470
StringStream ss = new StringStream(this.pipe);
6571
string responseString = ss.ReadString();
@@ -69,26 +75,44 @@ private void ReceiveThread()
6975
string state = response.Get("state");
7076
string error = response.Get("error");
7177

72-
this.pipe.Close();
73-
7478
this.OnAuthorizationFinished(code, state, error);
7579
}
76-
catch
80+
finally
7781
{
78-
// ignore
82+
this.pipe.Close();
7983
}
8084
}
8185

86+
private void OnAuthorizationFinished(string code, string state, string error)
87+
{
88+
this.AuthorizationFinished?.Invoke(this, new AuthEventArgs(code, state, error));
89+
}
90+
91+
#region Dispose
92+
8293
public void Dispose()
8394
{
84-
this.receiveThread?.Abort();
85-
this.pipe?.Dispose();
86-
this.webHost?.Dispose();
95+
this.Dispose(TimeSpan.FromSeconds(1));
8796
}
8897

89-
private void OnAuthorizationFinished(string code, string state, string error)
98+
public void Dispose(TimeSpan timeout)
9099
{
91-
this.AuthorizationFinished?.Invoke(this, new AuthEventArgs(code, state, error));
100+
try
101+
{
102+
this.cts.Cancel();
103+
this.receiveThread.Join(timeout);
104+
}
105+
catch
106+
{
107+
// ignore
108+
}
109+
110+
this.pipe?.Dispose();
111+
this.webHost?.Dispose();
112+
113+
this.cts.Dispose();
92114
}
115+
116+
#endregion
93117
}
94118
}

Toastify/src/Core/Auth/ToastifyWebAuth.cs

+38-18
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ public class ToastifyWebAuth : BaseSpotifyWebAuth
1919
{
2020
private static readonly ILog logger = LogManager.GetLogger(typeof(ToastifyWebAuth));
2121

22-
private AuthHttpServer authHttpServer;
22+
private readonly ManualResetEvent abortAuthEvent;
2323

24+
private AuthHttpServer authHttpServer;
2425
private WindowThread<WebView> webViewWindowThread;
2526

2627
#region Non-Public Properties
@@ -47,6 +48,7 @@ public ToastifyWebAuth(Scope scopes, string state, bool showDialog)
4748
this.ShowDialog = showDialog;
4849

4950
this.authHttpServer = new AuthHttpServer();
51+
this.abortAuthEvent = new ManualResetEvent(false);
5052
}
5153

5254
protected override void Authorize()
@@ -66,7 +68,7 @@ protected override void Authorize()
6668
AuthorizationCodeFlow.Authorize(url => window.NavigateTo(url), this.Scopes, this.State, this.ShowDialog, "en");
6769
}));
6870
},
69-
OnWindowClosingAction = windowThread => windowThread.Abort()
71+
OnWindowClosingAction = window => this.abortAuthEvent.Set()
7072
};
7173

7274
this.webViewWindowThread = ThreadManager.Instance.CreateWindowThread(ApartmentState.STA, windowThreadOptions);
@@ -87,34 +89,52 @@ protected override IToken CreateToken(SpotifyTokenResponse spotifyTokenResponse)
8789

8890
protected override Task<bool> ShouldAbortAuthorization()
8991
{
90-
bool shouldAbortAuthorization = App.ShutdownEvent.WaitOne(100);
91-
return Task.FromResult(shouldAbortAuthorization);
92-
}
93-
94-
public override void Dispose()
95-
{
96-
this.authHttpServer?.Dispose();
97-
this.authHttpServer = null;
98-
99-
this.webViewWindowThread?.Dispose();
100-
this.webViewWindowThread = null;
92+
try
93+
{
94+
bool shouldAbortAuthorization = this.abortAuthEvent.WaitOne(20) || App.ShutdownEvent.WaitOne(20);
95+
return Task.FromResult(shouldAbortAuthorization);
96+
}
97+
catch
98+
{
99+
return Task.FromResult(true);
100+
}
101101
}
102102

103103
protected override void AuthHttpServer_AuthorizationFinished(object sender, AuthEventArgs e)
104104
{
105105
try
106106
{
107107
base.AuthHttpServer_AuthorizationFinished(sender, e);
108+
this.DisposeWebViewWindow(TimeSpan.FromSeconds(2));
108109
}
109110
catch (Exception ex)
110111
{
111112
logger.Error($"Unhandled error in {nameof(this.AuthHttpServer_AuthorizationFinished)}", ex);
112113
}
113-
finally
114-
{
115-
this.webViewWindowThread?.Dispose();
116-
this.webViewWindowThread = null;
117-
}
118114
}
115+
116+
#region Dispose
117+
118+
public override void Dispose()
119+
{
120+
TimeSpan timeout = TimeSpan.FromSeconds(2);
121+
this.abortAuthEvent.Set();
122+
this.DisposeAuthHttpServer(timeout);
123+
this.DisposeWebViewWindow(timeout);
124+
}
125+
126+
private void DisposeAuthHttpServer(TimeSpan timeout)
127+
{
128+
this.authHttpServer?.Dispose(timeout);
129+
this.authHttpServer = null;
130+
}
131+
132+
private void DisposeWebViewWindow(TimeSpan timeout)
133+
{
134+
this.webViewWindowThread?.Dispose(timeout);
135+
this.webViewWindowThread = null;
136+
}
137+
138+
#endregion
119139
}
120140
}

0 commit comments

Comments
 (0)