1/*
2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include <RWLockManager.h>
7
8#include <AutoLocker.h>
9
10#include <syscalls.h>
11#include <user_thread.h>
12
13
14RWLockable::RWLockable()
15	:
16	fOwner(-1),
17	fOwnerCount(0),
18	fReaderCount(0)
19{
20}
21
22
23RWLockManager::RWLockManager()
24	:
25	fLock("r/w lock manager")
26{
27}
28
29
30RWLockManager::~RWLockManager()
31{
32}
33
34
35bool
36RWLockManager::ReadLock(RWLockable* lockable)
37{
38	AutoLocker<RWLockManager> locker(this);
39
40	if (lockable->fWaiters.IsEmpty()) {
41		lockable->fReaderCount++;
42		return true;
43	}
44
45	return _Wait(lockable, false, B_INFINITE_TIMEOUT) == B_OK;
46}
47
48
49bool
50RWLockManager::TryReadLock(RWLockable* lockable)
51{
52	AutoLocker<RWLockManager> locker(this);
53
54	if (lockable->fWaiters.IsEmpty()) {
55		lockable->fReaderCount++;
56		return true;
57	}
58
59	return false;
60}
61
62
63status_t
64RWLockManager::ReadLockWithTimeout(RWLockable* lockable, bigtime_t timeout)
65{
66	AutoLocker<RWLockManager> locker(this);
67
68	if (lockable->fWaiters.IsEmpty()) {
69		lockable->fReaderCount++;
70		return B_OK;
71	}
72
73	return _Wait(lockable, false, timeout);
74}
75
76
77void
78RWLockManager::ReadUnlock(RWLockable* lockable)
79{
80	AutoLocker<RWLockManager> locker(this);
81
82	if (lockable->fReaderCount <= 0) {
83		debugger("RWLockManager::ReadUnlock(): Not read-locked!");
84		return;
85	}
86
87	if (--lockable->fReaderCount == 0)
88		_Unblock(lockable);
89}
90
91
92bool
93RWLockManager::WriteLock(RWLockable* lockable)
94{
95	AutoLocker<RWLockManager> locker(this);
96
97	thread_id thread = find_thread(NULL);
98
99	if (lockable->fOwner == thread) {
100		lockable->fOwnerCount++;
101		return true;
102	}
103
104	if (lockable->fReaderCount == 0 && lockable->fWaiters.IsEmpty()) {
105		lockable->fOwnerCount = 1;
106		lockable->fOwner = find_thread(NULL);
107		return true;
108	}
109
110	return _Wait(lockable, true, B_INFINITE_TIMEOUT) == B_OK;
111}
112
113
114bool
115RWLockManager::TryWriteLock(RWLockable* lockable)
116{
117	AutoLocker<RWLockManager> locker(this);
118
119	thread_id thread = find_thread(NULL);
120
121	if (lockable->fOwner == thread) {
122		lockable->fOwnerCount++;
123		return true;
124	}
125
126	if (lockable->fReaderCount == 0 && lockable->fWaiters.IsEmpty()) {
127		lockable->fOwnerCount++;
128		lockable->fOwner = thread;
129		return true;
130	}
131
132	return false;
133}
134
135
136status_t
137RWLockManager::WriteLockWithTimeout(RWLockable* lockable, bigtime_t timeout)
138{
139	AutoLocker<RWLockManager> locker(this);
140
141	thread_id thread = find_thread(NULL);
142
143	if (lockable->fOwner == thread) {
144		lockable->fOwnerCount++;
145		return B_OK;
146	}
147
148	if (lockable->fReaderCount == 0 && lockable->fWaiters.IsEmpty()) {
149		lockable->fOwnerCount++;
150		lockable->fOwner = thread;
151		return B_OK;
152	}
153
154	return _Wait(lockable, true, timeout);
155}
156
157
158void
159RWLockManager::WriteUnlock(RWLockable* lockable)
160{
161	AutoLocker<RWLockManager> locker(this);
162
163	if (find_thread(NULL) != lockable->fOwner) {
164		debugger("RWLockManager::WriteUnlock(): Not write-locked by calling "
165			"thread!");
166		return;
167	}
168
169	if (--lockable->fOwnerCount == 0) {
170		lockable->fOwner = -1;
171		_Unblock(lockable);
172	}
173}
174
175
176status_t
177RWLockManager::_Wait(RWLockable* lockable, bool writer, bigtime_t timeout)
178{
179	if (timeout == 0)
180		return B_TIMED_OUT;
181
182	// enqueue a waiter
183	RWLockable::Waiter waiter(writer);
184	lockable->fWaiters.Add(&waiter);
185	waiter.queued = true;
186	get_user_thread()->wait_status = 1;
187
188	// wait
189	Unlock();
190
191	status_t error;
192	do {
193		error = _kern_block_thread(
194			timeout >= 0 ? B_RELATIVE_TIMEOUT : 0, timeout);
195			// TODO: When interrupted we should adjust the timeout, respectively
196			// convert to an absolute timeout in the first place!
197	} while (error == B_INTERRUPTED);
198
199	Lock();
200
201	if (!waiter.queued)
202		return waiter.status;
203
204	// we're still queued, which means an error (timeout, interrupt)
205	// occurred
206	lockable->fWaiters.Remove(&waiter);
207
208	_Unblock(lockable);
209
210	return error;
211}
212
213
214void
215RWLockManager::_Unblock(RWLockable* lockable)
216{
217	// Check whether there any waiting threads at all and whether anyone
218	// has the write lock
219	RWLockable::Waiter* waiter = lockable->fWaiters.Head();
220	if (waiter == NULL || lockable->fOwner >= 0)
221		return;
222
223	// writer at head of queue?
224	if (waiter->writer) {
225		if (lockable->fReaderCount == 0) {
226			waiter->status = B_OK;
227			waiter->queued = false;
228			lockable->fWaiters.Remove(waiter);
229			lockable->fOwner = waiter->thread;
230			lockable->fOwnerCount = 1;
231
232			_kern_unblock_thread(waiter->thread, B_OK);
233		}
234		return;
235	}
236
237	// wake up one or more readers -- we unblock more than one reader at
238	// a time to save trips to the kernel
239	while (!lockable->fWaiters.IsEmpty()
240			&& !lockable->fWaiters.Head()->writer) {
241		static const int kMaxReaderUnblockCount = 128;
242		thread_id readers[kMaxReaderUnblockCount];
243		int readerCount = 0;
244
245		while (readerCount < kMaxReaderUnblockCount
246				&& (waiter = lockable->fWaiters.Head()) != NULL
247				&& !waiter->writer) {
248			waiter->status = B_OK;
249			waiter->queued = false;
250			lockable->fWaiters.Remove(waiter);
251
252			readers[readerCount++] = waiter->thread;
253			lockable->fReaderCount++;
254		}
255
256		if (readerCount > 0)
257			_kern_unblock_threads(readers, readerCount, B_OK);
258	}
259}
260