cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

rwlock.c (8760B)


      1/*
      2 * Licensed to the Apache Software Foundation (ASF) under one
      3 * or more contributor license agreements.  See the NOTICE file
      4 * distributed with this work for additional information
      5 * regarding copyright ownership.  The ASF licenses this file
      6 * to you under the Apache License, Version 2.0 (the
      7 * "License"); you may not use this file except in compliance
      8 * with the License.  You may obtain a copy of the License at
      9 *
     10 *   http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing,
     13 * software distributed under the License is distributed on an
     14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     15 * KIND, either express or implied.  See the License for the
     16 * specific language governing permissions and limitations
     17 * under the License.
     18 */
     19
     20#include <pthread.h>
     21#include <stdint.h>
     22#include "guacamole/error.h"
     23#include "guacamole/rwlock.h"
     24
     25/**
     26 * The value indicating that the current thread holds neither the read or write
     27 * locks.
     28 */
     29#define GUAC_REENTRANT_LOCK_NO_LOCK 0
     30
     31/**
     32 * The value indicating that the current thread holds the read lock.
     33 */
     34#define GUAC_REENTRANT_LOCK_READ_LOCK 1
     35
     36/**
     37 * The value indicating that the current thread holds the write lock.
     38 */
     39#define GUAC_REENTRANT_LOCK_WRITE_LOCK 2
     40
     41void guac_rwlock_init(guac_rwlock* lock) {
     42
     43    /* Configure to allow sharing this lock with child processes */
     44    pthread_rwlockattr_t lock_attributes;
     45    pthread_rwlockattr_init(&lock_attributes);
     46    pthread_rwlockattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED);
     47
     48    /* Initialize the rwlock */
     49    pthread_rwlock_init(&(lock->lock), &lock_attributes);
     50
     51    /* Initialize the  flags to 0, as threads won't have acquired it yet */
     52    pthread_key_create(&(lock->key), (void *) 0);
     53
     54}
     55
     56void guac_rwlock_destroy(guac_rwlock* lock) {
     57
     58    /* Destroy the rwlock */
     59    pthread_rwlock_destroy(&(lock->lock));
     60
     61    /* Destroy the thread-local key */
     62    pthread_key_delete(lock->key);
     63
     64}
     65
     66/**
     67 * Clean up and destroy the provided guac reentrant rwlock.
     68 *
     69 * @param lock
     70 *     The guac reentrant rwlock to be destroyed.
     71 */
     72void guac_rwlock_destroy(guac_rwlock* lock);
     73
     74/**
     75 * Extract and return the flag indicating which lock is held, if any, from the
     76 * provided key value. The flag is always stored in the least-significant
     77 * nibble of the value.
     78 *
     79 * @param value
     80 *     The key value containing the flag.
     81 *
     82 * @return
     83 *     The flag indicating which lock is held, if any.
     84 */
     85static uintptr_t get_lock_flag(uintptr_t value) {
     86    return value & 0xF;
     87}
     88
     89/**
     90 * Extract and return the lock count from the provided key. This returned value
     91 * is the difference between the number of lock and unlock requests made by the
     92 * current thread. This count is always stored in the remaining value after the
     93 * least-significant nibble where the flag is stored.
     94 *
     95 * @param value
     96 *     The key value containing the count.
     97 *
     98 * @return
     99 *     The difference between the number of lock and unlock requests made by
    100 *     the current thread.
    101 */
    102static uintptr_t get_lock_count(uintptr_t value) {
    103    return value >> 4;
    104}
    105
    106/**
    107 * Given a flag indicating if and how the current thread controls a lock, and
    108 * a count of the depth of lock requests, return a value containing the flag
    109 * in the least-significant nibble, and the count in the rest.
    110 *
    111 * @param flag
    112 *     A flag indiciating which lock, if any, is held by the current thread.
    113 *
    114 * @param count
    115 *     The depth of the lock attempt by the current thread, i.e. the number of
    116 *     lock requests minus unlock requests.
    117 *
    118 * @return
    119 *     A value containing the flag in the least-significant nibble, and the
    120 *     count in the rest, cast to a void* for thread-local storage.
    121 */
    122static void* get_value_from_flag_and_count(
    123        uintptr_t flag, uintptr_t count) {
    124    return (void*) ((flag & 0xF) | count << 4);
    125}
    126
    127/**
    128 * Return zero if adding one to the current count would overflow the storage
    129 * allocated to the count, or a non-zero value otherwise.
    130 *
    131 * @param current_count
    132 *     The current count for a lock that the current thread is trying to
    133 *     reentrantly acquire.
    134 *
    135 * @return
    136 *     Zero if adding one to the current count would overflow the storage
    137 *     allocated to the count, or a non-zero value otherwise.
    138 */
    139static int would_overflow_count(uintptr_t current_count) {
    140
    141    /**
    142     * The count will overflow if it's already equal or greated to the maximum
    143     * possible value that can be stored in a uintptr_t excluding the first nibble.
    144     */
    145    return current_count >= (UINTPTR_MAX >> 4);
    146
    147}
    148
    149int guac_rwlock_acquire_write_lock(guac_rwlock* reentrant_rwlock) {
    150
    151    uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key);
    152    uintptr_t flag = get_lock_flag(key_value);
    153    uintptr_t count = get_lock_count(key_value);
    154
    155    /* If acquiring this lock again would overflow the counter storage */
    156    if (would_overflow_count(count)) {
    157
    158        guac_error = GUAC_STATUS_TOO_MANY;
    159        guac_error_message = "Unable to acquire write lock because there's"
    160                " insufficient space to store another level of lock depth";
    161
    162        return 1;
    163
    164    }
    165
    166    /* If the current thread already holds the write lock, increment the count */
    167    if (flag == GUAC_REENTRANT_LOCK_WRITE_LOCK) {
    168        pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
    169                flag, count + 1));
    170
    171        /* This thread already has the lock */
    172        return 0;
    173    }
    174
    175    /*
    176     * The read lock must be released before the write lock can be acquired.
    177     * This is a little odd because it may mean that a function further down
    178     * the stack may have requested a read lock, which will get upgraded to a
    179     * write lock by another function without the caller knowing about it. This
    180     * shouldn't cause any issues, however.
    181     */
    182    if (key_value == GUAC_REENTRANT_LOCK_READ_LOCK)
    183        pthread_rwlock_unlock(&(reentrant_rwlock->lock));
    184
    185    /* Acquire the write lock */
    186    pthread_rwlock_wrlock(&(reentrant_rwlock->lock));
    187
    188    /* Mark that the current thread has the lock, and increment the count */
    189    pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
    190            GUAC_REENTRANT_LOCK_WRITE_LOCK, count + 1));
    191
    192    return 0;
    193
    194}
    195
    196int guac_rwlock_acquire_read_lock(guac_rwlock* reentrant_rwlock) {
    197
    198    uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key);
    199    uintptr_t flag = get_lock_flag(key_value);
    200    uintptr_t count = get_lock_count(key_value);
    201
    202    /* If acquiring this lock again would overflow the counter storage */
    203    if (would_overflow_count(count)) {
    204
    205        guac_error = GUAC_STATUS_TOO_MANY;
    206        guac_error_message = "Unable to acquire read lock because there's"
    207                " insufficient space to store another level of lock depth";
    208
    209        return 1;
    210
    211    }
    212
    213    /* The current thread may read if either the read or write lock is held */
    214    if (
    215            flag == GUAC_REENTRANT_LOCK_READ_LOCK ||
    216            flag == GUAC_REENTRANT_LOCK_WRITE_LOCK
    217    ) {
    218
    219        /* Increment the depth counter */
    220        pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
    221                flag, count + 1));
    222
    223        /* This thread already has the lock */
    224        return 0;
    225    }
    226
    227    /* Acquire the lock */
    228    pthread_rwlock_rdlock(&(reentrant_rwlock->lock));
    229
    230    /* Set the flag that the current thread has the read lock */
    231    pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
    232                GUAC_REENTRANT_LOCK_READ_LOCK, 1));
    233
    234    return 0;
    235
    236}
    237
    238int guac_rwlock_release_lock(guac_rwlock* reentrant_rwlock) {
    239
    240    uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key);
    241    uintptr_t flag = get_lock_flag(key_value);
    242    uintptr_t count = get_lock_count(key_value);
    243
    244    /*
    245     * Return an error if an attempt is made to release a lock that the current
    246     * thread does not control.
    247     */
    248    if (count <= 0) {
    249
    250        guac_error = GUAC_STATUS_INVALID_ARGUMENT;
    251        guac_error_message = "Unable to free rwlock because it's not held by"
    252                " the current thread";
    253
    254        return 1;
    255
    256    }
    257
    258    /* Release the lock if this is the last locked level */
    259    if (count == 1) {
    260
    261        pthread_rwlock_unlock(&(reentrant_rwlock->lock));
    262
    263        /* Set the flag that the current thread holds no locks */
    264        pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
    265                GUAC_REENTRANT_LOCK_NO_LOCK, 0));
    266
    267        return 0;
    268    }
    269
    270    /* Do not release the lock since it's still in use - just decrement */
    271    pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
    272            flag, count - 1));
    273
    274    return 0;
    275
    276}