diff options
Diffstat (limited to 'libcutils/selector.c')
-rw-r--r-- | libcutils/selector.c | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/libcutils/selector.c b/libcutils/selector.c new file mode 100644 index 0000000..9436393 --- /dev/null +++ b/libcutils/selector.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed 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. + */ + +#define LOG_TAG "selector" + +#include <assert.h> +#include <errno.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include <cutils/array.h> +#include <cutils/selector.h> + +#include "loghack.h" + +struct Selector { + Array* selectableFds; + bool looping; + fd_set readFds; + fd_set writeFds; + fd_set exceptFds; + int maxFd; + int wakeupPipe[2]; + SelectableFd* wakeupFd; + + bool inSelect; + pthread_mutex_t inSelectLock; +}; + +/** Reads and ignores wake up data. */ +static void eatWakeupData(SelectableFd* wakeupFd) { + static char garbage[64]; + if (read(wakeupFd->fd, garbage, sizeof(garbage)) < 0) { + if (errno == EINTR) { + LOGI("read() interrupted."); + } else { + LOG_ALWAYS_FATAL("This should never happen: %s", strerror(errno)); + } + } +} + +static void setInSelect(Selector* selector, bool inSelect) { + pthread_mutex_lock(&selector->inSelectLock); + selector->inSelect = inSelect; + pthread_mutex_unlock(&selector->inSelectLock); +} + +static bool isInSelect(Selector* selector) { + pthread_mutex_lock(&selector->inSelectLock); + bool inSelect = selector->inSelect; + pthread_mutex_unlock(&selector->inSelectLock); + return inSelect; +} + +void selectorWakeUp(Selector* selector) { + if (!isInSelect(selector)) { + // We only need to write wake-up data if we're blocked in select(). + return; + } + + static char garbage[1]; + if (write(selector->wakeupPipe[1], garbage, sizeof(garbage)) < 0) { + if (errno == EINTR) { + LOGI("read() interrupted."); + } else { + LOG_ALWAYS_FATAL("This should never happen: %s", strerror(errno)); + } + } +} + +Selector* selectorCreate(void) { + Selector* selector = calloc(1, sizeof(Selector)); + if (selector == NULL) { + LOG_ALWAYS_FATAL("malloc() error."); + } + selector->selectableFds = arrayCreate(); + + // Set up wake-up pipe. + if (pipe(selector->wakeupPipe) < 0) { + LOG_ALWAYS_FATAL("pipe() error: %s", strerror(errno)); + } + + LOGD("Wakeup fd: %d", selector->wakeupPipe[0]); + + SelectableFd* wakeupFd = selectorAdd(selector, selector->wakeupPipe[0]); + if (wakeupFd == NULL) { + LOG_ALWAYS_FATAL("malloc() error."); + } + wakeupFd->onReadable = &eatWakeupData; + + pthread_mutex_init(&selector->inSelectLock, NULL); + + return selector; +} + +SelectableFd* selectorAdd(Selector* selector, int fd) { + assert(selector != NULL); + + SelectableFd* selectableFd = calloc(1, sizeof(SelectableFd)); + if (selectableFd != NULL) { + selectableFd->selector = selector; + selectableFd->fd = fd; + + arrayAdd(selector->selectableFds, selectableFd); + } + + return selectableFd; +} + +/** + * Adds an fd to the given set if the callback is non-null. Returns true + * if the fd was added. + */ +static inline bool maybeAdd(SelectableFd* selectableFd, + void (*callback)(SelectableFd*), fd_set* fdSet) { + if (callback != NULL) { + FD_SET(selectableFd->fd, fdSet); + return true; + } + return false; +} + +/** + * Removes stale file descriptors and initializes file descriptor sets. + */ +static void prepareForSelect(Selector* selector) { + fd_set* exceptFds = &selector->exceptFds; + fd_set* readFds = &selector->readFds; + fd_set* writeFds = &selector->writeFds; + + FD_ZERO(exceptFds); + FD_ZERO(readFds); + FD_ZERO(writeFds); + + Array* selectableFds = selector->selectableFds; + int i = 0; + selector->maxFd = 0; + int size = arraySize(selectableFds); + while (i < size) { + SelectableFd* selectableFd = arrayGet(selectableFds, i); + if (selectableFd->remove) { + // This descriptor should be removed. + arrayRemove(selectableFds, i); + size--; + if (selectableFd->onRemove != NULL) { + selectableFd->onRemove(selectableFd); + } + free(selectableFd); + } else { + if (selectableFd->beforeSelect != NULL) { + selectableFd->beforeSelect(selectableFd); + } + + bool inSet = false; + if (maybeAdd(selectableFd, selectableFd->onExcept, exceptFds)) { + LOGD("Selecting fd %d for writing...", selectableFd->fd); + inSet = true; + } + if (maybeAdd(selectableFd, selectableFd->onReadable, readFds)) { + LOGD("Selecting fd %d for reading...", selectableFd->fd); + inSet = true; + } + if (maybeAdd(selectableFd, selectableFd->onWritable, writeFds)) { + inSet = true; + } + + if (inSet) { + // If the fd is in a set, check it against max. + int fd = selectableFd->fd; + if (fd > selector->maxFd) { + selector->maxFd = fd; + } + } + + // Move to next descriptor. + i++; + } + } +} + +/** + * Invokes a callback if the callback is non-null and the fd is in the given + * set. + */ +static inline void maybeInvoke(SelectableFd* selectableFd, + void (*callback)(SelectableFd*), fd_set* fdSet) { + if (callback != NULL && !selectableFd->remove && + FD_ISSET(selectableFd->fd, fdSet)) { + LOGD("Selected fd %d.", selectableFd->fd); + callback(selectableFd); + } +} + +/** + * Notifies user if file descriptors are readable or writable, or if + * out-of-band data is present. + */ +static void fireEvents(Selector* selector) { + Array* selectableFds = selector->selectableFds; + int size = arraySize(selectableFds); + int i; + for (i = 0; i < size; i++) { + SelectableFd* selectableFd = arrayGet(selectableFds, i); + maybeInvoke(selectableFd, selectableFd->onExcept, + &selector->exceptFds); + maybeInvoke(selectableFd, selectableFd->onReadable, + &selector->readFds); + maybeInvoke(selectableFd, selectableFd->onWritable, + &selector->writeFds); + } +} + +void selectorLoop(Selector* selector) { + // Make sure we're not already looping. + if (selector->looping) { + LOG_ALWAYS_FATAL("Already looping."); + } + selector->looping = true; + + while (true) { + setInSelect(selector, true); + + prepareForSelect(selector); + + LOGD("Entering select()."); + + // Select file descriptors. + int result = select(selector->maxFd + 1, &selector->readFds, + &selector->writeFds, &selector->exceptFds, NULL); + + LOGD("Exiting select()."); + + setInSelect(selector, false); + + if (result == -1) { + // Abort on everything except EINTR. + if (errno == EINTR) { + LOGI("select() interrupted."); + } else { + LOG_ALWAYS_FATAL("select() error: %s", + strerror(errno)); + } + } else if (result > 0) { + fireEvents(selector); + } + } +} |