Skip to content

Conversation

mihails-strasuns
Copy link

Specifically, explain why it is not OK to refer to
other GC managed objects from invariant.

Specifically, explain why it is not OK to refer to
other GC managed objects from invariant.
@mihails-strasuns
Copy link
Author

See also http://forum.dlang.org/post/ossuvfmqthllgdpgzntm@forum.dlang.org for an example of confusion current docs cause.

@CyberShadow
Copy link
Member

I think this is a crappy reason which will massively restrict invariant usefulness. The definition of when an object is valid is, in practice, almost always in relation to other heap-allocated objects. For example, this is supposedly invalid:

class Account
{
    Currency balance;
    Transaction[] transactions;
    invariant()
    {
        assert(balance == transactions.map!(t => t.debit).sum);
    }
}

You say that the core reason is that the GC might destroy objects in arbitrary order. But if those objects don't themselves have destructors, then their state is actually valid, as the GC does not clobber freed objects' memory until it has finished running all destructors.

I've heard the argument that the above is just how the current implementation works, and should not be solidified into the language, as it might constrain future GC implementations. I'm not sure I agree, since with the current implementation, things are still memory-safe, no matter what you do in the destructor. Sure, you are allowed to access an object whose destructor has run and is now logically invalid, but that doesn't jeopardize memory safety.

I think we should go into the opposite direction and guarantee memory safety inside a destructor (with the remaining caveat that finalization order is arbitrary).

And as for the invariant getting called after the destructor... well, that's a bug. I checked TDPL, and even though it doesn't say much about invariants accessing other objects, it is explicit in that the invariant does not run after the destructor.

@andralex What do you think?

@mihails-strasuns
Copy link
Author

My intent was to simply document existing situation and avoid awkward surprises for newcomers. If official spec and/or implementation will change (intentionally or by fixing the supposed bug), it should be changed too.

@mihails-strasuns
Copy link
Author

I am personally OK with approach of not calling the invariants in the middle of collection cycle. But I also have not ever written a single invariant that uses other GC objects either.

@@ -181,6 +181,13 @@ class Date
$(LI postconditions)
)

$(P Because at the start of class/struct destructor object instance may
point to other object instances already collected by the GC trying to
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a comma after "GC" so it doesn't parse as "... instances collected by [the GC trying to check...]".

@1fish2
Copy link

1fish2 commented Jan 26, 2015

It's really good to document the situation!

However, it only applies to object internal consistency.

I'd want it to be plainly obvious what counts as "internal consistency". The spec on destructors says:

This means that when the garbage collector calls a destructor for an object of a class that has members that are references to garbage collected objects, those references may no longer be valid.

That means an invariant() had better not check, say, that a string holds a suitable value (suitable for this object, as distinct from just being a well-formed string) or even that it's not a null reference.

[Separately, the situation is a nasty surprise. Should the collector skip calling the invariant() in the middle of a collection cycle? At least, not after invalidating any of the object's references?]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants