tty: fix endless work loop when the buffer fills up
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 4 Apr 2011 21:26:54 +0000 (14:26 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 4 Apr 2011 21:26:54 +0000 (14:26 -0700)
Commit f23eb2b2b285 ('tty: stop using "delayed_work" in the tty layer')
ended up causing hung machines on UP with no preemption, because the
work routine to flip the buffer data to the ldisc would endlessly re-arm
itself if the destination buffer had filled up.

With the delayed work, that only caused a timer-driving polling of the
tty state every timer tick, but without the delay we just ended up with
basically a busy loop instead.

Stop the insane polling, and instead make the code that opens up the
receive room re-schedule the buffer flip work.  That's what we should
have been doing anyway.

This same "poll for tty room" issue is almost certainly also the cause
of excessive kworker activity when idle reported by Dave Jones, who also
reported "flush_to_ldisc executing 2500 times a second" back in Nov 2010:

  http://lkml.org/lkml/2010/11/30/592

which is that silly flushing done every timer tick.  Wasting both power
and CPU for no good reason.

Reported-and-tested-by: Alexander Beregalov <a.beregalov@gmail.com>
Reported-and-tested-by: Sitsofe Wheeler <sitsofe@yahoo.com>
Cc: Greg KH <gregkh@suse.de>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: Dave Jones <davej@redhat.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
drivers/tty/n_tty.c
drivers/tty/tty_buffer.c

index 428f4fe..0ad3288 100644 (file)
@@ -95,6 +95,7 @@ static void n_tty_set_room(struct tty_struct *tty)
 {
        /* tty->read_cnt is not read locked ? */
        int     left = N_TTY_BUF_SIZE - tty->read_cnt - 1;
+       int old_left;
 
        /*
         * If we are doing input canonicalization, and there are no
@@ -104,7 +105,12 @@ static void n_tty_set_room(struct tty_struct *tty)
         */
        if (left <= 0)
                left = tty->icanon && !tty->canon_data;
+       old_left = tty->receive_room;
        tty->receive_room = left;
+
+       /* Did this open up the receive buffer? We may need to flip */
+       if (left && !old_left)
+               schedule_work(&tty->buf.work);
 }
 
 static void put_tty_queue_nolock(unsigned char c, struct tty_struct *tty)
index b945121..f1a7918 100644 (file)
@@ -442,10 +442,8 @@ static void flush_to_ldisc(struct work_struct *work)
                           line discipline as we want to empty the queue */
                        if (test_bit(TTY_FLUSHPENDING, &tty->flags))
                                break;
-                       if (!tty->receive_room || seen_tail) {
-                               schedule_work(&tty->buf.work);
+                       if (!tty->receive_room || seen_tail)
                                break;
-                       }
                        if (count > tty->receive_room)
                                count = tty->receive_room;
                        char_buf = head->char_buf_ptr + head->read;