News for hackers. Stuff that matters.
Re:Conc: Better Structured Concurrency for Go by klodolph on Wednesday January 11, 2023 @05:52PM (#34346233)
I don’t think this is really a question of whether your code is imperative, since Haskell code will terminate just as surely as Go code if you try to access an array element out of range.

(Haskell’s lazy evaluation just makes it a bit harder to catch, since you need to force evaluation of the thunk within the catch statement, and it’s far too easy to end up passing your thunk to somebody who won’t catch the exception.)

As a matter of Go style, of course, you should almost always defer unlock() after you lock(), but some people sometimes get clever and think that they can just lock() and unlock() manually without using defer. This is not hypothetical, and it causes other problems besides leaving dangling locks after a panic(). Somebody sticks a “return” between lock() and unlock(), without noticing, for example.

So my impression of catching panic() is that it is about as safe as not catching panic(). What I mean by that is that if recover() is not safe in your code base, there is a good chance that there are other, related bugs in your code base, and being a bit more strict about using defer and not trying to be clever will go a long way.

Re:Conc: Better Structured Concurrency for Go by morelisp on Wednesday January 11, 2023 @05:59PM (#34346304)
Since the Go 1.14 optimizations I don't believe I've found a single case where `X(); defer Unx()` has been worse.

Unfortunately this was not the case before 1.14, so there's a lot of "middle-aged" code floating around setting a bad example.

Re:Conc: Better Structured Concurrency for Go by klodolph on Wednesday January 11, 2023 @06:01PM (#34346334)
Old habits die hard. For what it’s worth, the bugs I saw were long before the 1.14 release. Somewhere around 1.7 or something.
Re:Conc: Better Structured Concurrency for Go by morelisp on Wednesday January 11, 2023 @06:02PM (#34346348)
Agreed - it's also not nearly as clear for `ctx, cancel := ...; defer cancel()` and I see that reflex in a few places where it's not only often much less efficient, but logically wrong / less safe.
Re:Conc: Better Structured Concurrency for Go by klodolph on Wednesday January 11, 2023 @06:06PM (#34346402)
What issues do you see with defer cancel()?
Re:Conc: Better Structured Concurrency for Go by preseinger on Wednesday January 11, 2023 @06:28PM (#34346591)
I think the OP is saying defer cancel() is correct, and the stuff people would do to avoid it are the things that are bad.
Re:Conc: Better Structured Concurrency for Go by jerf on Wednesday January 11, 2023 @10:15PM (#34348713)
It is a fundamental problem with the imperative paradigm because of the equivalent of:

    lock.Take()
    thingThatMayCrash()
    lock.Release()
The imperative/structured paradigm is that those statements are evaluated in order. While this is not the only way of writing Go, it is legal Go, and the runtime must account for it. Paradigms for which that is not fundamentally true have different options available to them. One of those options is still to crash.

However, "access an array element out of range" isn't the question. The question is, what can the language do in the face of any exception? Haskell, perhaps ironically, doesn't avail itself of the opportunity to do something useful about it. You can implement a wide variety of mechanisms that are safe to have exceptions in even in the face of concurrency, but at the base language, it is essentially as exception unsafe as Go, exactly as you observe. (And you can create "exception-safe wrappers" in Go as well, as easily as having an intermediate function that does something with panics, then invokes some code. Nominally this is unsafe because the code being invoked really ought to have a guarantee that the user has written it to be safe in this usage; in practice it works fairly well at significant scales due to a combination of other things beyond the scope of this already-large reply.) This causes significant practical stress within the community and probably would be in the Top 5 wishlist for a lot of people as to something a sequel language would fix. (It is very annoying that the supposedly "pure" code "head []", supposedly of type "[a] -> a", throws an IO exception.) Just as in Haskell you can write safe code, you can write safe code in Go here as well.

    lock.Take()
    defer lock.Release()
    thingThatMayCrash()
is every bit as safe as anything you can write in Haskell. (It may not compose as well, because "locks" are arguably the least composable primitive ever devised by computer science, but that's an entirely different discussion.)

Thus it is not a coincidence that when I named a language that has a different paradigm that allows it to systematically recover from this sort of fault, I named Erlang, not Haskell. Erlang has no locks. (At the Erlang level. NIFs may do their own thing but they are basically a form of "unsafe" and for language analysis should be treated as such.) Since it has no locks it can't blow up the way the Go code can and leave dangling locks. Processes get access to resources that need to be cleaned up through the IMHO somewhat confusingly named "ports" system, which can be conceived of as wrapping up sockets and files and other such resources behind other Erlang processes, and then there's a linking system that says "if this process crashes, send this message to that other process or crash it also". So an Erlang process can safely crash, release all associated resources, and the runtime may continue on with the assurance that there's no dangling locks or other concurrency things half done.

In fact in my opinion Erlang isn't even a particularly "functional" language. This is idiosyncratic and I don't deny it. A better way of understanding Erlang is that it was built around this functionality existing, and the ports system and immutable terms are the tools that were used to accomplish this goal, with the fact that the result looked sort of "functional" being an accident. So I wouldn't necessarily highlight "functional" languages in general as being natively good at this sort of handling. I think it's pretty clear it could be added to a Haskell++ without much effort but it is not something that merely "being functional" automatically gets you out of the box.

(Historically, Erlang actually descends from Prolog. While all the logic functionality is stripped out, the heritage is very visible. You can't really call it a "logic language" since it can do no logic, but in my opinion it isn't in the functional stream of languages either. Your mileage will vary; like I said I don't deny this is an idiosyncratic take, but for what it's worth, it's one from someone who used Erlang professionally for many years and knows Haskell fairly well too, so it's not an entirely uninformed one.)

Re:Conc: Better Structured Concurrency for Go by tsegratis on Thursday January 12, 2023 @07:22AM (#34352455)
Nice points

I suppose the core issue is transactions. Erlang could still fail with

  them ! {sub, n}
  crash()
  me ! {add, n}
But typically Erlang's isolated processes means many transactions can fail or rollback in isolation

I suppose instead transactions could be a core language semantic. But hard to do, and erlang actors gives a lot, for very little overhead

Re:Conc: Better Structured Concurrency for Go by jerf on Thursday January 12, 2023 @10:42AM (#34355150)
Erlang qua Erlang doesn't have any transactions, either, so Erlang qua Erlang can't fail that way.

In general, whatever it was the crashing process was doing can still have failed. This obviously doesn't magically fix database transactions, or make it so files are never half written because it crashed in the meantime, or any other "external" action that you can still have failed to protect properly from crashes. It only means that the runtime can't get stuck due to locks failing to be released.

But hey, that's something!