Skip to content

Commit 2e9e1a0

Browse files
committed
Mitigate race condition, introduce timeout and simplify code for property gets
1 parent 93f457d commit 2e9e1a0

File tree

3 files changed

+142
-653
lines changed

3 files changed

+142
-653
lines changed

src/ElectronNET.API/API/ApiBase.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
namespace ElectronNET.API
22
{
3+
using System;
34
using System.Collections.Concurrent;
5+
using System.Diagnostics;
46
using System.Runtime.CompilerServices;
7+
using System.Threading.Tasks;
58
using ElectronNET.Common;
69

710
public abstract class ApiBase
811
{
912
internal const int PropertyTimeout = 1000;
1013

1114
private readonly string objectName;
15+
private readonly ConcurrentDictionary<string, PropertyGetter> propertyGetters = new ConcurrentDictionary<string, PropertyGetter>();
16+
private readonly ConcurrentDictionary<string, string> propertyEventNames = new ConcurrentDictionary<string, string>();
17+
private readonly ConcurrentDictionary<string, string> propertyMessageNames = new ConcurrentDictionary<string, string>();
1218
private readonly ConcurrentDictionary<string, string> methodMessageNames = new ConcurrentDictionary<string, string>();
1319

1420
public virtual int Id
@@ -82,5 +88,77 @@ protected void CallMethod3(object val1, object val2, object val3, [CallerMemberN
8288
BridgeConnector.Socket.Emit(messageName, val1, val2, val3);
8389
}
8490
}
91+
92+
protected Task<T> GetPropertyAsync<T>([CallerMemberName] string callerName = null)
93+
{
94+
Debug.Assert(callerName != null, nameof(callerName) + " != null");
95+
96+
return this.propertyGetters.GetOrAdd(callerName, _ =>
97+
{
98+
var getter = new PropertyGetter<T>(this, callerName, PropertyTimeout);
99+
100+
getter.Task<T>().ContinueWith(_ => this.propertyGetters.TryRemove(callerName, out var _));
101+
102+
return getter;
103+
}).Task<T>();
104+
}
105+
106+
internal abstract class PropertyGetter
107+
{
108+
public abstract Task<T> Task<T>();
109+
}
110+
111+
internal class PropertyGetter<T> : PropertyGetter
112+
{
113+
private readonly Task<T> tcsTask;
114+
private TaskCompletionSource<T> tcs;
115+
116+
public PropertyGetter(ApiBase apiBase, string callerName, int timeoutMs)
117+
{
118+
this.tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
119+
this.tcsTask = this.tcs.Task;
120+
121+
var eventName = apiBase.propertyEventNames.GetOrAdd(callerName, s => $"{apiBase.objectName}-{s.StripAsync().LowerFirst()}{apiBase.SocketEventCompleteSuffix}");
122+
var messageName = apiBase.propertyMessageNames.GetOrAdd(callerName, s => apiBase.objectName + s.StripAsync());
123+
124+
BridgeConnector.Socket.On<T>(eventName, (result) =>
125+
{
126+
BridgeConnector.Socket.Off(eventName);
127+
128+
lock (this)
129+
{
130+
this.tcs?.SetResult(result);
131+
this.tcs = null;
132+
}
133+
});
134+
135+
if (apiBase.Id >= 0)
136+
{
137+
BridgeConnector.Socket.Emit(messageName, apiBase.Id);
138+
}
139+
else
140+
{
141+
BridgeConnector.Socket.Emit(messageName);
142+
}
143+
144+
System.Threading.Tasks.Task.Delay(ApiBase.PropertyTimeout).ContinueWith(_ =>
145+
{
146+
if (this.tcs != null)
147+
{
148+
lock (this)
149+
{
150+
var ex = new TimeoutException($"No response after {timeoutMs:D}ms trying to retrieve value {apiBase.objectName}.{callerName}()");
151+
this.tcs.TrySetException(ex);
152+
this.tcs = null;
153+
}
154+
}
155+
});
156+
}
157+
158+
public override Task<T1> Task<T1>()
159+
{
160+
return this.tcsTask as Task<T1>;
161+
}
162+
}
85163
}
86164
}

src/ElectronNET.API/API/App.cs

