55using System . Threading . Tasks ;
66using WasiPollWorld . wit . imports . wasi . io . v0_2_1 ;
77using Pollable = WasiPollWorld . wit . imports . wasi . io . v0_2_1 . IPoll . Pollable ;
8+ using MonotonicClockInterop = WasiPollWorld . wit . imports . wasi . clocks . v0_2_1 . MonotonicClockInterop ;
89
910namespace System . Threading
1011{
@@ -14,7 +15,9 @@ internal static class WasiEventLoop
1415 // it will be leaked and stay in this list forever.
1516 // it will also keep the Pollable handle alive and prevent it from being disposed
1617 private static readonly List < PollableHolder > s_pollables = new ( ) ;
17- private static bool s_tasksCanceled ;
18+ private static bool s_checkScheduled ;
19+ private static Pollable ? s_resolvedPollable ;
20+ private static Task ? s_mainTask ;
1821
1922 internal static Task RegisterWasiPollableHandle ( int handle , CancellationToken cancellationToken )
2023 {
@@ -29,18 +32,70 @@ internal static Task RegisterWasiPollable(Pollable pollable, CancellationToken c
2932 // this will register the pollable holder into s_pollables
3033 var holder = new PollableHolder ( pollable , cancellationToken ) ;
3134 s_pollables . Add ( holder ) ;
35+
36+ ScheduleCheck ( ) ;
37+
3238 return holder . taskCompletionSource . Task ;
3339 }
3440
35- // this is not thread safe
36- internal static void DispatchWasiEventLoop ( )
41+
42+ internal static T PollWasiEventLoopUntilResolved < T > ( Task < T > mainTask )
43+ {
44+ try
45+ {
46+ s_mainTask = mainTask ;
47+ while ( ! mainTask . IsCompleted )
48+ {
49+ ThreadPoolWorkQueue . Dispatch ( ) ;
50+ }
51+ }
52+ finally
53+ {
54+ s_mainTask = null ;
55+ }
56+ var exception = mainTask . Exception ;
57+ if ( exception is not null )
58+ {
59+ throw exception ;
60+ }
61+
62+ return mainTask . Result ;
63+ }
64+
65+ internal static void PollWasiEventLoopUntilResolvedVoid ( Task mainTask )
66+ {
67+ try
68+ {
69+ s_mainTask = mainTask ;
70+ while ( ! mainTask . IsCompleted )
71+ {
72+ ThreadPoolWorkQueue . Dispatch ( ) ;
73+ }
74+ }
75+ finally
76+ {
77+ s_mainTask = null ;
78+ }
79+
80+ var exception = mainTask . Exception ;
81+ if ( exception is not null )
82+ {
83+ throw exception ;
84+ }
85+ }
86+
87+ internal static void ScheduleCheck ( )
3788 {
38- ThreadPoolWorkQueue . Dispatch ( ) ;
39- if ( s_tasksCanceled )
89+ if ( ! s_checkScheduled && s_pollables . Count > 0 )
4090 {
41- s_tasksCanceled = false ;
42- return ;
91+ s_checkScheduled = true ;
92+ ThreadPool . UnsafeQueueUserWorkItem ( CheckPollables , null ) ;
4393 }
94+ }
95+
96+ internal static void CheckPollables ( object ? _ )
97+ {
98+ s_checkScheduled = false ;
4499
45100 var holders = new List < PollableHolder > ( s_pollables . Count ) ;
46101 var pending = new List < Pollable > ( s_pollables . Count ) ;
@@ -58,13 +113,28 @@ internal static void DispatchWasiEventLoop()
58113
59114 if ( pending . Count > 0 )
60115 {
116+ var resolvedPollableIndex = - 1 ;
117+ // if there is CPU-bound work to do, we should not block on PollInterop.Poll below
118+ // so we will append pollable resolved in 0ms
119+ // in effect, the PollInterop.Poll would not block us
120+ if ( ThreadPool . PendingWorkItemCount > 0 || ( s_mainTask != null && s_mainTask . IsCompleted ) )
121+ {
122+ s_resolvedPollable ??= MonotonicClockInterop . SubscribeDuration ( 0 ) ;
123+ resolvedPollableIndex = pending . Count ;
124+ pending . Add ( s_resolvedPollable ) ;
125+ }
126+
61127 var readyIndexes = PollInterop . Poll ( pending ) ;
62128 for ( int i = 0 ; i < readyIndexes . Length ; i ++ )
63129 {
64130 uint readyIndex = readyIndexes [ i ] ;
65- var holder = holders [ ( int ) readyIndex ] ;
66- holder . ResolveAndDispose ( ) ;
131+ if ( resolvedPollableIndex != readyIndex )
132+ {
133+ var holder = holders [ ( int ) readyIndex ] ;
134+ holder . ResolveAndDispose ( ) ;
135+ }
67136 }
137+
68138 for ( int i = 0 ; i < holders . Count ; i ++ )
69139 {
70140 PollableHolder holder = holders [ i ] ;
@@ -73,6 +143,8 @@ internal static void DispatchWasiEventLoop()
73143 s_pollables . Add ( holder ) ;
74144 }
75145 }
146+
147+ ScheduleCheck ( ) ;
76148 }
77149 }
78150
@@ -112,19 +184,14 @@ public void ResolveAndDispose()
112184 }
113185
114186 // for GC of abandoned Tasks or for cancellation
115- private static void CancelAndDispose ( object ? s )
187+ public static void CancelAndDispose ( object ? s )
116188 {
117189 PollableHolder self = ( PollableHolder ) s ! ;
118190 if ( self . isDisposed )
119191 {
120192 return ;
121193 }
122194
123- // Tell event loop to exit early, giving the application a
124- // chance to quit if the task(s) it is interested in have
125- // completed.
126- s_tasksCanceled = true ;
127-
128195 // it will be removed from s_pollables on the next run
129196 self . isDisposed = true ;
130197 self . pollable . Dispose ( ) ;
0 commit comments