Skip to content

Commit c38c00c

Browse files
committed
feat(runtime): Add ability to provide a CT for InvokeAsync
1 parent 7e5f868 commit c38c00c

File tree

2 files changed

+131
-30
lines changed

2 files changed

+131
-30
lines changed

src/Uno.Foundation.Runtime.WebAssembly/Interop/Runtime.wasm.cs

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System;
1+
#nullable enable
2+
3+
using System;
24
using System.Collections.Generic;
35
using System.Collections.Immutable;
46
using System.ComponentModel;
@@ -87,7 +89,7 @@ public static bool InvokeJSUnmarshalled(string functionIdentifier, IntPtr arg0)
8789
/// Invoke a Javascript method using unmarshaled conversion.
8890
/// </summary>
8991
/// <param name="functionIdentifier">A function identifier name</param>
90-
internal static bool InvokeJSUnmarshalled(string functionIdentifier, IntPtr arg0, out Exception exception)
92+
internal static bool InvokeJSUnmarshalled(string functionIdentifier, IntPtr arg0, out Exception? exception)
9193
{
9294
if (_trace.IsEnabled)
9395
{
@@ -102,7 +104,7 @@ internal static bool InvokeJSUnmarshalled(string functionIdentifier, IntPtr arg0
102104
}
103105
}
104106

105-
private static bool InnerInvokeJSUnmarshalled(string functionIdentifier, IntPtr arg0, out Exception exception)
107+
private static bool InnerInvokeJSUnmarshalled(string functionIdentifier, IntPtr arg0, out Exception? exception)
106108
{
107109
exception = null;
108110
var methodId = GetMethodId(functionIdentifier);
@@ -115,7 +117,7 @@ private static bool InnerInvokeJSUnmarshalled(string functionIdentifier, IntPtr
115117
{
116118
_trace.WriteEvent(
117119
TraceProvider.InvokeException,
118-
new object[] { functionIdentifier, exception.ToString() }
120+
new object[] { functionIdentifier, exceptionMessage }
119121
);
120122
}
121123

@@ -169,7 +171,7 @@ private static bool InnerInvokeJSUnmarshalled(string functionIdentifier, IntPtr
169171
/// <summary>
170172
/// Provides an override for javascript invokes.
171173
/// </summary>
172-
public static Func<string, string> InvokeJSOverride;
174+
public static Func<string, string>? InvokeJSOverride;
173175

174176
public static string InvokeJS(string str)
175177
{
@@ -222,7 +224,7 @@ private static string InnerInvokeJS(String str)
222224
return result;
223225
}
224226

225-
public static object GetObjectFromGcHandle(string intPtr)
227+
public static object? GetObjectFromGcHandle(string intPtr)
226228
{
227229
var ptr = Marshal.StringToHGlobalAuto(intPtr);
228230
var handle = GCHandle.FromIntPtr(ptr);
@@ -268,74 +270,81 @@ public static string InvokeJSWithInterop(FormattableString formattable)
268270
return InvokeJS(command);
269271
}
270272

271-
private static readonly Dictionary<long, TaskCompletionSource<string>> _asyncWaitingList = new Dictionary<long, TaskCompletionSource<string>>();
273+
private static readonly Dictionary<long, (TaskCompletionSource<string> task, CancellationTokenRegistration ctReg)> _asyncWaitingList
274+
= new Dictionary<long, (TaskCompletionSource<string> task, CancellationTokenRegistration ctReg)>();
272275

273276
private static long _nextAsync;
274277

278+
279+
/// <summary>
280+
/// DO NOT USE, use overload with CancellationToken instead
281+
/// </remarks>
282+
public static Task<string> InvokeAsync(string promiseCode)
283+
=> InvokeAsync(promiseCode, CancellationToken.None);
284+
275285
/// <summary>
276286
/// Invoke async javascript code.
277287
/// </summary>
278288
/// <remarks>
279289
/// The javascript code is expected to return a Promise&lt;string&gt;
280290
/// </remarks>
281-
public static Task<string> InvokeAsync(string promiseCode)
291+
public static Task<string> InvokeAsync(string promiseCode, CancellationToken ct)
282292
{
283-
var id = Interlocked.Increment(ref _nextAsync);
284-
293+
var handle = Interlocked.Increment(ref _nextAsync);
285294
var tcs = new TaskCompletionSource<string>();
295+
var ctReg = ct.CanBeCanceled ? ct.Register(() => RemoveAsyncTask(handle)?.TrySetCanceled()) : default;
286296

287297
lock (_asyncWaitingList)
288298
{
289-
_asyncWaitingList[id] = tcs;
299+
_asyncWaitingList[handle] = (tcs, ctReg);
290300
}
291301

292302
var js = new[]
293303
{
294304
"const __f = ()=>",
295305
promiseCode,
296306
";\nUno.UI.Interop.AsyncInteropHelper.Invoke(",
297-
id.ToStringInvariant(),
307+
handle.ToStringInvariant(),
298308
", __f);"
299309
};
300310

301311
try
302312
{
303-
304313
WebAssemblyRuntime.InvokeJS(string.Concat(js));
314+
315+
return tcs.Task;
305316
}
306317
catch (Exception ex)
307318
{
319+
RemoveAsyncTask(handle);
320+
308321
return Task.FromException<string>(ex);
309322
}
310-
311-
return tcs.Task;
312323
}
313324

314325
[EditorBrowsable(EditorBrowsableState.Never)]
315326
public static void DispatchAsyncResult(long handle, string result)
316-
{
317-
lock (_asyncWaitingList)
318-
{
319-
if (_asyncWaitingList.TryGetValue(handle, out var tcs))
320-
{
321-
tcs.TrySetResult(result);
322-
_asyncWaitingList.Remove(handle);
323-
}
324-
}
325-
}
327+
=> RemoveAsyncTask(handle)?.TrySetResult(result);
326328

327329
[EditorBrowsable(EditorBrowsableState.Never)]
328330
public static void DispatchAsyncError(long handle, string error)
331+
=> RemoveAsyncTask(handle)?.TrySetException(new ApplicationException(error));
332+
333+
private static TaskCompletionSource<string>? RemoveAsyncTask(long handle)
329334
{
335+
(TaskCompletionSource<string> task, CancellationTokenRegistration ctReg) listener;
330336
lock (_asyncWaitingList)
331337
{
332-
if (_asyncWaitingList.TryGetValue(handle, out var tcs))
338+
if (!_asyncWaitingList.TryGetValue(handle, out listener))
333339
{
334-
var exception = new ApplicationException(error);
335-
tcs.TrySetException(exception);
336-
_asyncWaitingList.Remove(handle);
340+
return default;
337341
}
342+
_asyncWaitingList.Remove(handle);
338343
}
344+
345+
listener.ctReg.Dispose();
346+
347+
return listener.task;
339348
}
340349

341350
[Pure]
@@ -417,7 +426,7 @@ bool NeedsEscape(string s2)
417426
}
418427
}
419428

420-
private static IDisposable WritePropertyEventTrace(int startEventId, int stopEventId, string script)
429+
private static IDisposable? WritePropertyEventTrace(int startEventId, int stopEventId, string script)
421430
{
422431
if (_trace.IsEnabled)
423432
{

src/Uno.Foundation.Runtime.WebAssembly/Interop/TSInteropMarshaller.wasm.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Diagnostics;
88
using System.Runtime.InteropServices;
99
using System.Text;
10+
using System.Threading;
1011
using Uno.Extensions;
1112
using Uno.Foundation;
1213

@@ -104,6 +105,97 @@ public static object InvokeJS(
104105
}
105106
}
106107

108+
public static HandleRef<T> Allocate<T>(string propertySetterName, string? propertyResetName = null)
109+
where T : struct
110+
{
111+
var value = new HandleRef<T>(propertyResetName);
112+
try
113+
{
114+
WebAssemblyRuntime.InvokeJSUnmarshalled(propertySetterName, value.Handle);
115+
}
116+
catch (Exception e)
117+
{
118+
value.Dispose();
119+
120+
if (_logger.Value.IsEnabled(LogLevel.Error))
121+
{
122+
_logger.Value.LogDebug($"Failed Allocate {propertySetterName}/{value.Type}: {e}");
123+
}
124+
125+
throw;
126+
}
127+
128+
return value;
129+
}
130+
131+
public sealed class HandleRef<T> : IDisposable
132+
where T : struct
133+
{
134+
private readonly string? _jsDisposeMethodName;
135+
136+
private int _isDisposed = 0;
137+
138+
public HandleRef(string? jsDisposeMethodName)
139+
{
140+
_jsDisposeMethodName = jsDisposeMethodName;
141+
Type = typeof(T);
142+
Handle = Marshal.AllocHGlobal(Marshal.SizeOf(Type));
143+
144+
DumpStructureLayout(Type);
145+
146+
// Make sure to init the allocated memory
147+
Marshal.StructureToPtr(default(T), Handle, false);
148+
}
149+
150+
public Type Type { get; }
151+
152+
public IntPtr Handle { get; }
153+
154+
public T Value
155+
{
156+
get
157+
{
158+
CheckDisposed();
159+
return (T)Marshal.PtrToStructure(Handle, Type);
160+
}
161+
set
162+
{
163+
CheckDisposed();
164+
Marshal.StructureToPtr(value, Handle, true);
165+
}
166+
}
167+
168+
private void CheckDisposed()
169+
{
170+
if (_isDisposed != 0)
171+
{
172+
throw new ObjectDisposedException(GetType().Name, "Marshalled object have been disposed.");
173+
}
174+
}
175+
176+
/// <inheritdoc />
177+
public void Dispose()
178+
{
179+
if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
180+
{
181+
Marshal.DestroyStructure(Handle, Type);
182+
Marshal.FreeHGlobal(Handle);
183+
184+
if (_jsDisposeMethodName.HasValue())
185+
{
186+
WebAssemblyRuntime.InvokeJSUnmarshalled(_jsDisposeMethodName!, Handle);
187+
}
188+
189+
GC.SuppressFinalize(this);
190+
}
191+
}
192+
193+
~HandleRef()
194+
{
195+
Dispose();
196+
}
197+
}
198+
107199
#if TRACE_MEMORY_LAYOUT
108200
private static HashSet<Type> _structureDump = new HashSet<Type>();
109201
#endif

0 commit comments

Comments
 (0)