18
$\begingroup$

Would it be possible for a runtime environment to detect infinite loops and subsequently stop the associated process, or would implementing such logic be equivalent to solving the halting problem?

For the purpose of this question, I define an "infinite loop" to mean a series of instructions and associated starting stack/heap data that, when executed, return the process to exactly the same state (including the data) as it was in before initiating the infinite loop. (In other words, a program generating an indefinitely long decimal expansion of pi isn't "stuck" in an "infinite loop," because at every iteration, it has more digits of pi somewhere in its associated memory.)

(Ported from https://stackoverflow.com/q/16250472/1858225)

$\endgroup$
  • 1
    $\begingroup$ possible duplicate of Is it possible to solve the halting problem if you have a constrained or a predictable input? $\endgroup$ – Juho Apr 28 '13 at 23:39
  • $\begingroup$ I don't think so; there are no constraints on the input. $\endgroup$ – Kyle Strand Apr 29 '13 at 1:54
  • $\begingroup$ Is your question about a real runtime environment (like the JVM), or about a programmatically-generic manner of detecting such a loop ? $\endgroup$ – Benj Apr 29 '13 at 10:44
  • $\begingroup$ @Benj stackoverflow.com/q/16249785/1858225 The original question (which wasn't mine) was about real runtime environments (or rather, about OSes). That got closed, though, so I rewrote it, I shifted the focus to the theoretical side. $\endgroup$ – Kyle Strand Apr 29 '13 at 13:48
  • $\begingroup$ OK. The only way I see it is to sample some keypoints and make a hash of them (this could be the last lines of a log output, or some CPU state such as stack ptr) and store the hashes of a set of probes (a set at a given time) in a Markov Chain. Then, you will be able (by choosing the right "probes") to detect cyclic locks. I'm thinking also about hooking system libraries accesses and using their entries as probes. Enjoy ;) $\endgroup$ – Benj Apr 29 '13 at 14:10
11
$\begingroup$

It might be theoretically possible for a runtime environment to check for such loops using the following procedure:

After ever instruction executed, the runtime environment would make a complete image of the state of a running process (i.e. all memory associated with it, including registers, P.C., stack, heap, and globals), save that image somewhere, and then check to see whether it matches any of its previously saved images for that process. If there is a match, then the process is stuck in an infinite loop. Otherwise, the next instruction is executed and the process is repeated.

In fact, rather than performing this check after every single instruction, the runtime environment could simply pause the process periodically and make a save-state. If the process is stuck in an infinite loop involving n states, then after at most n checks, a duplicate state will be observed.

Note, of course, that this is not a solution to the halting problem; the distinction is discussed here.

But such a feature would be an enormous waste of resources; continually pausing a process to save all memory associated with it would slow it down tremendously and consume an enormous amount of memory very quickly. (Although old images could be deleted after a while, it would be risky to limit the total number of images that could be saved because a large infinite loop--i.e., one with many states--might not get caught if there are too few states kept in memory.) Moreover, this feature wouldn't actually provide that much benefit, since its ability to catch errors would be extremely limited and because it's relatively simple to find infinite loops with other debugging methods (such as simply stepping through the code and recognizing the logic error).

Therefore, I doubt that such a runtime environment exists or that it will ever exist, unless someone programs it just for kicks. (Which I am somewhat tempted to do now.)

$\endgroup$
  • 8
    $\begingroup$ It is possible (at least in the idealized world of Turing Machines and such) that a program enters an infinite loop without repeating a state. Think of something like the C loop for(i = 0; ; i++) ; $\endgroup$ – vonbrand Apr 29 '13 at 0:52
  • $\begingroup$ The problem with your idea is that you can't know what $n$ is, or whether there is such an $n < \infty$. The fact that there have been no repeated states after $n$ steps doesn't say anything about the $n+1$st step. You can easily reduce the halting problem to the problem of deciding whether state will ever repeat. $\endgroup$ – Huck Bennett Apr 29 '13 at 1:14
  • $\begingroup$ @vonbrand, that particular loop doesn't fit my definition of "loop" for the purpose of this particular question (which is why I made my definition explicit in the question itself). $\endgroup$ – Kyle Strand Apr 29 '13 at 1:57
  • $\begingroup$ @Huck, of course you can't know whether such an $n$ exists; that's the crux of the halting problem. This is merely a proof that these types of loops are detectable at runtime. $\endgroup$ – Kyle Strand Apr 29 '13 at 1:57
  • $\begingroup$ Maybe I didn't understand your question. I thought you wanted to know whether it was possible to decide whether any program repeats state. Were you just asking whether it was possible to decide whether some programs repeat state? $\endgroup$ – Huck Bennett Apr 29 '13 at 2:35
6
$\begingroup$

Let's assume that the program does not interact with the outside world, so that's it's really possible to encapsulate the entire state of the program. (This means it does not do any input, at least.) Furthermore, let's assume that the program is running in some deterministic environment so that each state has a unique successor, which means that the runtime is either not threaded, or that the threading can be deterministically reduced to a sequence.

Under these highly improbable but theoretically non-limiting assumptions, we can duplicate the program and run it in two separate runtimes; each will do exactly the same computation.

So let's do that. We'll run it once in the Tortoise runtime, and at the same time we'll run it in the Hare runtime. However, we'll arrange for the Hare runtime to operate exactly twice as fast; every time the Tortoise runtime makes one step, the Hare runtime makes two steps.

Now we can (theoretically) compare the states after every step of the Tortoise runtime. If the program reaches an endless loop of $n$ steps after some non-looping prefix of $p$ steps, then the Hare and Tortoise states will be identical at every Tortoise step $kn$ for any integer $k$ where $kn\ge p$.

The total cost of the test is one extra state and one state comparison per step, and it will terminate in no more than three times the number of steps it takes for the program to complete its first loop. (One time in the Tortoise and twice in the Hare, for a total of three times.)

As the terms I used imply, this is just Robert Floyd's famous Tortoise and Hare cycle-detection algorithm.

$\endgroup$
3
$\begingroup$

Just as I was going to suggest Floyd's cycle-detection algorithm, rici's post beat me to it. However, the whole thing can be made more practical by speeding up comparisons of full states.

The bottleneck of the proposed algorithm would be in comparing full state. These comparisons will usually not finish, but stop early --- at the first difference. One optimization is to remember where the past differences occurred, and check those parts of the state first. For example, maintain a list of locations, and go through this list before making a full comparison. When a location from this list exposes a difference, stop the comparison (with failure), and move the location to the front of the list.

A different (and potentially more scalable) approach is to use incremental hashing. Pick a function of the full state such that the hash values are easy to adjust in O(1) when some part of the state changes. For example, take a weighted sum of state words mod some large prime and concatenate with the unweighted sum mod some other large prime (can also throw in a modular weighted sum of squares of words, with different weight and modulus). This way, hash updates will take O(1) time on each execution step, and comparisons will take O(1) time until you get a hit. The probability of a false positive (i.e., the hashes match while the states differ) is very low, and even if this ever happens, it will amortize over a large number of true negatives (false negatives are impossible).

Of course, in practice, it seems more likely to get into situations like generating digits of the number pi --- things keep on changing, but never end. Another frequent possibility is that the infinite loop allocates memory, in which case it quickly exhausts all available memory.

In my course on algorithms and data structures, our autograder has to deal with student submissions that sometimes get into infinite loops. This is taken care of by a 30-second time-out and a certain memory limit. Both are much looser than runtime and memory budgets we impose as part of grading. I am not sure if implementing true infinite-loop detection would make a lot of sense in this context because such programs will run quite a bit slower (this is where hardware support for state hashing could help, but again you'd need additional uses to justify this). When students know that their program timed out, they can usually find the infinite loop.

$\endgroup$
2
$\begingroup$

The AProVE termination tool performs static analysis on rewrite systems (including a subclass of Haskell programs) which can prove non-termination, giving an actual example of non-terminating program. The technique is quite powerful and works using a variant of a technique called narrowing.

As far as I know though, there has not been much work on detecting actual non-termination for general languages.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.