Live data from Hacker News

Don't defer Close() on writable files (2017)

joeshaw.org

1–10 of 311 posts

Re: Don't defer Close() on writable files (2017)

#2
Rust has the same problem. Files are closed in `Drop` when the value goes out of scope, but all errors are silently ignored. To solve this there's `sync_all`[0].

Generally, relying on defer in Go or Drop in Rust for anything that can fail seems like an anti-pattern to me.

0: https://doc.rust-lang.org/std/fs/struct.File.html#method.syn...

Re: Don't defer Close() on writable files (2017)

#3
Don't call close() twice if you're not using pidfds, this is racy. The fd could be reused in between the two calls. You'll risk closing random things a frustratingly small fraction of the time, creating very hard bugs for yourself.

Re: Don't defer Close() on writable files (2017)

#4
Surely this would all go away if Go had an exception handling mechanism like most mainstream languages do?

You'd just concentrate on the "happy path", you'd close the file, there'd be nothing to forget or write blog posts about because the exception would be propagated, without needing to write any lines of code.

Re: Don't defer Close() on writable files (2017)

#5

Surely this would all go away if Go had an exception handling mechanism like most mainstream languages do? You'd just concentrate on the "happy path", you'd close the file, there'd be nothing to forget or write blog posts about because the exception would be propagated, without needing to write any lines of code.

You're not wrong, but that ship sailed away a dozen years ago.

Re: Don't defer Close() on writable files (2017)

#6
post #2

Rust has the same problem. Files are closed in `Drop` when the value goes out of scope, but all errors are silently ignored. To solve this there's `sync_all`[0]. Generally, relying on defer in Go or Drop in Rust for anything that can fail seems like an anti-pattern to me. 0: https://doc.rust-lang.org/std/fs/struct.File.html#method.syn...

An ownership consuming close(self) would make sense, but has not been added, there must be some good reason for that?

Re: Don't defer Close() on writable files (2017)

#7
There are more wrinkles with this:

- if you are creating a file, to ensure full synchronisation you also need to fsync the parent directory, otherwise the file can be fsynced but the update to the directory lost

- if sync fails, you can not assume anything about the file, whether on-disk or in memory, critically one understanding which got dubbed "fsyncgate" and lead to many RDBMS having to be updated is that you can not portably retry fsync after failure: the earlier error may have invalidated the IO buffers but IO errors may not be sticky, so a later fsync will have nothing to write and report success

Re: Don't defer Close() on writable files (2017)

#8
post #3

Don't call close() twice if you're not using pidfds, this is racy. The fd could be reused in between the two calls. You'll risk closing random things a frustratingly small fraction of the time, creating very hard bugs for yourself.

Surely File.close will clear the fd on success...

Re: Don't defer Close() on writable files (2017)

#9

Surely this would all go away if Go had an exception handling mechanism like most mainstream languages do? You'd just concentrate on the "happy path", you'd close the file, there'd be nothing to forget or write blog posts about because the exception would be propagated, without needing to write any lines of code.

If you've never seen somebody type 'catch (Exception e) { logger.log("should never happen", e);}' then sure. In the real world people will often explicitly ignore the error, even when they are confronted with it.

Re: Don't defer Close() on writable files (2017)

#10

Surely this would all go away if Go had an exception handling mechanism like most mainstream languages do? You'd just concentrate on the "happy path", you'd close the file, there'd be nothing to forget or write blog posts about because the exception would be propagated, without needing to write any lines of code.

Explicit error handling is a choice and implicit error handling through exceptions is not necessarily a feature.

Both have advantages and disadvantages, I’d say the more “modern” approach actually the opposite to what you state here, and is in my opinion the way to Go (pun int intended), though it’s also how Haskell does it. You’ll find the same philosophy in Rust, Zig, Swift and others which all build on the previous decades of throwing exceptions and how terribly that scales in terms of maintainability. Even in the “old world” like with Java you have Kotlin which does both.

Post reply on HN