Dino's Blog

.NET Stuff

  • Cooperative Fiber Mode Sample - Day 4

    Last time we left off with the CLR calling into the host to create a task.  So far everything’s been very simple.  For each interface we’ve only needed to implement a couple of methods to get the bulk of the work done.  While I’ve certainly left out a couple of lines of code here and there, nearly every other API in the interface merely returns S_OK.

     

    Now things start to get really interesting!  The IHostTask interface has just a few functions which are exposed to the runtime.  But there’s a lot of other functionality related to the task embedded in here.

     

    We’ll start off at the natural place to start a task: Start.  It’s pretty simple:

     

          FlagSet(TASK_FLAG_NOTSTARTED,false);

     

          _ASSERTE(m_fiberAddr == NULL);

          m_fiberAddr = ::CreateFiber(0,(LPFIBER_START_ROUTINE )CHostFiberProc,(PVOID)this);

     

          // create the new thread

          ::CreateThread(NULL,NULL,reinterpret_cast<LPTHREAD_START_ROUTINE>(::StartNewThread),this,NULL,NULL);

     

        return(S_OK);

     

     

    We create a fiber for the new task, and we create a new thread.            This new thread starts in StartNewThread, defined in callbacks.cpp.  It looks something like:

     

          CHostTaskManager *myManager = CHostTask::GetManager();

          CHostTask *curTask = static_cast<CHostTask*>(lpParameter);

     

          LPVOID fiberAddr = ConvertThreadToFiber(NULL);

               

          curTask->SwitchIn();

         

          BOOL fResult = ConvertFiberToThread();

     

    Here we simply convert the new thread over to fibers, and then switch in the fiber passed as the argument.  This brings us back to our CHostTask implementation where we need to look at the SwitchIn logic.  This is the most complicated piece of code we’ve encountered yet.

     

           CHostTask *curTask = GetCurrentTask();

           _ASSERTE(curTask != this);

          

           TlsSetValue(CHostTask::CurTaskTlsIndex,this);

           this->AddRef();

     

           // Save our thread handle so we can queue APCs

           if(!DuplicateHandle(GetCurrentProcess(),

    GetCurrentThread(),

    GetCurrentProcess(),

    &m_hCurThread, 0 , FALSE,

    DUPLICATE_SAME_ACCESS))

           {

                  m_hCurThread = INVALID_HANDLE_VALUE;

                  _ASSERTE(!"Duplicate handle failed");

           }

     

           FlagSet(TASK_FLAG_RUNNING, true);

           ::SwitchToFiber(GetFiberAddress());     

     

           // when the fiber switches back we need to switch in our

           // previous task.

           if(curTask->m_pCallback!=NULL)

           {

                  HRESULT hr = curTask->m_pCallback->SwitchIn(GetCurrentThread());

                  _ASSERTE(SUCCEEDED(hr));

           }

          

           if(curTask->FlagCheck(TASK_FLAG_EXITING))

           {

                  _ASSERTE(curTask->GetSwitchingTo());

                  curTask->ExitTask();

                  curTask->GetSwitchingTo()->SwitchIn();

           }

     

           // release the ref for TLS from the previous thread

           CloseHandle(m_hCurThread);

           m_hCurThread = INVALID_HANDLE_VALUE;

           Release();

                 

           SetThreadPriority(GetCurrentThread(), curTask->m_iPriority);

           SetThreadLocale(curTask->m_lcid);

     

     

    What’s going on here?  We have a couple of things to worry about when switching tasks.  We have the task that we’re switching to (this) and we have the task we’re switching from (curTask).

     

    One of the issues we’re concerned with is the lifetime of the task.  While it’s running we don’t want it cleaned up, so we hold a reference to it (in Thread Local Store).  A more complicated host would probably have a pool of tasks rather than the simple TLS mechanism. 

     

    If we need to alert a task we’ll queue an Asynchronous Procedure Call (APC) to it.  Therefore the next thing we do is save the current thread’s handle into this task.  We’ll use this in IHostTask::Alert.  Finally we set the running flag and switch over to the new task.

     

    The interesting thing to note about this method is there are 2 halves to it.  After we call SwitchToFiber we are running on a different fiber on a different stack.  We’ll only return to the bottom half after someone has switched back to “curTask”.  This is the task that we switched away from.  When this happens it’s now the bottom half’s job to tell the runtime that “curTask” is once again running.

     

    It’s possible when we get to the bottom half “curTask” was only switched in to exit.  If so we’ll notify the runtime and immediately re-run the task we’ve been set to re-run.  This is an implementation detail of CoopFiber to allow calling ExitTask on the managed fiber implementation. 

     

    We’re nearly done so we clean up the resources we allocated in the top half for “this”.  It’s no longer running, so we don’t need a reference to it.  Finally we restore the settings for the thread that were stored in the task.  These would have been changed when we did the intital SwitchToFiber which either switched in a task that was at the bottom half of SwitchIn, or the top of CHostFiberProc (in callbacks.cpp).

     

    Wow, so that’s how we start a task!  We create a new thread, that thread gets converted over to fibers, and we switch in the newly created task (which has a fiber already associated with it).  That’ll end up in CHostFiber proc which we’ll cover in a future edition. 

     

    There’s just one more detail to cap off the life time of a task, and that is our internal SwitchOut API.  All it essentially does is set some state and notify the runtime of the switch out:

     

          FlagSet(TASK_FLAG_RUNNING,false);

     

          if(m_pCallback!=NULL)

          {

                HRESULT hr = m_pCallback->SwitchOut();   

          }

     

     

    Next time we’ll go over the remaining APIs on IHostTask that we haven’t covered yet.

  • Cooperative Fiber Mode Sample - Day 3

    When I last left off we went over the host control.  The host control hands off our task and synchronization managers.  Now it’s time to take a look at the two host-implemented managers, starting with the task manager.

     

    I’m going to skip over the minor details such as constructors, destructors, etc… and only focus in on the details of the APIs that communicate with the CLR.  The APIs we care about the most are GetCurrentTask and CreateTask.  A fiber mode host with it’s own scheduler would pay more attention to APIs like Sleep (where we merely call the OS API), SwitchToTask (which is a NOP in CoopFiber), and the other APIs.  Because CoopFiber doesn’t have a scheduler we aren’t concerned with these APIs.  So if we look at GetCurrentTask:

     

          _ASSERTE(pTask);

     

          

           CHostTask *curTask = CHostTask::GetCurrentTask(false);

     

           if(curTask==NULL)

           {

                  // 0x1E00 - value returned by GetCurrentFiber when not a fiber

                  if(GetCurrentFiber()==(PVOID)0x1E00)

                  {

                         curTask = CHostTask::CreateTask(ConvertThreadToFiber(NULL),TASK_FLAG_RUNNING);

                  }

            else

            {

                         curTask = CHostTask::CreateTask(GetCurrentFiber(),TASK_FLAG_RUNNING);

                  }

           }     

          

           curTask->AddRef();

           *pTask = (IHostTask *)curTask;

           return(S_OK);

     

    We simply check to see if the current task has been created.  If it hasn’t we create a new task.  Depending on whether or not we’re already running on a fiber (by some chance) we’ll re-use the fiber, otherwise we’ll create a new fiber.  Here we choose to allow any random threads that enter into the CLR to be converted to fibers.  A host could also deny these threads entrance by returning HOST_E_INVALIDOPERATION.

     

    The other interesting API is CreateTask:

     

          _ASSERTE(ppTask!=NULL);

          _ASSERTE(pStartAddress);

     

          CHostTask *tmp = CHostTask::CreateTask();

          if (tmp == NULL)

          {

                _ASSERTE(FALSE && "Out of memory in CreateTask!");

                return E_OUTOFMEMORY;

          }

         

          tmp->SetStart(pStartAddress, pParameter);

          tmp->AddRef();

     

          *ppTask = (IHostTask *)tmp;

               

          return(S_OK);

     

    This isn’t really doing anything too fancy.  We’re just creating out internal task implementation which implements IHostTask.  We’re handing that off to the runtime.  Later the runtime will call Start on it and it’ll kick off the running of a new task.  We’ll look at the IHostTask implementation next time.

  • Cooperative Fiber Mode Sample - Day 2

    In the last blog entry I went over how we start the runtime.  Once it was loaded we were running managed code, but I glossed over the managers that were present and participating in running the system.  Today we start off with CHostControl::GetHostManager.  CHostControl is CoopFiber’s implementation of IHostControl, and GetHostManager is the only API we implement.

     

    HRESULT __stdcall CHostControl::GetHostManager(

       /* [in] */ REFIID riid,

       /* [out] */ void **ppObject)

    {

     

          if(riid == IID_IHostTaskManager)

          {

                if(m_pTaskMan == NULL)

                {

                      m_pTaskMan = new CHostTaskManager();

                }

                *ppObject = static_cast<IHostTaskManager*>(m_pTaskMan);

                m_pTaskMan->AddRef();

                return(S_OK);

          }

          else if(riid == IID_IHostSyncManager)

          {

                if(m_pTaskMan == NULL)

                {

                      m_pTaskMan = new CHostTaskManager();

                }

                *ppObject = static_cast<IHostSyncManager*>(m_pTaskMan);

                m_pTaskMan->AddRef();

                return(S_OK);

          }

          return(E_NOINTERFACE);

    }

     

    What are we doing here?  CHostControl has one task manager.  That task manager actually implements both IHostTaskManager and IHostSyncManager.  These are the minimal APIs you need to implement fiber mode of any sort.  IHostTaskManager provides the mechanisms for creating tasks.  IHostSyncManager provides the mechanisms for providing synchronization between those tasks.

     

    At load time, the CLR calls the host-implemented IHostControl interface and asks for the managers that the CLR knows about.  The host can respond by handing back an interface pointer or it can respond by returning E_NOINTERFACE.  Virtually any combination of managers is supported.  The one gotcha is your thread and synchronization must be compatible.  The default synchronization implementation in the CLR is not fiber aware.  That’s the reason that we implement both a task and synchronization manager for this fiber mode sample.

     

    Here our GetHostManager implementation simply lazily creates the task manager for the first caller and hands it off to the runtime.  If it’s a manager the host doesn’t know about we return E_NOINTERFACE.

     

    The host control also provides a couple of other APIs that we’re not using here.  The first is SetAppDomainManager.  This is where the host is notified of the creation of an app domain.  The host is passed both the ID of the newly created app domain and an IUnknown pointer for the app domain manager.  Because CoopFiber doesn’t use an app domain manager we’ll never get any calls here.  We simply return E_NOTIMPL.

     

    The other API is GetDomainNeutralAssemblies.  This is another API that CoopFiber doesn’t use because it doesn’t care whether assemblies are loaded domain neutral or not.  Again, it simply returns E_NOTIMPL.

     

    So that’s it for the host control.  Stay tuned for the next entry when I’ll dig into our task manager.  Things will start to get really interesting then!

  • Cooperative Fiber Mode Sample - Day 1

    Well it’s been a while since I’ve blogged…  There’ve been a few distractions, starting with Beta 1, then vacation, and now things are back to normal.  So it seems reasonable to blog about something that we’ve just shipped in Whidbey Beta 1: The cooperative fiber mode sample that’s been included in the SDK.  You can get the freely downloadable SDK here.  If you load up the documentation, go to the Contents, and select Samples->Application Samples->Cooperative Fiber Mode Application Sample you’ll be able to get to the code.

     

    This sample shows how to write an extremely simple fiber mode host.  But it does so with a twist.  Instead of scheduling the tasks in a cooperative, automatic, and transparent fashion this sample exposes the concept of fibers to managed code .  This allows the managed code author to use fibers as the unmanaged code author would.  This results in an implementation that for the most part relies upon native threads.  The only time fibers are actually used is when the user explicitly chooses to use them.  This closely matches the experience you’d have programming with fibers in the unmanaged world.  More sophisticated hosts would want to implement their own synchronization primitives that would schedule other tasks while blocking.

     

    Over the next several blog entries I plan to give a walk through of the new fiber mode sample.  During this blog I’ll start with loading the runtime and getting bootstrapped into managed code.  But first let me give a brief warning.  Fiber mode is complex and hard to get right.  You probably don’t want to implement fiber mode into your application on a whim.  Weigh your other options carefully before jumping into running in fiber mode.

     

    Loading the runtime hasn’t changed in a significant fashion from v1.0 and v1.1.  We start with a call that should be familiar to anyone who has written code to host the runtime before:

     

          // load up the runtime

          HRESULT hr = CorBindToRuntimeEx(

                NULL,                 // version of the runtime to request

                NULL,                 // flavor of the runtime to request

                0,                    // runtime startup flags

                CLSID_CLRRuntimeHost, // clsid of ICLRRuntimeHost

                IID_ICLRRuntimeHost,  // IID of ICLRRuntimeHost

                (PVOID*)&pClrHost);   // a pointer to our punk that we get

     

    The only difference you may notice is that we’re asking for a different interface.  Previously we asked for CLSID_CorRuntimeHost and IID_ICorRuntimeHost.  In Whidbey we have a brand new interface that let you do much more.

     

    Next, we have a new call:

     

    hr = pClrHost->SetHostControl(static_cast<IHostControl *>(&hostControl));

     

     

    This new command is handing off to the runtime our IHostControl interface.  It’s not a required call for hosting the runtime, but it’s the mechanism through which we provide our threading managers (so it is required for fiber mode).  One callback on this API is GetHostManager which when called by the runtime allows the host to provide a manager.  The host managers provide threading, memory, assembly loading, or other low level functionality to the runtime.  Throughout the next couple of articles I’ll be strictly focused on the threading managers.

     

    After we hand off our host control we’re back to familiar territory again:

     

          hr = pClrHost->Start();

     

    Now the runtime has been started and we can start executing managed code.  And the way we start doing this has changed in Whidbey.  There are a few different mechanisms, and I’ll mention 2 of them.  The first is the app domain manager.  This allows a host to have an assembly loaded into every domain, including the default domain.  Once this assembly is loaded the host can start running managed code directly from it.  The other option is a sample call to execute an assembly passing it a string of arguments.  This sample uses the 2nd method as it’s simpler and sufficient for our purposes:

     

          hr = pClrHost->ExecuteInDefaultAppDomain(

                curDir,                             // directory to assembly

                L"Microsoft.Samples.FiberBootstrap",// Type name to load

                L"EntryPoint",                      // Method name to execute

                bootstrapArgs,                      // Arguments to be passed in

                &retVal);                           // return value of function

     

    The executed function exists in a separate managed DLL (FiberBootstrap.dll).  It simply parses the string and then calls AppDomain.ExecuteAssembly on the current domain.  This allows the sample to run any managed assembly you pass to it.  This EXE that gets loaded then can interact with the fiber mode APIs.

     

    At this point we’ve loaded the runtime, loaded an assembly into the default domain and executed managed code.  That’s really all there is to getting started.  If you were a simpler host you wouldn’t even need the SetHostControl call!  In the next article I’ll discuss what that SetHostControl call is setting up for us.

  • Testing Fiber Mode

    Testing Fiber Mode Fiber mode is one of the interesting new features we’ve done in hosting. Fiber support in .NET comes up as a common question too. I’d like to give an overview of what we’ve done to test fiber mode to ensure that you’ll get a good experience if you choose to use it – and however you choose to use it.

    To test fiber mode we created a test harness (called HostDriver) which could host the CLR. HostDriver supports a wide number of options, but the one I’ll focus on today is the ability to plug-in a user-defined set of managers. Those managers include things like a task manager (IHostTaskManager), a synchronization manager (IHostSynchronizationManager), a threadpool manager (IHostThreadpoolManager), an I/O completion manager (IHostIoCompletion) and several others. These managers can be mixed and matched from multiple DLLs, or they can all reside in the same DLL. HostDriver merges them together and presents a unified host to the CLR.

    Once I had this infrastructure built the next task was to create a set of managers to plug-in. The initial one is obvious: Create a task manager that is a thin wrapper over the Win32 APIs. This provides a nice, simple task manager that is easy to debug (this is important!) when things go wrong. Unfortunately it doesn’t do nearly a good enough job of exercising the hosting APIs. So once this one was up and running I moved onto implementing a fiber task manager to run in this environment. As you may imagine, that was a lot of fun (and certainly was challenging).

    There were a few really great things about this test harness. It allowed us to get broad coverage across how the threading APIs are changed when hosted. It allowed us to get broad coverage across how the programmer uses the hosting APIs. Finally it allowed us to get broad coverage of the various features running in fiber mode.

    The first tests were the threading tests running inside the hosted environment. Obviously the threading tests are some of the most interesting because fiber mode is primarily changing the threading model. This includes things like exercising the thread-pool, synchronization, starting threads, aborting threads, etc… Today we exercise a large amount of threading functionality in our automation test passes to ensure this doesn’t regress.

    The second win was the ability to cover the matrix of mixing and matching various managers. Want to use just a memory manager? That works. How about a memory manager and a thread pool manager? That works too. How about I/O completion and synchronization? Absolutely! Again, these tests make it into automation to ensure we don’t regress in these areas.

    Want to use a task manager that’s running in fiber mode but not a synchronization manager? Don’t do that – your locks won’t be aware of what threads they’re running on! This is a fact that’s easy to overlook when you’re first implementing a fiber mode task manager. Unfortunately the runtime cannot detect this (and therefore it cannot be tested) but it is something you’ll need to consider when using the new hosting interfaces in fiber mode. If you think about it this makes sense: The threading and synchronization APIs need to be aware of what the other one is doing to function correctly.

    And the final win was the ability to get broad coverage across all the product features running in fiber mode. This came to life in the form of a tool called Hostify and HostDriver’s evil cousin HostDriverDll. Hostify provides a mechanism to alter the CLR’s shim to allow the execution of arbitrary EXEs in a hosted environment. This allowed us to modify a machine and then run a normal test pass that would automatically run in a fiber environment. We can quickly cover a large portion of our code base in Fiber mode with minimal effort allowing us to run large fiber mode test passes. Unfortunately this is a long test pass that can’t just be put into our daily automation like another test. But before major releases (eg, alphas, betas, RCs, full releases) we do a full test pass that ensures that there have been no regressions introduced.

    So that’s the short version of how we’ve worked to ensure that fiber mode is a solid feature. We’ve strived to test both the essential APIs in depth and get broad coverage across the feature set of the product. The matrix of possibilities is intense in the hosting layer but this effort should make your experience with them more pleasant.
  • .NET Trivia

    Here’s a fun piece of .NET trivia involving thread aborts and exception handling.  What does the following code do?  And why does it do it?

     

    using System;

    using System.Threading;

     

    class TATest

    {

            public static void Main(string []args)

            {

                    try

                    {

                            Console.WriteLine("Aborting!");

                            Thread.CurrentThread.Abort();

                    }

                    finally

                    {

                            if((Thread.CurrentThread.ThreadState & ThreadState.AbortRequested)!=0)

                            {

                                    Console.WriteLine("Aborted!");

                                    Thread.ResetAbort();

                            }

                    }

                    Console.WriteLine(“Bye bye!“);  

            }

    }

     

    (In writing this I've discovered my fingers really prefer to type byte over bye...  that t just seems so natural to come next).b

  • Inaugural Blog

    Welcome to my blog. Yes, that’s right; you’re reading YAIB (Yet another Inaugural Blog). So, who am I, and why should you care? My name is Robert “Dino” Viehland (as in everyone calls me Dino, except for people who knew me in high school or before. E.g. parents, old friends, etc… So this is the only time I’ll mention Robert). I’ve worked at Microsoft for nearly 3 years now and that entire time I’ve been on the CLR team as an SDE/T. If you’ve missed the other introductions an SDE/T stands for Software Design Engineer/Test. That essentially means that I spend my time writing tests or test tools to test the product, running those tests or tools over the product, and investigating why they failed. In particular I own testing a portion of the CLR hosting interfaces test a portion of the CLR hosting interfaces and do a large amount of directed reliability testing. On the hosting side of things that includes new Whidbey features like threading abstraction APIs (fiber mode, deadlock detection, etc…) and memory abstraction APIs. On the reliability side of things I work on primarily directed reliability testing like fault injection and leak detection. Over the future of my blog I hope to blog about those new features and the work we’ve done to allow the CLR to run in-process with Yukon. And hopefully there’ll be some random anecdotes and other topics along the way. So once again, welcome to my blog!
More Posts « Previous page

© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker