[LWN Logo]

Date:	Tue, 30 May 2000 21:07:52 +1000
From:	Andrew Morton <andrewm@uow.edu.au>
To:	Alan Cox <alan@lxorguk.ukuu.org.uk>
Subject: [patch] timers: plan B

This is a multi-part message in MIME format.
--------------A4B9E3059BDDC0DD800C98A7
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Last week the proposal was to fix the obvious deadlocks, make del_timer
synchronous and to rely on the deadlock detection to find things.

This would find the most bugs in the shortest time but would
disrupt a lot of developers who may not want to review
their timer code immediately.

But there are no quick fixes and it is preferable to fix the important
stuff a bit at a time.  Keep the patches smaller.

So here's plan B:

  - Fix del_timer_sync() and give it a deadlock detector

  - Rename del_timer to del_timer_async

  - #define del_timer del_timer_async.

That is what the attached patch does. It is, effectively, a no-op.

  - Over the next couple of weeks I work with the IDE, SCSI, net,
    drivers/net and drivers/video maintainers to review their timer
    code and get it migrated across to use del_timer_sync and
    del_timer_async.

  - We can then #define del_timer del_timer_sync and rely on the
    deadlock detector to accelerate the auditing process.  If it causes
    problems then people can just revert that #define.

The long-term plan is to remove del_timer ALTOGETHER.  All code should
use either del_timer_async or del_timer_sync.  In the interim del_timer
will continue to work, but the presence of del_timer should be a flag
which says "unaudited, possibly buggy".

Migrating code to use del_timer_sync/async will break
back-compatibility, so the various k-compat headers
will need to gain a couple of new #defines:

  #define del_timer_async del_timer
  #define del_timer_sync del_timer

How does that all sound?



The attached patch is against test1-ac5.

  - del_timer becomes del_timer_async.

  - del_timer_sync is made unracy and gets a deadlock detector and
    a very temporary race-reporter.

  - For UP, we keep del_timer.  del_timer_sync and del_timer_async
    are #defined onto it.

  - Removes:

        timer_list.running
        timer_set_running()
        timer_synchronize()
        timer_is_running()

        The latter breaks some debug code in include/net/tcp.h,
        but it is already ifdefed out.  Easy to fix.

When reviewing the new del_timer_sync() please bear in mind that
there is a fair bit of diagnostic stuff there.  It's all on a very
rarely travelled path and most of this will go away, but I expect the
deadlock detector will be there for a long time.

--
-akpm-
(But what to do about 2.2?)
--------------A4B9E3059BDDC0DD800C98A7
Content-Type: text/plain; charset=us-ascii;
 name="timer-core-8.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="timer-core-8.patch"

--- linux-2.4.0test1-ac5/kernel/timer.c	Mon May 15 21:25:15 2000
+++ linux-akpm/kernel/timer.c	Tue May 30 20:47:28 2000
@@ -13,6 +13,8 @@
  *              serialize accesses to xtime/lost_ticks).
  *                              Copyright (C) 1998  Andrea Arcangeli
  *  1999-03-10  Improved NTP compatibility by Ulrich Windl
+ *  2000-05-27  Make del_timer synchronous, add del_timer_async - Andrew Morton.
+ *              http://kernelnotes.org/lnxlists/linux-kernel/lk_0005_04/msg00672.html
  */
 
 #include <linux/config.h>
@@ -72,6 +74,23 @@
 unsigned long prof_len;
 unsigned long prof_shift;
 
+#ifdef CONFIG_SMP
+/*
+ * A non-zero entry here indicates a currently-running timer on
+ * the CPU identified by running_timer_cpu.
+ */
+static struct timer_list *running_timer;
+static int running_timer_cpu;
+
+/* FIXME: delete this stuff */
+#ifdef CONFIG_X86
+extern void show_stack(unsigned long* esp);	/* Why doesn't this have a prototype? */
+#define x86_show_stack() show_stack(0)
+#else
+#define x86_show_stack() do { } while (0)
+#endif
+#endif		/* CONFIG_SMP */
+
 /*
  * Event timer code
  */
@@ -201,6 +220,7 @@
 	return ret;
 }
 
+#ifndef CONFIG_SMP
 int del_timer(struct timer_list * timer)
 {
 	int ret;
@@ -212,39 +232,83 @@
 	spin_unlock_irqrestore(&timerlist_lock, flags);
 	return ret;
 }
