summaryrefslogtreecommitdiffstats
path: root/winsup/cygwin/sync.cc
blob: 821c214f7f09df4eee8eeb9df1fbc0ad09bb64b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/* sync.cc: Synchronization functions for cygwin.

   This file implements the methods for controlling the "muto" class
   which is intended to operate similarly to a mutex but attempts to
   avoid making expensive calls to the kernel.

   Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009, 2010, 2011, 2012
   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. */

#include "winsup.h"
#include "miscfuncs.h"
#include "sync.h"
#include "thread.h"
#include "cygtls.h"

#undef WaitForSingleObject

muto NO_COPY lock_process::locker;

void
muto::grab ()
{
  tls = &_my_tls;
}

/* Constructor */
muto *
muto::init (const char *s)
{
  char *already_exists = (char *) InterlockedExchangePointer ((PVOID *) &name,
							      (PVOID) s);
  if (already_exists)
    while (!bruteforce)
      yield ();
  else
    {
      waiters = -1;
      /* Create event which is used in the fallback case when blocking is necessary */
      bruteforce = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL);
      if (!bruteforce)
	  api_fatal ("couldn't allocate muto '%s', %E", s);
    }

  return this;
}

#if 0 /* FIXME: Do we need this? mutos aren't destroyed until process exit */
/* Destructor (racy?) */
muto::~muto ()
{
  while (visits)
    release ();

  HANDLE h = bruteforce;
  bruteforce = NULL;
  /* Just need to close the event handle */
  if (h)
    CloseHandle (h);
}
#endif

/* Acquire the lock.  Argument is the number of milliseconds to wait for
   the lock.  Multiple visits from the same thread are allowed and should
   be handled correctly.

   Note: The goal here is to minimize, as much as possible, calls to the
   OS.  Hence the use of InterlockedIncrement, etc., rather than (much) more
   expensive OS mutexes.  */
int
muto::acquire (DWORD ms)
{
  void *this_tls = &_my_tls;

  if (tls != this_tls)
    {
      /* Increment the waiters part of the class.  Need to do this first to
	 avoid potential races. */
      LONG was_waiting = ms ? InterlockedIncrement (&waiters) : 0;

      while (was_waiting || InterlockedExchange (&sync, 1) != 0)
	switch (WaitForSingleObject (bruteforce, ms))
	    {
	    case WAIT_OBJECT_0:
	      was_waiting = 0;
	      break;
	    default:
	      return 0;	/* failed. */
	    }

      /* Have to do it this way to avoid a race */
      if (!ms)
	InterlockedIncrement (&waiters);

      tls = this_tls;	/* register this thread. */
    }

  return ++visits;	/* Increment visit count. */
}

bool
muto::acquired ()
{
  return tls == &_my_tls;
}

/* Return the muto lock.  Needs to be called once per every acquire. */
int
muto::release (_cygtls *this_tls)
{
  if (tls != this_tls || !visits)
    {
      SetLastError (ERROR_NOT_OWNER);	/* Didn't have the lock. */
      return 0;	/* failed. */
    }

  /* FIXME: Need to check that other thread has not exited, too. */
  if (!--visits)
    {
      tls = 0;		/* We were the last unlocker. */
      InterlockedExchange (&sync, 0); /* Reset trigger. */
      /* This thread had incremented waiters but had never decremented it.
	 Decrement it now.  If it is >= 0 then there are possibly other
	 threads waiting for the lock, so trigger bruteforce.  */
      if (InterlockedDecrement (&waiters) >= 0)
	SetEvent (bruteforce); /* Wake up one of the waiting threads */
      else if (*name == '!')
	{
	  CloseHandle (bruteforce);	/* If *name == '!' and there are no
					   other waiters, then this is the
					   last time this muto will ever be
					   used, so close the handle. */
#ifdef DEBUGGING
	  bruteforce = NULL;
#endif
	}
    }

  return 1;	/* success. */
}