‹ BackHN Continuity

Thread

C++26: Trivial infinite loops are no longer undefined behaviour

173 points · 290 comments · ibobev

  1. JoshTriplett · · focus · HN ↗
    > When both conditions are met, the loop body is replaced with a call to std::this_thread::yield().

    Insert screaming here.

    An infinite loop, with no library calls whatsoever, gets a system call inserted. That's a horrible surprise waiting to happen.

    The entire concept of the "forward progress guarantee" is broken. An infinite loop should compile to an infinite loop. Nothing more, nothing less.

    1. amluto · · focus · HN ↗
      I grilled an LLM for a bit to see if it could justify the old forward progress rule. The only thing I got that passed the smell test was that it’s useful for the optimizer to be able to optimize:

          messy_pure_computation();
          some_atomic.store(1, relaxed);
      
      by moving the store before the computation. (Stronger stores would require additional analysis.)

      I admit I’m unconvinced that this is particularly useful.

      (I got many other ideas that did not pass my personal smell test.)

      1. raphlinus · · focus · HN ↗
        You will find the answer you seek not from an LLM, but from the talk Forward Progress Guarantees in C++ by Olivier Giroux at CppNow 2023. It's a long talk, with lots of details about forward progress, but I've set the timestamp[1] to the infinite loop bit.

        [1]: <a href="https:&#x2F;&#x2F;youtu.be&#x2F;g9Rgu6YEuqY?si=_l9JwKhjvIdFEDEX&amp;t=3819" rel="nofollow">https:&#x2F;&#x2F;youtu.be&#x2F;g9Rgu6YEuqY?si=_l9JwKhjvIdFEDEX&amp;t=3819

        1. amluto · · focus · HN ↗
          I don’t really buy that justification, for three reasons:

          1. Most implementations do not do this automatic cooperative multitasking trick and most users [0] don’t want it done to their code.

          2. The fact that a “step” is guaranteed to happen in finite time is far too weak for most use cases. I’ve done plenty of kernel programming, and a lot of kernels are partially or fully cooperative scheduled. Even somewhat long loops need manually inserted preemption points.

          3. “Finite” can be a very long time indeed. There are literally competitions to see who can make the largest busy beaver machine.

          Put another way, undefined behavior is a sharp line - if code has UB, it has UB and it if doesn’t, it doesn’t. But code being slow is not a sharp line - something can take 1 ns or 1 ms or 1 second or 1 hour or 1 year or 100 years or 1M years, etc.

          A scheduler that fails to schedule a runnable thread in finite time is wrong, but so is a scheduler that fails to schedule it for 100 years or for a week. If it merely takes a minute, then whether it’s right or wrong depends on the situation.

          So if you’re talking about schedulers (which that part of talk mostly is), then I don’t think the ability to say “infinite loop without side effects are UB, so my scheduler is correct if I assume that all side-effect-free loops are finite” is actually useful.

          There are real world examples. At one point, Go only preempted its cooperative threads at certain points, but this was a problem and newer versions of Go can even preempt tight loops. Python, which threads the worst-of-both-worlds middle ground between asynchronous and cooperative preemption, does not allow an infinite loop (in ordinary Python code) to starve other threads.

          [0] Most users of C- or Rust-like languages anyway. Quite a few more managed languages (e.g. Go) are the other way around.

Open on Hacker News to reply ↗

Unofficial Hacker News client; not affiliated with Y Combinator.