Register the Product Version also.
[wine] / dlls / ntdll / critsection.c
1 /*
2  * Win32 critical sections
3  *
4  * Copyright 1998 Alexandre Julliard
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <assert.h>
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <sys/types.h>
28 #include "winerror.h"
29 #include "windef.h"
30 #include "winbase.h"
31 #include "winreg.h"
32 #include "winternl.h"
33 #include "wine/debug.h"
34 #include "ntdll_misc.h"
35
36 WINE_DEFAULT_DEBUG_CHANNEL(ntdll);
37 WINE_DECLARE_DEBUG_CHANNEL(relay);
38
39 inline static LONG interlocked_inc( PLONG dest )
40 {
41     return interlocked_xchg_add( dest, 1 ) + 1;
42 }
43
44 inline static LONG interlocked_dec( PLONG dest )
45 {
46     return interlocked_xchg_add( dest, -1 ) - 1;
47 }
48
49 inline static void small_pause(void)
50 {
51 #ifdef __i386__
52     __asm__ __volatile__( "rep;nop" : : : "memory" );
53 #else
54     __asm__ __volatile__( "" : : : "memory" );
55 #endif
56 }
57
58 /***********************************************************************
59  *           get_semaphore
60  */
61 static inline HANDLE get_semaphore( RTL_CRITICAL_SECTION *crit )
62 {
63     HANDLE ret = crit->LockSemaphore;
64     if (!ret)
65     {
66         HANDLE sem;
67         if (NtCreateSemaphore( &sem, SEMAPHORE_ALL_ACCESS, NULL, 0, 1 )) return 0;
68         if (!(ret = (HANDLE)interlocked_cmpxchg_ptr( (PVOID *)&crit->LockSemaphore,
69                                                      (PVOID)sem, 0 )))
70             ret = sem;
71         else
72             NtClose(sem);  /* somebody beat us to it */
73     }
74     return ret;
75 }
76
77 /***********************************************************************
78  *           RtlInitializeCriticalSection   (NTDLL.@)
79  *
80  * Initialises a new critical section.
81  *
82  * PARAMS
83  *  crit [O] Critical section to initialise
84  *
85  * RETURNS
86  *  STATUS_SUCCESS.
87  *
88  * SEE
89  *  RtlInitializeCriticalSectionAndSpinCount(), RtlDeleteCriticalSection(),
90  *  RtlEnterCriticalSection(), RtlLeaveCriticalSection(),
91  *  RtlTryEnterCriticalSection(), RtlSetCriticalSectionSpinCount()
92  */
93 NTSTATUS WINAPI RtlInitializeCriticalSection( RTL_CRITICAL_SECTION *crit )
94 {
95     return RtlInitializeCriticalSectionAndSpinCount( crit, 0 );
96 }
97
98 /***********************************************************************
99  *           RtlInitializeCriticalSectionAndSpinCount   (NTDLL.@)
100  *
101  * Initialises a new critical section with a given spin count.
102  *
103  * PARAMS
104  *   crit      [O] Critical section to initialise
105  *   spincount [I] Spin count for crit
106  * 
107  * RETURNS
108  *  STATUS_SUCCESS.
109  *
110  * NOTES
111  *  Available on NT4 SP3 or later.
112  *
113  * SEE
114  *  RtlInitializeCriticalSection(), RtlDeleteCriticalSection(),
115  *  RtlEnterCriticalSection(), RtlLeaveCriticalSection(),
116  *  RtlTryEnterCriticalSection(), RtlSetCriticalSectionSpinCount()
117  */
118 NTSTATUS WINAPI RtlInitializeCriticalSectionAndSpinCount( RTL_CRITICAL_SECTION *crit, ULONG spincount )
119 {
120     if (!GetProcessHeap()) crit->DebugInfo = NULL;
121     else
122     {
123         crit->DebugInfo = RtlAllocateHeap(GetProcessHeap(), 0, sizeof(CRITICAL_SECTION_DEBUG));
124         if (crit->DebugInfo)
125         {
126             crit->DebugInfo->Type = 0;
127             crit->DebugInfo->CreatorBackTraceIndex = 0;
128             crit->DebugInfo->CriticalSection = crit;
129             crit->DebugInfo->ProcessLocksList.Blink = &(crit->DebugInfo->ProcessLocksList);
130             crit->DebugInfo->ProcessLocksList.Flink = &(crit->DebugInfo->ProcessLocksList);
131             crit->DebugInfo->EntryCount = 0;
132             crit->DebugInfo->ContentionCount = 0;
133             crit->DebugInfo->Spare[0] = 0;
134             crit->DebugInfo->Spare[1] = 0;
135         }
136     }
137     crit->LockCount      = -1;
138     crit->RecursionCount = 0;
139     crit->OwningThread   = 0;
140     crit->LockSemaphore  = 0;
141     crit->SpinCount      = spincount;
142     return STATUS_SUCCESS;
143 }
144
145 /***********************************************************************
146  *           RtlSetCriticalSectionSpinCount   (NTDLL.@)
147  *
148  * Sets the spin count of a critical section.
149  *
150  * PARAMS
151  *   crit      [I/O] Critical section
152  *   spincount [I] Spin count for crit
153  *
154  * RETURNS
155  *  The previous spin count.
156  *
157  * NOTES
158  *  If the system is not SMP, spincount is ignored and set to 0.
159  *
160  * SEE
161  *  RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
162  *  RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
163  *  RtlLeaveCriticalSection(), RtlTryEnterCriticalSection()
164  */
165 ULONG WINAPI RtlSetCriticalSectionSpinCount( RTL_CRITICAL_SECTION *crit, ULONG spincount )
166 {
167     ULONG oldspincount = crit->SpinCount;
168     if (NtCurrentTeb()->Peb->NumberOfProcessors <= 1) spincount = 0;
169     crit->SpinCount = spincount;
170     return oldspincount;
171 }
172
173 /***********************************************************************
174  *           RtlDeleteCriticalSection   (NTDLL.@)
175  *
176  * Frees the resources used by a critical section.
177  *
178  * PARAMS
179  *  crit [I/O] Critical section to free
180  *
181  * RETURNS
182  *  STATUS_SUCCESS.
183  *
184  * SEE
185  *  RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
186  *  RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
187  *  RtlLeaveCriticalSection(), RtlTryEnterCriticalSection()
188  */
189 NTSTATUS WINAPI RtlDeleteCriticalSection( RTL_CRITICAL_SECTION *crit )
190 {
191     crit->LockCount      = -1;
192     crit->RecursionCount = 0;
193     crit->OwningThread   = 0;
194     if (crit->LockSemaphore) NtClose( crit->LockSemaphore );
195     crit->LockSemaphore  = 0;
196     if (crit->DebugInfo)
197     {
198         /* only free the ones we made in here */
199         if (!crit->DebugInfo->Spare[1])
200         {
201             RtlFreeHeap( GetProcessHeap(), 0, crit->DebugInfo );
202             crit->DebugInfo = NULL;
203         }
204     }
205     return STATUS_SUCCESS;
206 }
207
208
209 /***********************************************************************
210  *           RtlpWaitForCriticalSection   (NTDLL.@)
211  *
212  * Waits for a busy critical section to become free.
213  * 
214  * PARAMS
215  *  crit [I/O] Critical section to wait for
216  *
217  * RETURNS
218  *  STATUS_SUCCESS.
219  *
220  * NOTES
221  *  Use RtlEnterCriticalSection() instead of this function as it is often much
222  *  faster.
223  *
224  * SEE
225  *  RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
226  *  RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
227  *  RtlLeaveCriticalSection(), RtlTryEnterCriticalSection()
228  */
229 NTSTATUS WINAPI RtlpWaitForCriticalSection( RTL_CRITICAL_SECTION *crit )
230 {
231     for (;;)
232     {
233         EXCEPTION_RECORD rec;
234         HANDLE sem = get_semaphore( crit );
235         LARGE_INTEGER time;
236         DWORD status;
237
238         time.QuadPart = -5000 * 10000;  /* 5 seconds */
239         status = NtWaitForSingleObject( sem, FALSE, &time );
240         if ( status == WAIT_TIMEOUT )
241         {
242             const char *name = NULL;
243             if (crit->DebugInfo) name = (char *)crit->DebugInfo->Spare[1];
244             if (!name) name = "?";
245             ERR( "section %p %s wait timed out in thread %04lx, blocked by %04lx, retrying (60 sec)\n",
246                  crit, debugstr_a(name), GetCurrentThreadId(), (DWORD)crit->OwningThread );
247             time.QuadPart = -60000 * 10000;
248             status = NtWaitForSingleObject( sem, FALSE, &time );
249             if ( status == WAIT_TIMEOUT && TRACE_ON(relay) )
250             {
251                 ERR( "section %p %s wait timed out in thread %04lx, blocked by %04lx, retrying (5 min)\n",
252                      crit, debugstr_a(name), GetCurrentThreadId(), (DWORD) crit->OwningThread );
253                 time.QuadPart = -300000 * (ULONGLONG)10000;
254                 status = NtWaitForSingleObject( sem, FALSE, &time );
255             }
256         }
257         if (status == STATUS_WAIT_0) return STATUS_SUCCESS;
258
259         /* Throw exception only for Wine internal locks */
260         if ((!crit->DebugInfo) || (!crit->DebugInfo->Spare[1])) continue;
261
262         rec.ExceptionCode    = STATUS_POSSIBLE_DEADLOCK;
263         rec.ExceptionFlags   = 0;
264         rec.ExceptionRecord  = NULL;
265         rec.ExceptionAddress = RtlRaiseException;  /* sic */
266         rec.NumberParameters = 1;
267         rec.ExceptionInformation[0] = (ULONG_PTR)crit;
268         RtlRaiseException( &rec );
269     }
270 }
271
272
273 /***********************************************************************
274  *           RtlpUnWaitCriticalSection   (NTDLL.@)
275  *
276  * Notifies other threads waiting on the busy critical section that it has
277  * become free.
278  * 
279  * PARAMS
280  *  crit [I/O] Critical section
281  *
282  * RETURNS
283  *  Success: STATUS_SUCCESS.
284  *  Failure: Any error returned by NtReleaseSemaphore()
285  *
286  * NOTES
287  *  Use RtlLeaveCriticalSection() instead of this function as it is often much
288  *  faster.
289  *
290  * SEE
291  *  RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
292  *  RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
293  *  RtlLeaveCriticalSection(), RtlTryEnterCriticalSection()
294  */
295 NTSTATUS WINAPI RtlpUnWaitCriticalSection( RTL_CRITICAL_SECTION *crit )
296 {
297     HANDLE sem = get_semaphore( crit );
298     NTSTATUS res = NtReleaseSemaphore( sem, 1, NULL );
299     if (res) RtlRaiseStatus( res );
300     return res;
301 }
302
303
304 /***********************************************************************
305  *           RtlEnterCriticalSection   (NTDLL.@)
306  *
307  * Enters a critical section, waiting for it to become available if necessary.
308  *
309  * PARAMS
310  *  crit [I/O] Critical section to enter
311  *
312  * RETURNS
313  *  STATUS_SUCCESS. The critical section is held by the caller.
314  *  
315  * SEE
316  *  RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
317  *  RtlDeleteCriticalSection(), RtlSetCriticalSectionSpinCount(),
318  *  RtlLeaveCriticalSection(), RtlTryEnterCriticalSection()
319  */
320 NTSTATUS WINAPI RtlEnterCriticalSection( RTL_CRITICAL_SECTION *crit )
321 {
322     if (crit->SpinCount)
323     {
324         ULONG count;
325
326         if (RtlTryEnterCriticalSection( crit )) return STATUS_SUCCESS;
327         for (count = crit->SpinCount; count > 0; count--)
328         {
329             if (crit->LockCount > 0) break;  /* more than one waiter, don't bother spinning */
330             if (crit->LockCount == -1)       /* try again */
331             {
332                 if (interlocked_cmpxchg( &crit->LockCount, 0, -1 ) == -1) goto done;
333             }
334             small_pause();
335         }
336     }
337
338     if (interlocked_inc( &crit->LockCount ))
339     {
340         if (crit->OwningThread == (HANDLE)GetCurrentThreadId())
341         {
342             crit->RecursionCount++;
343             return STATUS_SUCCESS;
344         }
345
346         /* Now wait for it */
347         RtlpWaitForCriticalSection( crit );
348     }
349 done:
350     crit->OwningThread   = (HANDLE)GetCurrentThreadId();
351     crit->RecursionCount = 1;
352     return STATUS_SUCCESS;
353 }
354
355
356 /***********************************************************************
357  *           RtlTryEnterCriticalSection   (NTDLL.@)
358  *
359  * Tries to enter a critical section without waiting.
360  *
361  * PARAMS
362  *  crit [I/O] Critical section to enter
363  *
364  * RETURNS
365  *  Success: TRUE. The critical section is held by the caller.
366  *  Failure: FALSE. The critical section is currently held by another thread.
367  *
368  * SEE
369  *  RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
370  *  RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
371  *  RtlLeaveCriticalSection(), RtlSetCriticalSectionSpinCount()
372  */
373 BOOL WINAPI RtlTryEnterCriticalSection( RTL_CRITICAL_SECTION *crit )
374 {
375     BOOL ret = FALSE;
376     if (interlocked_cmpxchg( &crit->LockCount, 0L, -1 ) == -1)
377     {
378         crit->OwningThread   = (HANDLE)GetCurrentThreadId();
379         crit->RecursionCount = 1;
380         ret = TRUE;
381     }
382     else if (crit->OwningThread == (HANDLE)GetCurrentThreadId())
383     {
384         interlocked_inc( &crit->LockCount );
385         crit->RecursionCount++;
386         ret = TRUE;
387     }
388     return ret;
389 }
390
391
392 /***********************************************************************
393  *           RtlLeaveCriticalSection   (NTDLL.@)
394  *
395  * Leaves a critical section.
396  *
397  * PARAMS
398  *  crit [I/O] Critical section to leave.
399  *
400  * RETURNS
401  *  STATUS_SUCCESS.
402  *
403  * SEE
404  *  RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
405  *  RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
406  *  RtlSetCriticalSectionSpinCount(), RtlTryEnterCriticalSection()
407  */
408 NTSTATUS WINAPI RtlLeaveCriticalSection( RTL_CRITICAL_SECTION *crit )
409 {
410     if (--crit->RecursionCount) interlocked_dec( &crit->LockCount );
411     else
412     {
413         crit->OwningThread = 0;
414         if (interlocked_dec( &crit->LockCount ) >= 0)
415         {
416             /* someone is waiting */
417             RtlpUnWaitCriticalSection( crit );
418         }
419     }
420     return STATUS_SUCCESS;
421 }