mirror of
https://gitlab.winehq.org/wine/wine-staging.git
synced 2024-11-21 16:46:54 -08:00
934 lines
31 KiB
Diff
934 lines
31 KiB
Diff
|
From 9e9f51741ee04bcf8123ae23241ba3c4f8c8b2cf Mon Sep 17 00:00:00 2001
|
||
|
From: Zebediah Figura <z.figura12@gmail.com>
|
||
|
Date: Mon, 31 Aug 2020 23:30:52 -0500
|
||
|
Subject: [PATCH 10/13] ntdll: Merge critsection.c into sync.c.
|
||
|
|
||
|
Signed-off-by: Zebediah Figura <z.figura12@gmail.com>
|
||
|
---
|
||
|
dlls/ntdll/Makefile.in | 1 -
|
||
|
dlls/ntdll/critsection.c | 543 ---------------------------------------
|
||
|
dlls/ntdll/sync.c | 334 +++++++++++++++++++++++-
|
||
|
3 files changed, 333 insertions(+), 545 deletions(-)
|
||
|
delete mode 100644 dlls/ntdll/critsection.c
|
||
|
|
||
|
diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in
|
||
|
index b2f63d9f63a..5ef28d2f722 100644
|
||
|
--- a/dlls/ntdll/Makefile.in
|
||
|
+++ b/dlls/ntdll/Makefile.in
|
||
|
@@ -9,7 +9,6 @@ EXTRADLLFLAGS = -mno-cygwin -nodefaultlibs -Wl,--image-base,0x7bc00000
|
||
|
C_SRCS = \
|
||
|
actctx.c \
|
||
|
atom.c \
|
||
|
- critsection.c \
|
||
|
crypt.c \
|
||
|
debugbuffer.c \
|
||
|
directory.c \
|
||
|
diff --git a/dlls/ntdll/critsection.c b/dlls/ntdll/critsection.c
|
||
|
deleted file mode 100644
|
||
|
index fe7d933c0fa..00000000000
|
||
|
--- a/dlls/ntdll/critsection.c
|
||
|
+++ /dev/null
|
||
|
@@ -1,543 +0,0 @@
|
||
|
-/*
|
||
|
- * Win32 critical sections
|
||
|
- *
|
||
|
- * Copyright 1998 Alexandre Julliard
|
||
|
- *
|
||
|
- * This library is free software; you can redistribute it and/or
|
||
|
- * modify it under the terms of the GNU Lesser General Public
|
||
|
- * License as published by the Free Software Foundation; either
|
||
|
- * version 2.1 of the License, or (at your option) any later version.
|
||
|
- *
|
||
|
- * This library is distributed in the hope that it will be useful,
|
||
|
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
- * Lesser General Public License for more details.
|
||
|
- *
|
||
|
- * You should have received a copy of the GNU Lesser General Public
|
||
|
- * License along with this library; if not, write to the Free Software
|
||
|
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||
|
- */
|
||
|
-
|
||
|
-#include <assert.h>
|
||
|
-#include <errno.h>
|
||
|
-#include <stdarg.h>
|
||
|
-#include <stdio.h>
|
||
|
-#include <sys/types.h>
|
||
|
-#include <time.h>
|
||
|
-#include "ntstatus.h"
|
||
|
-#define WIN32_NO_STATUS
|
||
|
-#include "windef.h"
|
||
|
-#include "winternl.h"
|
||
|
-#include "wine/debug.h"
|
||
|
-#include "ntdll_misc.h"
|
||
|
-
|
||
|
-WINE_DEFAULT_DEBUG_CHANNEL(ntdll);
|
||
|
-WINE_DECLARE_DEBUG_CHANNEL(relay);
|
||
|
-
|
||
|
-static inline void small_pause(void)
|
||
|
-{
|
||
|
-#ifdef __i386__
|
||
|
- __asm__ __volatile__( "rep;nop" : : : "memory" );
|
||
|
-#else
|
||
|
- __asm__ __volatile__( "" : : : "memory" );
|
||
|
-#endif
|
||
|
-}
|
||
|
-
|
||
|
-static void *no_debug_info_marker = (void *)(ULONG_PTR)-1;
|
||
|
-
|
||
|
-static BOOL crit_section_has_debuginfo(const RTL_CRITICAL_SECTION *crit)
|
||
|
-{
|
||
|
- return crit->DebugInfo != NULL && crit->DebugInfo != no_debug_info_marker;
|
||
|
-}
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * get_semaphore
|
||
|
- */
|
||
|
-static inline HANDLE get_semaphore( RTL_CRITICAL_SECTION *crit )
|
||
|
-{
|
||
|
- HANDLE ret = crit->LockSemaphore;
|
||
|
- if (!ret)
|
||
|
- {
|
||
|
- HANDLE sem;
|
||
|
- if (NtCreateSemaphore( &sem, SEMAPHORE_ALL_ACCESS, NULL, 0, 1 )) return 0;
|
||
|
- if (!(ret = InterlockedCompareExchangePointer( &crit->LockSemaphore, sem, 0 )))
|
||
|
- ret = sem;
|
||
|
- else
|
||
|
- NtClose(sem); /* somebody beat us to it */
|
||
|
- }
|
||
|
- return ret;
|
||
|
-}
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * wait_semaphore
|
||
|
- */
|
||
|
-static inline NTSTATUS wait_semaphore( RTL_CRITICAL_SECTION *crit, int timeout )
|
||
|
-{
|
||
|
- NTSTATUS ret;
|
||
|
-
|
||
|
- /* debug info is cleared by MakeCriticalSectionGlobal */
|
||
|
- if (!crit_section_has_debuginfo( crit ) ||
|
||
|
- ((ret = unix_funcs->fast_RtlpWaitForCriticalSection( crit, timeout )) == STATUS_NOT_IMPLEMENTED))
|
||
|
- {
|
||
|
- HANDLE sem = get_semaphore( crit );
|
||
|
- LARGE_INTEGER time;
|
||
|
-
|
||
|
- time.QuadPart = timeout * (LONGLONG)-10000000;
|
||
|
- ret = NtWaitForSingleObject( sem, FALSE, &time );
|
||
|
- }
|
||
|
- return ret;
|
||
|
-}
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlInitializeCriticalSection (NTDLL.@)
|
||
|
- *
|
||
|
- * Initialises a new critical section.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [O] Critical section to initialise
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * STATUS_SUCCESS.
|
||
|
- *
|
||
|
- * SEE
|
||
|
- * RtlInitializeCriticalSectionEx(),
|
||
|
- * RtlInitializeCriticalSectionAndSpinCount(), RtlDeleteCriticalSection(),
|
||
|
- * RtlEnterCriticalSection(), RtlLeaveCriticalSection(),
|
||
|
- * RtlTryEnterCriticalSection(), RtlSetCriticalSectionSpinCount()
|
||
|
- */
|
||
|
-NTSTATUS WINAPI RtlInitializeCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
-{
|
||
|
- return RtlInitializeCriticalSectionEx( crit, 0, 0 );
|
||
|
-}
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlInitializeCriticalSectionAndSpinCount (NTDLL.@)
|
||
|
- *
|
||
|
- * Initialises a new critical section with a given spin count.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [O] Critical section to initialise
|
||
|
- * spincount [I] Spin count for crit
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * STATUS_SUCCESS.
|
||
|
- *
|
||
|
- * NOTES
|
||
|
- * Available on NT4 SP3 or later.
|
||
|
- *
|
||
|
- * SEE
|
||
|
- * RtlInitializeCriticalSectionEx(),
|
||
|
- * RtlInitializeCriticalSection(), RtlDeleteCriticalSection(),
|
||
|
- * RtlEnterCriticalSection(), RtlLeaveCriticalSection(),
|
||
|
- * RtlTryEnterCriticalSection(), RtlSetCriticalSectionSpinCount()
|
||
|
- */
|
||
|
-NTSTATUS WINAPI RtlInitializeCriticalSectionAndSpinCount( RTL_CRITICAL_SECTION *crit, ULONG spincount )
|
||
|
-{
|
||
|
- return RtlInitializeCriticalSectionEx( crit, spincount, 0 );
|
||
|
-}
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlInitializeCriticalSectionEx (NTDLL.@)
|
||
|
- *
|
||
|
- * Initialises a new critical section with a given spin count and flags.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [O] Critical section to initialise.
|
||
|
- * spincount [I] Number of times to spin upon contention.
|
||
|
- * flags [I] RTL_CRITICAL_SECTION_FLAG_ flags from winnt.h.
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * STATUS_SUCCESS.
|
||
|
- *
|
||
|
- * NOTES
|
||
|
- * Available on Vista or later.
|
||
|
- *
|
||
|
- * SEE
|
||
|
- * RtlInitializeCriticalSection(), RtlDeleteCriticalSection(),
|
||
|
- * RtlEnterCriticalSection(), RtlLeaveCriticalSection(),
|
||
|
- * RtlTryEnterCriticalSection(), RtlSetCriticalSectionSpinCount()
|
||
|
- */
|
||
|
-NTSTATUS WINAPI RtlInitializeCriticalSectionEx( RTL_CRITICAL_SECTION *crit, ULONG spincount, ULONG flags )
|
||
|
-{
|
||
|
- if (flags & (RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN|RTL_CRITICAL_SECTION_FLAG_STATIC_INIT))
|
||
|
- FIXME("(%p,%u,0x%08x) semi-stub\n", crit, spincount, flags);
|
||
|
-
|
||
|
- /* FIXME: if RTL_CRITICAL_SECTION_FLAG_STATIC_INIT is given, we should use
|
||
|
- * memory from a static pool to hold the debug info. Then heap.c could pass
|
||
|
- * this flag rather than initialising the process heap CS by hand. If this
|
||
|
- * is done, then debug info should be managed through Rtlp[Allocate|Free]DebugInfo
|
||
|
- * so (e.g.) MakeCriticalSectionGlobal() doesn't free it using HeapFree().
|
||
|
- */
|
||
|
- if (flags & RTL_CRITICAL_SECTION_FLAG_NO_DEBUG_INFO)
|
||
|
- crit->DebugInfo = no_debug_info_marker;
|
||
|
- else
|
||
|
- {
|
||
|
- crit->DebugInfo = RtlAllocateHeap(GetProcessHeap(), 0, sizeof(RTL_CRITICAL_SECTION_DEBUG));
|
||
|
- if (crit->DebugInfo)
|
||
|
- {
|
||
|
- crit->DebugInfo->Type = 0;
|
||
|
- crit->DebugInfo->CreatorBackTraceIndex = 0;
|
||
|
- crit->DebugInfo->CriticalSection = crit;
|
||
|
- crit->DebugInfo->ProcessLocksList.Blink = &(crit->DebugInfo->ProcessLocksList);
|
||
|
- crit->DebugInfo->ProcessLocksList.Flink = &(crit->DebugInfo->ProcessLocksList);
|
||
|
- crit->DebugInfo->EntryCount = 0;
|
||
|
- crit->DebugInfo->ContentionCount = 0;
|
||
|
- memset( crit->DebugInfo->Spare, 0, sizeof(crit->DebugInfo->Spare) );
|
||
|
- }
|
||
|
- }
|
||
|
- crit->LockCount = -1;
|
||
|
- crit->RecursionCount = 0;
|
||
|
- crit->OwningThread = 0;
|
||
|
- crit->LockSemaphore = 0;
|
||
|
- if (NtCurrentTeb()->Peb->NumberOfProcessors <= 1) spincount = 0;
|
||
|
- crit->SpinCount = spincount & ~0x80000000;
|
||
|
- return STATUS_SUCCESS;
|
||
|
-}
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlSetCriticalSectionSpinCount (NTDLL.@)
|
||
|
- *
|
||
|
- * Sets the spin count of a critical section.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [I/O] Critical section
|
||
|
- * spincount [I] Spin count for crit
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * The previous spin count.
|
||
|
- *
|
||
|
- * NOTES
|
||
|
- * If the system is not SMP, spincount is ignored and set to 0.
|
||
|
- *
|
||
|
- * SEE
|
||
|
- * RtlInitializeCriticalSectionEx(),
|
||
|
- * RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
|
||
|
- * RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
|
||
|
- * RtlLeaveCriticalSection(), RtlTryEnterCriticalSection()
|
||
|
- */
|
||
|
-ULONG WINAPI RtlSetCriticalSectionSpinCount( RTL_CRITICAL_SECTION *crit, ULONG spincount )
|
||
|
-{
|
||
|
- ULONG oldspincount = crit->SpinCount;
|
||
|
- if (NtCurrentTeb()->Peb->NumberOfProcessors <= 1) spincount = 0;
|
||
|
- crit->SpinCount = spincount;
|
||
|
- return oldspincount;
|
||
|
-}
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlDeleteCriticalSection (NTDLL.@)
|
||
|
- *
|
||
|
- * Frees the resources used by a critical section.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [I/O] Critical section to free
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * STATUS_SUCCESS.
|
||
|
- *
|
||
|
- * SEE
|
||
|
- * RtlInitializeCriticalSectionEx(),
|
||
|
- * RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
|
||
|
- * RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
|
||
|
- * RtlLeaveCriticalSection(), RtlTryEnterCriticalSection()
|
||
|
- */
|
||
|
-NTSTATUS WINAPI RtlDeleteCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
-{
|
||
|
- crit->LockCount = -1;
|
||
|
- crit->RecursionCount = 0;
|
||
|
- crit->OwningThread = 0;
|
||
|
- if (crit_section_has_debuginfo( crit ))
|
||
|
- {
|
||
|
- /* only free the ones we made in here */
|
||
|
- if (!crit->DebugInfo->Spare[0])
|
||
|
- {
|
||
|
- RtlFreeHeap( GetProcessHeap(), 0, crit->DebugInfo );
|
||
|
- crit->DebugInfo = NULL;
|
||
|
- }
|
||
|
- if (unix_funcs->fast_RtlDeleteCriticalSection( crit ) == STATUS_NOT_IMPLEMENTED)
|
||
|
- NtClose( crit->LockSemaphore );
|
||
|
- }
|
||
|
- else NtClose( crit->LockSemaphore );
|
||
|
- crit->LockSemaphore = 0;
|
||
|
- return STATUS_SUCCESS;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlpWaitForCriticalSection (NTDLL.@)
|
||
|
- *
|
||
|
- * Waits for a busy critical section to become free.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [I/O] Critical section to wait for
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * STATUS_SUCCESS.
|
||
|
- *
|
||
|
- * NOTES
|
||
|
- * Use RtlEnterCriticalSection() instead of this function as it is often much
|
||
|
- * faster.
|
||
|
- *
|
||
|
- * SEE
|
||
|
- * RtlInitializeCriticalSectionEx(),
|
||
|
- * RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
|
||
|
- * RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
|
||
|
- * RtlLeaveCriticalSection(), RtlTryEnterCriticalSection()
|
||
|
- */
|
||
|
-NTSTATUS WINAPI RtlpWaitForCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
-{
|
||
|
- LONGLONG timeout = NtCurrentTeb()->Peb->CriticalSectionTimeout.QuadPart / -10000000;
|
||
|
-
|
||
|
- /* Don't allow blocking on a critical section during process termination */
|
||
|
- if (RtlDllShutdownInProgress())
|
||
|
- {
|
||
|
- WARN( "process %s is shutting down, returning STATUS_SUCCESS\n",
|
||
|
- debugstr_w(NtCurrentTeb()->Peb->ProcessParameters->ImagePathName.Buffer) );
|
||
|
- return STATUS_SUCCESS;
|
||
|
- }
|
||
|
-
|
||
|
- for (;;)
|
||
|
- {
|
||
|
- EXCEPTION_RECORD rec;
|
||
|
- NTSTATUS status = wait_semaphore( crit, 5 );
|
||
|
- timeout -= 5;
|
||
|
-
|
||
|
- if ( status == STATUS_TIMEOUT )
|
||
|
- {
|
||
|
- const char *name = NULL;
|
||
|
- if (crit_section_has_debuginfo( crit )) name = (char *)crit->DebugInfo->Spare[0];
|
||
|
- if (!name) name = "?";
|
||
|
- ERR( "section %p %s wait timed out in thread %04x, blocked by %04x, retrying (60 sec)\n",
|
||
|
- crit, debugstr_a(name), GetCurrentThreadId(), HandleToULong(crit->OwningThread) );
|
||
|
- status = wait_semaphore( crit, 60 );
|
||
|
- timeout -= 60;
|
||
|
-
|
||
|
- if ( status == STATUS_TIMEOUT && TRACE_ON(relay) )
|
||
|
- {
|
||
|
- ERR( "section %p %s wait timed out in thread %04x, blocked by %04x, retrying (5 min)\n",
|
||
|
- crit, debugstr_a(name), GetCurrentThreadId(), HandleToULong(crit->OwningThread) );
|
||
|
- status = wait_semaphore( crit, 300 );
|
||
|
- timeout -= 300;
|
||
|
- }
|
||
|
- }
|
||
|
- if (status == STATUS_WAIT_0) break;
|
||
|
-
|
||
|
- /* Throw exception only for Wine internal locks */
|
||
|
- if (!crit_section_has_debuginfo( crit ) || !crit->DebugInfo->Spare[0]) continue;
|
||
|
-
|
||
|
- /* only throw deadlock exception if configured timeout is reached */
|
||
|
- if (timeout > 0) continue;
|
||
|
-
|
||
|
- rec.ExceptionCode = STATUS_POSSIBLE_DEADLOCK;
|
||
|
- rec.ExceptionFlags = 0;
|
||
|
- rec.ExceptionRecord = NULL;
|
||
|
- rec.ExceptionAddress = RtlRaiseException; /* sic */
|
||
|
- rec.NumberParameters = 1;
|
||
|
- rec.ExceptionInformation[0] = (ULONG_PTR)crit;
|
||
|
- RtlRaiseException( &rec );
|
||
|
- }
|
||
|
- if (crit_section_has_debuginfo( crit )) crit->DebugInfo->ContentionCount++;
|
||
|
- return STATUS_SUCCESS;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlpUnWaitCriticalSection (NTDLL.@)
|
||
|
- *
|
||
|
- * Notifies other threads waiting on the busy critical section that it has
|
||
|
- * become free.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [I/O] Critical section
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * Success: STATUS_SUCCESS.
|
||
|
- * Failure: Any error returned by NtReleaseSemaphore()
|
||
|
- *
|
||
|
- * NOTES
|
||
|
- * Use RtlLeaveCriticalSection() instead of this function as it is often much
|
||
|
- * faster.
|
||
|
- *
|
||
|
- * SEE
|
||
|
- * RtlInitializeCriticalSectionEx(),
|
||
|
- * RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
|
||
|
- * RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
|
||
|
- * RtlLeaveCriticalSection(), RtlTryEnterCriticalSection()
|
||
|
- */
|
||
|
-NTSTATUS WINAPI RtlpUnWaitCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
-{
|
||
|
- NTSTATUS ret;
|
||
|
-
|
||
|
- /* debug info is cleared by MakeCriticalSectionGlobal */
|
||
|
- if (!crit_section_has_debuginfo( crit ) ||
|
||
|
- ((ret = unix_funcs->fast_RtlpUnWaitCriticalSection( crit )) == STATUS_NOT_IMPLEMENTED))
|
||
|
- {
|
||
|
- HANDLE sem = get_semaphore( crit );
|
||
|
- ret = NtReleaseSemaphore( sem, 1, NULL );
|
||
|
- }
|
||
|
- if (ret) RtlRaiseStatus( ret );
|
||
|
- return ret;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlEnterCriticalSection (NTDLL.@)
|
||
|
- *
|
||
|
- * Enters a critical section, waiting for it to become available if necessary.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [I/O] Critical section to enter
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * STATUS_SUCCESS. The critical section is held by the caller.
|
||
|
- *
|
||
|
- * SEE
|
||
|
- * RtlInitializeCriticalSectionEx(),
|
||
|
- * RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
|
||
|
- * RtlDeleteCriticalSection(), RtlSetCriticalSectionSpinCount(),
|
||
|
- * RtlLeaveCriticalSection(), RtlTryEnterCriticalSection()
|
||
|
- */
|
||
|
-NTSTATUS WINAPI RtlEnterCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
-{
|
||
|
- if (crit->SpinCount)
|
||
|
- {
|
||
|
- ULONG count;
|
||
|
-
|
||
|
- if (RtlTryEnterCriticalSection( crit )) return STATUS_SUCCESS;
|
||
|
- for (count = crit->SpinCount; count > 0; count--)
|
||
|
- {
|
||
|
- if (crit->LockCount > 0) break; /* more than one waiter, don't bother spinning */
|
||
|
- if (crit->LockCount == -1) /* try again */
|
||
|
- {
|
||
|
- if (InterlockedCompareExchange( &crit->LockCount, 0, -1 ) == -1) goto done;
|
||
|
- }
|
||
|
- small_pause();
|
||
|
- }
|
||
|
- }
|
||
|
-
|
||
|
- if (InterlockedIncrement( &crit->LockCount ))
|
||
|
- {
|
||
|
- if (crit->OwningThread == ULongToHandle(GetCurrentThreadId()))
|
||
|
- {
|
||
|
- crit->RecursionCount++;
|
||
|
- return STATUS_SUCCESS;
|
||
|
- }
|
||
|
-
|
||
|
- /* Now wait for it */
|
||
|
- RtlpWaitForCriticalSection( crit );
|
||
|
- }
|
||
|
-done:
|
||
|
- crit->OwningThread = ULongToHandle(GetCurrentThreadId());
|
||
|
- crit->RecursionCount = 1;
|
||
|
- return STATUS_SUCCESS;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlTryEnterCriticalSection (NTDLL.@)
|
||
|
- *
|
||
|
- * Tries to enter a critical section without waiting.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [I/O] Critical section to enter
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * Success: TRUE. The critical section is held by the caller.
|
||
|
- * Failure: FALSE. The critical section is currently held by another thread.
|
||
|
- *
|
||
|
- * SEE
|
||
|
- * RtlInitializeCriticalSectionEx(),
|
||
|
- * RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
|
||
|
- * RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
|
||
|
- * RtlLeaveCriticalSection(), RtlSetCriticalSectionSpinCount()
|
||
|
- */
|
||
|
-BOOL WINAPI RtlTryEnterCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
-{
|
||
|
- BOOL ret = FALSE;
|
||
|
- if (InterlockedCompareExchange( &crit->LockCount, 0, -1 ) == -1)
|
||
|
- {
|
||
|
- crit->OwningThread = ULongToHandle(GetCurrentThreadId());
|
||
|
- crit->RecursionCount = 1;
|
||
|
- ret = TRUE;
|
||
|
- }
|
||
|
- else if (crit->OwningThread == ULongToHandle(GetCurrentThreadId()))
|
||
|
- {
|
||
|
- InterlockedIncrement( &crit->LockCount );
|
||
|
- crit->RecursionCount++;
|
||
|
- ret = TRUE;
|
||
|
- }
|
||
|
- return ret;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlIsCriticalSectionLocked (NTDLL.@)
|
||
|
- *
|
||
|
- * Checks if the critical section is locked by any thread.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [I/O] Critical section to check.
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * Success: TRUE. The critical section is locked.
|
||
|
- * Failure: FALSE. The critical section is not locked.
|
||
|
- */
|
||
|
-BOOL WINAPI RtlIsCriticalSectionLocked( RTL_CRITICAL_SECTION *crit )
|
||
|
-{
|
||
|
- return crit->RecursionCount != 0;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlIsCriticalSectionLockedByThread (NTDLL.@)
|
||
|
- *
|
||
|
- * Checks if the critical section is locked by the current thread.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [I/O] Critical section to check.
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * Success: TRUE. The critical section is locked.
|
||
|
- * Failure: FALSE. The critical section is not locked.
|
||
|
- */
|
||
|
-BOOL WINAPI RtlIsCriticalSectionLockedByThread( RTL_CRITICAL_SECTION *crit )
|
||
|
-{
|
||
|
- return crit->OwningThread == ULongToHandle(GetCurrentThreadId()) &&
|
||
|
- crit->RecursionCount;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
-/***********************************************************************
|
||
|
- * RtlLeaveCriticalSection (NTDLL.@)
|
||
|
- *
|
||
|
- * Leaves a critical section.
|
||
|
- *
|
||
|
- * PARAMS
|
||
|
- * crit [I/O] Critical section to leave.
|
||
|
- *
|
||
|
- * RETURNS
|
||
|
- * STATUS_SUCCESS.
|
||
|
- *
|
||
|
- * SEE
|
||
|
- * RtlInitializeCriticalSectionEx(),
|
||
|
- * RtlInitializeCriticalSection(), RtlInitializeCriticalSectionAndSpinCount(),
|
||
|
- * RtlDeleteCriticalSection(), RtlEnterCriticalSection(),
|
||
|
- * RtlSetCriticalSectionSpinCount(), RtlTryEnterCriticalSection()
|
||
|
- */
|
||
|
-NTSTATUS WINAPI RtlLeaveCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
-{
|
||
|
- if (--crit->RecursionCount)
|
||
|
- {
|
||
|
- if (crit->RecursionCount > 0) InterlockedDecrement( &crit->LockCount );
|
||
|
- else ERR( "section %p is not acquired\n", crit );
|
||
|
- }
|
||
|
- else
|
||
|
- {
|
||
|
- crit->OwningThread = 0;
|
||
|
- if (InterlockedDecrement( &crit->LockCount ) >= 0)
|
||
|
- {
|
||
|
- /* someone is waiting */
|
||
|
- RtlpUnWaitCriticalSection( crit );
|
||
|
- }
|
||
|
- }
|
||
|
- return STATUS_SUCCESS;
|
||
|
-}
|
||
|
diff --git a/dlls/ntdll/sync.c b/dlls/ntdll/sync.c
|
||
|
index 05bccf698b6..ea327172b86 100644
|
||
|
--- a/dlls/ntdll/sync.c
|
||
|
+++ b/dlls/ntdll/sync.c
|
||
|
@@ -2,7 +2,7 @@
|
||
|
* Process synchronisation
|
||
|
*
|
||
|
* Copyright 1996, 1997, 1998 Marcus Meissner
|
||
|
- * Copyright 1997, 1999 Alexandre Julliard
|
||
|
+ * Copyright 1997, 1998, 1999 Alexandre Julliard
|
||
|
* Copyright 1999, 2000 Juergen Schmied
|
||
|
* Copyright 2003 Eric Pouech
|
||
|
*
|
||
|
@@ -38,6 +38,7 @@
|
||
|
#include "ntdll_misc.h"
|
||
|
|
||
|
WINE_DEFAULT_DEBUG_CHANNEL(sync);
|
||
|
+WINE_DECLARE_DEBUG_CHANNEL(relay);
|
||
|
|
||
|
static const char *debugstr_timeout( const LARGE_INTEGER *timeout )
|
||
|
{
|
||
|
@@ -626,3 +627,334 @@ void WINAPI RtlWakeAddressSingle( const void *addr )
|
||
|
}
|
||
|
teb_list_rdunlock();
|
||
|
}
|
||
|
+
|
||
|
+
|
||
|
+/***********************************************************************
|
||
|
+ * Critical sections
|
||
|
+ ***********************************************************************/
|
||
|
+
|
||
|
+
|
||
|
+static void *no_debug_info_marker = (void *)(ULONG_PTR)-1;
|
||
|
+
|
||
|
+static BOOL crit_section_has_debuginfo( const RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ return crit->DebugInfo != NULL && crit->DebugInfo != no_debug_info_marker;
|
||
|
+}
|
||
|
+
|
||
|
+static inline HANDLE get_semaphore( RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ HANDLE ret = crit->LockSemaphore;
|
||
|
+ if (!ret)
|
||
|
+ {
|
||
|
+ HANDLE sem;
|
||
|
+ if (NtCreateSemaphore( &sem, SEMAPHORE_ALL_ACCESS, NULL, 0, 1 )) return 0;
|
||
|
+ if (!(ret = InterlockedCompareExchangePointer( &crit->LockSemaphore, sem, 0 )))
|
||
|
+ ret = sem;
|
||
|
+ else
|
||
|
+ NtClose(sem); /* somebody beat us to it */
|
||
|
+ }
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static inline NTSTATUS wait_semaphore( RTL_CRITICAL_SECTION *crit, int timeout )
|
||
|
+{
|
||
|
+ NTSTATUS ret;
|
||
|
+
|
||
|
+ /* debug info is cleared by MakeCriticalSectionGlobal */
|
||
|
+ if (!crit_section_has_debuginfo( crit ) ||
|
||
|
+ ((ret = unix_funcs->fast_RtlpWaitForCriticalSection( crit, timeout )) == STATUS_NOT_IMPLEMENTED))
|
||
|
+ {
|
||
|
+ HANDLE sem = get_semaphore( crit );
|
||
|
+ LARGE_INTEGER time;
|
||
|
+
|
||
|
+ time.QuadPart = timeout * (LONGLONG)-10000000;
|
||
|
+ ret = NtWaitForSingleObject( sem, FALSE, &time );
|
||
|
+ }
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlInitializeCriticalSection (NTDLL.@)
|
||
|
+ */
|
||
|
+NTSTATUS WINAPI RtlInitializeCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ return RtlInitializeCriticalSectionEx( crit, 0, 0 );
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlInitializeCriticalSectionAndSpinCount (NTDLL.@)
|
||
|
+ */
|
||
|
+NTSTATUS WINAPI RtlInitializeCriticalSectionAndSpinCount( RTL_CRITICAL_SECTION *crit, ULONG spincount )
|
||
|
+{
|
||
|
+ return RtlInitializeCriticalSectionEx( crit, spincount, 0 );
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlInitializeCriticalSectionEx (NTDLL.@)
|
||
|
+ */
|
||
|
+NTSTATUS WINAPI RtlInitializeCriticalSectionEx( RTL_CRITICAL_SECTION *crit, ULONG spincount, ULONG flags )
|
||
|
+{
|
||
|
+ if (flags & (RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN|RTL_CRITICAL_SECTION_FLAG_STATIC_INIT))
|
||
|
+ FIXME("(%p,%u,0x%08x) semi-stub\n", crit, spincount, flags);
|
||
|
+
|
||
|
+ /* FIXME: if RTL_CRITICAL_SECTION_FLAG_STATIC_INIT is given, we should use
|
||
|
+ * memory from a static pool to hold the debug info. Then heap.c could pass
|
||
|
+ * this flag rather than initialising the process heap CS by hand. If this
|
||
|
+ * is done, then debug info should be managed through Rtlp[Allocate|Free]DebugInfo
|
||
|
+ * so (e.g.) MakeCriticalSectionGlobal() doesn't free it using HeapFree().
|
||
|
+ */
|
||
|
+ if (flags & RTL_CRITICAL_SECTION_FLAG_NO_DEBUG_INFO)
|
||
|
+ crit->DebugInfo = no_debug_info_marker;
|
||
|
+ else
|
||
|
+ {
|
||
|
+ crit->DebugInfo = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(RTL_CRITICAL_SECTION_DEBUG ));
|
||
|
+ if (crit->DebugInfo)
|
||
|
+ {
|
||
|
+ crit->DebugInfo->Type = 0;
|
||
|
+ crit->DebugInfo->CreatorBackTraceIndex = 0;
|
||
|
+ crit->DebugInfo->CriticalSection = crit;
|
||
|
+ crit->DebugInfo->ProcessLocksList.Blink = &crit->DebugInfo->ProcessLocksList;
|
||
|
+ crit->DebugInfo->ProcessLocksList.Flink = &crit->DebugInfo->ProcessLocksList;
|
||
|
+ crit->DebugInfo->EntryCount = 0;
|
||
|
+ crit->DebugInfo->ContentionCount = 0;
|
||
|
+ memset( crit->DebugInfo->Spare, 0, sizeof(crit->DebugInfo->Spare) );
|
||
|
+ }
|
||
|
+ }
|
||
|
+ crit->LockCount = -1;
|
||
|
+ crit->RecursionCount = 0;
|
||
|
+ crit->OwningThread = 0;
|
||
|
+ crit->LockSemaphore = 0;
|
||
|
+ if (NtCurrentTeb()->Peb->NumberOfProcessors <= 1) spincount = 0;
|
||
|
+ crit->SpinCount = spincount & ~0x80000000;
|
||
|
+ return STATUS_SUCCESS;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlSetCriticalSectionSpinCount (NTDLL.@)
|
||
|
+ */
|
||
|
+ULONG WINAPI RtlSetCriticalSectionSpinCount( RTL_CRITICAL_SECTION *crit, ULONG spincount )
|
||
|
+{
|
||
|
+ ULONG oldspincount = crit->SpinCount;
|
||
|
+ if (NtCurrentTeb()->Peb->NumberOfProcessors <= 1) spincount = 0;
|
||
|
+ crit->SpinCount = spincount;
|
||
|
+ return oldspincount;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlDeleteCriticalSection (NTDLL.@)
|
||
|
+ */
|
||
|
+NTSTATUS WINAPI RtlDeleteCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ crit->LockCount = -1;
|
||
|
+ crit->RecursionCount = 0;
|
||
|
+ crit->OwningThread = 0;
|
||
|
+ if (crit_section_has_debuginfo( crit ))
|
||
|
+ {
|
||
|
+ /* only free the ones we made in here */
|
||
|
+ if (!crit->DebugInfo->Spare[0])
|
||
|
+ {
|
||
|
+ RtlFreeHeap( GetProcessHeap(), 0, crit->DebugInfo );
|
||
|
+ crit->DebugInfo = NULL;
|
||
|
+ }
|
||
|
+ if (unix_funcs->fast_RtlDeleteCriticalSection( crit ) == STATUS_NOT_IMPLEMENTED)
|
||
|
+ NtClose( crit->LockSemaphore );
|
||
|
+ }
|
||
|
+ else NtClose( crit->LockSemaphore );
|
||
|
+ crit->LockSemaphore = 0;
|
||
|
+ return STATUS_SUCCESS;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlpWaitForCriticalSection (NTDLL.@)
|
||
|
+ */
|
||
|
+NTSTATUS WINAPI RtlpWaitForCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ LONGLONG timeout = NtCurrentTeb()->Peb->CriticalSectionTimeout.QuadPart / -10000000;
|
||
|
+
|
||
|
+ /* Don't allow blocking on a critical section during process termination */
|
||
|
+ if (RtlDllShutdownInProgress())
|
||
|
+ {
|
||
|
+ WARN( "process %s is shutting down, returning STATUS_SUCCESS\n",
|
||
|
+ debugstr_w(NtCurrentTeb()->Peb->ProcessParameters->ImagePathName.Buffer) );
|
||
|
+ return STATUS_SUCCESS;
|
||
|
+ }
|
||
|
+
|
||
|
+ for (;;)
|
||
|
+ {
|
||
|
+ EXCEPTION_RECORD rec;
|
||
|
+ NTSTATUS status = wait_semaphore( crit, 5 );
|
||
|
+ timeout -= 5;
|
||
|
+
|
||
|
+ if ( status == STATUS_TIMEOUT )
|
||
|
+ {
|
||
|
+ const char *name = NULL;
|
||
|
+ if (crit_section_has_debuginfo( crit )) name = (char *)crit->DebugInfo->Spare[0];
|
||
|
+ if (!name) name = "?";
|
||
|
+ ERR( "section %p %s wait timed out in thread %04x, blocked by %04x, retrying (60 sec)\n",
|
||
|
+ crit, debugstr_a(name), GetCurrentThreadId(), HandleToULong(crit->OwningThread) );
|
||
|
+ status = wait_semaphore( crit, 60 );
|
||
|
+ timeout -= 60;
|
||
|
+
|
||
|
+ if ( status == STATUS_TIMEOUT && TRACE_ON(relay) )
|
||
|
+ {
|
||
|
+ ERR( "section %p %s wait timed out in thread %04x, blocked by %04x, retrying (5 min)\n",
|
||
|
+ crit, debugstr_a(name), GetCurrentThreadId(), HandleToULong(crit->OwningThread) );
|
||
|
+ status = wait_semaphore( crit, 300 );
|
||
|
+ timeout -= 300;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ if (status == STATUS_WAIT_0) break;
|
||
|
+
|
||
|
+ /* Throw exception only for Wine internal locks */
|
||
|
+ if (!crit_section_has_debuginfo( crit ) || !crit->DebugInfo->Spare[0]) continue;
|
||
|
+
|
||
|
+ /* only throw deadlock exception if configured timeout is reached */
|
||
|
+ if (timeout > 0) continue;
|
||
|
+
|
||
|
+ rec.ExceptionCode = STATUS_POSSIBLE_DEADLOCK;
|
||
|
+ rec.ExceptionFlags = 0;
|
||
|
+ rec.ExceptionRecord = NULL;
|
||
|
+ rec.ExceptionAddress = RtlRaiseException; /* sic */
|
||
|
+ rec.NumberParameters = 1;
|
||
|
+ rec.ExceptionInformation[0] = (ULONG_PTR)crit;
|
||
|
+ RtlRaiseException( &rec );
|
||
|
+ }
|
||
|
+ if (crit_section_has_debuginfo( crit )) crit->DebugInfo->ContentionCount++;
|
||
|
+ return STATUS_SUCCESS;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlpUnWaitCriticalSection (NTDLL.@)
|
||
|
+ */
|
||
|
+NTSTATUS WINAPI RtlpUnWaitCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ NTSTATUS ret;
|
||
|
+
|
||
|
+ /* debug info is cleared by MakeCriticalSectionGlobal */
|
||
|
+ if (!crit_section_has_debuginfo( crit ) ||
|
||
|
+ ((ret = unix_funcs->fast_RtlpUnWaitCriticalSection( crit )) == STATUS_NOT_IMPLEMENTED))
|
||
|
+ {
|
||
|
+ HANDLE sem = get_semaphore( crit );
|
||
|
+ ret = NtReleaseSemaphore( sem, 1, NULL );
|
||
|
+ }
|
||
|
+ if (ret) RtlRaiseStatus( ret );
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+static inline void small_pause(void)
|
||
|
+{
|
||
|
+#ifdef __i386__
|
||
|
+ __asm__ __volatile__( "rep;nop" : : : "memory" );
|
||
|
+#else
|
||
|
+ __asm__ __volatile__( "" : : : "memory" );
|
||
|
+#endif
|
||
|
+}
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlEnterCriticalSection (NTDLL.@)
|
||
|
+ */
|
||
|
+NTSTATUS WINAPI RtlEnterCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ if (crit->SpinCount)
|
||
|
+ {
|
||
|
+ ULONG count;
|
||
|
+
|
||
|
+ if (RtlTryEnterCriticalSection( crit )) return STATUS_SUCCESS;
|
||
|
+ for (count = crit->SpinCount; count > 0; count--)
|
||
|
+ {
|
||
|
+ if (crit->LockCount > 0) break; /* more than one waiter, don't bother spinning */
|
||
|
+ if (crit->LockCount == -1) /* try again */
|
||
|
+ {
|
||
|
+ if (InterlockedCompareExchange( &crit->LockCount, 0, -1 ) == -1) goto done;
|
||
|
+ }
|
||
|
+ small_pause();
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ if (InterlockedIncrement( &crit->LockCount ))
|
||
|
+ {
|
||
|
+ if (crit->OwningThread == ULongToHandle(GetCurrentThreadId()))
|
||
|
+ {
|
||
|
+ crit->RecursionCount++;
|
||
|
+ return STATUS_SUCCESS;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Now wait for it */
|
||
|
+ RtlpWaitForCriticalSection( crit );
|
||
|
+ }
|
||
|
+done:
|
||
|
+ crit->OwningThread = ULongToHandle(GetCurrentThreadId());
|
||
|
+ crit->RecursionCount = 1;
|
||
|
+ return STATUS_SUCCESS;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlTryEnterCriticalSection (NTDLL.@)
|
||
|
+ */
|
||
|
+BOOL WINAPI RtlTryEnterCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ BOOL ret = FALSE;
|
||
|
+ if (InterlockedCompareExchange( &crit->LockCount, 0, -1 ) == -1)
|
||
|
+ {
|
||
|
+ crit->OwningThread = ULongToHandle(GetCurrentThreadId());
|
||
|
+ crit->RecursionCount = 1;
|
||
|
+ ret = TRUE;
|
||
|
+ }
|
||
|
+ else if (crit->OwningThread == ULongToHandle(GetCurrentThreadId()))
|
||
|
+ {
|
||
|
+ InterlockedIncrement( &crit->LockCount );
|
||
|
+ crit->RecursionCount++;
|
||
|
+ ret = TRUE;
|
||
|
+ }
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlIsCriticalSectionLocked (NTDLL.@)
|
||
|
+ */
|
||
|
+BOOL WINAPI RtlIsCriticalSectionLocked( RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ return crit->RecursionCount != 0;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlIsCriticalSectionLockedByThread (NTDLL.@)
|
||
|
+ */
|
||
|
+BOOL WINAPI RtlIsCriticalSectionLockedByThread( RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ return crit->OwningThread == ULongToHandle(GetCurrentThreadId()) &&
|
||
|
+ crit->RecursionCount;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/******************************************************************************
|
||
|
+ * RtlLeaveCriticalSection (NTDLL.@)
|
||
|
+ */
|
||
|
+NTSTATUS WINAPI RtlLeaveCriticalSection( RTL_CRITICAL_SECTION *crit )
|
||
|
+{
|
||
|
+ if (--crit->RecursionCount)
|
||
|
+ {
|
||
|
+ if (crit->RecursionCount > 0) InterlockedDecrement( &crit->LockCount );
|
||
|
+ else ERR( "section %p is not acquired\n", crit );
|
||
|
+ }
|
||
|
+ else
|
||
|
+ {
|
||
|
+ crit->OwningThread = 0;
|
||
|
+ if (InterlockedDecrement( &crit->LockCount ) >= 0)
|
||
|
+ {
|
||
|
+ /* someone is waiting */
|
||
|
+ RtlpUnWaitCriticalSection( crit );
|
||
|
+ }
|
||
|
+ }
|
||
|
+ return STATUS_SUCCESS;
|
||
|
+}
|
||
|
--
|
||
|
2.29.2
|
||
|
|