January 31, 2013 Re: Understanding the GC | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | On Wednesday, 30 January 2013 at 10:29:26 UTC, monarch_dodra wrote:
> To add to that, you also have to keep in mind that when the program terminates (even legally), instead of running a *full* collect cycle, the program just leaves, and lets the OS clear any allocated memory. This is both faster, and safer.
>
> What this means is that while there is a guarantee that "collection=>destruction", there is no guarantee that actual collection will happen.
>
> If you absolutely must be sure that something allocated gets *destroyed*, either destroy it yourself via an explicit call, or bind it to a stack based RAII scheme, possibly with reference counting.
>
So there is no guarantee at all that a destructor will be called even at the end of the program? Because there is an example in the book using a class destructor to free allocated data.
I definitely understand now about how not to rely on a destructor to free up memory during runtime, but it seems counterintuitive to have the ability to write a destructor with no guarantee it would ever be called even at cleanup.
|
January 31, 2013 Re: Understanding the GC | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jeremy DeHaan | On Thu, 31 Jan 2013 18:27:59 -0500, Jeremy DeHaan <dehaan.jeremiah@gmail.com> wrote: > On Wednesday, 30 January 2013 at 10:29:26 UTC, monarch_dodra wrote: >> To add to that, you also have to keep in mind that when the program terminates (even legally), instead of running a *full* collect cycle, the program just leaves, and lets the OS clear any allocated memory. This is both faster, and safer. >> >> What this means is that while there is a guarantee that "collection=>destruction", there is no guarantee that actual collection will happen. >> >> If you absolutely must be sure that something allocated gets *destroyed*, either destroy it yourself via an explicit call, or bind it to a stack based RAII scheme, possibly with reference counting. >> > > So there is no guarantee at all that a destructor will be called even at the end of the program? Because there is an example in the book using a class destructor to free allocated data. I'm pretty sure all GCs do not have a guarantee of running all destructors. I think D's GC makes a good effort to do so. > I definitely understand now about how not to rely on a destructor to free up memory during runtime, but it seems counterintuitive to have the ability to write a destructor with no guarantee it would ever be called even at cleanup. A destructor should ONLY be used to free up resources other than GC allocated memory. Because of that, it's generally not used. It should be used almost as a "last resort". For example, a class that holds a file descriptor should have both a destructor (which closes the descriptor) and a manual close method. The former is to clean up the file descriptor in case nobody thought to close it manually before all references were gone, and the latter is because file descriptors are not really managed by the GC, and so should be cleaned up when they are no longer used. This kind of gives us a paradox, since the class is managed via the GC, how do you know it's no longer used (that is, how do you know this is the last reference to it)? That is really up to the application design. But I wouldn't recommend relying on the GC to clean up your descriptors. -Steve |
February 01, 2013 Re: Understanding the GC | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Thursday, 31 January 2013 at 23:53:26 UTC, Steven Schveighoffer wrote:
> On Thu, 31 Jan 2013 18:27:59 -0500, Jeremy DeHaan <dehaan.jeremiah@gmail.com> wrote:
>
>> On Wednesday, 30 January 2013 at 10:29:26 UTC, monarch_dodra wrote:
>>> To add to that, you also have to keep in mind that when the program terminates (even legally), instead of running a *full* collect cycle, the program just leaves, and lets the OS clear any allocated memory. This is both faster, and safer.
>>>
>>> What this means is that while there is a guarantee that "collection=>destruction", there is no guarantee that actual collection will happen.
>>>
>>> If you absolutely must be sure that something allocated gets *destroyed*, either destroy it yourself via an explicit call, or bind it to a stack based RAII scheme, possibly with reference counting.
>>>
>>
>> So there is no guarantee at all that a destructor will be called even at the end of the program? Because there is an example in the book using a class destructor to free allocated data.
>
> I'm pretty sure all GCs do not have a guarantee of running all destructors. I think D's GC makes a good effort to do so.
>
>> I definitely understand now about how not to rely on a destructor to free up memory during runtime, but it seems counterintuitive to have the ability to write a destructor with no guarantee it would ever be called even at cleanup.
>
> A destructor should ONLY be used to free up resources other than GC allocated memory. Because of that, it's generally not used.
>
> It should be used almost as a "last resort".
>
> For example, a class that holds a file descriptor should have both a destructor (which closes the descriptor) and a manual close method. The former is to clean up the file descriptor in case nobody thought to close it manually before all references were gone, and the latter is because file descriptors are not really managed by the GC, and so should be cleaned up when they are no longer used.
>
> This kind of gives us a paradox, since the class is managed via the GC, how do you know it's no longer used (that is, how do you know this is the last reference to it)? That is really up to the application design. But I wouldn't recommend relying on the GC to clean up your descriptors.
>
> -Steve
This makes a whole lot of sense to me. I never realized that a GC could be imperfect in this regard. I also don't know much about GC design and implementation, but that was the purpose of this thread!
I definitely like the idea of having a manual way of cleaning up and a destructor as a back up for times such as these.
Thanks for all the help guys! I learned a lot!
|
February 01, 2013 Re: Understanding the GC | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Thursday, 31 January 2013 at 23:53:26 UTC, Steven Schveighoffer wrote:
>
> A destructor should ONLY be used to free up resources other than GC allocated memory. Because of that, it's generally not used.
>
> It should be used almost as a "last resort".
>
> For example, a class that holds a file descriptor should have both a destructor (which closes the descriptor) and a manual close method. The former is to clean up the file descriptor in case nobody thought to close it manually before all references were gone, and the latter is because file descriptors are not really managed by the GC, and so should be cleaned up when they are no longer used.
>
> This kind of gives us a paradox, since the class is managed via the GC, how do you know it's no longer used (that is, how do you know this is the last reference to it)? That is really up to the application design. But I wouldn't recommend relying on the GC to clean up your descriptors.
>
> -Steve
I've actually run into this very issue: I was iterating on files, opening them, and placing the descriptor in GC-allocated RAII data. I can't remember if class or struct, but not a big issue. Come to think about it, I think I was using "File", but allocating them because I thought they were classes `auto f = new File("my file", "r")`.
After running for a second, my program halts, because an exception was thrown trying to open a new file:
"Cannot open file: Too many open file handles".
It was basically: Sure, the GC will destroy and close files for you... if you forget... eventually...
I ended up closing them in scope(exit) blocks. Problem immediately solved. Or I could have stopped allocating my File's on the heap.
Either way, it shows you shouldn't rely on the GC for deterministic destruction.
|
February 01, 2013 Re: Understanding the GC | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | On Fri, 01 Feb 2013 02:07:00 -0500, monarch_dodra <monarchdodra@gmail.com> wrote:
> On Thursday, 31 January 2013 at 23:53:26 UTC, Steven Schveighoffer wrote:
>>
>> A destructor should ONLY be used to free up resources other than GC allocated memory. Because of that, it's generally not used.
>>
>> It should be used almost as a "last resort".
>>
>> For example, a class that holds a file descriptor should have both a destructor (which closes the descriptor) and a manual close method. The former is to clean up the file descriptor in case nobody thought to close it manually before all references were gone, and the latter is because file descriptors are not really managed by the GC, and so should be cleaned up when they are no longer used.
>>
>> This kind of gives us a paradox, since the class is managed via the GC, how do you know it's no longer used (that is, how do you know this is the last reference to it)? That is really up to the application design. But I wouldn't recommend relying on the GC to clean up your descriptors.
>>
>> -Steve
>
> I've actually run into this very issue: I was iterating on files, opening them, and placing the descriptor in GC-allocated RAII data. I can't remember if class or struct, but not a big issue. Come to think about it, I think I was using "File", but allocating them because I thought they were classes `auto f = new File("my file", "r")`.
>
> After running for a second, my program halts, because an exception was thrown trying to open a new file:
> "Cannot open file: Too many open file handles".
>
> It was basically: Sure, the GC will destroy and close files for you... if you forget... eventually...
>
> I ended up closing them in scope(exit) blocks. Problem immediately solved. Or I could have stopped allocating my File's on the heap.
>
> Either way, it shows you shouldn't rely on the GC for deterministic destruction.
Actually, that's a different problem. File is a struct, and structs do NOT have their destructor run by the GC. Only Objects do.
This is a GC limitation, since structs do not contain a pointer to their typeinfo like classes do, and there is no provision for storing a pointer to the typeinfo of a block. It could be fixed by a more precise GC. AIUI, we have something like that coming, but I've been hearing that for more than a year ;)
-Steve
|
February 01, 2013 Re: Understanding the GC | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Friday, 1 February 2013 at 15:37:25 UTC, Steven Schveighoffer wrote:
> On Fri, 01 Feb 2013 02:07:00 -0500, monarch_dodra <monarchdodra@gmail.com> wrote:
>
>> On Thursday, 31 January 2013 at 23:53:26 UTC, Steven Schveighoffer wrote:
>>>
>>> A destructor should ONLY be used to free up resources other than GC allocated memory. Because of that, it's generally not used.
>>>
>>> It should be used almost as a "last resort".
>>>
>>> For example, a class that holds a file descriptor should have both a destructor (which closes the descriptor) and a manual close method. The former is to clean up the file descriptor in case nobody thought to close it manually before all references were gone, and the latter is because file descriptors are not really managed by the GC, and so should be cleaned up when they are no longer used.
>>>
>>> This kind of gives us a paradox, since the class is managed via the GC, how do you know it's no longer used (that is, how do you know this is the last reference to it)? That is really up to the application design. But I wouldn't recommend relying on the GC to clean up your descriptors.
>>>
>>> -Steve
>>
>> I've actually run into this very issue: I was iterating on files, opening them, and placing the descriptor in GC-allocated RAII data. I can't remember if class or struct, but not a big issue. Come to think about it, I think I was using "File", but allocating them because I thought they were classes `auto f = new File("my file", "r")`.
>>
>> After running for a second, my program halts, because an exception was thrown trying to open a new file:
>> "Cannot open file: Too many open file handles".
>>
>> It was basically: Sure, the GC will destroy and close files for you... if you forget... eventually...
>>
>> I ended up closing them in scope(exit) blocks. Problem immediately solved. Or I could have stopped allocating my File's on the heap.
>>
>> Either way, it shows you shouldn't rely on the GC for deterministic destruction.
>
> Actually, that's a different problem. File is a struct, and structs do NOT have their destructor run by the GC. Only Objects do.
>
> This is a GC limitation, since structs do not contain a pointer to their typeinfo like classes do, and there is no provision for storing a pointer to the typeinfo of a block. It could be fixed by a more precise GC. AIUI, we have something like that coming, but I've been hearing that for more than a year ;)
>
> -Steve
Oh. Wow. That's news to me.
I'd say as long as you don't new your structs, you are fine, but apparently, dynamic arrays don't clean up after themselves either (!) That's a whole other ballgame...
That's quite scary. It brings back into question a few of my implementations...
|
February 01, 2013 Re: Understanding the GC | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra |
On Jan 31, 2013, at 11:07 PM, "monarch_dodra" <monarchdodra@gmail.com> wrote:
> On Thursday, 31 January 2013 at 23:53:26 UTC, Steven Schveighoffer wrote:
>>
>> A destructor should ONLY be used to free up resources other than GC allocated memory. Because of that, it's generally not used.
>>
>> It should be used almost as a "last resort".
>>
>> For example, a class that holds a file descriptor should have both a destructor (which closes the descriptor) and a manual close method. The former is to clean up the file descriptor in case nobody thought to close it manually before all references were gone, and the latter is because file descriptors are not really managed by the GC, and so should be cleaned up when they are no longer used.
>>
>> This kind of gives us a paradox, since the class is managed via the GC, how do you know it's no longer used (that is, how do you know this is the last reference to it)? That is really up to the application design. But I wouldn't recommend relying on the GC to clean up your descriptors.
>>
>> -Steve
>
> I've actually run into this very issue: I was iterating on files, opening them, and placing the descriptor in GC-allocated RAII data. I can't remember if class or struct, but not a big issue. Come to think about it, I think I was using "File", but allocating them because I thought they were classes `auto f = new File("my file", "r")`.
>
> After running for a second, my program halts, because an exception was thrown trying to open a new file: "Cannot open file: Too many open file handles".
>
> It was basically: Sure, the GC will destroy and close files for you... if you forget... eventually...
>
> I ended up closing them in scope(exit) blocks. Problem immediately solved. Or I could have stopped allocating my File's on the heap.
>
> Either way, it shows you shouldn't rely on the GC for deterministic destruction.
The GC currently doesn't finalize structs, only classes. So that's an issue as well.
|
February 01, 2013 Re: Understanding the GC | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On 2013-02-01 16:37, Steven Schveighoffer wrote:
> Actually, that's a different problem. File is a struct, and structs do NOT have
> their destructor run by the GC. Only Objects do.
>
> This is a GC limitation, since structs do not contain a pointer to their
> typeinfo like classes do, and there is no provision for storing a pointer to the
> typeinfo of a block. It could be fixed by a more precise GC. AIUI, we have
> something like that coming, but I've been hearing that for more than a year ;)
So currently the only way to make a struct's destructor work when the struct is on the heap is to encapsulate that struct in a class?
I have tested the following:
struct A { ... }
class C { A a; ... }
A a = A(); // OK
A *b = new A(); // BAD, no finalization
C c = new C(); // OK, a's destructor will be called
|
February 01, 2013 Re: Understanding the GC | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | On Fri, 01 Feb 2013 10:46:11 -0500, monarch_dodra <monarchdodra@gmail.com> wrote: > On Friday, 1 February 2013 at 15:37:25 UTC, Steven Schveighoffer wrote: >> This is a GC limitation, since structs do not contain a pointer to their typeinfo like classes do, and there is no provision for storing a pointer to the typeinfo of a block. It could be fixed by a more precise GC. AIUI, we have something like that coming, but I've been hearing that for more than a year ;) >> >> -Steve > > Oh. Wow. That's news to me. > > I'd say as long as you don't new your structs, you are fine, but apparently, dynamic arrays don't clean up after themselves either (!) That's a whole other ballgame... Dynamic arrays do clean up the array memory. For arrays of objects, or arrays of structs that have only GC-based resources, those items are also individually cleaned up by the GC. But if you had, for instance, an array of self-closing file descriptor structs, those would NOT be cleaned up by the GC. > That's quite scary. It brings back into question a few of my implementations... People should definitely be aware of this limitation. It usually is not of any concern because it is rare to have a struct that has a non-GC reference on the heap. But it is something to be aware of. Note that if your struct is a *member* of a class, it's destructor will be called because it's destructor is called as part of the object's destructor. But this isn't always what you want! I think people have been harping on this limitation of File (and reference-counted structs in general) for a long time. There is a bugzilla issue for it somewhere. -Steve |
Copyright © 1999-2021 by the D Language Foundation