Should CurrentPrincipal carry across async+await method calls?

So I have this example code:

<br />using System;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;

namespace PrincipalTestConsoleApplication
{
   public class Program
   {
      public static void Main()
      {
         ThreadPool.SetMinThreads(20, 20);
         ThreadPool.SetMaxThreads(20, 20);
         Console.WriteLine($"\n Started {DateTime.UtcNow}\n");
         for (int i = 1; i <= 10; i++)
         {
            Lemming lemming = new Lemming(i);
            lemming.BumpedHead += OnLemmingBumpedHead;
            Thread thread = new Thread(lemming.March);
            thread.Start();
         }
         Console.WriteLine("Press CTRL+C to quit.");
         Console.ReadLine();
      }

      private static void OnLemmingBumpedHead(object sender, HeadBumpArgs e)
      {
         Console.WriteLine(e.Message);
      }
   }

   public class Lemming
   {
      private readonly int m_LemmingId;

      public Lemming(int id)
      {
         m_LemmingId = id;
      }

      /// <summary>
      /// Primary thread method for a lemming.
      /// </summary>
      public void March()
      {
         while (true)
         {
            MarchStepAsync().Wait();
         }
      }

      /// <summary>
      /// Async method for marching a single lemming step.
      /// </summary>
      private async Task MarchStepAsync()
      {
         //prepare for this step
         Thread.CurrentPrincipal = new LemmingPrincipal(m_LemmingId);
         if (ReferenceEquals(SynchronizationContext.Current, null))
         {
            SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext());
         }

         //check for initial anomalies
         LemmingPrincipal principal = Thread.CurrentPrincipal as LemmingPrincipal;
         if (ReferenceEquals(principal, null))
         {
            FireHeadBumpEvent($"Lemming #{m_LemmingId} immediately thinks his principal type is: {Thread.CurrentPrincipal?.GetType()}");
         }
         else if (principal.LemmingId != m_LemmingId)
         {
            FireHeadBumpEvent($"Lemming #{m_LemmingId} immediately thinks he's lemming #{principal.LemmingId}");
         }

         //wait
         await Task.Delay(TimeSpan.FromMilliseconds(500 + m_LemmingId));

         //check for anomalies after wait
         principal = Thread.CurrentPrincipal as LemmingPrincipal;
         if (ReferenceEquals(principal, null))
         {
            FireHeadBumpEvent($"Lemming #{m_LemmingId} bumped his head and thinks his principal type is: {Thread.CurrentPrincipal?.GetType()}");
         }
         else if (principal.LemmingId != m_LemmingId)
         {
            FireHeadBumpEvent($"Lemming #{m_LemmingId} bumped his head and thinks he's lemming #{principal.LemmingId}");
         }
      }

      public event EventHandler<HeadBumpArgs> BumpedHead;

      private void FireHeadBumpEvent(string message)
      {
         BumpedHead?.Invoke(this, new HeadBumpArgs { Message = $"{DateTime.UtcNow}: {message}" });
      }

   }

   public class HeadBumpArgs : EventArgs
   {
      public string Message { get; set; }
   }

   public class CustomSynchronizationContext : SynchronizationContext
   {
      public override void OperationStarted()
      {
         base.OperationStarted();
      }

      public override void Post(SendOrPostCallback d, object state)
      {
         var theThis = this;
         SendOrPostCallback newD = (s) =>
         {
            d(s);
            if (ReferenceEquals(SynchronizationContext.Current, null))
            {
               SynchronizationContext.SetSynchronizationContext(theThis);
            }
         };
         base.Post(newD, state);
      }


      public override SynchronizationContext CreateCopy()
      {
         var toReturn = base.CreateCopy();
         return toReturn;
      }

      public override void OperationCompleted()
      {
         base.OperationCompleted();
      }

      public override void Send(SendOrPostCallback d, object state)
      {
         base.Send(d, state);
      }

      public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
      {
         return base.Wait(waitHandles, waitAll, millisecondsTimeout);
      }
   }

   /// <summary>
   /// Custom principal class.
   /// </summary>
   [Serializable]
   public class LemmingPrincipal : IPrincipal
   {
      public int LemmingId { get; }

      public IIdentity Identity { get; }

      public LemmingPrincipal(int id)
      {
         LemmingId = id;
         Identity = new LemmingIdentity("Lemming" + id);
      }

      public bool IsInRole(string role)
      {
         return false;
      }
   }

   /// <summary>
   /// Identity for custom principal.
   /// </summary>
   [Serializable]
   public class LemmingIdentity : IIdentity
   {
      public string AuthenticationType => "Default";

      public bool IsAuthenticated => false;

      public string Name { get; }

      public LemmingIdentity(string name)
      {
         Name = name;
      }
   }
}

Basically, it runs "lemming" threads which repeatedly set up a custom Thread.CurrentPrincipal, await Task.Delay, and then check the principal after the await.

On windows, it runs fine with no problems found; each lemming thread's logical thread of execution keeps its principal just fine for minutes on end.

On mono on linux (and when I port the code into a simple xamarin iOS app), I get constant problems found, where the Thread.CurrentPrincipal is lost immediately after each await Task.Delay:

<br />Started 11/8/2016 5:22:08 PM

Press CTRL+C to quit.
11/8/2016 5:22:08 PM: Lemming #3 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:08 PM: Lemming #1 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:08 PM: Lemming #4 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:08 PM: Lemming #7 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:08 PM: Lemming #6 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:08 PM: Lemming #8 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:08 PM: Lemming #9 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:08 PM: Lemming #2 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:08 PM: Lemming #10 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:08 PM: Lemming #5 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:09 PM: Lemming #1 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:09 PM: Lemming #3 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:09 PM: Lemming #4 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:09 PM: Lemming #6 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
11/8/2016 5:22:09 PM: Lemming #7 bumped his head and thinks his principal type is: System.Security.Principal.GenericPrincipal
...


Is my expectation of CurrentPrincipal incorrect and the windows case is just working by happenstance? Or is this another xamarin/mono bug?

Tagged:

Answers

Sign In or Register to comment.