Live data from Hacker News

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

joeshaw.org

281–290 of 311 posts

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

#281
post #26

Earlier quoted context omitted.

This problem isn't solved with exceptions either. The problem is that finalizers (C++ destructors, Java's `finally` blocks, Go's `defer` etc.) shouldn't fail but `close()` can fail. Therefore, for 100% correctness, `close()` calls should be handled explicitly and not left to finalizers. Finalizers shouldn't fail because they might be executed while another exception is already in flight. Three languages have three di…

Python handles this case by raising the new error but including a reference to the original error. By default, the formatted error shows both: >>> mylist = [] >>> try: ... first = mylist[0] ... finally: ... inverse_length = 1.0 / len(mylist) # imagine this was something more complex ... Traceback (most recent call last): File " ", line 2, in IndexError: list index out of range During handling of the above exception,…

No. Your example is not similar to what we are discussing here

    class A:
        def __del__(self):
            1/0
There is no way to catch that exception. It will just print a warning with the exception but it can't be handled.

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

#282
post #155
post #22

Earlier quoted context omitted.

> Explicit error handling is a choice Yeah, it is - a bad choice IMO. I know the "if err != nil" pattern is spoken up as some sort of cultural idiosyncrasy of Go, similar to the whitespace formatting in python. But so far, I haven't seen any actual data (or even arguments) why it is superior to exceptions, or which inherent problems of exceptions it solves. (The classical example of "it makes control flow more obviou…

> But so far, I haven't seen any actual data (or even arguments) OK, here's an argument. - In order to write resilient software, programs must handle not only the "happy path" when things succeed, but the path where things might fail. - Thus it is important for developers to 1) be aware of which operations may fail fail, and b) think about what the program should do in that case. - Exceptions make it easier for the p…

Because nobody has ever ignored errors in go?

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

#283
post #237

Earlier quoted context omitted.

> Every approach to error handling has advantages and shortcomings If under ‘every approach’ you explicitly exclude go’s terrible errno syntax sugar, and include exceptions and sum types, then yeah. There is zero advantage to go’s error handling compared to the proper sum typed solutions.

You don't like how Go implements error handling. We get it. Lots of people complain about that. Use a "proper" language more to your liking. Go's designers have explained their decisions many times, I won't repeat their justifications here. Obviously they had to make choices in line with their overall vision for Go. They feel strongly that programmers should handle or at least acknowledge the possibility of every err…

How are sum types not better in literally every possible way? Like, it’s as objective as it gets. Sum types makes it mandatory to unwrap the result - ergo some form of error handling. It is not the case with go.

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

#284

Earlier quoted context omitted.

