aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/SkSharedMutex.cpp
blob: 4cf63120676e658f43b1b354c22d27b08eb7d549 (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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkSharedMutex.h"

#include "SkAtomics.h"
#include "SkSemaphore.h"
#include "SkTypes.h"

#if defined(THREAD_SANITIZER)

/* Report that a lock has been created at address "lock". */
#define ANNOTATE_RWLOCK_CREATE(lock) \
    AnnotateRWLockCreate(__FILE__, __LINE__, lock)

/* Report that the lock at address "lock" is about to be destroyed. */
#define ANNOTATE_RWLOCK_DESTROY(lock) \
    AnnotateRWLockDestroy(__FILE__, __LINE__, lock)

/* Report that the lock at address "lock" has been acquired.
   is_w=1 for writer lock, is_w=0 for reader lock. */
#define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) \
    AnnotateRWLockAcquired(__FILE__, __LINE__, lock, is_w)

/* Report that the lock at address "lock" is about to be released. */
#define ANNOTATE_RWLOCK_RELEASED(lock, is_w) \
  AnnotateRWLockReleased(__FILE__, __LINE__, lock, is_w)

#ifdef DYNAMIC_ANNOTATIONS_WANT_ATTRIBUTE_WEAK
# ifdef __GNUC__
#  define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK __attribute__((weak))
# else
/* TODO(glider): for Windows support we may want to change this macro in order
   to prepend __declspec(selectany) to the annotations' declarations. */
#  error weak annotations are not supported for your compiler
# endif
#else
# define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK
#endif

extern "C" {
void AnnotateRWLockCreate(
    const char *file, int line,
    const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
void AnnotateRWLockDestroy(
    const char *file, int line,
    const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
void AnnotateRWLockAcquired(
    const char *file, int line,
    const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
void AnnotateRWLockReleased(
    const char *file, int line,
    const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
}

#else

#define ANNOTATE_RWLOCK_CREATE(lock)
#define ANNOTATE_RWLOCK_DESTROY(lock)
#define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w)
#define ANNOTATE_RWLOCK_RELEASED(lock, is_w)

#endif

// The fQueueCounts fields holds many counts in an int32_t in order to make managing them atomic.
// These three counts must be the same size, so each gets 10 bits. The 10 bits represent
// the log of the count which is 1024.
//
// The three counts held in fQueueCounts are:
// * Shared - the number of shared lock holders currently running.
// * WaitingExclusive - the number of threads waiting for an exclusive lock.
// * WaitingShared - the number of threads waiting to run while waiting for an exclusive thread
//   to finish.
static const int kLogThreadCount = 10;

enum {
    kSharedOffset          = (0 * kLogThreadCount),
    kWaitingExlusiveOffset = (1 * kLogThreadCount),
    kWaitingSharedOffset   = (2 * kLogThreadCount),
    kSharedMask            = ((1 << kLogThreadCount) - 1) << kSharedOffset,
    kWaitingExclusiveMask  = ((1 << kLogThreadCount) - 1) << kWaitingExlusiveOffset,
    kWaitingSharedMask     = ((1 << kLogThreadCount) - 1) << kWaitingSharedOffset,
};

SkSharedMutex::SkSharedMutex() : fQueueCounts(0) { ANNOTATE_RWLOCK_CREATE(this); }
SkSharedMutex::~SkSharedMutex() {  ANNOTATE_RWLOCK_DESTROY(this); }
void SkSharedMutex::acquire() {
    // Increment the count of exclusive queue waiters.
    int32_t oldQueueCounts = fQueueCounts.fetch_add(1 << kWaitingExlusiveOffset,
                                                    sk_memory_order_acquire);

    // If there are no other exclusive waiters and no shared threads are running then run
    // else wait.
    if ((oldQueueCounts & kWaitingExclusiveMask) > 0 || (oldQueueCounts & kSharedMask) > 0) {
        fExclusiveQueue.wait();
    }
    ANNOTATE_RWLOCK_ACQUIRED(this, 1);
}

void SkSharedMutex::release() {
    ANNOTATE_RWLOCK_RELEASED(this, 1);

    int32_t oldQueueCounts = fQueueCounts.load(sk_memory_order_relaxed);
    int32_t waitingShared;
    int32_t newQueueCounts;
    do {
        newQueueCounts = oldQueueCounts;

        // Decrement exclusive waiters.
        newQueueCounts -= 1 << kWaitingExlusiveOffset;

        // The number of threads waiting to acquire a shared lock.
        waitingShared = (oldQueueCounts & kWaitingSharedMask) >> kWaitingSharedOffset;

        // If there are any move the counts of all the shared waiters to actual shared. They are
        // going to run next.
        if (waitingShared > 0) {

            // Set waiting shared to zero.
            newQueueCounts &= ~kWaitingSharedMask;

            // Because this is the exclusive release, then there are zero readers. So, the bits
            // for shared locks should be zero. Since those bits are zero, we can just |= in the
            // waitingShared count instead of clearing with an &= and then |= the count.
            newQueueCounts |= waitingShared << kSharedOffset;
        }

    } while (!fQueueCounts.compare_exchange(&oldQueueCounts, newQueueCounts,
                                            sk_memory_order_release, sk_memory_order_relaxed));

    if (waitingShared > 0) {
        // Run all the shared.
        fSharedQueue.signal(waitingShared);
    } else if ((newQueueCounts & kWaitingExclusiveMask) > 0) {
        // Run a single exclusive waiter.
        fExclusiveQueue.signal();
    }
}

#ifdef SK_DEBUG
void SkSharedMutex::assertHeld() const {
    int32_t queueCounts = fQueueCounts.load(sk_memory_order_relaxed);
    // These are very loose asserts about the mutex being held exclusively.
    SkASSERTF(0 == (queueCounts & kSharedMask),
              "running shared: %d, exclusive: %d, waiting shared: %d",
              (queueCounts & kSharedMask) >> kSharedOffset,
              (queueCounts & kWaitingExclusiveMask) >> kWaitingExlusiveOffset,
              (queueCounts & kWaitingSharedMask) >> kWaitingSharedOffset);
    SkASSERTF((queueCounts & kWaitingExclusiveMask) > 0,
              "running shared: %d, exclusive: %d, waiting shared: %d",
              (queueCounts & kSharedMask) >> kSharedOffset,
              (queueCounts & kWaitingExclusiveMask) >> kWaitingExlusiveOffset,
              (queueCounts & kWaitingSharedMask) >> kWaitingSharedOffset);
}
#endif

void SkSharedMutex::acquireShared() {
    int32_t oldQueueCounts = fQueueCounts.load(sk_memory_order_relaxed);
    int32_t newQueueCounts;
    do {
        newQueueCounts = oldQueueCounts;
        // If there are waiting exclusives then this shared lock waits else it runs.
        if ((newQueueCounts & kWaitingExclusiveMask) > 0) {
            newQueueCounts += 1 << kWaitingSharedOffset;
        } else {
            newQueueCounts += 1 << kSharedOffset;
        }
    } while (!fQueueCounts.compare_exchange(&oldQueueCounts, newQueueCounts,
                                            sk_memory_order_acquire, sk_memory_order_relaxed));

    // If there are waiting exclusives, then this shared waits until after it runs.
    if ((newQueueCounts & kWaitingExclusiveMask) > 0) {
        fSharedQueue.wait();
    }
    ANNOTATE_RWLOCK_ACQUIRED(this, 0);
   
}

void SkSharedMutex::releaseShared() {
    ANNOTATE_RWLOCK_RELEASED(this, 0);

    // Decrement the shared count.
    int32_t oldQueueCounts = fQueueCounts.fetch_add(~0U << kSharedOffset,
                                                    sk_memory_order_release);

    // If shared count is going to zero (because the old count == 1) and there are exclusive
    // waiters, then run a single exclusive waiter.
    if (((oldQueueCounts & kSharedMask) >> kSharedOffset) == 1
        && (oldQueueCounts & kWaitingExclusiveMask) > 0) {
        fExclusiveQueue.signal();
    }
}

#ifdef SK_DEBUG
void SkSharedMutex::assertHeldShared() const {
    int32_t queueCounts = fQueueCounts.load(sk_memory_order_relaxed);
    // A very loose assert about the mutex being shared.
    SkASSERTF((queueCounts & kSharedMask) > 0,
              "running shared: %d, exclusive: %d, waiting shared: %d",
              (queueCounts & kSharedMask) >> kSharedOffset,
              (queueCounts & kWaitingExclusiveMask) >> kWaitingExlusiveOffset,
              (queueCounts & kWaitingSharedMask) >> kWaitingSharedOffset);
}

#endif