Skip to content
Closed
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
33b5c4a
[wasm] Bump emscripten to 3.1.34
radekdoulik Mar 27, 2023
dff44d4
Update emsdk deps
radekdoulik Mar 27, 2023
ce1a265
Update icu deps
radekdoulik Mar 27, 2023
ed96a04
Use new images
radekdoulik Mar 27, 2023
8e6a28c
Use emscripten_main_runtime_thread_id
radekdoulik Mar 27, 2023
81672f4
Ignore ExitStatus exceptions
radekdoulik Mar 28, 2023
c2cbb94
Handle UnhandledPromiseRejection for ExitStatus
radekdoulik Mar 28, 2023
f3edd6e
Merge branch 'main' into pr-wasm-emscripten-3-1-34
radekdoulik Mar 28, 2023
4c0cb9e
[wasm][threads] flip YieldFromDispatchLoop; specialize PortableThread…
lambdageek Mar 15, 2023
3d88608
[mono] Implement a LifoJSSemaphore
lambdageek Mar 20, 2023
55b4649
Make managed LowLevelJSSemaphore
lambdageek Mar 20, 2023
8c74dea
copy-paste PortableThreadPool.WorkerThread for threaded WASM
lambdageek Mar 20, 2023
b323f0e
fixup native code for lifo semaphore
lambdageek Mar 21, 2023
51b1fbf
fixup managed code for LowLevelJSSemaphore
lambdageek Mar 21, 2023
3727ff1
Implement PortableThreadPool loop using semaphore callbacks
lambdageek Mar 21, 2023
763edab
manage emscripten event loop from PortableThreadPool.WorkerThread
lambdageek Mar 22, 2023
b45d001
FIXME: thread equality assertion in timeout callback
lambdageek Mar 22, 2023
1f7620c
XXX REVERT ME - minimal async timeout test
lambdageek Mar 22, 2023
06a3cc1
BUGFIX: &wait_entry ===> wait_entry
lambdageek Mar 23, 2023
8574368
nit: log thread id as hex in .ts
lambdageek Mar 23, 2023
c86cb26
XXX minimal sample - fetch on a background thread works
lambdageek Mar 23, 2023
c3cad74
fix non-wasm non-threads builds
lambdageek Mar 24, 2023
e57b262
Add WebWorkerEventLoop internal class to managed event loop keepalive
lambdageek Mar 24, 2023
07eabcd
Start threadpool threads with keepalive checks
lambdageek Mar 24, 2023
474607e
HACK: kind of work around the emscripten_runtime_keepalive_push/pop n…
lambdageek Mar 24, 2023
9f45167
support JS Semaphore with --print-icall-table cross compiler
lambdageek Mar 27, 2023
2e1f31f
make minimal FetchBackground sample more like a unit test
lambdageek Mar 27, 2023
dacc0cb
Share PortableThreadPool.WorkerThread common code
lambdageek Mar 29, 2023
10ca330
make both kinds of lifo semaphore share a base struct
lambdageek Mar 30, 2023
f4a2c02
Unify LowLevelLifoSemaphore for normal and async waiting
lambdageek Mar 30, 2023
42388d8
WebWorkerEventLoop: remove dead code, update comments
lambdageek Mar 31, 2023
853938e
remove unused arg from async wait semaphore
lambdageek Mar 31, 2023
cb8b168
rename native semaphore to LifoSemaphoreAsyncWait
lambdageek Mar 31, 2023
3e8fee4
Rename managed file to LowLevelLifoSemaphore.AsyncWait.Browser.Thread…
lambdageek Mar 31, 2023
552f9a5
Remove unnecessary indirections and allocations from managed AsyncWai…
lambdageek Mar 31, 2023
812524f
fix non-browser+threads builds
lambdageek Mar 31, 2023
50c0f1a
Keep track of unsettled JS interop promises in threadpool workers
lambdageek Apr 3, 2023
c8afaba
change minimal sample's fetch helper to artificially delay
lambdageek Apr 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fixup managed code for LowLevelJSSemaphore
  • Loading branch information