You can kind of achieve this at runtime like so struct Foo { bool dirty, } impl Foo { fn clean_up(&mut self) { // ... self.dirty = false; } } impl Drop for Foo { fn drop(&mut self) { if self.dirty { panic!("Foo was not cleaned up before its lifetime ended"); } } } fn bar(foo: &mut foo) { foo.clean_up(); }

A method which takes foo and turns it into another type is slightly better. You can then unconditionally panic in the drop impl.

Neat, I didn't realize that would skip drop()!

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

#285

Earlier quoted context omitted.

> Stringly typed errors are also idiomatic in go, if you follow the stdlib. Realistically, you can't follow the standard library, except perhaps the newest additions. Idioms emerge and evolve with use. Much of the standard library was written before Go saw much use, being largely in place before the world got to see Go for the first time. Also, thanks to the Go1 guarantee, cannot be changed now. If the aforementioned…

netip is one of the newer additions to the stdlib (added in ~2022), and follows the venerable stringly typed error idiom. I always interpreted the preference for stringly typed errors as a way to keep the Go language simpler. Good error handling is complicated and hard to read, and one of Go's values is that programs should be easy to read. As such, if you want good error handling, you should use a different language…

> and follows the venerable stringly typed error idiom.

Not exactly. It assumes that all error conditions within the functions provided by netip are of the same nature as it pertains to a single unit of work. In other words, there is only one type (not referring to the language's type system). Error type reuse where different failure points produce the same type of error does not violate current idioms. I cannot immediately think of any reason for why their assumption is wrong, so unless you have other ideas?

That is not the same situation as the deferred close wrapper, though. It is assuming that closing multiple file handles is the same operation, but clearly that's not true. If you were, say, writing a copy function the error handling of the read handle failure is unlikely to be the same as the handling of the write handle failure. The former doesn't tell you much, the latter is quite actionable. The failure points are distinct, and thus of different types (again, not referring to the type system).

The author's answer was basically that he is the only caller so if that problem arises in his code he'll simply modify the function to return idiomatic types. Which is fair for the lone wolf developer. When working alone anything goes! But it is not good API design generally speaking. It certainly wouldn't fly in something like the standard library or anywhere you have other developers.

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

#286

Earlier quoted context omitted.

netip is one of the newer additions to the stdlib (added in ~2022), and follows the venerable stringly typed error idiom. I always interpreted the preference for stringly typed errors as a way to keep the Go language simpler. Good error handling is complicated and hard to read, and one of Go's values is that programs should be easy to read. As such, if you want good error handling, you should use a different language…

> and follows the venerable stringly typed error idiom. Not exactly. It assumes that all error conditions within the functions provided by netip are of the same nature as it pertains to a single unit of work. In other words, there is only one type (not referring to the language's type system). Error type reuse where different failure points produce the same type of error does not violate current idioms. I cannot imme…

> Error type reuse where different failure points produce the same type of error does not violate current idioms. I cannot immediately think of any reason for why their assumption is wrong, so unless you have other ideas?

I have a program that takes user input and parses it, and then displays an error. My program is for a language other than english so having it display a pop up with the message "invalid ip:port, square brackets can only be used with ipv6 addresses" in english is bad. Therefore I want to switch on the error message to display translated errors, but of course Go does not think that parsing errors are something that is important.

If parsing user input isn't a place to expose clear non-stringly-typed-errors, I don't know what is.

Note, it also gives bad errors in that some of them include details and the user input, and some don't, so displaying them to users will stutter or require parsing.

For example:

     _, err = netip.ParseAddrPort("foo:bar")
 // invalid port "bar" parsing "foo:bar"
 _, err = netip.ParseAddrPort("1.2.3.4:")
 // no port
So in one case it has included the original user string, to make it clear what failed, and in another case it doesn't, so I'll have to always add in the context of the input (i.e. `fmt.Errorf("error parsing %q: %w", input, err)`) anyway in order to know what failed, but it'll stutter in every case where they do include the input.

I know the answer to all my issues is the usual go thing of "a little copying is better than depending on the go stdlib" of course. At least for netip, forking it is fine, having to maintain a fork of the go net/http stack just to get halfway decent errors is a real pain.

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

#287

Earlier quoted context omitted.

> and follows the venerable stringly typed error idiom. Not exactly. It assumes that all error conditions within the functions provided by netip are of the same nature as it pertains to a single unit of work. In other words, there is only one type (not referring to the language's type system). Error type reuse where different failure points produce the same type of error does not violate current idioms. I cannot imme…

> Error type reuse where different failure points produce the same type of error does not violate current idioms. I cannot immediately think of any reason for why their assumption is wrong, so unless you have other ideas? I have a program that takes user input and parses it, and then displays an error. My program is for a language other than english so having it display a pop up with the message "invalid ip:port, squ…

I'm not sure the desire to perform a transformation on a value implies that there are multiple types. You speak to a real problem, of course, but perhaps at the wrong layer of abstraction. It seems the deeper seeded issue is that Go strings assume one language, which is not true. I wonder what native internationalization support might look like?

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

#288

Earlier quoted context omitted.

The bug of which we speak is in that your app is crashing. Exhausting open file handles is expected behaviour! Expected behaviour should not lead to a crash. Crashing is only for exceptional behaviour. The filesystem hanging is unlikely to be a bug. The filesystems you'd realistically use in conjunction with Kubernetes are pretty heavily tested. More likely it is supposed to hang under whatever conditions has lead th…

You're really hung up on Kubernetes but it was an incidental comment in a hypothetical story. "You wake up and find out that Heroku's staff is anxiously awaiting your departure from your apartment to tell you that your app is down."

Kubernetes is really here nor there. It's the crashing of the app that is our focus. An app should not be crashing on expected behaviour.

That's clearly a bug, and the bug you need to fix first so that you can have your failsafes start working again. You asked where to start and that's the answer, unquestionably.

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

#289

I dislike the multiple close pattern - I was bitten by this behavior years ago, when the second close() ended up closing another file which had been opened between the first and second close ( I think they were actually sockets ). It was a bona fide bug on my side , but it made for unpleasant memories, and a general distrust of such idioms on my side unless there's a language wide guarantee somewhere in the picture.

In the stdlib Go code for file.Close, it doesn't actually do the syscall after the first call to Close, so there's your language guarantee. That is a scary sounding error though.

Older versions of go (1.0 for example ) were much less safe. I had a look at the code, and it closes the file directly, and marks it as unusable. However, if you do concurrent operations, you can race and close twice the underlying fd - which I think was my bug ( I shouldn’t have been closing things twice anyway !)

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

#290
post #172

Boggles my mind that after more than 60 years of computer science, we still design tools (programming languages) where the simplest tasks are full of gotchas and footguns. This is a great example.

Gotta keep the profession interesting somehow.
Post reply on HN