Lines changed: 13 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -374,20 +374,7 @@ public Task<string> NameAsync
374374
{
375375
get
376376
{
377-
return Task.Run<string>(() =>
378-
{
379-
var taskCompletionSource = new TaskCompletionSource<string>();
380-
381-
BridgeConnector.Socket.On("appGetNameCompleted", (result) =>
382-
{
383-
BridgeConnector.Socket.Off("appGetNameCompleted");
384-
taskCompletionSource.SetResult((string)result);
385-
});
386-
387-
BridgeConnector.Socket.Emit("appGetName");
388-
389-
return taskCompletionSource.Task;
390-
});
377+
return this.GetPropertyAsync<string>();
391378
}
392379
}
393380

@@ -525,21 +512,7 @@ public void Show()
525512
public async Task<string> GetAppPathAsync(CancellationToken cancellationToken = default)
526513
{
527514
cancellationToken.ThrowIfCancellationRequested();
528-
529-
var taskCompletionSource = new TaskCompletionSource<string>();
530-
using(cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
531-
{
532-
BridgeConnector.Socket.On("appGetAppPathCompleted", (path) =>
533-
{
534-
BridgeConnector.Socket.Off("appGetAppPathCompleted");
535-
taskCompletionSource.SetResult(path.ToString());
536-
});
537-
538-
BridgeConnector.Socket.Emit("appGetAppPath");
539-
540-
return await taskCompletionSource.Task
541-
.ConfigureAwait(false);
542-
}
515+
return await this.GetPropertyAsync<string>().ConfigureAwait(false);
543516
}
544517

545518
/// <summary>
@@ -609,21 +582,7 @@ public void SetPath(PathName name, string path)
609582
public async Task<string> GetVersionAsync(CancellationToken cancellationToken = default)
610583
{
611584
cancellationToken.ThrowIfCancellationRequested();
612-
613-
var taskCompletionSource = new TaskCompletionSource<string>();
614-
using(cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
615-
{
616-
BridgeConnector.Socket.On("appGetVersionCompleted", (version) =>
617-
{
618-
BridgeConnector.Socket.Off("appGetVersionCompleted");
619-
taskCompletionSource.SetResult(version.ToString());
620-
});
621-
622-
BridgeConnector.Socket.Emit("appGetVersion");
623-
624-
return await taskCompletionSource.Task
625-
.ConfigureAwait(false);
626-
}
585+
return await this.GetPropertyAsync<string>().ConfigureAwait(false);
627586
}
628587

629588
/// <summary>
@@ -637,21 +596,7 @@ public async Task<string> GetVersionAsync(CancellationToken cancellationToken =
637596
public async Task<string> GetLocaleAsync(CancellationToken cancellationToken = default)
638597
{
639598
cancellationToken.ThrowIfCancellationRequested();
640-
641-
var taskCompletionSource = new TaskCompletionSource<string>();
642-
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
643-
{
644-
BridgeConnector.Socket.On("appGetLocaleCompleted", (local) =>
645-
{
646-
BridgeConnector.Socket.Off("appGetLocaleCompleted");
647-
taskCompletionSource.SetResult(local.ToString());
648-
});
649-
650-
BridgeConnector.Socket.Emit("appGetLocale");
651-
652-
return await taskCompletionSource.Task
653-
.ConfigureAwait(false);
654-
}
599+
return await this.GetPropertyAsync<string>().ConfigureAwait(false);
655600
}
656601

657602
/// <summary>
@@ -942,21 +887,7 @@ public async Task<bool> SetUserTasksAsync(UserTask[] userTasks, CancellationToke
942887
public async Task<JumpListSettings> GetJumpListSettingsAsync(CancellationToken cancellationToken = default)
943888
{
944889
cancellationToken.ThrowIfCancellationRequested();
945-
946-
var taskCompletionSource = new TaskCompletionSource<JumpListSettings>();
947-
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
948-
{
949-
BridgeConnector.Socket.On("appGetJumpListSettingsCompleted", (jumpListSettings) =>
950-
{
951-
BridgeConnector.Socket.Off("appGetJumpListSettingsCompleted");
952-
taskCompletionSource.SetResult(JObject.Parse(jumpListSettings.ToString()).ToObject<JumpListSettings>());
953-
});
954-
955-
BridgeConnector.Socket.Emit("appGetJumpListSettings");
956-
957-
return await taskCompletionSource.Task
958-
.ConfigureAwait(false);
959-
}
890+
return await this.GetPropertyAsync<JumpListSettings>().ConfigureAwait(false);
960891
}
961892

962893
/// <summary>
@@ -1049,21 +980,7 @@ public void ReleaseSingleInstanceLock()
1049980
public async Task<bool> HasSingleInstanceLockAsync(CancellationToken cancellationToken = default)
1050981
{
1051982
cancellationToken.ThrowIfCancellationRequested();
1052-
1053-
var taskCompletionSource = new TaskCompletionSource<bool>();
1054-
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
1055-
{
1056-
BridgeConnector.Socket.On("appHasSingleInstanceLockCompleted", (hasLock) =>
1057-
{
1058-
BridgeConnector.Socket.Off("appHasSingleInstanceLockCompleted");
1059-
taskCompletionSource.SetResult((bool) hasLock);
1060-
});
1061-
1062-
BridgeConnector.Socket.Emit("appHasSingleInstanceLock");
1063-
1064-
return await taskCompletionSource.Task
1065-
.ConfigureAwait(false);
1066-
}
983+
return await this.GetPropertyAsync<bool>().ConfigureAwait(false);
1067984
}
1068985

1069986
/// <summary>
@@ -1102,21 +1019,7 @@ public void SetUserActivity(string type, object userInfo, string webpageUrl)
11021019
public async Task<string> GetCurrentActivityTypeAsync(CancellationToken cancellationToken = default)
11031020
{
11041021
cancellationToken.ThrowIfCancellationRequested();
1105-
1106-
var taskCompletionSource = new TaskCompletionSource<string>();
1107-
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
1108-
{
1109-
BridgeConnector.Socket.On("appGetCurrentActivityTypeCompleted", (activityType) =>
1110-
{
1111-
BridgeConnector.Socket.Off("appGetCurrentActivityTypeCompleted");
1112-
taskCompletionSource.SetResult(activityType.ToString());
1113-
});
1114-
1115-
BridgeConnector.Socket.Emit("appGetCurrentActivityType");
1116-
1117-
return await taskCompletionSource.Task
1118-
.ConfigureAwait(false);
1119-
}
1022+
return await this.GetPropertyAsync<string>().ConfigureAwait(false);
11201023
}
11211024

11221025
/// <summary>
@@ -1184,23 +1087,7 @@ public async Task<int> ImportCertificateAsync(ImportCertificateOptions options,
11841087
public async Task<ProcessMetric[]> GetAppMetricsAsync(CancellationToken cancellationToken = default)
11851088
{
11861089
cancellationToken.ThrowIfCancellationRequested();
1187-
1188-
var taskCompletionSource = new TaskCompletionSource<ProcessMetric[]>();
1189-
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
1190-
{
1191-
BridgeConnector.Socket.On("appGetAppMetricsCompleted", (result) =>
1192-
{
1193-
BridgeConnector.Socket.Off("appGetAppMetricsCompleted");
1194-
var processMetrics = ((JArray)result).ToObject<ProcessMetric[]>();
1195-
1196-
taskCompletionSource.SetResult(processMetrics);
1197-
});
1198-
1199-
BridgeConnector.Socket.Emit("appGetAppMetrics");
1200-
1201-
return await taskCompletionSource.Task
1202-
.ConfigureAwait(false);
1203-
}
1090+
return await this.GetPropertyAsync<ProcessMetric[]>().ConfigureAwait(false);
12041091
}
12051092

12061093
/// <summary>
@@ -1212,23 +1099,7 @@ public async Task<ProcessMetric[]> GetAppMetricsAsync(CancellationToken cancella
12121099
public async Task<GPUFeatureStatus> GetGpuFeatureStatusAsync(CancellationToken cancellationToken = default)
12131100
{
12141101
cancellationToken.ThrowIfCancellationRequested();
1215-
1216-
var taskCompletionSource = new TaskCompletionSource<GPUFeatureStatus>();
1217-
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
1218-
{
1219-
BridgeConnector.Socket.On("appGetGpuFeatureStatusCompleted", (result) =>
1220-
{
1221-
BridgeConnector.Socket.Off("appGetGpuFeatureStatusCompleted");
1222-
var gpuFeatureStatus = ((JObject)result).ToObject<GPUFeatureStatus>();
1223-
1224-
taskCompletionSource.SetResult(gpuFeatureStatus);
1225-
});
1226-
1227-
BridgeConnector.Socket.Emit("appGetGpuFeatureStatus");
1228-
1229-
return await taskCompletionSource.Task
1230-
.ConfigureAwait(false);
1231-
}
1102+
return await this.GetPropertyAsync<GPUFeatureStatus>().ConfigureAwait(false);
12321103
}
12331104

12341105
/// <summary>
@@ -1268,21 +1139,7 @@ public async Task<bool> SetBadgeCountAsync(int count, CancellationToken cancella
12681139
public async Task<int> GetBadgeCountAsync(CancellationToken cancellationToken = default)
12691140
{
12701141
cancellationToken.ThrowIfCancellationRequested();
1271-
1272-
var taskCompletionSource = new TaskCompletionSource<int>();
1273-
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
1274-
{
1275-
BridgeConnector.Socket.On("appGetBadgeCountCompleted", (count) =>
1276-
{
1277-
BridgeConnector.Socket.Off("appGetBadgeCountCompleted");
1278-
taskCompletionSource.SetResult((int)count);
1279-
});
1280-
1281-
BridgeConnector.Socket.Emit("appGetBadgeCount");
1282-
1283-
return await taskCompletionSource.Task
1284-
.ConfigureAwait(false);
1285-
}
1142+
return await this.GetPropertyAsync<int>().ConfigureAwait(false);
12861143
}
12871144

12881145
/// <summary>
@@ -1297,21 +1154,7 @@ public async Task<int> GetBadgeCountAsync(CancellationToken cancellationToken =
12971154
public async Task<bool> IsUnityRunningAsync(CancellationToken cancellationToken = default)
12981155
{
12991156
cancellationToken.ThrowIfCancellationRequested();
1300-
1301-
var taskCompletionSource = new TaskCompletionSource<bool>();
1302-
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
1303-
{
1304-
BridgeConnector.Socket.On("appIsUnityRunningCompleted", (isUnityRunning) =>
1305-
{
1306-
BridgeConnector.Socket.Off("appIsUnityRunningCompleted");
1307-
taskCompletionSource.SetResult((bool)isUnityRunning);
1308-
});
1309-
1310-
BridgeConnector.Socket.Emit("appIsUnityRunning");
1311-
1312-
return await taskCompletionSource.Task
1313-
.ConfigureAwait(false);
1314-
}
1157+
return await this.GetPropertyAsync<bool>().ConfigureAwait(false);
13151158
}
13161159

13171160
/// <summary>
@@ -1379,21 +1222,7 @@ public void SetLoginItemSettings(LoginSettings loginSettings)
13791222
public async Task<bool> IsAccessibilitySupportEnabledAsync(CancellationToken cancellationToken = default)
13801223
{
13811224
cancellationToken.ThrowIfCancellationRequested();
1382-
1383-
var taskCompletionSource = new TaskCompletionSource<bool>();
1384-
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
1385-
{
1386-
BridgeConnector.Socket.On("appIsAccessibilitySupportEnabledCompleted", (isAccessibilitySupportEnabled) =>
1387-
{
1388-
BridgeConnector.Socket.Off("appIsAccessibilitySupportEnabledCompleted");
1389-
taskCompletionSource.SetResult((bool)isAccessibilitySupportEnabled);
1390-
});
1391-
1392-
BridgeConnector.Socket.Emit("appIsAccessibilitySupportEnabled");
1393-
1394-
return await taskCompletionSource.Task
1395-
.ConfigureAwait(false);
1396-
}
1225+
return await this.GetPropertyAsync<bool>().ConfigureAwait(false);
13971226
}
13981227

13991228
/// <summary>
@@ -1469,20 +1298,7 @@ public Task<string> UserAgentFallbackAsync
14691298
{
14701299
get
14711300
{
1472-
return Task.Run<string>(() =>
1473-
{
1474-
var taskCompletionSource = new TaskCompletionSource<string>();
1475-
1476-
BridgeConnector.Socket.On("appGetUserAgentFallbackCompleted", (result) =>
1477-
{
1478-
BridgeConnector.Socket.Off("appGetUserAgentFallbackCompleted");
1479-
taskCompletionSource.SetResult((string)result);
1480-
});
1481-
1482-
BridgeConnector.Socket.Emit("appGetUserAgentFallback");
1483-
1484-
return taskCompletionSource.Task;
1485-
});
1301+
return this.GetPropertyAsync<string>();
14861302
}
14871303
}
14881304

0 commit comments

Comments
 (0)