/* bsd_mutex.cc Copyright 2003 Red Hat Inc. This file is part of Cygwin. This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ #ifdef __OUTSIDE_CYGWIN__ #include "woutsup.h" #include "cygerrno.h" #define _KERNEL 1 #define __BSD_VISIBLE 1 #include #include "process.h" #include "cygserver_ipc.h" /* A BSD kernel global mutex. */ struct mtx Giant; void mtx_init (mtx *m, const char *name, const void *, int) { m->name = name; m->owner = 0; /* Can't use Windows Mutexes here since Windows Mutexes are only unlockable by the lock owner. */ m->h = CreateSemaphore (NULL, 1, 1, NULL); if (!m->h) panic ("couldn't allocate %s mutex, %E\n", name); } void _mtx_lock (mtx *m, DWORD winpid, const char *file, int line) { _log (file, line, LOG_DEBUG, "Try locking mutex %s", m->name); if (WaitForSingleObject (m->h, INFINITE) != WAIT_OBJECT_0) _panic (file, line, "wait for %s in %d failed, %E", m->name, winpid); m->owner = winpid; _log (file, line, LOG_DEBUG, "Locked mutex %s", m->name); } int mtx_owned (mtx *m) { return m->owner > 0; } void _mtx_assert (mtx *m, int what, const char *file, int line) { switch (what) { case MA_OWNED: if (!mtx_owned (m)) _panic (file, line, "Mutex %s not owned", m->name); break; case MA_NOTOWNED: if (mtx_owned (m)) _panic (file, line, "Mutex %s is owned", m->name); break; default: break; } } void _mtx_unlock (mtx *m, const char *file, int line) { m->owner = 0; /* Cautiously check if mtx_destroy has been called (shutdown). In that case, m->h is NULL. */ if (m->h && !ReleaseSemaphore (m->h, 1, NULL)) { /* Check if the semaphore was already on it's max value. In this case, ReleaseSemaphore returns FALSE with an error code which *sic* depends on the OS. */ if ( (!wincap.is_winnt () && GetLastError () != ERROR_INVALID_PARAMETER) || (wincap.is_winnt () && GetLastError () != ERROR_TOO_MANY_POSTS)) _panic (file, line, "release of mutex %s failed, %E", m->name); } _log (file, line, LOG_DEBUG, "Unlocked mutex %s", m->name); } void mtx_destroy (mtx *m) { HANDLE tmp = m->h; m->h = NULL; if (tmp) CloseHandle (tmp); } /* * Helper functions for msleep/wakeup. */ static char * msleep_event_name (void *ident, char *name) { if (wincap.has_terminal_services ()) __small_sprintf (name, "Global\\cygserver.msleep.evt.%08x", ident); else __small_sprintf (name, "cygserver.msleep.evt.%08x", ident); return name; } static int win_priority (int priority) { int p = (int)((priority) & PRIO_MASK) - PZERO; /* Generating a valid priority value is a bit tricky. The only valid values on 9x and NT4 are -15, -2, -1, 0, 1, 2, 15. */ switch (p) { case -15: case -14: case -13: case -12: case -11: return THREAD_PRIORITY_IDLE; case -10: case -9: case -8: case -7: case -6: return THREAD_PRIORITY_LOWEST; case -5: case -4: case -3: case -2: case -1: return THREAD_PRIORITY_BELOW_NORMAL; case 0: return THREAD_PRIORITY_NORMAL; case 1: case 2: case 3: case 4: case 5: return THREAD_PRIORITY_ABOVE_NORMAL; case 6: case 7: case 8: case 9: case 10: return THREAD_PRIORITY_HIGHEST; case 11: case 12: case 13: case 14: case 15: return THREAD_PRIORITY_TIME_CRITICAL; } return THREAD_PRIORITY_NORMAL; } /* * Sets the thread priority, returns the old priority. */ static int set_priority (int priority) { int old_prio = GetThreadPriority (GetCurrentThread ()); if (!SetThreadPriority (GetCurrentThread (), win_priority (priority))) log (LOG_WARNING, "Warning: Setting thread priority to %d failed with error %lu\n", win_priority (priority), GetLastError ()); return old_prio; } /* * Original description from BSD code: * * General sleep call. Suspends the current process until a wakeup is * performed on the specified identifier. The process will then be made * runnable with the specified priority. Sleeps at most timo/hz seconds * (0 means no timeout). If pri includes PCATCH flag, signals are checked * before and after sleeping, else signals are not checked. Returns 0 if * awakened, EWOULDBLOCK if the timeout expires. If PCATCH is set and a * signal needs to be delivered, ERESTART is returned if the current system * call should be restarted if possible, and EINTR is returned if the system * call should be interrupted by the signal (return EINTR). * * The mutex argument is exited before the caller is suspended, and * entered before msleep returns. If priority includes the PDROP * flag the mutex is not entered before returning. */ static HANDLE msleep_glob_evt; void msleep_init (void) { msleep_glob_evt = CreateEvent (NULL, TRUE, FALSE, NULL); if (!msleep_glob_evt) panic ("CreateEvent in msleep_init failed: %E"); } int _msleep (void *ident, struct mtx *mtx, int priority, const char *wmesg, int timo, struct thread *td) { int ret = -1; char name[64]; msleep_event_name (ident, name); HANDLE evt = OpenEvent (EVENT_ALL_ACCESS, FALSE, name); if (!evt) evt = CreateEvent (NULL, TRUE, FALSE, name); if (!evt) panic ("CreateEvent in msleep (%s) failed: %E", wmesg); if (mtx) mtx_unlock (mtx); int old_priority = set_priority (priority); HANDLE obj[4] = { evt, msleep_glob_evt, td->client->handle (), td->client->signal_arrived () }; /* PCATCH handling. If PCATCH is given and signal_arrived is a valid handle, then it's used in the WaitFor call and EINTR is returned. */ int obj_cnt = 3; if ((priority & PCATCH) && td->client->signal_arrived () != INVALID_HANDLE_VALUE) obj_cnt = 4; td->client->release (); switch (WaitForMultipleObjects (obj_cnt, obj, FALSE, timo ?: INFINITE)) { case WAIT_OBJECT_0: /* wakeup() has been called. */ ret = 0; break; case WAIT_OBJECT_0 + 1: /* Shutdown event (triggered by wakeup_all). */ priority |= PDROP; /*FALLTHRU*/ case WAIT_OBJECT_0 + 2: /* The dependent process has exited. */ ret = EIDRM; break; case WAIT_OBJECT_0 + 3: /* Signal for calling process arrived. */ ret = EINTR; break; case WAIT_TIMEOUT: ret = EWOULDBLOCK; break; default: panic ("wait in msleep (%s) failed, %E", wmesg); break; } #if 0 /* Dismiss event before entering mutex. */ /* CV 2004-09-06, Don't dismiss for now. TODO: Dismissing was meant to solve a problem with heavy load but there's no proof that it helps. On the contrary, it breaks msgtest in the testsuite. As long as I don't get a testcase to track that down, I'll keep it that way. */ ResetEvent (evt); #endif CloseHandle (evt); td->client->hold (); set_priority (old_priority); if (mtx && !(priority & PDROP)) mtx_lock (mtx); return ret; } /* * Make all threads sleeping on the specified identifier runnable. */ int wakeup (void *ident) { char name[64]; msleep_event_name (ident, name); HANDLE evt = OpenEvent (EVENT_MODIFY_STATE, FALSE, name); if (!evt) { /* Another round of different error codes returned by 9x and NT systems. Oh boy... */ if ( (!wincap.is_winnt () && GetLastError () != ERROR_INVALID_NAME) || (wincap.is_winnt () && GetLastError () != ERROR_FILE_NOT_FOUND)) panic ("OpenEvent (%s) in wakeup failed: %E", name); } if (evt) { if (!SetEvent (evt)) panic ("SetEvent (%s) in wakeup failed, %E", name); CloseHandle (evt); } return 0; } /* * Wakeup all sleeping threads. Only called in the context of cygserver * shutdown. */ void wakeup_all (void) { SetEvent (msleep_glob_evt); } #endif /* __OUTSIDE_CYGWIN__ */