Process Hacker
sync.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * misc. synchronization utilities
4  *
5  * Copyright (C) 2010-2015 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /*
24  * This file contains code for several synchronization objects.
25  *
26  * Event. This is a lightweight notification event object that does not
27  * create a kernel event object until needed. Additionally the kernel
28  * event object is automatically freed when no longer needed. Note that
29  * PhfResetEvent is NOT thread-safe.
30  *
31  * Barrier. This is a non-traditional implementation of a barrier, built
32  * on the wake event object. I have identified three types of participants
33  * in this process:
34  * 1. The slaves, who wait for the master to release them. This is the
35  * main mechanism through which the threads are synchronized.
36  * 2. The master, who is the last thread to wait on the barrier. This thread
37  * triggers the waking process, and waits until all slaves have woken.
38  * 3. The observers, who are simply threads which were slaves before, were
39  * woken, and have tried to wait on the barrier again before all other
40  * slaves have woken.
41  *
42  * Rundown protection. This object allows a thread to wait until all other
43  * threads have finished using a particular resource before freeing the
44  * resource.
45  *
46  * Init-once. This is a lightweight one-time initialization mechanism which
47  * uses the event object for any required blocking. The overhead is very
48  * small - only a single inlined comparison.
49  */
50 
51 #include <phbase.h>
52 
59  _Out_ PPH_EVENT Event
60  )
61 {
62  Event->Value = PH_EVENT_REFCOUNT_INC;
63  Event->EventHandle = NULL;
64 }
65 
73  _Inout_ PPH_EVENT Event,
74  _In_opt_ HANDLE EventHandle
75  )
76 {
77  ULONG_PTR value;
78 
79  value = _InterlockedExchangeAddPointer((PLONG_PTR)&Event->Value, -PH_EVENT_REFCOUNT_INC);
80 
81  // See if the reference count has become 0.
82  if (((value >> PH_EVENT_REFCOUNT_SHIFT) & PH_EVENT_REFCOUNT_MASK) - 1 == 0)
83  {
84  if (EventHandle)
85  {
86  NtClose(EventHandle);
87  Event->EventHandle = NULL;
88  }
89  }
90 }
91 
97 FORCEINLINE VOID PhpReferenceEvent(
98  _Inout_ PPH_EVENT Event
99  )
100 {
101  _InterlockedExchangeAddPointer((PLONG_PTR)&Event->Value, PH_EVENT_REFCOUNT_INC);
102 }
103 
111  _Inout_ PPH_EVENT Event
112  )
113 {
114  HANDLE eventHandle;
115 
116  // Only proceed if the event isn't set already.
117  if (!_InterlockedBitTestAndSetPointer((PLONG_PTR)&Event->Value, PH_EVENT_SET_SHIFT))
118  {
119  eventHandle = Event->EventHandle;
120 
121  if (eventHandle)
122  {
123  NtSetEvent(eventHandle, NULL);
124  }
125 
126  PhpDereferenceEvent(Event, eventHandle);
127  }
128 }
129 
143  _Inout_ PPH_EVENT Event,
144  _In_opt_ PLARGE_INTEGER Timeout
145  )
146 {
147  BOOLEAN result;
148  ULONG_PTR value;
149  HANDLE eventHandle;
150 
151  value = Event->Value;
152 
153  // Shortcut: if the event is set, return immediately.
154  if (value & PH_EVENT_SET)
155  return TRUE;
156 
157  // Shortcut: if the timeout is 0, return immediately
158  // if the event isn't set.
159  if (Timeout && Timeout->QuadPart == 0)
160  return FALSE;
161 
162  // Prevent the event from being invalidated.
163  PhpReferenceEvent(Event);
164 
165  eventHandle = Event->EventHandle;
166 
167  if (!eventHandle)
168  {
169  NtCreateEvent(&eventHandle, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE);
170  assert(eventHandle);
171 
172  // Try to set the event handle to our event.
174  &Event->EventHandle,
175  eventHandle,
176  NULL
177  ) != NULL)
178  {
179  // Someone else set the event before we did.
180  NtClose(eventHandle);
181  eventHandle = Event->EventHandle;
182  }
183  }
184 
185  // Essential: check the event one last time to see if
186  // it is set.
187  if (!(Event->Value & PH_EVENT_SET))
188  {
189  result = NtWaitForSingleObject(eventHandle, FALSE, Timeout) == STATUS_WAIT_0;
190  }
191  else
192  {
193  result = TRUE;
194  }
195 
196  PhpDereferenceEvent(Event, eventHandle);
197 
198  return result;
199 }
200 
211  _Inout_ PPH_EVENT Event
212  )
213 {
214  assert(!Event->EventHandle);
215 
216  if (PhTestEvent(Event))
217  Event->Value = PH_EVENT_REFCOUNT_INC;
218 }
219 
221  _Out_ PPH_BARRIER Barrier,
222  _In_ ULONG_PTR Target
223  )
224 {
225  Barrier->Value = Target << PH_BARRIER_TARGET_SHIFT;
226  PhInitializeQueuedLock(&Barrier->WakeEvent);
227 }
228 
230  _Inout_ PPH_BARRIER Barrier,
231  _In_ ULONG Role,
232  _In_ BOOLEAN Spin
233  )
234 {
235  PH_QUEUED_WAIT_BLOCK waitBlock;
236  ULONG_PTR cancel;
237 
238  PhQueueWakeEvent(&Barrier->WakeEvent, &waitBlock);
239 
240  cancel = 0;
241 
242  switch (Role)
243  {
244  case PH_BARRIER_MASTER:
245  cancel = ((Barrier->Value >> PH_BARRIER_COUNT_SHIFT) & PH_BARRIER_COUNT_MASK) == 1;
246  break;
247  case PH_BARRIER_SLAVE:
248  cancel = Barrier->Value & PH_BARRIER_WAKING;
249  break;
250  case PH_BARRIER_OBSERVER:
251  cancel = !(Barrier->Value & PH_BARRIER_WAKING);
252  break;
253  default:
255  }
256 
257  if (cancel)
258  {
259  PhSetWakeEvent(&Barrier->WakeEvent, &waitBlock);
260  return;
261  }
262 
263  PhWaitForWakeEvent(&Barrier->WakeEvent, &waitBlock, Spin, NULL);
264 }
265 
282  _Inout_ PPH_BARRIER Barrier,
283  _In_ BOOLEAN Spin
284  )
285 {
286  ULONG_PTR value;
287  ULONG_PTR newValue;
288  ULONG_PTR count;
289  ULONG_PTR target;
290 
291  value = Barrier->Value;
292 
293  while (TRUE)
294  {
295  if (!(value & PH_BARRIER_WAKING))
296  {
297  count = (value >> PH_BARRIER_COUNT_SHIFT) & PH_BARRIER_COUNT_MASK;
298  target = (value >> PH_BARRIER_TARGET_SHIFT) & PH_BARRIER_TARGET_MASK;
299  assert(count != target);
300 
301  count++;
302 
303  if (count != target)
304  newValue = value + PH_BARRIER_COUNT_INC;
305  else
306  newValue = value + PH_BARRIER_COUNT_INC + PH_BARRIER_WAKING;
307 
308  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
309  (PVOID *)&Barrier->Value,
310  (PVOID)newValue,
311  (PVOID)value
312  )) == value)
313  {
314  if (count != target)
315  {
316  // Wait for the master signal (the last thread to reach the barrier).
317  // Once we get it, decrement the count to allow the master to continue.
318 
319  do
320  {
321  PhpBlockOnBarrier(Barrier, PH_BARRIER_SLAVE, Spin);
322  } while (!(Barrier->Value & PH_BARRIER_WAKING));
323 
324  value = _InterlockedExchangeAddPointer((PLONG_PTR)&Barrier->Value, -PH_BARRIER_COUNT_INC);
325 
326  if (((value >> PH_BARRIER_COUNT_SHIFT) & PH_BARRIER_COUNT_MASK) - 1 == 1)
327  {
328  PhSetWakeEvent(&Barrier->WakeEvent, NULL); // for the master
329  }
330 
331  return FALSE;
332  }
333  else
334  {
335  // We're the last one to reach the barrier, so we become the master.
336  // Wake the slaves and wait for them to decrease the count to 1. This
337  // is so that we know the slaves have woken and we don't clear the waking
338  // bit before they wake.
339 
340  PhSetWakeEvent(&Barrier->WakeEvent, NULL); // for slaves
341 
342  do
343  {
344  PhpBlockOnBarrier(Barrier, PH_BARRIER_MASTER, Spin);
345  } while (((Barrier->Value >> PH_BARRIER_COUNT_SHIFT) & PH_BARRIER_COUNT_MASK) != 1);
346 
347  _InterlockedExchangeAddPointer((PLONG_PTR)&Barrier->Value, -(PH_BARRIER_WAKING + PH_BARRIER_COUNT_INC));
348  PhSetWakeEvent(&Barrier->WakeEvent, NULL); // for observers
349 
350  return TRUE;
351  }
352  }
353  }
354  else
355  {
356  // We're too early; other threads are still waking. Wait for them to finish.
357 
358  PhpBlockOnBarrier(Barrier, PH_BARRIER_OBSERVER, Spin);
359  newValue = Barrier->Value;
360  }
361 
362  value = newValue;
363  }
364 }
365 
367  _Out_ PPH_RUNDOWN_PROTECT Protection
368  )
369 {
370  Protection->Value = 0;
371 }
372 
374  _Inout_ PPH_RUNDOWN_PROTECT Protection
375  )
376 {
377  ULONG_PTR value;
378 
379  // Increment the reference count only if rundown
380  // has not started.
381 
382  while (TRUE)
383  {
384  value = Protection->Value;
385 
386  if (value & PH_RUNDOWN_ACTIVE)
387  return FALSE;
388 
389  if ((ULONG_PTR)_InterlockedCompareExchangePointer(
390  (PVOID *)&Protection->Value,
391  (PVOID)(value + PH_RUNDOWN_REF_INC),
392  (PVOID)value
393  ) == value)
394  return TRUE;
395  }
396 }
397 
399  _Inout_ PPH_RUNDOWN_PROTECT Protection
400  )
401 {
402  ULONG_PTR value;
403 
404  while (TRUE)
405  {
406  value = Protection->Value;
407 
408  if (value & PH_RUNDOWN_ACTIVE)
409  {
410  PPH_RUNDOWN_WAIT_BLOCK waitBlock;
411 
412  // Since rundown is active, the reference count has been
413  // moved to the waiter's wait block. If we are the last
414  // user, we must wake up the waiter.
415 
416  waitBlock = (PPH_RUNDOWN_WAIT_BLOCK)(value & ~PH_RUNDOWN_ACTIVE);
417 
418  if (_InterlockedDecrementPointer(&waitBlock->Count) == 0)
419  {
420  PhSetEvent(&waitBlock->WakeEvent);
421  }
422 
423  break;
424  }
425  else
426  {
427  // Decrement the reference count normally.
428 
429  if ((ULONG_PTR)_InterlockedCompareExchangePointer(
430  (PVOID *)&Protection->Value,
431  (PVOID)(value - PH_RUNDOWN_REF_INC),
432  (PVOID)value
433  ) == value)
434  break;
435  }
436  }
437 }
438 
440  _Inout_ PPH_RUNDOWN_PROTECT Protection
441  )
442 {
443  ULONG_PTR value;
444  ULONG_PTR count;
445  PH_RUNDOWN_WAIT_BLOCK waitBlock;
446  BOOLEAN waitBlockInitialized;
447 
448  // Fast path. If the reference count is 0 or
449  // rundown has already been completed, return.
450  value = (ULONG_PTR)_InterlockedCompareExchangePointer(
451  (PVOID *)&Protection->Value,
452  (PVOID)PH_RUNDOWN_ACTIVE,
453  (PVOID)0
454  );
455 
456  if (value == 0 || value == PH_RUNDOWN_ACTIVE)
457  return;
458 
459  waitBlockInitialized = FALSE;
460 
461  while (TRUE)
462  {
463  value = Protection->Value;
464  count = value >> PH_RUNDOWN_REF_SHIFT;
465 
466  // Initialize the wait block if necessary.
467  if (count != 0 && !waitBlockInitialized)
468  {
469  PhInitializeEvent(&waitBlock.WakeEvent);
470  waitBlockInitialized = TRUE;
471  }
472 
473  // Save the existing reference count.
474  waitBlock.Count = count;
475 
476  if ((ULONG_PTR)_InterlockedCompareExchangePointer(
477  (PVOID *)&Protection->Value,
478  (PVOID)((ULONG_PTR)&waitBlock | PH_RUNDOWN_ACTIVE),
479  (PVOID)value
480  ) == value)
481  {
482  if (count != 0)
483  PhWaitForEvent(&waitBlock.WakeEvent, NULL);
484 
485  break;
486  }
487  }
488 }
489 
491  _Out_ PPH_INITONCE InitOnce
492  )
493 {
494  PhInitializeEvent(&InitOnce->Event);
495 }
496 
498  _Inout_ PPH_INITONCE InitOnce
499  )
500 {
502  return TRUE;
503 
504  PhWaitForEvent(&InitOnce->Event, NULL);
505 
506  return FALSE;
507 }
508 
510  _Inout_ PPH_INITONCE InitOnce
511  )
512 {
513  PhSetEvent(&InitOnce->Event);
514 }