CloseStream and GC

BlitzMax Forums/BlitzMax Programming/CloseStream and GC

Adam Novagen(Posted 2014) [#1]
It's been a very long time since I last coded, and that was in Blitz3D, so I find myself encountering occasional small hiccups by jumping right in with Max nowadays. My basic understanding of garbage collection is that if there is anything in memory which is not referenced by something - like an image that is no longer "contained" in any existing variables - it will eventually get deleted. Being used to always closely managing my own memory cleanup, however, I sometimes get a bit nervous, as per the following:

SuperStrict

Function readSomeFile(filePath:string)
	Local file:TStream = ReadFile(filePath)
	
	' Do naughty things to file
	
	CloseStream file ; file = Null
EndFunction


From my understanding, because file is a local variable and therefore only exists within the scope of readSomeFile(), once the program gets back out of the function, the file's TStream will no longer be referenced by the file variable, setting it for garbage collection and making the whole CloseStream line unnecessary. Is that right?


Derron(Posted 2014) [#2]
TStream.Delete() (a method) gets called as soon as a stream is marked for "garbage collection".

The default implementation of "Delete()" (for TStream) calls "Close()".

Close() is also called when using "CloseStream()" (which is just a convencience function to the streams "Close" Method).


EDIT: Filestreams call "fclose_" in their "Close". So this means they work with the filehandle, telling the system it is used (or then unused) etc.
As soon as "Method Delete()" does not get overwritten BY YOU or your modules ... you could rely on "Delete()" to get called during garbage collection.

"Close()" (or CloseStream()) therefore just ensures that the file handle is available right afterwards (imagine threads - and the time between "unused" and "garbage collection" taking place ...)


bye
Ron


Yasha(Posted 2014) [#3]
The final `file = null` is redundant; you don't need to "remove" the reference from `file` when the variable itself will stop existing too (it might be less redundant if more things, such as a long loop, were to happen in that scope afterwards but generally unnecessary).

Anyway Derron's post covers the basic part - that external resources require manual cleanup code, but this code can be placed in the managed Max object's finalizer, so the external resource will be freed when the Max object is collected.

However, there's a second aspect to this: there's no guarantee the GC will ever actually run. This is a good thing - GCs are tuned towards reducing memory pressure and staying out of the way. But if the application isn't under memory pressure, it isn't guaranteed to ever actually collect objects, which means their finalizers won't run, or more likely that they just won't run as soon as the object falls out of scope, but at some indeterminate point later, when memory starts to run low and it decides to do something about it. Therefore for scarce non-memory resources like file handles, which actually do need to be returned to the system as soon as you're done with them, an explicit call to Close (or the relevant release method) is still good practice.


Adam Novagen(Posted 2014) [#4]
So it's just a question of forcing a closing of something early if you want to keep things streamlined, got it. And in retrospect, I dunno what I was thinking with file = Null.

external resources require manual cleanup code, but this code can be placed in the managed Max object's finalizer, so the external resource will be freed when the Max object is collected.
Not gonna lie, I'm new enough to Max that I only understand half of this. Does this mean that if I have an image loaded into a Global, for example, then later I set that Global to Null, the image will stay in memory as a leak?


Derron(Posted 2014) [#5]
No ... what he said was:

- you unset a variable
- you do other things
- if GC decides you are getting low on memory ("it is time!"), the unset variables will get freed

in the case of file streams this "close()" is needed to ensure the file handle is freed right after use... not "sometimes / maybe / ... in the future".


bye
Ron


Brucey(Posted 2014) [#6]
You should close a stream/file as soon as it is no longer required. Really. Why keep a handle open on something just because the GC will get around to it sometime later. That's just laziness.

:o)


*(Posted 2014) [#7]
Coming from C++ I never rely on GC for clean up and always try to release resources when I've finished with them, its only a few extra lines so isn't major.

If GC is only fired when memory is low its good practice to release/close stuff yourself otherwise sometimes debugging an unexpected crash can stall even the best project. For example back in the day you could only have x amount of files open so leaving handles to GC to get rid of would be a no-no I don't know if its still the case but I don't want to find out :)


Yasha(Posted 2014) [#8]
Well in C++ you should never need to release anything either, RAII and all that. Eager and deterministic is great for scarce resources like file handles, even if it is suboptimal for memory.

Some primarily-GC-oriented languages also offer limited RAII in the form of "with" or "using" blocks, that are guaranteed to close a resource at the end of the scope regardless of normal collection rules. You could probably emulate this in Max somehow using a block-wrapper function (although I'm not sure how to do it elegantly, with the lack of either macros - which you'd use to hack it together in C, over a `for` - or proper nested functions).


Being able to put together constructs like this is a seriously strong argument in favour of having either macros, or at least some kind of generic "block expression" (templates/do-blocks/lambdas), in a language.