Skip to content

System.Runtime.InteropServices.StructLayoutAttribute.Size behavior on reference types #112104

Open
@seanoct

Description

@seanoct

Description

We’re seeing this issue when we try to migrate our solution from .NET 6 to .NET 8.

Once we got the code compiling and running, our application freezes. I guess something changed from .NET 6 to .NET 8 that isn’t apparent.

After some digging around, this is what I've found.
We have some 3rd party objects that implement IDispose. The 3rd party objects seems to do a lock (this) {} in their Dispose and it is out of our control.

Now when we instantiate an instance of this object inside a using {} block, the code hangs at the closing scope.

key.Down() returns a new instance of SegmentKey (as far as I can tell from VS decompilation) with some vendor bookkeeping logic.

If we remove the using block, the GC thread will freeze (eventually) – I’m assuming that it’s also hitting the lock.

This is what our code looks like when it freezes:

public static bool HasSubkey(this SegmentKey key, string name)
{
    using (SegmentKey subkey = key.Down(name, false))
    {
        try
        {
            return subkey.Name() == name;
        }
        catch { }
    } // freezes here
    return false;
}

This is what the SegmentKey’s (3rd party) Dispose looks like from VS decompilation:

public override void Dispose()
{
    lock (this) // debugger says that we freeze here
    {
        .
        .
        .
    }
}

Looking into this, I peeked around the memory and cross referenced with documentation on how CLR objects work (https://mycodingplace.wordpress.com/2018/01/10/object-header-get-complicated/). I see this:

Image

If my understanding is correct, that value of 0x0100007f indicates that our object is BIT_SBLK_SPIN_LOCK’ed.

I thought maybe the vendor is doing something silly, so I experimented with a plain C# object. It too had BIT_SBLK_SPIN_LOCK (among other bits set).

Image

If I try to lock o, then the thread freezes – even if I do so directly after allocation:

public static bool HasSubkey(this SegmentKey key, string name)
{
    System.Object o = new System.Object();
    lock (o) { } // freezes on lock

    using (SegmentKey subkey = key.Down(name, false))
    {
        try
        {
            return subkey.Name() == name;
        }
        catch { }
    }
    return false;
}

I don't think threads are involved. This is all happening on the main thread. It seems like the main thread is deadlocking itself.

For me, this is 100% reproducible on .NET 8. It always happens on the 4th call to HasSubKey (which I determined by counting breakpoint hits).

I tried clearing out the bin/ directory and forcing a clean rebuild. Didn't help.

I’ve also tried the clrgc trick from this post: https://maoni0.medium.com/is-the-regression-in-gc-or-something-else-38f10018dd21 (which I discovered after reading through #95191). I was successfully able to load clrgc, but it didn’t have any noticeable effect on preventing the freeze.

Reproduction Steps

not sure if I can provide a sandboxed repro steps

Expected behavior

a newly instantiated object isn't locked

Actual behavior

a newly instantiated object is locked

Regression?

No response

Known Workarounds

No response

Configuration

Windows 11 x64

trying to migrate .NET 6 -> 8

Other information

Not sure if this is useful, I think if you try to do Monitor.TryLock() on this object, the function never returns -- and the thread deadlocks as well

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions