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