[ofw] patch: Fix a race in the cl_timer code that caused deadlocks in opensm

Fab Tillier ftillier at microsoft.com
Wed Jun 23 12:32:17 PDT 2010


Ok, so HTML format mails suck for responding to patches...

timer.patch:

>Index: a/core/complib/user/cl_timer.c
>===================================================================
>--- a/core/complib/user/cl_timer.c	(revision 5977)
>+++ b/core/complib/user/cl_timer.c	(revision 5978)
>@@ -33,25 +33,27 @@
> 
> #include "complib/cl_timer.h"
> 
>-
> static void CALLBACK
> __timer_callback( 
> 	IN cl_timer_t* const p_timer,
> 	IN BOOLEAN timer_signalled )
> {
> 	/* timer_signalled is always TRUE, and has no value. */
>+	
>+	DWORD thread_id = GetCurrentThreadId();
> 	CL_ASSERT( timer_signalled );
>+

Don't need this blank line.

> 	UNUSED_PARAM( timer_signalled );
> 
>+	CL_ASSERT(thread_id!=0);

Move this assert down, reference p_timer->thread_id, and you can eliminate the local variable.

>+
> 	p_timer->timeout_time = 0;
>-	p_timer->thread_id = GetCurrentThreadId();
>+	p_timer->thread_id = thread_id;
> 
> 	(p_timer->pfn_callback)( (void*)p_timer->context );
> 
>-	p_timer->thread_id = 0;
> }
> 
>-
> void
> cl_timer_construct(
> 	IN	cl_timer_t* const	p_timer )

Where's the call to cl_spinlock_construct?

 >Index: a/inc/complib/cl_timer.h
>===================================================================
>--- a/inc/complib/cl_timer.h	(revision 5977)
>+++ b/inc/complib/cl_timer.h	(revision 5978)
>@@ -45,8 +45,8 @@
> 
> 
> #include <complib/cl_types.h>
>+#include <complib/cl_spinlock.h>

This belongs in the cl_timer_osd.h, not here.


cl_timer.c.patch & cl_timer_osd.h should have been a single patch, as they are related.  No need for per-file patches.

>--- a/core/complib/user/cl_timer.c	Wed Jun 23 11:09:38 2010
>+++ b/core/complib/user/cl_timer.c	Wed Jun 23 11:13:04 2010
>@@ -59,6 +60,7 @@
> 	IN	cl_timer_t* const	p_timer )
> {
> 	p_timer->h_timer = NULL;
>+	p_timer->pfn_callback = NULL;
> 	p_timer->timeout_time = 0;
> 	p_timer->thread_id = 0;

Where's the call to cl_spinlock_construct?

> }
>@@ -89,6 +96,12 @@
> 	CL_ASSERT( p_timer );
> 	
> 	cl_timer_stop( p_timer );
>+	if ( p_timer->pfn_callback )

Don't use the callback as a flag.  If you properly construct the spinlocks, you don't need the check.

>+	{
>+		/* construct does not set these, _init does */

construct should construct, init should init.

>+		cl_spinlock_destroy( &p_timer->spinlock );
>+		cl_spinlock_destroy( &p_timer->cb_serialize );
>+	}
> }
> 
>

kernel-timer.patch:

