Merge pull request #2545 from ermshiperete/Xamarin-24974
[mono.git] / mcs / class / corlib / Test / System.Runtime.CompilerServices / TaskAwaiterTest.cs
index 3e8233a1fe0ddb923fd5f2b39cf625d779d1be4c..57b1d2c27642c1dafc45d854a7ccf92f086f1cee 100644 (file)
@@ -34,6 +34,8 @@ using System.Threading.Tasks;
 using NUnit.Framework;
 using System.Runtime.CompilerServices;
 using System.Collections.Generic;
+using System.Collections;
+using System.Collections.Concurrent;
 
 namespace MonoTests.System.Runtime.CompilerServices
 {
@@ -43,14 +45,15 @@ namespace MonoTests.System.Runtime.CompilerServices
                class Scheduler : TaskScheduler
                {
                        string name;
+                       int ic, qc;
 
                        public Scheduler (string name)
                        {
                                this.name = name;
                        }
 
-                       public int InlineCalls { get; set; }
-                       public int QueueCalls { get; set; }
+                       public int InlineCalls { get { return ic; } }
+                       public int QueueCalls { get { return qc; } }
 
                        protected override IEnumerable<Task> GetScheduledTasks ()
                        {
@@ -59,7 +62,7 @@ namespace MonoTests.System.Runtime.CompilerServices
 
                        protected override void QueueTask (Task task)
                        {
-                               ++QueueCalls;
+                               Interlocked.Increment (ref qc);
                                ThreadPool.QueueUserWorkItem (o => {
                                        TryExecuteTask (task);
                                });
@@ -67,9 +70,94 @@ namespace MonoTests.System.Runtime.CompilerServices
 
                        protected override bool TryExecuteTaskInline (Task task, bool taskWasPreviouslyQueued)
                        {
-                               ++InlineCalls;
+                               Interlocked.Increment (ref ic);
                                return false;
                        }
+
+                       public override string ToString ()
+                       {
+                               return "Scheduler-" + name;
+                       }
+               }
+
+               class SingleThreadSynchronizationContext : SynchronizationContext
+               {
+                       readonly Queue _queue = new Queue ();
+
+                       public void RunOnCurrentThread ()
+                       {
+                               while (_queue.Count != 0) {
+                                       var workItem = (KeyValuePair<SendOrPostCallback, object>) _queue.Dequeue ();
+                                       workItem.Key (workItem.Value);
+                               }
+                       }
+                               
+                       public override void Post (SendOrPostCallback d, object state)
+                       {
+                               if (d == null) {
+                                       throw new ArgumentNullException ("d");
+                               }
+
+                               _queue.Enqueue (new KeyValuePair<SendOrPostCallback, object> (d, state));
+                       }
+
+                       public override void Send (SendOrPostCallback d, object state)
+                       {
+                               throw new NotSupportedException ("Synchronously sending is not supported.");
+                       }
+               }
+
+               class NestedSynchronizationContext : SynchronizationContext
+               {
+                       Thread thread;
+                       readonly ConcurrentQueue<Tuple<SendOrPostCallback, object, ExecutionContext>> workQueue = new ConcurrentQueue<Tuple<SendOrPostCallback, object, ExecutionContext>> ();
+                       readonly AutoResetEvent workReady = new AutoResetEvent (false);
+
+                       public NestedSynchronizationContext ()
+                       {
+                               thread = new Thread (WorkerThreadProc) { IsBackground = true };
+                               thread.Start ();
+                       }
+
+                       public override void Post (SendOrPostCallback d, object state)
+                       {
+                               var context = ExecutionContext.Capture ();
+                               workQueue.Enqueue (Tuple.Create (d, state, context));
+                               workReady.Set ();
+                       }
+
+                       void WorkerThreadProc ()
+                       {
+                               if (!workReady.WaitOne (10000))
+                                       return;
+
+                               Tuple<SendOrPostCallback, object, ExecutionContext> work;
+
+                               while (workQueue.TryDequeue (out work)) {
+                                       ExecutionContext.Run (work.Item3, _ => {
+                                               var oldSyncContext = SynchronizationContext.Current;
+                                               SynchronizationContext.SetSynchronizationContext (this);
+                                               work.Item1 (_);
+                                               SynchronizationContext.SetSynchronizationContext (oldSyncContext);
+                                       }, work.Item2); 
+                               }
+                       }
+               }
+
+               string progress;
+               SynchronizationContext sc;
+               ManualResetEvent mre;
+
+               [SetUp]
+               public void Setup ()
+               {
+                       sc = SynchronizationContext.Current;
+               }
+
+               [TearDown]
+               public void TearDown ()
+               {
+                       SynchronizationContext.SetSynchronizationContext (sc);
                }
 
                [Test]
@@ -133,7 +221,7 @@ namespace MonoTests.System.Runtime.CompilerServices
                        Assert.IsTrue (t.Wait (3000), "#0");
                        Assert.AreEqual (0, t.Result, "#1");
                        Assert.AreEqual (0, b.InlineCalls, "#2b");
-                       Assert.AreEqual (2, a.QueueCalls, "#3a");
+                       Assert.IsTrue (a.QueueCalls == 1 || a.QueueCalls == 2, "#3a");
                        Assert.AreEqual (1, b.QueueCalls, "#3b");
                }
 
@@ -160,6 +248,7 @@ namespace MonoTests.System.Runtime.CompilerServices
                        return res.Result;
                }
 
+#if !MOBILE_STATIC
                [Test]
                public void FinishedTaskOnCompleted ()
                {
@@ -183,6 +272,127 @@ namespace MonoTests.System.Runtime.CompilerServices
                        // e.g. Touch.Unit defaults to run tests on the main thread and this will return false
                        Assert.AreEqual (Thread.CurrentThread.IsBackground, mres2.WaitOne (2000), "#2");;
                }
+
+#endif
+
+               [Test]
+               public void CompletionOnSameCustomSynchronizationContext ()
+               {
+                       progress = "";
+                       var syncContext = new SingleThreadSynchronizationContext ();
+                       SynchronizationContext.SetSynchronizationContext (syncContext);
+
+                       syncContext.Post (delegate {
+                               Go (syncContext);
+                       }, null);
+
+                       // Custom message loop
+                       var cts = new CancellationTokenSource ();
+                       cts.CancelAfter (5000);
+                       while (progress.Length != 3 && !cts.IsCancellationRequested) {
+                               syncContext.RunOnCurrentThread ();
+                               Thread.Sleep (0);
+                       }
+
+                       Assert.AreEqual ("123", progress);
+               }
+
+               async void Go (SynchronizationContext ctx)
+               {
+                       await Wait (ctx);
+
+                       progress += "2";
+               }
+
+               async Task Wait (SynchronizationContext ctx)
+               {
+                       await Task.Delay (10); // Force block suspend/return
+
+                       ctx.Post (l => progress += "3", null);
+
+                       progress += "1";
+
+                       // Exiting same context - no need to post continuation
+               }
+
+               [Test]
+               public void CompletionOnDifferentCustomSynchronizationContext ()
+               {
+                       mre = new ManualResetEvent (false);
+                       progress = "";
+                       var syncContext = new SingleThreadSynchronizationContext ();
+                       SynchronizationContext.SetSynchronizationContext (syncContext);
+
+                       syncContext.Post (delegate {
+                               Task t = new Task (delegate() { });
+                               Go2 (syncContext, t);
+                               t.Start ();
+                       }, null);
+
+                       // Custom message loop
+                       var cts = new CancellationTokenSource ();
+                       cts.CancelAfter (5000);
+                       while (progress.Length != 3 && !cts.IsCancellationRequested) {
+                               syncContext.RunOnCurrentThread ();
+                               Thread.Sleep (0);
+                       }
+
+                       Assert.AreEqual ("13xa2", progress);
+               }
+
+               async void Go2 (SynchronizationContext ctx, Task t)
+               {
+                       await Wait2 (ctx, t);
+
+                       progress += "a";
+
+                       if (mre.WaitOne (5000))
+                               progress += "2";
+                       else
+                               progress += "b";
+               }
+
+               async Task Wait2 (SynchronizationContext ctx, Task t)
+               {
+                       await t; // Force block suspend/return
+
+                       ctx.Post (l => {
+                               progress += "3";
+                               mre.Set ();
+                               progress += "x";
+                       }, null);
+
+                       progress += "1";
+
+                       SynchronizationContext.SetSynchronizationContext (null);
+               }
+
+               [Test]
+               public void NestedLeakingSynchronizationContext ()
+               {
+                       var sc = SynchronizationContext.Current;
+                       if (sc == null)
+                               Assert.IsTrue (NestedLeakingSynchronizationContext_MainAsync (sc).Wait (5000), "#1");
+                       else
+                               Assert.Ignore ("NestedSynchronizationContext may never complete on custom context");
+               }
+
+               static async Task NestedLeakingSynchronizationContext_MainAsync (SynchronizationContext sc)
+               {
+                       Assert.AreSame (sc, SynchronizationContext.Current, "#1");
+                       await NestedLeakingSynchronizationContext_DoWorkAsync ();
+                       Assert.AreSame (sc, SynchronizationContext.Current, "#2");
+               }
+
+               static async Task NestedLeakingSynchronizationContext_DoWorkAsync ()
+               {
+                       var sc = new NestedSynchronizationContext ();
+                       SynchronizationContext.SetSynchronizationContext (sc);
+
+                       Assert.AreSame (sc, SynchronizationContext.Current);
+                       await Task.Yield ();
+                       Assert.AreSame (sc, SynchronizationContext.Current);
+               }
        }
 }