The attached patch introduces an unnested Win32 Event-based thread_mutex
that is compatible with the assumption that a single thread may obtain a lock
for another thread, then wait for the second thread to release it.

It further degrades to Mutex objects for nested Win9x locks, since we couldn't
'try' a thread_mutex based on CriticalSection objects before Windows NT.

I also attached a simple patch to illustrate the fix to mod_isapi with the
change to the API.  Hoping Sebastian could take a look as well.

If I don't hear objections, I'll commit later this afternoon. Comments welcome.

Bill

At 10:19 AM 5/29/2002, William A. Rowe, Jr. wrote:
Attached is a patch to introduce a new locking semantic to our thread_mutex.
Right now we presume the 'default' is unnested. Well, that saves 30% of the
processing time on Unix, but it would cripple Win32 (which gets a critical section
in 10 instructions or so IF there is no contention... and it's always nested...)


Sebastian tossed me a really interesting and invalid assumption I made
when apr'izing mod_isapi...

At 09:38 AM 5/25/2002, Sebastian Hantsch wrote to me:
[...] the current Async emulation won't work as designed:

This is a snippet from isapi_handler:
comp = cid->completed;
if (cid->completed && (rv == APR_SUCCESS)) {
       rv = apr_thread_mutex_lock(comp);
}
/* The completion port is now locked.  When we regain the
* lock, we may destroy the request.
*/
if (cid->completed && (rv == APR_SUCCESS)) {
       rv = apr_thread_mutex_lock(comp);
}
break;

apr_thread_mutex functions internally use Critical Sections on win32 platform.
In the Win32 SDK at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/synchro_3xym.asp
is the following statement:
> After a thread has ownership of a critical section, it can make additional calls to
> EnterCriticalSection or TryEnterCriticalSection without blocking its execution. This
> prevents a thread from deadlocking itself while waiting for a critical section that it already owns.


So, the method currently used in mod_isapi.c does not block as supposed until a HSE_REQ_DONE_WITH_SESSION message arrives.
I would suggest to use SetEvent/CreateEvent/WaitForSingleObject as in 2.0.36, that synchronization methods work for me.
Index: include/apr_thread_mutex.h
===================================================================
RCS file: /home/cvs/apr/include/apr_thread_mutex.h,v
retrieving revision 1.10
diff -u -r1.10 apr_thread_mutex.h
--- include/apr_thread_mutex.h  9 Apr 2002 06:56:55 -0000       1.10
+++ include/apr_thread_mutex.h  29 May 2002 16:41:19 -0000
@@ -79,6 +79,7 @@
 
 #define APR_THREAD_MUTEX_DEFAULT  0x0
 #define APR_THREAD_MUTEX_NESTED   0x1
+#define APR_THREAD_MUTEX_UNNESTED 0x2
 
 /* Delayed the include to avoid a circular reference */
 #include "apr_pools.h"
@@ -89,10 +90,14 @@
  *        stored.
  * @param flags Or'ed value of:
  * <PRE>
- *           APR_THREAD_MUTEX_DEFAULT   normal lock behavior (non-recursive).
+ *           APR_THREAD_MUTEX_DEFAULT   platform-optimal lock behavior.
  *           APR_THREAD_MUTEX_NESTED    enable nested (recursive) locks.
+ *           APR_THREAD_MUTEX_UNNESTED  disable nested locks (non-recursive).
  * </PRE>
  * @param pool the pool from which to allocate the mutex.
+ * @tip Be cautious in using APR_THREAD_MUTEX_DEFAULT.  While this is the
+ * most optimial mutex based on a given platform's performance charateristics,
+ * it will behave as either a nested or an unnested lock.
  */
 APR_DECLARE(apr_status_t) apr_thread_mutex_create(apr_thread_mutex_t **mutex,
                                                   unsigned int flags,
Index: include/arch/win32/thread_mutex.h
===================================================================
RCS file: /home/cvs/apr/include/arch/win32/thread_mutex.h,v
retrieving revision 1.4
diff -u -r1.4 thread_mutex.h
--- include/arch/win32/thread_mutex.h   9 Apr 2002 06:56:56 -0000       1.4
+++ include/arch/win32/thread_mutex.h   29 May 2002 16:41:19 -0000
@@ -57,9 +57,21 @@
 
 #include "apr_pools.h"
 
+typedef enum thread_mutex_type {
+    thread_mutex_critical_section,
+    thread_mutex_unnested_event,
+    thread_mutex_nested_mutex
+} thread_mutex_type;
+
+/* handle applies only to unnested_event on all platforms 
+ * and nested_mutex on Win9x only.  Otherwise critical_section 
+ * is used for NT nexted mutexes providing optimal performance.
+ */
 struct apr_thread_mutex_t {
-    apr_pool_t *pool;
-    CRITICAL_SECTION section;
+    apr_pool_t       *pool;
+    thread_mutex_type type;
+    HANDLE            handle;
+    CRITICAL_SECTION  section;
 };
 
 #endif  /* THREAD_MUTEX_H */
Index: locks/beos/thread_mutex.c
===================================================================
RCS file: /home/cvs/apr/locks/beos/thread_mutex.c,v
retrieving revision 1.7
diff -u -r1.7 thread_mutex.c
--- locks/beos/thread_mutex.c   13 Mar 2002 20:39:20 -0000      1.7
+++ locks/beos/thread_mutex.c   29 May 2002 16:41:19 -0000
@@ -96,6 +96,10 @@
     new_m->LockCount = 0;
     new_m->Lock = stat;  
     new_m->pool  = pool;
+
+    /* Optimal default is APR_THREAD_MUTEX_UNNESTED, 
+     * no additional checks required for either flag.
+     */
     new_m->nested = flags & APR_THREAD_MUTEX_NESTED;
 
     apr_pool_cleanup_register(new_m->pool, (void *)new_m, 
_thread_mutex_cleanup,
Index: locks/netware/thread_mutex.c
===================================================================
RCS file: /home/cvs/apr/locks/netware/thread_mutex.c,v
retrieving revision 1.7
diff -u -r1.7 thread_mutex.c
--- locks/netware/thread_mutex.c        13 Mar 2002 20:39:20 -0000      1.7
+++ locks/netware/thread_mutex.c        29 May 2002 16:41:19 -0000
@@ -73,6 +73,11 @@
 {
     apr_thread_mutex_t *new_mutex = NULL;
 
+    /* XXX: Implement _UNNESTED flavor and favor _DEFAULT for performance
+     */
+    if (flags & APR_THREAD_MUTEX_UNNESTED) {
+        return APR_ENOTIMPL;
+    }
     new_mutex = (apr_thread_mutex_t *)apr_pcalloc(pool, 
sizeof(apr_thread_mutex_t));
        
        if(new_mutex ==NULL) {
@@ -80,7 +85,6 @@
     }     
     new_mutex->pool = pool;
 
-    /* FIXME: only use recursive locks if (flags & APR_THREAD_MUTEX_NESTED) */
     new_mutex->mutex = NXMutexAlloc(NX_MUTEX_RECURSIVE, NULL, NULL);
     
     if(new_mutex->mutex == NULL)
Index: locks/os2/thread_mutex.c
===================================================================
RCS file: /home/cvs/apr/locks/os2/thread_mutex.c,v
retrieving revision 1.6
diff -u -r1.6 thread_mutex.c
--- locks/os2/thread_mutex.c    13 Mar 2002 20:39:21 -0000      1.6
+++ locks/os2/thread_mutex.c    29 May 2002 16:41:19 -0000
@@ -69,6 +69,9 @@
 
 
 
+/* XXX: Need to respect APR_THREAD_MUTEX_[UN]NESTED flags argument
+ *      or return APR_ENOTIMPL!!!
+ */
 APR_DECLARE(apr_status_t) apr_thread_mutex_create(apr_thread_mutex_t **mutex,
                                                   unsigned int flags,
                                                   apr_pool_t *pool)
Index: locks/unix/thread_mutex.c
===================================================================
RCS file: /home/cvs/apr/locks/unix/thread_mutex.c,v
retrieving revision 1.10
diff -u -r1.10 thread_mutex.c
--- locks/unix/thread_mutex.c   28 Apr 2002 03:21:24 -0000      1.10
+++ locks/unix/thread_mutex.c   29 May 2002 16:41:19 -0000
@@ -89,6 +89,10 @@
     }
 
     new_mutex->pool = pool;
+
+    /* Optimal default is APR_THREAD_MUTEX_UNNESTED, 
+     * no additional checks required for either flag.
+     */
     new_mutex->nested = flags & APR_THREAD_MUTEX_NESTED;
 
     if ((rv = pthread_mutexattr_init(&mattr))) {
Index: locks/win32/thread_mutex.c
===================================================================
RCS file: /home/cvs/apr/locks/win32/thread_mutex.c,v
retrieving revision 1.9
diff -u -r1.9 thread_mutex.c
--- locks/win32/thread_mutex.c  13 Mar 2002 20:39:22 -0000      1.9
+++ locks/win32/thread_mutex.c  29 May 2002 16:41:19 -0000
@@ -65,7 +65,12 @@
 {
     apr_thread_mutex_t *lock = data;
 
-    DeleteCriticalSection(&lock->section);
+    if (lock->type == thread_mutex_critical_section) {
+        DeleteCriticalSection(&lock->section);
+    }
+    else {
+        CloseHandle(lock->handle);
+    }
     return APR_SUCCESS;
 }
 
@@ -73,12 +78,41 @@
                                                   unsigned int flags,
                                                   apr_pool_t *pool)
 {
+    if (flags & APR_THREAD_MUTEX_UNNESTED) {
+        return APR_ENOTIMPL;
+    }
+
     (*mutex) = (apr_thread_mutex_t *)apr_palloc(pool, sizeof(**mutex));
 
     (*mutex)->pool = pool;
-    /* FIXME: Implement nested (aka recursive) locks or use a native
-     * win32 implementation if available. */
-    InitializeCriticalSection(&(*mutex)->section);
+
+    if (flags & APR_THREAD_MUTEX_UNNESTED) {
+        /* Use an auto-reset signaled event, ready to accept one
+         * waiting thread.
+         */
+        (*mutex)->type = thread_mutex_unnested_event;
+        (*mutex)->handle = CreateEvent(NULL, FALSE, TRUE, NULL);
+    }
+    else {
+#if APR_HAS_UNICODE_FS
+        /* Critical Sections are terrific, performance-wise, on NT.
+         * On Win9x, we cannot 'try' on a critical section, so we 
+         * use a [slower] mutex object, instead.
+         */
+        IF_WIN_OS_IS_UNICODE {
+            (*mutex)->type = thread_mutex_critical_section;
+            InitializeCriticalSection(&(*mutex)->section);
+        }
+#endif
+#if APR_HAS_ANSI_FS
+        ELSE_WIN_OS_IS_ANSI {
+            (*mutex)->type = thread_mutex_nested_mutex;
+            (*mutex)->handle = CreateMutex(NULL, FALSE, NULL);
+
+        }
+#endif
+    }
+
     apr_pool_cleanup_register((*mutex)->pool, (*mutex), thread_mutex_cleanup,
                               apr_pool_cleanup_null);
     return APR_SUCCESS;
@@ -86,24 +120,41 @@
 
 APR_DECLARE(apr_status_t) apr_thread_mutex_lock(apr_thread_mutex_t *mutex)
 {
-    EnterCriticalSection(&mutex->section);
+    if (mutex->type == thread_mutex_critical_section) {
+        EnterCriticalSection(&mutex->section);
+    }
+    else {
+        WaitForSingleObject(mutex->handle, INFINITE);
+    }        
     return APR_SUCCESS;
 }
 
 APR_DECLARE(apr_status_t) apr_thread_mutex_trylock(apr_thread_mutex_t *mutex)
 {
-    if (apr_os_level < APR_WIN_NT) {
-        return APR_ENOTIMPL;
-    }
-    if (TryEnterCriticalSection(&mutex->section)) {
-        return APR_SUCCESS;
+    if (mutex->type == thread_mutex_critical_section) {
+        if (TryEnterCriticalSection(&mutex->section)) {
+            return APR_SUCCESS;
+        }
     }
+    else {
+        if (WaitForSingleObject(mutex->handle, 0) != WAIT_TIMEOUT) {
+            return APR_SUCCESS;
+        }
+    }        
     return APR_EBUSY;
 }
 
 APR_DECLARE(apr_status_t) apr_thread_mutex_unlock(apr_thread_mutex_t *mutex)
 {
-    LeaveCriticalSection(&mutex->section);
+    if (mutex->type == thread_mutex_critical_section) {
+        LeaveCriticalSection(&mutex->section);
+    }
+    else if (mutex->type == thread_mutex_unnested_event) {
+        SetEvent(mutex->handle);
+    }
+    else if (mutex->type == thread_mutex_nested_mutex) {
+        ReleaseMutex(mutex->handle);
+    }
     return APR_SUCCESS;
 }
 
Index: modules/arch/win32/mod_isapi.c
===================================================================
RCS file: /home/cvs/httpd-2.0/modules/arch/win32/mod_isapi.c,v
retrieving revision 1.77
diff -u -r1.77 mod_isapi.c
--- modules/arch/win32/mod_isapi.c      25 May 2002 15:31:27 -0000      1.77
+++ modules/arch/win32/mod_isapi.c      29 May 2002 16:42:27 -0000
@@ -1487,7 +1487,7 @@
                 apr_thread_mutex_t *comp;
 
                 rv = apr_thread_mutex_create(&cid->completed, 
-                                             APR_THREAD_MUTEX_DEFAULT, 
+                                             APR_THREAD_MUTEX_UNNESTED, 
                                              r->pool);
                 comp = cid->completed;
                 if (cid->completed && (rv == APR_SUCCESS)) {

Reply via email to