Pin_Ptr Race Condition (C++/CLI) RRS feed

  • General discussion

  • To provide native code access to a managed array, MS provides the following example of "pin_ptr" usage:

    However, I wonder greatly about a key line in the sample code - that looks like this:

    pin_ptr<int> pp = &nums[0];

    How do I know that the assignment operator will not be interrupted by a GC collect, which invalidates the pointer the instant after it is grabbed?  In other words, it seems there is a race condition that would allow "pin_ptr<int> pp" to be assigned an address that is no longer valid.

    If in fact there is a race condition in the MS sample code, then please somebody explain how to do it correctly.  :)

    -Brent Arias
    • Changed type nobugzModerator Saturday, August 15, 2009 12:23 AM didn't look
    Wednesday, August 12, 2009 4:31 AM

All replies

  • There's not of course, it would be quite unusable.  There's a lock inside the CLR's implementation of GCHandle::Alloc().  In the Rotor source code, sscli20/clr/src/vm/handletablecache.cpp, TableCacheMissOnAlloc(), CrstHolder variable ch.

    Hans Passant.
    Wednesday, August 12, 2009 11:46 AM
  • Are you saying that GCHandle::Alloc() is invoked during the assignment operation of this piece of code:

    pin_ptr<int> pp = &nums[0];

    Unless that lock occurs during the assignment operation itself, then there would be a race condition.

    -Brent Arias
    Wednesday, August 12, 2009 5:47 PM
  • It is allocated on the stack, there is no possible race.  Every thread has its own stack.  Please post sample code that demonstrates this race condition.

    Hans Passant.
    Wednesday, August 12, 2009 11:57 PM
  • I know the pin_ptr is allocated on the stack...but the location of the pin_ptr is not what I'm concerned about.  I'm concerned about the assignment operation itself.  The race condition could take decades to demonstrate (race conditions don't always show immediately).  I'm simply noticing a problem by inspecting the code provided in the first link (

    If the assignment operation does not perform a "lock" or "freeze" on the GC, then it is possible that:

    1) the assignment operation reads the address
    2) the GC interrupts the assignment operation, and does compaction - it relocates the value in memory!
    3) the assignment operation resumes where it left off, and assigns the (bad) address it read in step one.
    4) your program explodes.

    I'd be curious to see the code "under the hood" of the assignment operation.
    -Brent Arias
    Friday, August 14, 2009 9:53 PM
  • I showed you where to look.  Did you look?
    Hans Passant.
    Friday, August 14, 2009 11:03 PM
  • The gcnew()?  To my knowledge, the gcnew does not occur during the assignment operation.  Only what happens during the assignment operation matters.

    Are you saying there is a gcnew() that occurs during the assignment operation?    Or are you saying that gcnew() creates values on the stack?  I just can't see how gcnew has any relevance to the race condition I believe I've spotted.

    Remember, from a multithreading pointing of view the following line of code...

    pin_ptr<int> foo = &num[0] not an atomic operation.  The GC can activate, presumably, right in the middle of the assignment (and there is no gcnew occuring there).

    I've not tried to reflect C++/CLI code before (viz. pin_ptr<T> overloaded assignment operation), but it is that code that would contain my answer.
    -Brent Arias
    • Edited by Brent Arias Friday, August 14, 2009 11:43 PM clarification
    Friday, August 14, 2009 11:38 PM
  • In the Rotor source code, sscli20/clr/src/vm/handletablecache.cpp, TableCacheMissOnAlloc(), CrstHolder variable ch.

    Hans Passant.
    Saturday, August 15, 2009 12:23 AM
  • I was wondering the same thing as the OP, and started looking through the CLR source code, but then I slapped my forehead and realised...

    We are dealing with a managed object, so &nums[0] is going to give you an interior_ptr<int>, not an int*. If the GC does interrupt during the assignment, the interior pointer will follow the object around in the managed heap, and everything will be okay.

    To demonstrate, let's put more than just an assignment operation in between getting the pointer and creating a pin_ptr out of it:

    array<int>^ nums = gcnew array<int> {1, 2, 3, 4};
    interior_ptr<int> ip = &nums[0];
    // Insert some long-running code in here, perhaps the GC will interrupt and move the array around in memory
    pin_ptr<int> pp = ip;
    // Okay, now the array is pinned, and we can safely get a native pointer
    int* np = pp;

    • Edited by Joe Daley Monday, November 23, 2009 2:05 AM Formatting
    Monday, November 23, 2009 2:04 AM