+#endif
 
 #ifdef CONFIG_SMP
-/*
- * SMP specific function to delete periodic timer.
- * Caller must disable by some means restarting the timer
- * for new. Upon exit the timer is not queued and handler is not running
- * on any CPU. It returns number of times, which timer was deleted
- * (for reference counting).
- */
+
+int del_timer_async(struct timer_list * timer)
+{
+	int ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&timerlist_lock, flags);
+	ret = detach_timer(timer);
+	timer->list.next = timer->list.prev = NULL;
+	spin_unlock_irqrestore(&timerlist_lock, flags);
+	return ret;
+}
 
 int del_timer_sync(struct timer_list * timer)
 {
-	int ret = 0;
+	int ret;
+	unsigned long flags;
 
-	for (;;) {
-		unsigned long flags;
-		int running;
+	spin_lock_irqsave(&timerlist_lock, flags);
+	ret = detach_timer(timer);
+	timer->list.next = timer->list.prev = 0;
 
-		spin_lock_irqsave(&timerlist_lock, flags);
-		ret += detach_timer(timer);
-		timer->list.next = timer->list.prev = 0;
-		running = timer->running;
+	if (running_timer == timer) {	/* This timer's handler is presently running */
+		int j;
+		struct timer_list * volatile *vtl;
+
+		if (running_timer_cpu == smp_processor_id())
+			goto out;	/* Handler is deleting timer.  Usually pointless. */
+
+		/* FIXME: temp */
+		printk( "del_timer_sync(%p) during handler execution! Called from %p\n"
+			"See http://www.uow.edu.au/~andrewm/linux/del_timer.html\n",
+			timer, __builtin_return_address(0));
+		x86_show_stack();
+re_del_timer:
+		/* Unlock the timer list and spin until handler completion. */
 		spin_unlock_irqrestore(&timerlist_lock, flags);
+		vtl = (struct timer_list * volatile *)&(running_timer);
+		for (j = 50 * 1000 * 1000; j != 0; --j) {
+			if (*vtl != timer)
+				break;
+		}
+		spin_lock_irqsave(&timerlist_lock, flags);
 
-		if (!running)
-			return ret;
-		timer_synchronize(timer);
+		if (j == 0) {
+			printk(	"del_timer_sync(%p): deadlock! Called from %p\n",
+				timer, __builtin_return_address(0));
+			printk("See http://www.uow.edu.au/~andrewm/linux/deadlock.html\n");
+			x86_show_stack();
+		} else {
+			if (running_timer == timer) {
+				/* There's a tiny chance that the same timer handler has started to run again */
+				printk("del_timer_sync(%p): the timer started running again!\n", timer);
+				x86_show_stack();
+				goto re_del_timer;
+			}
+			/*
+			 * OK, we have the lock and the timer handler is definitely not running.  But the
+			 * handler may have re-added the timer, and it may have deleted the timer.
+			 * But we assume that the handler has NOT kfree'ed the timer.  We can assume this
+			 * because calling del_timer() on a timer which is freed in the handler is a BUG.
+			 */
+			if (timer_pending(timer)) {
+				printk("del_timer_sync(): timer was re-added\n");
+				ret += detach_timer(timer);
+				timer->list.next = timer->list.prev = 0;
+			}
+		}
 	}
-
+out:
+	spin_unlock_irqrestore(&timerlist_lock, flags);
 	return ret;
 }
-#endif
-
+#endif		/* CONFIG_SMP */
 
 static inline void cascade_timers(struct timer_vec *tv)
 {
@@ -295,10 +359,20 @@
 
 			detach_timer(timer);
 			timer->list.next = timer->list.prev = NULL;
-			timer_set_running(timer);
+#ifdef CONFIG_SMP
+			if (running_timer != 0)
+				BUG();
+			running_timer = timer;
+			running_timer_cpu = smp_processor_id();
+#endif
 			spin_unlock_irq(&timerlist_lock);
 			fn(data);
 			spin_lock_irq(&timerlist_lock);
+#ifdef CONFIG_SMP
+			if (running_timer != timer)
+				BUG();
+			running_timer = 0;
+#endif
 			goto repeat;
 		}
 		++timer_jiffies; 
--- linux-2.4.0test1-ac5/include/linux/timer.h	Tue May 30 18:45:52 2000
+++ linux-akpm/include/linux/timer.h	Tue May 30 20:24:01 2000
@@ -54,11 +54,36 @@
 	unsigned long expires;
 	unsigned long data;
 	void (*function)(unsigned long);
-	volatile int running;
 };
 
 extern void add_timer(struct timer_list * timer);
