-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] Autoclosing files #780
Comments
Leaning towards option 3 too. However, I remember Ary said he doesn't like warning messages as a solution because they're usually ignored anyway? Also, option 2 means raise an exception after the second failure correct? |
I'm leaning towards 1 actually, not closing the file is a clear bug and all languages that automatically close files still strongly recommend doing it manually as soon as possible, where manually can mean to use the appropriate language construct, e.g. |
I actually prefer 2. Imagine you want to return a String iterator over the lines of a file, but streaming from disk: def stream_lines
File.open("...").each_line
end Now there will be an open file that won't be closed, and can't be closed from outside that method because the File isn't provided. After running the program for a long time (if you invoke that method multiple times) you will get that warning/error if anything but solution 2 is chosen. Maybe the above method isn't designed well. But I don't think the check in point 2 is very expensive: it's just checking if the return value from Another problem is that you get the error after trying to open the last File. You get that error and understand: "OK, there must be some file that isn't being closed.". How do you find that file? Maybe you forgot to do it in your code. Maybe a library you are using forgot to close one. Wouldn't it be better for the program not to crash and the language to just take care of this? If later you find that the GC is being invoked a lot in |
Those never closing files are like memory leaks: they might be hard to find. |
Also, my point is: if a file is being closed by the finalizer, there is definitely a bad design. If the language and the runtime should be there to make the programmers life easier, then it should warn as soon as possible about the problem. It has the ability to detect it but if it just tries keep the program running it's like hiding the trash under the carpet. One might argue that this is just exactly like the memory garbage collection, but for me it's not. The files are a much more expensive and limited resource. And why am I just talking about files. It's the same, or even worse, if we think about open sockets or pipes. Any IO should be explicitly closed, otherwise it's a bug. |
@waj How do you detect the file that's leaking? |
Well, nevermind. Probably just find all |
We could print the path of the leaked file in the finalizer. That should help a lot. |
I'm not really a Ruby user too much, but I like the way Python solves it: close it in the finalizer anyway, but add a mechanism for automatic closing: with open('abc', 'r') as f:
print(f.read())
# The 'with' block closes 'f' afterwards It's kind of like an implicit try-finally. |
@kirbyfan64 In Crystal (and Ruby) the same mechanism is available with block methods: File.open(...) do |f|
f.gets
...
end The file is automatically closed in this case. |
In this trivial case, a simple static analysis would show that the file reference doesn't escape the loop and can be stack allocated, so its destructor can be run at the end of the basic block. Is this not currently done, or is it that are destructors only run at the method boundary? |
It's not currently done. The class instances are always allocated in the heap. In this case the local variable (or the implicit reference) is allocated in the stack, so ideally with escape analysis we could destroy the object and even return the memory space to the heap before the GC runs. Still that would be an optimisation and I don't think we should relay on such mechanism to release the resources. |
I don't know, I'd rather have the compiler give me an error or warning instead. As @waj says:
This way I feel that the programmer's time is saved the most (overall). |
@vyp Note that it won't be a compile error, it will be a runtime error |
Sorry yes waj mentioned that at the start, hence "leaning". |
Oh right, you meant the rust comment is useless. Correct, sorry. |
Oh, now that I read everything again, I think I prefer options 3 or 4. I'm not sure which one, having your program print warnings but continue running is strange. |
Yes exactly, I keep thinking 4 is just too strange/confusing too. |
So far we're warning free, either you're doing it wrong or you're doing it right in Crystal. I like that. |
Ah, didn't know that, thanks. |
I vote for 4 (2 & 3). If this thing would happen in some routine seldom called, implementing the heart surveillance system that your grandma is hooked up to at the hospital, you'd be glad it didn't crash because some coders decided "it's not the right way, so the coder should be punished with a crash". The warning should of course be raised, so that it can be fixed properly for the next release. |
My vote goes to 4. Programs shouldn't crash from simple oversights like this when possible. |
I've tested this on Crystal Looped over 15 million times for 5 times and haven't crashed once. index = 0
loop do
index += 1
puts index
File.new(__FILE__)
end
|
Using I'd vote for option 3, but I wouldn't mind 2 & 3. Printing a warning is good, because explicit closing is obviously the optimal solution. It all depends on how tricky 2 is to implement. |
Just rename new to open_returning, so it will be rarely used :-) |
The backtrace gets a bit messed up, I presume since it can't open files;
|
Maybe just spit a message to stderr if it's compiled without |
Currently this crashes very quickly in Crystal, but it works on Ruby:
The reason: the files are never closed so it eventually hits the system/user limit. In Ruby it doesn't fail because once that happens it performs a garbage collection and tries one more time before raising an exception (https://github.com/ruby/ruby/blob/trunk/io.c#L5451-L5461). The
File#finalizer
will close the file for us and that frees the file descriptor resource.We could do the very same thing in Crystal but I'm not totally convinced by this approach. This could hide potential bugs in any application. In general, I think the
File#finalizer
should never be the point where the file is actually closed.I can see the following options:
Running the GC just in case sounds overkill, so I'm more inclined by option 3, but I wanted to hear other's opinions. Maybe I'm missing any other good reason for the Ruby implementation? (besides convenience).
The text was updated successfully, but these errors were encountered: