Crazy issue with Runtime.ObjectIterator affecting Destructors

We encountered a wild issue today. I’m not sure I can solve it in code, but thought I’d make others aware.

In threads we use CriticalSections, but since we don’t want them hung up in case of exceptions or other early exits, we created a “holder” class. When the holder object goes out of scope, it calls Leave on the CriticalSection it’s holding. The code that instantiates it might look something like this:

MyCriticalSectionProp.Enter
dim holder as new CSHolder( MyCriticalSectionProp )
// Some code
holder = nil // Will call Leave; explicitly setting it to nil is usually optional

In case of exception or other early exit, we know that our CriticalSection will be released. This works great.

Now enter the Runtime.ObjectIterator. We have code that can run in either its own Thread or the main thread that will gather and print information about all the objects in memory. That works great too, except…

Thread A creates a holder and is still using it while Thread B accesses the ObjectIterator. While Thread B is creating the report, Thread A nils the holder which should release the CriticalSection. But it doesn’t because Thread B still has a reference in the ObjectIterator. Instead, when done, Thread B releases the holder which, in turn, calls CS.Leave. But now it’s a different thread! Thread B throws an exception which is handled, and the CriticalSection that should have been released by Thread A has not been released, blocking all further access to that CriticalSection. At that point, there is nothing left to do but restart the app.

Our solution for now is to stop using that report. :confused:

Interesting. It’s an entirely logical situation, but I can see why it’s problematic.

Is it feasible that you could avoid yielding while holding on to the ObjectIterator? Perhaps create an array of objects from the ObjectIterator, filtering out your CSHolder instances, and then process the array?

Or maybe DONT yield at all when processing the report so the object iteration is a true snapshot in time

Wouldn’t matter since framework functions may yield

fun race condition

Yes, we added the pragmas without effect. That explains why.

Joe, it won’t matter if the original CSHolder goes nil while I’m compiling the objects, right?

It cant which is exactly the problem you’ve run into :slight_smile:

since you want this to be a “snapshot” of whats “in memory right this instant”

  1. suspend all other threads remembering their state on entry to the reporting thread
  2. run the report
  3. restore the other threads back to their state on entry

otherwise you’re reporting on a filtered list which may be misleading since it deliberately skips some items

its one other option but may induce “pauses” while this reporting thread does its thing

It won’t be able to because the loop wouldn’t yield. For example:

Dim objects() As Object

// Atomically gather all of the objects meeting a certain criteria
If True Then
  #Pragma BackgroundTasks False
  Dim iter As Introspection.ObjectIterator = Runtime.IterateObjects
  While iter.Next
    If Not (iter.Current IsA CSHolder) Then
      objects.Append(iter.Current)
    End If
  Wend
  #Pragma BackgroundTasks True
End If

// Now do something with the objects
...

I’ll try it. But might it not yield within iter.Next or iter.Current?

The other drawback is that this code won’t be particularly portable, and will exhibit the same behavior where other destructor-reliant objects are concerned.

[quote=246133:@Norman Palardy]since you want this to be a “snapshot” of whats “in memory right this instant”

  1. suspend all other threads remembering their state on entry to the reporting thread
    [/quote]

that doesn’t work due to these problems:
<https://xojo.com/issue/36511>
<https://xojo.com/issue/39949>

[quote=246171:@Michael Diehr]that doesn’t work due to these problems:
<https://xojo.com/issue/36511>
[/quote]
This doesn’t make it “not work” - it uses more cpu than necessary while suspended

This definitely is “not working” in 64 bit