1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*Read/Write locking implementation based on the MultiLock code from
18 * Stephen Beaulieu <hippo@be.com>
19 */
20
21#include "apr_arch_thread_rwlock.h"
22#include "apr_strings.h"
23#include "apr_portable.h"
24
25#define BIG_NUM 100000
26
27static apr_status_t _thread_rw_cleanup(void * data)
28{
29    apr_thread_rwlock_t *mutex = (apr_thread_rwlock_t*)data;
30
31    if (mutex->ReadCount != 0) {
32    	while (atomic_add(&mutex->ReadCount , -1) > 1){
33            release_sem (mutex->Read);
34    	}
35    }
36    if (mutex->WriteCount != 0) {
37    	while (atomic_add(&mutex->WriteCount , -1) > 1){
38            release_sem (mutex->Write);
39    	}
40    }
41    if (mutex->LockCount != 0) {
42    	while (atomic_add(&mutex->LockCount , -1) > 1){
43            release_sem (mutex->Lock);
44    	}
45    }
46
47    delete_sem(mutex->Read);
48    delete_sem(mutex->Write);
49    delete_sem(mutex->Lock);
50    return APR_SUCCESS;
51}
52
53APR_DECLARE(apr_status_t) apr_thread_rwlock_create(apr_thread_rwlock_t **rwlock,
54                                                   apr_pool_t *pool)
55{
56    apr_thread_rwlock_t *new;
57
58    new = (apr_thread_rwlock_t *)apr_pcalloc(pool, sizeof(apr_thread_rwlock_t));
59    if (new == NULL){
60        return APR_ENOMEM;
61    }
62
63    new->pool  = pool;
64    /* we need to make 3 locks... */
65    new->ReadCount = 0;
66    new->WriteCount = 0;
67    new->LockCount = 0;
68    new->Read  = create_sem(0, "APR_ReadLock");
69    new->Write = create_sem(0, "APR_WriteLock");
70    new->Lock  = create_sem(0, "APR_Lock");
71
72    if (new->Lock < 0 || new->Read < 0 || new->Write < 0) {
73        _thread_rw_cleanup(new);
74        return -1;
75    }
76
77    apr_pool_cleanup_register(new->pool, (void *)new, _thread_rw_cleanup,
78                              apr_pool_cleanup_null);
79    (*rwlock) = new;
80    return APR_SUCCESS;
81}
82
83APR_DECLARE(apr_status_t) apr_thread_rwlock_rdlock(apr_thread_rwlock_t *rwlock)
84{
85    int32 rv = APR_SUCCESS;
86
87    if (find_thread(NULL) == rwlock->writer) {
88        /* we're the writer - no problem */
89        rwlock->Nested++;
90    } else {
91        /* we're not the writer */
92        int32 r = atomic_add(&rwlock->ReadCount, 1);
93        if (r < 0) {
94            /* Oh dear, writer holds lock, wait for sem */
95            rv = acquire_sem_etc(rwlock->Read, 1, B_DO_NOT_RESCHEDULE,
96                                 B_INFINITE_TIMEOUT);
97        }
98    }
99
100    return rv;
101}
102
103APR_DECLARE(apr_status_t) apr_thread_rwlock_tryrdlock(apr_thread_rwlock_t *rwlock)
104{
105    return APR_ENOTIMPL;
106}
107
108APR_DECLARE(apr_status_t) apr_thread_rwlock_wrlock(apr_thread_rwlock_t *rwlock)
109{
110    int rv = APR_SUCCESS;
111
112    if (find_thread(NULL) == rwlock->writer) {
113        rwlock->Nested++;
114    } else {
115        /* we're not the writer... */
116        if (atomic_add(&rwlock->LockCount, 1) >= 1) {
117            /* we're locked - acquire the sem */
118            rv = acquire_sem_etc(rwlock->Lock, 1, B_DO_NOT_RESCHEDULE,
119                                 B_INFINITE_TIMEOUT);
120        }
121        if (rv == APR_SUCCESS) {
122            /* decrement the ReadCount to a large -ve number so that
123             * we block on new readers...
124             */
125            int32 readers = atomic_add(&rwlock->ReadCount, -BIG_NUM);
126            if (readers > 0) {
127                /* readers are holding the lock */
128                rv = acquire_sem_etc(rwlock->Write, readers, B_DO_NOT_RESCHEDULE,
129                                     B_INFINITE_TIMEOUT);
130            }
131            if (rv == APR_SUCCESS)
132                rwlock->writer = find_thread(NULL);
133        }
134    }
135
136    return rv;
137}
138
139APR_DECLARE(apr_status_t) apr_thread_rwlock_trywrlock(apr_thread_rwlock_t *rwlock)
140{
141    return APR_ENOTIMPL;
142}
143
144APR_DECLARE(apr_status_t) apr_thread_rwlock_unlock(apr_thread_rwlock_t *rwlock)
145{
146    apr_status_t rv = APR_SUCCESS;
147    int32 readers;
148
149    /* we know we hold the lock, so don't check it :) */
150    if (find_thread(NULL) == rwlock->writer) {
151    /* we know we hold the lock, so don't check it :) */
152        if (rwlock->Nested > 1) {
153            /* we're recursively locked */
154            rwlock->Nested--;
155            return APR_SUCCESS;
156        }
157        /* OK so we need to release the sem if we have it :) */
158        readers = atomic_add(&rwlock->ReadCount, BIG_NUM) + BIG_NUM;
159        if (readers > 0) {
160            rv = release_sem_etc(rwlock->Read, readers, B_DO_NOT_RESCHEDULE);
161        }
162        if (rv == APR_SUCCESS) {
163            rwlock->writer = -1;
164            if (atomic_add(&rwlock->LockCount, -1) > 1) {
165                rv = release_sem_etc(rwlock->Lock, 1, B_DO_NOT_RESCHEDULE);
166            }
167        }
168    } else {
169       /* We weren't the Writer, so just release the ReadCount... */
170       if (atomic_add(&rwlock->ReadCount, -1) < 0) {
171            /* we have a writer waiting for the lock, so release it */
172            rv = release_sem_etc(rwlock->Write, 1, B_DO_NOT_RESCHEDULE);
173        }
174    }
175
176    return rv;
177}
178
179APR_DECLARE(apr_status_t) apr_thread_rwlock_destroy(apr_thread_rwlock_t *rwlock)
180{
181    apr_status_t stat;
182    if ((stat = _thread_rw_cleanup(rwlock)) == APR_SUCCESS) {
183        apr_pool_cleanup_kill(rwlock->pool, rwlock, _thread_rw_cleanup);
184        return APR_SUCCESS;
185    }
186    return stat;
187}
188
189APR_POOL_IMPLEMENT_ACCESSOR(thread_rwlock)
190
191