168 lines
5.4 KiB
C
168 lines
5.4 KiB
C
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
#include "guacamole/flag.h"
|
|
|
|
#include <pthread.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
/**
|
|
* The number of nanoseconds in a whole second.
|
|
*/
|
|
#define NANOS_PER_SECOND 1000000000L
|
|
|
|
void guac_flag_init(guac_flag* event_flag) {
|
|
|
|
/* The condition used by guac_flag to signal changes in its
|
|
* value must be safe to share between processes, and must use the
|
|
* system-wide monotonic clock (not the realtime clock, which is subject to
|
|
* time changes) */
|
|
pthread_condattr_t cond_attr;
|
|
pthread_condattr_init(&cond_attr);
|
|
pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC);
|
|
pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
|
|
pthread_cond_init(&event_flag->value_changed, &cond_attr);
|
|
|
|
/* In addition to being safe to share between processes, the mutex used by
|
|
* guac_flag to guard concurrent access to its value (AND to
|
|
* signal changes in its value) must be recursive (you can lock the mutex
|
|
* again even if the current thread has already locked it) */
|
|
pthread_mutexattr_t mutex_attr;
|
|
pthread_mutexattr_init(&mutex_attr);
|
|
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
|
|
pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
|
|
pthread_mutex_init(&event_flag->value_mutex, &mutex_attr);
|
|
|
|
/* The initial value of all flags is unset (0) */
|
|
event_flag->value = 0;
|
|
|
|
}
|
|
|
|
void guac_flag_destroy(guac_flag* event_flag) {
|
|
pthread_cond_destroy(&event_flag->value_changed);
|
|
pthread_mutex_destroy(&event_flag->value_mutex);
|
|
}
|
|
|
|
void guac_flag_set_and_lock(guac_flag* event_flag,
|
|
unsigned int flags) {
|
|
|
|
guac_flag_lock(event_flag);
|
|
|
|
/* Set specific bits of flag, leaving other bits unaffected */
|
|
unsigned int old_value = event_flag->value;
|
|
event_flag->value |= flags;
|
|
|
|
/* Signal other threads only if flag has changed as a result of this call */
|
|
if (event_flag->value != old_value)
|
|
pthread_cond_broadcast(&event_flag->value_changed);
|
|
|
|
}
|
|
|
|
void guac_flag_set(guac_flag* event_flag,
|
|
unsigned int flags) {
|
|
guac_flag_set_and_lock(event_flag, flags);
|
|
guac_flag_unlock(event_flag);
|
|
}
|
|
|
|
void guac_flag_clear_and_lock(guac_flag* event_flag,
|
|
unsigned int flags) {
|
|
|
|
guac_flag_lock(event_flag);
|
|
|
|
/* Clear specific bits of flag, leaving other bits unaffected */
|
|
event_flag->value &= ~flags;
|
|
|
|
/* NOTE: Other threads are NOT signalled here. Threads wait only for flags
|
|
* to be set, not for flags to be cleared. */
|
|
|
|
}
|
|
|
|
void guac_flag_clear(guac_flag* event_flag,
|
|
unsigned int flags) {
|
|
guac_flag_clear_and_lock(event_flag, flags);
|
|
guac_flag_unlock(event_flag);
|
|
}
|
|
|
|
void guac_flag_lock(guac_flag* event_flag) {
|
|
pthread_mutex_lock(&event_flag->value_mutex);
|
|
}
|
|
|
|
void guac_flag_unlock(guac_flag* event_flag) {
|
|
pthread_mutex_unlock(&event_flag->value_mutex);
|
|
}
|
|
|
|
void guac_flag_wait_and_lock(guac_flag* event_flag,
|
|
unsigned int flags) {
|
|
|
|
guac_flag_lock(event_flag);
|
|
|
|
/* Continue waiting until at least one of the desired flags has been set */
|
|
while (!(event_flag->value & flags)) {
|
|
|
|
/* Wait for any change to any flags, bailing out if something is wrong
|
|
* that would prevent waiting from ever succeeding (such a failure
|
|
* would turn this into a busy loop) */
|
|
if (pthread_cond_wait(&event_flag->value_changed,
|
|
&event_flag->value_mutex)) {
|
|
abort(); /* This should not happen except due to a bug */
|
|
}
|
|
|
|
}
|
|
|
|
/* If we reach this point, at least one of the desired flags has been set,
|
|
* and it is intentional that we continue to hold the lock (acquired on
|
|
* behalf of the caller) */
|
|
|
|
}
|
|
|
|
int guac_flag_timedwait_and_lock(guac_flag* event_flag,
|
|
unsigned int flags, unsigned int msec_timeout) {
|
|
|
|
guac_flag_lock(event_flag);
|
|
|
|
struct timespec ts_timeout;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts_timeout);
|
|
|
|
uint64_t nsec_timeout = msec_timeout * 1000000 + ts_timeout.tv_nsec;
|
|
ts_timeout.tv_sec += nsec_timeout / NANOS_PER_SECOND;
|
|
ts_timeout.tv_nsec = nsec_timeout % NANOS_PER_SECOND;
|
|
|
|
/* Continue waiting until at least one of the desired flags has been set */
|
|
while (!(event_flag->value & flags)) {
|
|
|
|
/* Wait for any change to any flags, failing if a timeout occurs */
|
|
if (pthread_cond_timedwait(&event_flag->value_changed,
|
|
&event_flag->value_mutex, &ts_timeout)) {
|
|
guac_flag_unlock(event_flag);
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
/* If we reach this point, at least one of the desired flags has been set,
|
|
* and it is intentional that we continue to hold the lock (acquired on
|
|
* behalf of the caller) */
|
|
return 1;
|
|
|
|
}
|
|
|