+#define timer_exit(t) (void)(t)
+
+#ifdef CONFIG_SMP
+extern int del_timer_async(struct timer_list * timer);
+extern int del_timer_sync(struct timer_list * timer);
+#ifndef del_timer
+#define del_timer del_timer_async
+#endif
+#else
 extern int del_timer(struct timer_list * timer);
+#ifndef del_timer_sync
+#define del_timer_sync del_timer
+#endif
+#ifndef del_timer_async
+#define del_timer_async del_timer
+#endif
+#endif
+
+#define INIT_TIMER_FND(fn, d) {		\
+	list: LIST_HEAD_INIT_NULL,	\
+	expires: 0,			\
+	data: d,			\
+	function: fn,			\
+}
+
+#define INIT_TIMER_FN(fn) INIT_TIMER_FND(fn, 0)
+#define INIT_TIMER INIT_TIMER_FN(0)
 
 /*
  * mod_timer is a more efficient way to update the expire field of an
@@ -72,29 +97,12 @@
 static inline void init_timer(struct timer_list * timer)
 {
 	timer->list.next = timer->list.prev = NULL;
-#ifdef CONFIG_SMP
-	timer->running = 0;
-#endif
 }
 
 static inline int timer_pending (const struct timer_list * timer)
 {
 	return timer->list.next != NULL;
 }
-
-#ifdef CONFIG_SMP
-#define timer_exit(t) do { (t)->running = 0; mb(); } while (0)
-#define timer_set_running(t) do { (t)->running = 1; mb(); } while (0)
-#define timer_is_running(t) ((t)->running != 0)
-#define timer_synchronize(t) while (timer_is_running(t)) barrier()
-extern int del_timer_sync(struct timer_list * timer);
-#else
-#define timer_exit(t) (void)(t)
-#define timer_set_running(t) (void)(t)
-#define timer_is_running(t) (0)
-#define timer_synchronize(t) do { (void)(t); barrier(); } while(0)
-#define del_timer_sync(t) del_timer(t)
-#endif
 
 /*
  *	These inlines deal with timer wrapping correctly. You are 
--- linux-2.4.0test1-ac5/include/linux/list.h	Wed Apr 26 22:52:03 2000
+++ linux-akpm/include/linux/list.h	Sat May 27 22:26:43 2000
@@ -19,6 +19,8 @@
 
 #define LIST_HEAD_INIT(name) { &(name), &(name) }
 
+#define LIST_HEAD_INIT_NULL { 0, 0 }
+
 #define LIST_HEAD(name) \
 	struct list_head name = LIST_HEAD_INIT(name)
 
--- linux-2.4.0test1-ac5/kernel/ksyms.c	Tue May 30 18:45:52 2000
+++ linux-akpm/kernel/ksyms.c	Tue May 30 18:51:13 2000
@@ -341,9 +341,16 @@
 EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax);
 EXPORT_SYMBOL(proc_doulongvec_minmax);
 
-/* interrupt handling */
+/* kernel timers */
 EXPORT_SYMBOL(add_timer);
+#ifdef CONFIG_SMP
+EXPORT_SYMBOL(del_timer_async);
+EXPORT_SYMBOL(del_timer_sync);
+#else
 EXPORT_SYMBOL(del_timer);
+#endif
+
+/* interrupt handling */
 EXPORT_SYMBOL(request_irq);
 EXPORT_SYMBOL(free_irq);
 
@@ -356,9 +363,6 @@
 EXPORT_SYMBOL(autoirq_report);
 #endif
 
-#ifdef CONFIG_SMP
-EXPORT_SYMBOL(del_timer_sync);
-#endif
 EXPORT_SYMBOL(mod_timer);
 EXPORT_SYMBOL(tq_timer);
 EXPORT_SYMBOL(tq_immediate);

--------------A4B9E3059BDDC0DD800C98A7--


-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/