| /* | |
| * 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); | |
| } | |
| } | |
| } |