>diff --git a/trunk/core/complib/kernel/cl_timer.c b/trunk/core/complib/kernel/cl_timer.c
>index 34cae8e..ecee4a6 100644
>--- a/trunk/core/complib/kernel/cl_timer.c
>+++ b/trunk/core/complib/kernel/cl_timer.c
>@@ -45,9 +45,13 @@ __timer_callback(
> 	UNUSED_PARAM( arg1 );
> 	UNUSED_PARAM( arg2 );
> 
>+	cl_spinlock_acquire( &p_timer->spinlock );
> 	p_timer->timeout_time = 0;

The only case where you could end up with multiple callbacks executing is if you call cl_timer_start from the callback.  It might be easier to flag that you're in a callback, store the new timeout time, and delay calling KeSetTimer until the callback unwinds.  You could add a flag here to indicate that you're in the timer callback, that you check in cl_timer_start to control whether you call KeSetTimer...

>+	cl_spinlock_release( &p_timer->spinlock );
> 
>+	KeAcquireGuardedMutex( &p_timer->cb_lock );

timer callback is in a DPC, at DISPATCH_LEVEL.  KeAcquireGuardedMutex must be <= APC_LEVEL.

> 	(p_timer->pfn_callback)( (void*)p_timer->context );

Check the timeout here, and if non-zero, call KeSetTimer, then clear the callback flag.  Note that similar logic could be used in the user-mode timer to prevent multiple timer callbacks from running concurrently, while also avoiding extra threads spinning on the callback spinlock.

>+	KeReleaseGuardedMutex( &p_timer->cb_lock );
> }
> 
> 
>@@ -75,6 +79,8 @@ cl_timer_init(
> 
> 	KeInitializeTimer( &p_timer->timer );
> 	KeInitializeDpc( &p_timer->dpc, __timer_callback, p_timer );
>+	cl_spinlock_init( &p_timer->spinlock );

Just use a KSPINLOCK here, no?  Then you can use the in-stack queued spinlocks if the lock might be contentious.

>+	KeInitializeGuardedMutex( &p_timer->cb_lock );
> 
> 	return( CL_SUCCESS );
> }
>@@ -108,6 +114,7 @@ cl_timer_start(
> 	IN	const uint32_t		time_ms )
> {
> 	LARGE_INTEGER	due_time;
>+	uint64_t		timeout_time;
> 
> 	CL_ASSERT( p_timer );
> 	CL_ASSERT( p_timer->pfn_callback );
>@@ -115,11 +122,13 @@ cl_timer_start(
> 
> 	/* Due time is in 100 ns increments.  Negative for relative time. */
> 	due_time.QuadPart = -(int64_t)(((uint64_t)time_ms) * 10000);
>+	timeout_time = cl_get_time_stamp() + (((uint64_t)time_ms) * 1000);
> 
>-	/* Store the timeout time in the timer object. */
>-	p_timer->timeout_time = cl_get_time_stamp() + (((uint64_t)time_ms) * 1000);
>-
>+	cl_spinlock_acquire( &p_timer->spinlock );
>+	p_timer->timeout_time = timeout_time;

Check the 'in callback' flag here to conditionally call KeSetTimer.

> 	KeSetTimer( &p_timer->timer, due_time, &p_timer->dpc );
>+	cl_spinlock_release( &p_timer->spinlock );
>+
> 	return( CL_SUCCESS );
> }
> 
>@@ -129,19 +138,25 @@ cl_timer_trim(
> 	IN	cl_timer_t* const	p_timer,
> 	IN	const uint32_t		time_ms )
> {
>+	LARGE_INTEGER	due_time;
> 	uint64_t		timeout_time;
> 
> 	CL_ASSERT( p_timer );
> 	CL_ASSERT( p_timer->pfn_callback );
> 
>-	/* Calculate the timeout time in the timer object. */
>+	/* Due time is in 100 ns increments.  Negative for relative time. */
>+	due_time.QuadPart = -(int64_t)(((uint64_t)time_ms) * 10000);

You can delay calculating this until the check that the new timeout time is before the current timeout time.

> 	timeout_time = cl_get_time_stamp() + (((uint64_t)time_ms) * 1000);
> 
> 	/* Only pull in the timeout time. */
>-	if( p_timer->timeout_time && p_timer->timeout_time < timeout_time )
>-		return( CL_SUCCESS );
>-
>-	return cl_timer_start( p_timer, time_ms );
>+	cl_spinlock_acquire( &p_timer->spinlock );
>+	if( !p_timer->timeout_time || p_timer->timeout_time > timeout_time )
>+	{
>+		p_timer->timeout_time = timeout_time;

Same here, check the 'in callback' flag and defer calling KeSetTimer if set.

>+		KeSetTimer( &p_timer->timer, due_time, &p_timer->dpc );
>+	}
>+	cl_spinlock_release( &p_timer->spinlock );
>+	return( CL_SUCCESS );
> }
> 
> 
>diff --git a/trunk/inc/kernel/complib/cl_timer_osd.h b/trunk/inc/kernel/complib/cl_timer_osd.h
>index 4cb99b0..6817a90 100644
>--- a/trunk/inc/kernel/complib/cl_timer_osd.h
>+++ b/trunk/inc/kernel/complib/cl_timer_osd.h
>@@ -38,7 +38,7 @@
> 
> 
> #include "complib/cl_types.h"
>-
>+#include "complib/cl_spinlock.h"
> 
> /* Timer object definition. */
> typedef struct _cl_timer
>@@ -48,6 +48,8 @@ typedef struct _cl_timer
> 	cl_pfn_timer_callback_t	pfn_callback;
> 	const void				*context;
> 	uint64_t				timeout_time;
>+	cl_spinlock_t			spinlock;
>+	KGUARDED_MUTEX			cb_lock;
> 
> } cl_timer_t;
> 
>
>

Smith, Stan wrote on Wed, 23 Jun 2010 at 11:46:39

> From: Smith, Stan [mailto:stan.smith at intel.com]
> Sent: Wednesday, June 23, 2010 11:47 AM
> To: Tzachi Dar; ofw at lists.openfabrics.org
> Cc: Yevgeny Kliteynik; Uri Habusha; Fab Tillier
> Subject: RE: [ofw] patch: Fix a race in the cl_timer code that caused
> deadlocks in opensm
> 
> Separated patch files so cut-n-paste into a single patch file did not
> introduce any problems.
> From svn trunk\
> Apply your timer patch as patch -p1 < timer.patch
> Apply:
>    patch -p1 < cl_timer_osd.h.patch    user-mode serialize
>    patch -p1 < cl_timer.c.patch           user-mode serialize
>    patch -p2 < kernel_timer.patch       sean's kernel patches
>    
>    if using opensm 3.3.6 (vnedor-umad) patch -p1 < umad.cpp.patch
> 



More information about the ofw mailing list