News for hackers. Stuff that matters.
Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by endgame on Monday June 03, 2024 @07:33AM (#40561639)
Haskell seems to be the only language where throwing exceptions at another thread isn't a box of footguns. Anyone know why that is?
Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by WJW on Monday June 03, 2024 @07:37AM (#40561664)
I don't know why you think that it isn't. You can do so, but AFAIK almost no libraries are written with the expectation that their computations might be interrupted at any time by another thread. The best you can hope for is that you "only" interrupt some pure computation and not throw your exception into the middle of some delicately sequenced IO side effects.
Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by whateveracct on Monday June 03, 2024 @11:14AM (#40563468)
> AFAIK almost no libraries are written with the expectation that their computations might be interrupted at any time by another thread.

Haskell libraries are written this way all the time. Exception-safe libraries with async-aware handling are pretty commonplace nowadays (partially thanks to safe-exceptions and unsafeio providing a nice API to deal with them.)

Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by kccqzy on Monday June 03, 2024 @04:00PM (#40566877)
Absolutely false. At least not in my experience. Only novice Haskell programmers write code that can't handle computations being interrupted by another thread. A published and maintained Haskell library is unlikely to have these issues.
Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by masklinn on Monday June 03, 2024 @08:52AM (#40562184)
Erlang / Elixir is almost above that:

- the shared-nothing structuring means it’s harder to corrupt external - that processes can die at any moment is part of the culture - the supervision tree means the most likely case for killing an other process is a parent killing a misbehaving child, which they can then handle as if the child had faulted

Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by jerf on Monday June 03, 2024 @09:42AM (#40562560)
Another key to Erlang's success here is the ports system. Basically "external" resources have a defined cleanup mechanism even if the process that created them dies. An open socket under the hood has its own thread it can use to clean up. With effort you can do this with your own things too through the linking mechanism, having some process watch another process using your resources and then have an execution thread that can clean up if the user spontaneously dies.

Some of it too is just Erlang being too, err, for lack of a better word "weak" to have some of the problematic resources. Since Erlang has no "locks", you simply can't take a lock and then kill the thread responsible for unlocking it. You can construct such locks at a higher level, but Erlang's design tends to encourage other designs. For instance, if I were designing a higher-level lock, I'd design it to link to the process taking the lock and release it if it dies. This can still theoretically get you into trouble but especially if this is documented as the semantics of the lock, you have to work a lot harder at it.

If you really worked at it you could manage to screw up your Erlang system with thread killing through your own lock implementation and such, but it still will be something you can recover from the REPL, you won't confuse BEAM itself. In an imperative language you'd be ranging from lucky to be able to reach in somehow and fix it to there simply being no way.

Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by nitros on Monday June 03, 2024 @10:16AM (#40562828)
bracket [1] is easy to use and ensures that the cleanup operation runs, the Control.Exception module is also well documented [2]

[1]: https://hackage.haskell.org/package/base-4.20.0.1/docs/Contr...

[2]: https://hackage.haskell.org/package/base-4.20.0.1/docs/Contr...

Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by whateveracct on Monday June 03, 2024 @11:15AM (#40563476)
Not to mention the widespread safe-exceptions and unliftio provide an API that makes it hard to shoot yourself in the foot.

Pretty funny that Haskell of all languages also has best-in-class exceptions & exception handling haha.

Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by valcron1000 on Monday June 03, 2024 @11:12PM (#40570332)
"The finest imperative programming language"
Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by whateveracct on Monday June 03, 2024 @11:13AM (#40563450)
It has good exception libraries that differentiate synchronous vs asynchronous exceptions.

It also has good learning material on the subject in the form of thorough blog posts.

It has robust, types abstractions for both throwing, catching, and masking exceptions.

Its exception runtime implementation is better built too.

Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by kccqzy on Monday June 03, 2024 @12:57PM (#40564611)
While I agree in the aggregate, I do want to mention that Haskell code written by novices is absolutely not correct in the face of asynchronous exceptions. Novice people write `hGetContents h <* hClose h` which obviously leaks file descriptors in the face of both synchronous and asynchronous exceptions.

There are also plenty of footguns when you attempt to catch exceptions. A piece of code might intend to catch all synchronous exceptions (raised in the current thread) but accidentally catches asynchronous exceptions. Again this is a novice issue. When that happens, cancelling a thread doesn't work because thread cancellation is implemented with async exceptions.

What makes things good is that firstly pure code can only raise exceptions but not catch them. (If you need error handling in pure code, you use the Either monad not exceptions.) This dramatically reduces chances of coding mistakes because exceptions aren't as overused as some other languages. Secondly the Haskell community more or less shuns on direct use of these low-level exception handling APIs and everyone uses the async library https://hackage.haskell.org/package/async-2.2.5/docs/Control... which solves not only this problem but also provides nice abstractions to build concurrent computations. Check out its Concurrently type: you now have an Applicative that represents concurrent computations which reuse all your intuition from all Applicative instances. You don't even need to manually create threads or kill threads. Overall insofar as Haskell being the only language where throwing exceptions at another thread isn't a box of footguns, it's only because the language allows a sufficient amount of abstraction power that frees the programmer from using the error-prone low-level APIs. The actual low-level APIs are still full of footguns.

Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by jwlato on Monday June 03, 2024 @01:11PM (#40564771)
Absolutely this. And when you look through the bug history of those low-level APIs there's a lot of evidence.

That said, the other big difference with Haskell is the low-level API actually provides functionality to solve the exact problem of an async exception being raised anywhere via `mask`. It's still hard to use correctly but at least it's possible.

Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by valcron1000 on Monday June 03, 2024 @11:20PM (#40570373)
Async is a bless. So many language that claim to be "great at concurrency" don't even provide 10% of the async API.
Re:Why Ruby's Timeout is dangerous (and Thread.raise is terr... by valcron1000 on Monday June 03, 2024 @11:18PM (#40570361)
I recommend reading "Parallel and Concurrent Programming in Haskell" by Simon Marlow in which he explains that back in 1997 he decided to "build concurrency right into the core of the system rather than making it an optional extra or an add-on library".