lambdageek committed Mar 28, 2023
commit 51b1fbf9a9de976fb3677859c07aca368dff4230
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System.Threading;
Expand All @@ -9,10 +13,33 @@ namespace System.Threading;
// This class provides a way for browser threads to asynchronously wait for a sempahore
// from JS, without using the threadpool. It is used to implement threadpool workers.
// </summary>
[StructLayout(LayoutOptions.Sequential)]
internal partial class LowLevelJSSemaphore : IDisposable
internal sealed partial class LowLevelJSSemaphore : IDisposable
{
// TODO: implement some of the managed stuff from LowLevelLifoSemaphore
private IntPtr lifo_semaphore;
private CacheLineSeparatedCounts _separated;

private readonly int _maximumSignalCount;
private readonly int _spinCount;
private readonly Action _onWait;

// private const int SpinSleep0Threshold = 10;

internal LowLevelJSSemaphore(int initialSignalCount, int maximumSignalCount, int spinCount, Action onWait)
{
Debug.Assert(initialSignalCount >= 0);
Debug.Assert(initialSignalCount <= maximumSignalCount);
Debug.Assert(maximumSignalCount > 0);
Debug.Assert(spinCount >= 0);

_separated = default;
_separated._counts.SignalCount = (uint)initialSignalCount;
_maximumSignalCount = maximumSignalCount;
_spinCount = spinCount;
_onWait = onWait;

Create(maximumSignalCount);
}

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern IntPtr InitInternal();
Expand All @@ -38,51 +65,170 @@ public void Dispose()

internal void Release(int additionalCount)
{
ReleaseInternal(lifo_semaphore, count);
ReleaseInternal(lifo_semaphore, additionalCount);
}

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void PrepareWaitInternal(IntPtr semaphore,
int timeout_ms,
delegate unmanaged*<IntPtr, GCHandle, IntPtr, void> success_cb,
delegate unmanaged*<IntPtr, GCHandle, IntPtr, void> timeout_cb,
GCHandle object,
IntPtr user_data);

private class WaitEntry
{
public WaitEntry(LowLevelJSSemaphore semaphore, Action<LowLevelJSSemaphore, object?> onSuccess, Action<LowLevelJSSemaphore, object?> onTimeout, object? state)
{
OnSuccess = onSuccess;
OnTimeout = onTimeout;
Semaphore = semaphore;
State = state;
}
public object? State {get; init; }
public Action<LowLevelJSSemaphore, object?> OnSuccess {get; init;}
public Action<LowLevelJSSemaphore, object?> OnTimeout {get; init;}
public LowLevelJSSemaphore Semaphore {get; init;}
}
private static extern unsafe void PrepareWaitInternal(IntPtr semaphore,
int timeoutMs,
/*delegate* unmanaged<IntPtr, GCHandle, IntPtr, void> successCallback*/ void* successCallback,
/*delegate* unmanaged<IntPtr, GCHandle, IntPtr, void> timeoutCallback*/ void* timeoutCallback,
GCHandle handle,
IntPtr userData);

private sealed record WaitEntry (LowLevelJSSemaphore Semaphore, Action<LowLevelJSSemaphore, object?> OnSuccess, Action<LowLevelJSSemaphore, object?> OnTimeout, object? State);

internal void PrepareWait(int timeout_ms, Action<LowLevelJSSemaphore, object?> onSuccess, Action<LowLeelJSSemaphore, object?> onTimeout, object? state)
internal void PrepareWait(int timeout_ms, Action<LowLevelJSSemaphore, object?> onSuccess, Action<LowLevelJSSemaphore, object?> onTimeout, object? state)
{
WaitEntry entry = new (this, onSuccess, onTimeout, state);
GCHandle gchandle = GCHandle.Alloc (entry);
PrepareWaitInternal (lifo_semaphore, timeout_ms, &SuccessCallback, &TimeoutCallback, gchandle, IntPtr.Zero);
unsafe {
delegate* unmanaged<IntPtr, GCHandle, IntPtr, void> successCallback = &SuccessCallback;
delegate* unmanaged<IntPtr, GCHandle, IntPtr, void> timeoutCallback = &TimeoutCallback;
PrepareWaitInternal (lifo_semaphore, timeout_ms, successCallback, timeoutCallback, gchandle, IntPtr.Zero);
}
}

[UnmanagedCallersOnly]
private static void SuccessCallback(IntPtr lifo_semaphore, GCHandle gchandle, IntPtr user_data)
{
WaitEntry entry = (WaitEntry)gchandle.Target!;
GCHandle.Free(gchandle);
gchandle.Free();
entry.OnSuccess(entry.Semaphore, entry.State);
}

[UnmanagedCallersOnly]
private static void TimeoutCallback(IntPtr lifo_semaphore, GCHandle gchandle, IntPtr user_data)
{
WaitEntry entry = (WaitEntry)gchandle.Target!;
GCHandle.Free(gchandle);
gchandle.Free();
entry.OnTimeout(entry.Semaphore, entry.State);
}

#region Counts
private struct Counts : IEquatable<Counts>
{
private const byte SignalCountShift = 0;
private const byte WaiterCountShift = 32;
private const byte SpinnerCountShift = 48;
private const byte CountOfWaitersSignaledToWakeShift = 56;

private ulong _data;

private Counts(ulong data) => _data = data;

private uint GetUInt32Value(byte shift) => (uint)(_data >> shift);
private void SetUInt32Value(uint value, byte shift) =>
_data = (_data & ~((ulong)uint.MaxValue << shift)) | ((ulong)value << shift);
private ushort GetUInt16Value(byte shift) => (ushort)(_data >> shift);
private void SetUInt16Value(ushort value, byte shift) =>
_data = (_data & ~((ulong)ushort.MaxValue << shift)) | ((ulong)value << shift);
private byte GetByteValue(byte shift) => (byte)(_data >> shift);
private void SetByteValue(byte value, byte shift) =>
_data = (_data & ~((ulong)byte.MaxValue << shift)) | ((ulong)value << shift);

public uint SignalCount
{
get => GetUInt32Value(SignalCountShift);
set => SetUInt32Value(value, SignalCountShift);
}

public void AddSignalCount(uint value)
{
Debug.Assert(value <= uint.MaxValue - SignalCount);
_data += (ulong)value << SignalCountShift;
}

public void IncrementSignalCount() => AddSignalCount(1);

public void DecrementSignalCount()
{
Debug.Assert(SignalCount != 0);
_data -= (ulong)1 << SignalCountShift;
}

public ushort WaiterCount
{
get => GetUInt16Value(WaiterCountShift);
set => SetUInt16Value(value, WaiterCountShift);
}

public void IncrementWaiterCount()
{
Debug.Assert(WaiterCount < ushort.MaxValue);
_data += (ulong)1 << WaiterCountShift;
}

public void DecrementWaiterCount()
{
Debug.Assert(WaiterCount != 0);
_data -= (ulong)1 << WaiterCountShift;
}

public void InterlockedDecrementWaiterCount()
{
var countsAfterUpdate = new Counts(Interlocked.Add(ref _data, unchecked((ulong)-1) << WaiterCountShift));
Debug.Assert(countsAfterUpdate.WaiterCount != ushort.MaxValue); // underflow check
}

public byte SpinnerCount
{
get => GetByteValue(SpinnerCountShift);
set => SetByteValue(value, SpinnerCountShift);
}

public void IncrementSpinnerCount()
{
Debug.Assert(SpinnerCount < byte.MaxValue);
_data += (ulong)1 << SpinnerCountShift;
}

public void DecrementSpinnerCount()
{
Debug.Assert(SpinnerCount != 0);
_data -= (ulong)1 << SpinnerCountShift;
}

public byte CountOfWaitersSignaledToWake
{
get => GetByteValue(CountOfWaitersSignaledToWakeShift);
set => SetByteValue(value, CountOfWaitersSignaledToWakeShift);
}

public void AddUpToMaxCountOfWaitersSignaledToWake(uint value)
{
uint availableCount = (uint)(byte.MaxValue - CountOfWaitersSignaledToWake);
if (value > availableCount)
{
value = availableCount;
}
_data += (ulong)value << CountOfWaitersSignaledToWakeShift;
}

public void DecrementCountOfWaitersSignaledToWake()
{
Debug.Assert(CountOfWaitersSignaledToWake != 0);
_data -= (ulong)1 << CountOfWaitersSignaledToWakeShift;
}

public Counts InterlockedCompareExchange(Counts newCounts, Counts oldCounts) =>
new Counts(Interlocked.CompareExchange(ref _data, newCounts._data, oldCounts._data));

public static bool operator ==(Counts lhs, Counts rhs) => lhs.Equals(rhs);
public static bool operator !=(Counts lhs, Counts rhs) => !lhs.Equals(rhs);

public override bool Equals([NotNullWhen(true)] object? obj) => obj is Counts other && Equals(other);
public bool Equals(Counts other) => _data == other._data;
public override int GetHashCode() => (int)_data + (int)(_data >> 32);
}

[StructLayout(LayoutKind.Sequential)]
private struct CacheLineSeparatedCounts
{
private readonly Internal.PaddingFor32 _pad1;
public Counts _counts;
private readonly Internal.PaddingFor32 _pad2;
}
#endregion

}