Skip to content

Commit cf0b12e

Browse files
authored
Merge pull request #908 from softworkz/submit_api_consolidation
Mitigate race conditions and simplify API invocations
2 parents 4671b9b + 4c3065c commit cf0b12e

File tree

5 files changed

+423
-975
lines changed

5 files changed

+423
-975
lines changed

src/ElectronNET.API/API/ApiBase.cs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
namespace ElectronNET.API
2+
{
3+
using System;
4+
using System.Collections.Concurrent;
5+
using System.Diagnostics;
6+
using System.Runtime.CompilerServices;
7+
using System.Threading.Tasks;
8+
using ElectronNET.Common;
9+
10+
public abstract class ApiBase
11+
{
12+
internal const int PropertyTimeout = 1000;
13+
14+
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>();
18+
private readonly ConcurrentDictionary<string, string> methodMessageNames = new ConcurrentDictionary<string, string>();
19+
private readonly object objLock = new object();
20+
21+
public virtual int Id
22+
{
23+
get
24+
{
25+
return -1;
26+
}
27+
28+
// ReSharper disable once ValueParameterNotUsed
29+
protected set
30+
{
31+
}
32+
}
33+
34+
protected abstract string SocketEventCompleteSuffix { get; }
35+
36+
protected ApiBase()
37+
{
38+
this.objectName = this.GetType().Name.LowerFirst();
39+
}
40+
41+
protected void CallMethod0([CallerMemberName] string callerName = null)
42+
{
43+
var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s);
44+
if (this.Id >= 0)
45+
{
46+
BridgeConnector.Socket.Emit(messageName, this.Id);
47+
}
48+
else
49+
{
50+
BridgeConnector.Socket.Emit(messageName);
51+
}
52+
}
53+
54+
protected void CallMethod1(object val1, [CallerMemberName] string callerName = null)
55+
{
56+
var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s);
57+
if (this.Id >= 0)
58+
{
59+
BridgeConnector.Socket.Emit(messageName, this.Id, val1);
60+
}
61+
else
62+
{
63+
BridgeConnector.Socket.Emit(messageName, val1);
64+
}
65+
}
66+
67+
protected void CallMethod2(object val1, object val2, [CallerMemberName] string callerName = null)
68+
{
69+
var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s);
70+
if (this.Id >= 0)
71+
{
72+
BridgeConnector.Socket.Emit(messageName, this.Id, val1, val2);
73+
}
74+
else
75+
{
76+
BridgeConnector.Socket.Emit(messageName, val1, val2);
77+
}
78+
}
79+
80+
protected void CallMethod3(object val1, object val2, object val3, [CallerMemberName] string callerName = null)
81+
{
82+
var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s);
83+
if (this.Id >= 0)
84+
{
85+
BridgeConnector.Socket.Emit(messageName, this.Id, val1, val2, val3);
86+
}
87+
else
88+
{
89+
BridgeConnector.Socket.Emit(messageName, val1, val2, val3);
90+
}
91+
}
92+
93+
protected Task<T> GetPropertyAsync<T>([CallerMemberName] string callerName = null)
94+
{
95+
Debug.Assert(callerName != null, nameof(callerName) + " != null");
96+
97+
lock (this.objLock)
98+
{
99+
return this.propertyGetters.GetOrAdd(callerName, _ =>
100+
{
101+
var getter = new PropertyGetter<T>(this, callerName, PropertyTimeout);
102+
103+
getter.Task<T>().ContinueWith(_ =>
104+
{
105+
lock (this.objLock)
106+
{
107+
return this.propertyGetters.TryRemove(callerName, out var _);
108+
}
109+
});
110+
111+
return getter;
112+
}).Task<T>();
113+
}
114+
}
115+
116+
internal abstract class PropertyGetter
117+
{
118+
public abstract Task<T> Task<T>();
119+
}
120+
121+
internal class PropertyGetter<T> : PropertyGetter
122+
{
123+
private readonly Task<T> tcsTask;
124+
private TaskCompletionSource<T> tcs;
125+
126+
public PropertyGetter(ApiBase apiBase, string callerName, int timeoutMs)
127+
{
128+
this.tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
129+
this.tcsTask = this.tcs.Task;
130+
131+
var eventName = apiBase.propertyEventNames.GetOrAdd(callerName, s => $"{apiBase.objectName}-{s.StripAsync().LowerFirst()}{apiBase.SocketEventCompleteSuffix}");
132+
var messageName = apiBase.propertyMessageNames.GetOrAdd(callerName, s => apiBase.objectName + s.StripAsync());
133+
134+
BridgeConnector.Socket.On<T>(eventName, (result) =>
135+
{
136+
BridgeConnector.Socket.Off(eventName);
137+
138+
lock (this)
139+
{
140+
this.tcs?.SetResult(result);
141+
this.tcs = null;
142+
}
143+
});
144+
145+
if (apiBase.Id >= 0)
146+
{
147+
BridgeConnector.Socket.Emit(messageName, apiBase.Id);
148+
}
149+
else
150+
{
151+
BridgeConnector.Socket.Emit(messageName);
152+
}
153+
154+
System.Threading.Tasks.Task.Delay(ApiBase.PropertyTimeout).ContinueWith(_ =>
155+
{
156+
if (this.tcs != null)
157+
{
158+
lock (this)
159+
{
160+
if (this.tcs != null)
161+
{
162+
var ex = new TimeoutException($"No response after {timeoutMs:D}ms trying to retrieve value {apiBase.objectName}.{callerName}()");
163+
this.tcs.TrySetException(ex);
164+
this.tcs = null;
165+
}
166+
}
167+
}
168+
});
169+
}
170+
171+
public override Task<T1> Task<T1>()
172+
{
173+
return this.tcsTask as Task<T1>;
174+
}
175+
}
176+
}
177+
}

0 commit comments

Comments
 (0)