dect
/
asterisk
Archived
13
0
Fork 0

Add kqueue(2) implementation to Asterisk in various places.

This will save a considerable amount of CPU on the BSDs, including Mac OS X,
as it eliminates several places in the code that we previously used a busy
loop.  Additionally, this adds a res_timing interface, using kqueue timers.

Review: https://reviewboard.asterisk.org/r/543/


git-svn-id: http://svn.digium.com/svn/asterisk/trunk@262852 f38db490-d61c-443f-a65b-d21fe96a405b
This commit is contained in:
tilghman 2010-05-13 05:37:31 +00:00
parent 46202599e2
commit 08b5d74894
9 changed files with 11358 additions and 10821 deletions

View File

@ -157,6 +157,9 @@ ifneq ($(findstring BSD,$(OSARCH)),)
else
ASTVARLIBDIR=$(localstatedir)/lib/asterisk
ASTDBDIR=$(ASTVARLIBDIR)
endif
ifneq ($(findstring darwin,$(OSARCH)),)
ASTVARRUNDIR=/Library/Application Support/Asterisk/Run
endif
ASTKEYDIR=$(ASTVARLIBDIR)
endif
@ -559,7 +562,7 @@ installdirs:
mkdir -p $(DESTDIR)$(ASTSBINDIR)
mkdir -p $(DESTDIR)$(ASTETCDIR)
mkdir -p $(DESTDIR)$(ASTBINDIR)
mkdir -p $(DESTDIR)$(ASTVARRUNDIR)
mkdir -p "$(DESTDIR)$(ASTVARRUNDIR)"
mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/voicemail
mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/dictate
mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/system
@ -572,7 +575,7 @@ bininstall: _all installdirs $(SUBDIRS_INSTALL)
$(LN) -sf asterisk $(DESTDIR)$(ASTSBINDIR)/rasterisk
$(INSTALL) -m 755 contrib/scripts/astgenkey $(DESTDIR)$(ASTSBINDIR)/
$(INSTALL) -m 755 contrib/scripts/autosupport $(DESTDIR)$(ASTSBINDIR)/
if [ ! -f $(DESTDIR)$(ASTSBINDIR)/safe_asterisk ]; then \
if [ ! -f $(DESTDIR)$(ASTSBINDIR)/safe_asterisk -a ! -f /sbin/launchd ]; then \
cat contrib/scripts/safe_asterisk | sed 's|__ASTERISK_SBIN_DIR__|$(ASTSBINDIR)|;s|__ASTERISK_VARRUN_DIR__|$(ASTVARRUNDIR)|;' > $(DESTDIR)$(ASTSBINDIR)/safe_asterisk ;\
chmod 755 $(DESTDIR)$(ASTSBINDIR)/safe_asterisk;\
fi

View File

@ -21,6 +21,7 @@ IODBC=@PBX_IODBC@
ISDNNET=@PBX_ISDNNET@
IXJUSER=@PBX_IXJUSER@
JACK=@PBX_JACK@
KQUEUE=@PBX_KQUEUE@
LDAP=@PBX_LDAP@
LIBXML2=@PBX_LIBXML2@
LTDL=@PBX_LTDL@

21421
configure vendored

File diff suppressed because it is too large Load Diff

View File

@ -319,6 +319,7 @@ AST_EXT_LIB_SETUP([INOTIFY], [inotify support], [inotify])
AST_EXT_LIB_SETUP([IODBC], [iODBC], [iodbc])
AST_EXT_LIB_SETUP([ISDNNET], [ISDN4Linux], [isdnnet])
AST_EXT_LIB_SETUP([JACK], [Jack Audio Connection Kit], [jack])
AST_EXT_LIB_SETUP([KQUEUE], [kqueue support], [kqueue])
AST_EXT_LIB_SETUP([LDAP], [OpenLDAP], [ldap])
AST_EXT_LIB_SETUP([LIBXML2], [LibXML2], [libxml2])
AST_EXT_LIB_SETUP([LTDL], [libtool], [ltdl])
@ -389,7 +390,7 @@ AC_FUNC_ALLOCA
AC_HEADER_DIRENT
AC_HEADER_STDC
AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h libintl.h limits.h locale.h malloc.h netdb.h netinet/in.h stddef.h stdint.h stdlib.h string.h strings.h sys/file.h sys/ioctl.h sys/param.h sys/socket.h sys/time.h syslog.h termios.h unistd.h utime.h arpa/nameser.h sys/io.h])
AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h libintl.h limits.h locale.h malloc.h netdb.h netinet/in.h stddef.h stdint.h stdlib.h string.h strings.h sys/event.h sys/file.h sys/ioctl.h sys/param.h sys/socket.h sys/time.h syslog.h termios.h unistd.h utime.h arpa/nameser.h sys/io.h])
AC_CHECK_HEADERS([winsock.h winsock2.h])
@ -540,6 +541,22 @@ then
AC_CHECK_FILE(/dev/urandom, AC_DEFINE([HAVE_DEV_URANDOM], 1, [Define to 1 if your system has /dev/urandom.]))
fi
AC_MSG_CHECKING(for O_EVTONLY in fcntl.h)
AC_LINK_IFELSE(
AC_LANG_PROGRAM([#include <fcntl.h>], [int a = O_EVTONLY;]),
AC_MSG_RESULT(yes)
AC_DEFINE([HAVE_O_EVTONLY], 1, [Define to 1 if your system defines the file flag O_EVTONLY in fcntl.h]),
AC_MSG_RESULT(no)
)
AC_MSG_CHECKING(for O_SYMLINK in fcntl.h)
AC_LINK_IFELSE(
AC_LANG_PROGRAM([#include <fcntl.h>], [int a = O_SYMLINK;]),
AC_MSG_RESULT(yes)
AC_DEFINE([HAVE_O_SYMLINK], 1, [Define to 1 if your system defines the file flag O_SYMLINK in fcntl.h]),
AC_MSG_RESULT(no)
)
AST_C_DEFINE_CHECK([PTHREAD_RWLOCK_INITIALIZER], [PTHREAD_RWLOCK_INITIALIZER], [pthread.h])
AC_MSG_CHECKING(for PTHREAD_RWLOCK_PREFER_WRITER_NP in pthread.h)
@ -1435,6 +1452,12 @@ AST_EXT_LIB_CHECK([INOTIFY], [c], [inotify_init], [sys/inotify.h])
AST_EXT_LIB_CHECK([JACK], [jack], [jack_activate], [jack/jack.h])
# BSD (and OS X) equivalent of inotify
AST_EXT_LIB_CHECK([KQUEUE], [c], [kqueue], [sys/event.h])
# 64-bit version of kevent (from kqueue) on OS X
AC_CHECK_FUNCS([kevent64])
# Needed by unixodbc
AST_EXT_LIB_CHECK([LTDL], [ltdl], [lt_dlinit], [ltdl.h], [])

View File

@ -342,6 +342,12 @@
/* Define to 1 if you have the Jack Audio Connection Kit library. */
#undef HAVE_JACK
/* Define to 1 if you have the `kevent64' function. */
#undef HAVE_KEVENT64
/* Define to 1 if you have the kqueue support library. */
#undef HAVE_KQUEUE
/* Define to 1 if you have the OpenLDAP library. */
#undef HAVE_LDAP
@ -490,6 +496,12 @@
/* Define to 1 if OSX atomic operations are supported. */
#undef HAVE_OSX_ATOMICS
/* Define to 1 if your system defines the file flag O_EVTONLY in fcntl.h */
#undef HAVE_O_EVTONLY
/* Define to 1 if your system defines the file flag O_SYMLINK in fcntl.h */
#undef HAVE_O_SYMLINK
/* Define to indicate the PostgreSQL library */
#undef HAVE_PGSQL
@ -817,6 +829,9 @@
*/
#undef HAVE_SYS_ENDIAN_SWAP16
/* Define to 1 if you have the <sys/event.h> header file. */
#undef HAVE_SYS_EVENT_H
/* Define to 1 if you have the <sys/file.h> header file. */
#undef HAVE_SYS_FILE_H
@ -1026,6 +1041,9 @@
/* Define to the version of this package. */
#undef PACKAGE_VERSION
/* Define to 1 if the C compiler supports function prototypes. */
#undef PROTOTYPES
/* Define to necessary symbol if this constant uses a non-standard name on
your system. */
#undef PTHREAD_CREATE_JOINABLE
@ -1045,6 +1063,11 @@
/* Define to the type of arg 5 for `select'. */
#undef SELECT_TYPE_ARG5
/* Define to 1 if the `setvbuf' function takes the buffering type as its
second argument and the buffer pointer as the third, as on System V before
release 3. */
#undef SETVBUF_REVERSED
/* The size of `char *', as computed by sizeof. */
#undef SIZEOF_CHAR_P
@ -1074,30 +1097,20 @@
/* Define to 1 if your <sys/time.h> declares `struct tm'. */
#undef TM_IN_SYS_TIME
/* Enable extensions on AIX 3, Interix. */
/* Define to 1 if on AIX 3.
System headers sometimes define this.
We just want to avoid a redefinition error message. */
#ifndef _ALL_SOURCE
# undef _ALL_SOURCE
#endif
/* Number of bits in a file offset, on hosts where this is settable. */
#undef _FILE_OFFSET_BITS
/* Enable GNU extensions on systems that have them. */
#ifndef _GNU_SOURCE
# undef _GNU_SOURCE
#endif
/* Enable threading extensions on Solaris. */
#ifndef _POSIX_PTHREAD_SEMANTICS
# undef _POSIX_PTHREAD_SEMANTICS
#endif
/* Enable extensions on HP NonStop. */
#ifndef _TANDEM_SOURCE
# undef _TANDEM_SOURCE
#endif
/* Enable general extensions on Solaris. */
#ifndef __EXTENSIONS__
# undef __EXTENSIONS__
#endif
/* Number of bits in a file offset, on hosts where this is settable. */
#undef _FILE_OFFSET_BITS
/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
#undef _LARGEFILE_SOURCE
@ -1115,6 +1128,20 @@
/* Define to 1 if you need to in order for `stat' and other things to work. */
#undef _POSIX_SOURCE
/* Enable extensions on Solaris. */
#ifndef __EXTENSIONS__
# undef __EXTENSIONS__
#endif
#ifndef _POSIX_PTHREAD_SEMANTICS
# undef _POSIX_PTHREAD_SEMANTICS
#endif
#ifndef _TANDEM_SOURCE
# undef _TANDEM_SOURCE
#endif
/* Define like PROTOTYPES; this can be used by system headers. */
#undef __PROTOTYPES
/* Define to empty if `const' does not conform to ANSI C. */
#undef const

View File

@ -1,7 +1,7 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 1999 - 2005, Digium, Inc.
* Copyright (C) 1999 - 2010, Digium, Inc.
*
* Mark Spencer <markster@digium.com>
*
@ -54,6 +54,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <float.h>
#ifdef HAVE_INOTIFY
#include <sys/inotify.h>
#elif HAVE_KQUEUE
#include <sys/types.h>
#include <sys/time.h>
#include <sys/event.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
#include "private.h"
@ -153,6 +160,13 @@ struct state {
struct lsinfo lsis[TZ_MAX_LEAPS];
#ifdef HAVE_INOTIFY
int wd[2];
#elif defined(HAVE_KQUEUE)
int fd;
# ifdef HAVE_O_SYMLINK
int fds;
# else
DIR *dir;
# endif /* defined(HAVE_O_SYMLINK) */
#else
time_t mtime[2];
#endif
@ -298,7 +312,7 @@ static void add_notify(struct state *sp, const char *path)
/* Give the thread a chance to initialize */
ast_cond_wait(&initialization, &initialization_lock);
} else {
ast_log(LOG_ERROR, "Unable to start notification thread\n");
fprintf(stderr, "Unable to start notification thread\n");
ast_mutex_unlock(&initialization_lock);
return;
}
@ -321,6 +335,180 @@ static void add_notify(struct state *sp, const char *path)
);
}
}
#elif HAVE_KQUEUE
static int queue_fd = -1;
static void *kqueue_daemon(void *data)
{
struct kevent kev;
struct state *sp;
struct timespec no_wait = { 0, 1 };
ast_mutex_lock(&initialization_lock);
if ((queue_fd = kqueue()) < 0) {
/* ast_log uses us to format messages, so if we called ast_log, we'd be
* in for a nasty loop (seen already in testing) */
fprintf(stderr, "Unable to initialize kqueue(): %s\n", strerror(errno));
inotify_thread = AST_PTHREADT_NULL;
/* Okay to proceed */
ast_cond_signal(&initialization);
ast_mutex_unlock(&initialization_lock);
return NULL;
}
ast_cond_signal(&initialization);
ast_mutex_unlock(&initialization_lock);
for (;/*ever*/;) {
if (kevent(queue_fd, NULL, 0, &kev, 1, NULL) < 0) {
AST_LIST_LOCK(&zonelist);
ast_cond_broadcast(&initialization);
AST_LIST_UNLOCK(&zonelist);
continue;
}
sp = kev.udata;
/*!\note
* If the file event fired, then the file was removed, so we'll need
* to reparse the entry. The directory event is a bit more
* interesting. Unfortunately, the queue doesn't contain information
* about the file that changed (only the directory itself), so unless
* we kept a record of the directory state before, it's not really
* possible to know what change occurred. But if we act paranoid and
* just purge the associated file, then it will get reparsed, and
* everything works fine. It may be more work, but it's a vast
* improvement over the alternative implementation, which is to stat
* the file repeatedly in what is essentially a busy loop. */
AST_LIST_LOCK(&zonelist);
AST_LIST_REMOVE(&zonelist, sp, list);
AST_LIST_UNLOCK(&zonelist);
/* If the directory event fired, remove the file event */
EV_SET(&kev, sp->fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL);
kevent(queue_fd, &kev, 1, NULL, 0, &no_wait);
close(sp->fd);
#ifdef HAVE_O_SYMLINK
if (sp->fds > -1) {
/* If the file event fired, remove the symlink event */
EV_SET(&kev, sp->fds, EVFILT_VNODE, EV_DELETE, 0, 0, NULL);
kevent(queue_fd, &kev, 1, NULL, 0, &no_wait);
close(sp->fds);
}
#else
if (sp->dir) {
/* If the file event fired, remove the directory event */
EV_SET(&kev, dirfd(sp->dir), EVFILT_VNODE, EV_DELETE, 0, 0, NULL);
kevent(queue_fd, &kev, 1, NULL, 0, &no_wait);
closedir(sp->dir);
}
#endif
free(sp);
/* Just in case the signal was sent late */
AST_LIST_LOCK(&zonelist);
ast_cond_broadcast(&initialization);
AST_LIST_UNLOCK(&zonelist);
}
}
static void add_notify(struct state *sp, const char *path)
{
struct kevent kev;
struct timespec no_wait = { 0, 1 };
char watchdir[PATH_MAX + 1] = "";
if (inotify_thread == AST_PTHREADT_NULL) {
ast_cond_init(&initialization, NULL);
ast_mutex_init(&initialization_lock);
ast_mutex_lock(&initialization_lock);
if (!(ast_pthread_create_background(&inotify_thread, NULL, kqueue_daemon, NULL))) {
/* Give the thread a chance to initialize */
ast_cond_wait(&initialization, &initialization_lock);
}
ast_mutex_unlock(&initialization_lock);
}
if (queue_fd < 0) {
/* Error already sent */
return;
}
#ifdef HAVE_O_SYMLINK
if (readlink(path, watchdir, sizeof(watchdir) - 1) != -1 && (sp->fds = open(path, O_RDONLY | O_SYMLINK
# ifdef HAVE_O_EVTONLY
| O_EVTONLY
# endif
)) >= 0) {
EV_SET(&kev, sp->fds, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_ONESHOT, NOTE_WRITE | NOTE_EXTEND | NOTE_DELETE | NOTE_REVOKE | NOTE_ATTRIB, 0, sp);
if (kevent(queue_fd, &kev, 1, NULL, 0, &no_wait) < 0 && errno != 0) {
/* According to the API docs, we may get -1 return value, due to the
* NULL space for a returned event, but errno should be 0 unless
* there's a real error. Otherwise, kevent will return 0 to indicate
* that the time limit expired. */
fprintf(stderr, "Unable to watch '%s': %s\n", path, strerror(errno));
close(sp->fds);
sp->fds = -1;
}
}
#else
if (readlink(path, watchdir, sizeof(watchdir) - 1) != -1) {
/* Special -- watch the directory for changes, because we cannot directly watch a symlink */
char *slash;
ast_copy_string(watchdir, path, sizeof(watchdir));
if ((slash = strrchr(watchdir, '/'))) {
*slash = '\0';
}
if (!(sp->dir = opendir(watchdir))) {
fprintf(stderr, "Unable to watch directory with symlink '%s': %s\n", path, strerror(errno));
goto watch_file;
}
/*!\note
* You may be wondering about whether there is a potential conflict
* with the kqueue interface, because we might be watching the same
* directory for multiple zones. The answer is no, because kqueue
* looks at the descriptor to know if there's a duplicate. Since we
* (may) have opened the directory multiple times, each represents a
* different event, so no replacement of an existing event will occur.
* Likewise, there's no potential leak of a descriptor.
*/
EV_SET(&kev, dirfd(sp->dir), EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_ONESHOT,
NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_REVOKE | NOTE_ATTRIB, 0, sp);
if (kevent(queue_fd, &kev, 1, NULL, 0, &no_wait) < 0 && errno != 0) {
fprintf(stderr, "Unable to watch '%s': %s\n", watchdir, strerror(errno));
closedir(sp->dir);
sp->dir = NULL;
}
}
watch_file:
#endif
if ((sp->fd = open(path, O_RDONLY
# ifdef HAVE_O_EVTONLY
| O_EVTONLY
# endif
)) < 0) {
fprintf(stderr, "Unable to watch '%s' for changes: %s\n", path, strerror(errno));
return;
}
EV_SET(&kev, sp->fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_ONESHOT, NOTE_WRITE | NOTE_EXTEND | NOTE_DELETE | NOTE_REVOKE | NOTE_ATTRIB, 0, sp);
if (kevent(queue_fd, &kev, 1, NULL, 0, &no_wait) < 0 && errno != 0) {
/* According to the API docs, we may get -1 return value, due to the
* NULL space for a returned event, but errno should be 0 unless
* there's a real error. Otherwise, kevent will return 0 to indicate
* that the time limit expired. */
fprintf(stderr, "Unable to watch '%s': %s\n", path, strerror(errno));
close(sp->fd);
sp->fd = -1;
}
}
#else
static void *notify_daemon(void *data)
{

View File

@ -32,6 +32,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <dirent.h>
#ifdef HAVE_INOTIFY
#include <sys/inotify.h>
#elif HAVE_KQUEUE
#include <sys/types.h>
#include <sys/time.h>
#include <sys/event.h>
#include <fcntl.h>
#endif
#include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
@ -435,7 +440,7 @@ static int scan_service(const char *fn, time_t now)
return res;
}
#ifdef HAVE_INOTIFY
#if defined(HAVE_INOTIFY) || defined(HAVE_KQUEUE)
struct direntry {
AST_LIST_ENTRY(direntry) list;
time_t mtime;
@ -471,6 +476,15 @@ static void queue_file(const char *filename, time_t when)
when = st.st_mtime;
}
#ifndef HAVE_INOTIFY
/* Need to check the existing list for kqueue(2), in order to avoid duplicates. */
AST_LIST_TRAVERSE(&dirlist, cur, list) {
if (cur->mtime == when && !strcmp(filename, cur->name)) {
return;
}
}
#endif
if ((res = when) > now || (res = scan_service(filename, now)) > 0) {
if (!(new = ast_calloc(1, sizeof(*new) + strlen(filename) + 1))) {
return;
@ -501,11 +515,11 @@ static void *scan_thread(void *unused)
{
DIR *dir;
struct dirent *de;
int res;
time_t now;
struct timespec ts = { .tv_sec = 1 };
#ifdef HAVE_INOTIFY
int res;
int inotify_fd = inotify_init();
struct direntry *cur;
struct {
struct inotify_event iev;
/* It may not look like we're using this element, but when we read
@ -514,17 +528,31 @@ static void *scan_thread(void *unused)
char name[FILENAME_MAX + 1];
} buf;
struct pollfd pfd = { .fd = inotify_fd, .events = POLLIN };
#else
struct timespec nowait = { 0, 1 };
int inotify_fd = kqueue();
struct kevent kev;
#endif
struct direntry *cur;
while (!ast_fully_booted) {
nanosleep(&ts, NULL);
}
if (inotify_fd < 0) {
ast_log(LOG_ERROR, "Unable to initialize inotify(7)\n");
ast_log(LOG_ERROR, "Unable to initialize "
#ifdef HAVE_INOTIFY
"inotify(7)"
#else
"kqueue(2)"
#endif
"\n");
return NULL;
}
#ifdef HAVE_INOTIFY
inotify_add_watch(inotify_fd, qdir, IN_CREATE | IN_ATTRIB | IN_MOVED_TO);
#endif
/* First, run through the directory and clear existing entries */
if (!(dir = opendir(qdir))) {
@ -532,11 +560,21 @@ static void *scan_thread(void *unused)
return NULL;
}
#ifndef HAVE_INOTIFY
EV_SET(&kev, dirfd(dir), EVFILT_VNODE, EV_ADD | EV_ENABLE, NOTE_WRITE | NOTE_EXTEND | NOTE_DELETE | NOTE_REVOKE | NOTE_ATTRIB, 0, NULL);
if (kevent(inotify_fd, &kev, 1, NULL, 0, &nowait) < 0 && errno != 0) {
ast_log(LOG_ERROR, "Unable to watch directory %s: %s\n", qdir, strerror(errno));
}
#endif
now = time(NULL);
while ((de = readdir(dir))) {
queue_file(de->d_name, 0);
}
#ifdef HAVE_INOTIFY
/* Directory needs to remain open for kqueue(2) */
closedir(dir);
#endif
/* Wait for either a) next timestamp to occur, or b) a change to happen */
for (;/* ever */;) {
@ -544,6 +582,7 @@ static void *scan_thread(void *unused)
time(&now);
if (next > now) {
#ifdef HAVE_INOTIFY
int stage = 0;
/* Convert from seconds to milliseconds, unless there's nothing
* in the queue already, in which case, we wait forever. */
@ -556,6 +595,19 @@ static void *scan_thread(void *unused)
} else if (res < 0 && errno != EINTR && errno != EAGAIN) {
ast_debug(1, "Got an error back from %s(2): %s\n", stage ? "read" : "poll", strerror(errno));
}
#else
struct timespec ts2 = { next - now, 0 };
if (kevent(inotify_fd, NULL, 0, &kev, 1, &ts2) <= 0) {
/* Interrupt or timeout, restart calculations */
continue;
} else {
/* Directory changed, rescan */
rewinddir(dir);
while ((de = readdir(dir))) {
queue_file(de->d_name, 0);
}
}
#endif
time(&now);
}

390
res/res_timing_kqueue.c Normal file
View File

@ -0,0 +1,390 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2010, Digium, Inc.
*
* Tilghman Lesher <tlesher AT digium DOT com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*!
* \file
* \author Tilghman Lesher <tlesher AT digium DOT com>
*
* \brief kqueue timing interface
*/
/*** MODULEINFO
<depend>kqueue</depend>
***/
#include "asterisk.h"
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include "asterisk/module.h"
#include "asterisk/astobj2.h"
#include "asterisk/timing.h"
#include "asterisk/logger.h"
#include "asterisk/utils.h"
#include "asterisk/time.h"
#include "asterisk/test.h"
static void *timing_funcs_handle;
static int kqueue_timer_open(void);
static void kqueue_timer_close(int handle);
static int kqueue_timer_set_rate(int handle, unsigned int rate);
static void kqueue_timer_ack(int handle, unsigned int quantity);
static int kqueue_timer_enable_continuous(int handle);
static int kqueue_timer_disable_continuous(int handle);
static enum ast_timer_event kqueue_timer_get_event(int handle);
static unsigned int kqueue_timer_get_max_rate(int handle);
static struct ast_timing_interface kqueue_timing = {
.name = "kqueue",
.priority = 150,
.timer_open = kqueue_timer_open,
.timer_close = kqueue_timer_close,
.timer_set_rate = kqueue_timer_set_rate,
.timer_ack = kqueue_timer_ack,
.timer_enable_continuous = kqueue_timer_enable_continuous,
.timer_disable_continuous = kqueue_timer_disable_continuous,
.timer_get_event = kqueue_timer_get_event,
.timer_get_max_rate = kqueue_timer_get_max_rate,
};
static struct ao2_container *kqueue_timers;
struct kqueue_timer {
int handle;
uint64_t nsecs;
uint64_t unacked;
unsigned int is_continuous:1;
};
static int kqueue_timer_hash(const void *obj, const int flags)
{
const struct kqueue_timer *timer = obj;
return timer->handle;
}
static int kqueue_timer_cmp(void *obj, void *args, int flags)
{
struct kqueue_timer *timer1 = obj, *timer2 = args;
return timer1->handle == timer2->handle ? CMP_MATCH | CMP_STOP : 0;
}
static void timer_destroy(void *obj)
{
struct kqueue_timer *timer = obj;
close(timer->handle);
}
#define lookup_timer(a) _lookup_timer(a, __FILE__, __LINE__, __PRETTY_FUNCTION__)
static struct kqueue_timer *_lookup_timer(int handle, const char *file, int line, const char *func)
{
struct kqueue_timer *our_timer, find_helper = {
.handle = handle,
};
if (!(our_timer = ao2_find(kqueue_timers, &find_helper, OBJ_POINTER))) {
ast_log(__LOG_ERROR, file, line, func, "Couldn't find timer with handle %d\n", handle);
/* API says we set errno */
errno = ESRCH;
return NULL;
}
return our_timer;
}
static int kqueue_timer_open(void)
{
struct kqueue_timer *timer;
int handle;
if (!(timer = ao2_alloc(sizeof(*timer), timer_destroy))) {
ast_log(LOG_ERROR, "Could not allocate memory for kqueue_timer structure\n");
return -1;
}
if ((handle = kqueue()) < 0) {
ast_log(LOG_ERROR, "Failed to create kqueue timer: %s\n", strerror(errno));
ao2_ref(timer, -1);
return -1;
}
timer->handle = handle;
ao2_link(kqueue_timers, timer);
/* Get rid of the reference from the allocation */
ao2_ref(timer, -1);
return handle;
}
static void kqueue_timer_close(int handle)
{
struct kqueue_timer *our_timer;
if (!(our_timer = lookup_timer(handle))) {
return;
}
ao2_unlink(kqueue_timers, our_timer);
ao2_ref(our_timer, -1);
}
static void kqueue_set_nsecs(struct kqueue_timer *our_timer, uint64_t nsecs)
{
struct timespec nowait = { 0, 1 };
#ifdef HAVE_KEVENT64
struct kevent64 kev;
EV_SET64(&kev, our_timer->handle, EVFILT_TIMER, EV_ADD | EV_ENABLE, NOTE_NSECONDS,
nsecs, NULL, 0, 0);
kevent64(our_timer->handle, &kev, 1, NULL, 0, &nowait);
#else
struct kevent kev;
EV_SET(&kev, our_timer->handle, EVFILT_TIMER, EV_ADD | EV_ENABLE,
#ifdef NOTE_NSECONDS
nsecs <= 0xFFffFFff ? NOTE_NSECONDS :
#endif
#ifdef NOTE_USECONDS
NOTE_USECONDS
#else /* Milliseconds, if no constants are defined */
0
#endif
,
#ifdef NOTE_NSECONDS
nsecs <= 0xFFffFFff ? nsecs :
#endif
#ifdef NOTE_USECONDS
nsecs / 1000
#else /* Milliseconds, if nothing else is defined */
nsecs / 1000000
#endif
, NULL);
kevent(our_timer->handle, &kev, 1, NULL, 0, &nowait);
#endif
}
static int kqueue_timer_set_rate(int handle, unsigned int rate)
{
struct kqueue_timer *our_timer;
if (!(our_timer = lookup_timer(handle))) {
return -1;
}
kqueue_set_nsecs(our_timer, (our_timer->nsecs = rate ? (long) (1000000000 / rate) : 0L));
ao2_ref(our_timer, -1);
return 0;
}
static void kqueue_timer_ack(int handle, unsigned int quantity)
{
struct kqueue_timer *our_timer;
if (!(our_timer = lookup_timer(handle))) {
return;
}
if (our_timer->unacked < quantity) {
ast_debug(1, "Acking more events than have expired?!!\n");
our_timer->unacked = 0;
} else {
our_timer->unacked -= quantity;
}
}
static int kqueue_timer_enable_continuous(int handle)
{
struct kqueue_timer *our_timer;
if (!(our_timer = lookup_timer(handle))) {
return -1;
}
kqueue_set_nsecs(our_timer, 1);
our_timer->is_continuous = 1;
our_timer->unacked = 0;
ao2_ref(our_timer, -1);
return 0;
}
static int kqueue_timer_disable_continuous(int handle)
{
struct kqueue_timer *our_timer;
if (!(our_timer = lookup_timer(handle))) {
return -1;
}
kqueue_set_nsecs(our_timer, our_timer->nsecs);
our_timer->is_continuous = 0;
our_timer->unacked = 0;
ao2_ref(our_timer, -1);
return 0;
}
static enum ast_timer_event kqueue_timer_get_event(int handle)
{
enum ast_timer_event res = -1;
struct kqueue_timer *our_timer;
struct timespec sixty_seconds = { 60, 0 };
struct kevent kev;
if (!(our_timer = lookup_timer(handle))) {
return -1;
}
/* If we have non-ACKed events, just return immediately */
if (our_timer->unacked == 0) {
if (kevent(handle, NULL, 0, &kev, 1, &sixty_seconds) > 0) {
our_timer->unacked += kev.data;
}
}
if (our_timer->unacked > 0) {
res = our_timer->is_continuous ? AST_TIMING_EVENT_CONTINUOUS : AST_TIMING_EVENT_EXPIRED;
}
ao2_ref(our_timer, -1);
return res;
}
static unsigned int kqueue_timer_get_max_rate(int handle)
{
/* Actually, the max rate is 2^64-1 seconds, but that's not representable in a 32-bit integer. */
return UINT_MAX;
}
#ifdef TEST_FRAMEWORK
AST_TEST_DEFINE(test_kqueue_timing)
{
int res = AST_TEST_PASS, handle, i;
uint64_t diff;
struct pollfd pfd = { 0, POLLIN, 0 };
struct kqueue_timer *kt;
struct timeval start;
switch (cmd) {
case TEST_INIT:
info->name = "test_kqueue_timing";
info->category = "res/res_timing_kqueue";
info->summary = "Test KQueue timing interface";
info->description = "Verify that the KQueue timing interface correctly generates timing events";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
if (!(handle = kqueue_timer_open())) {
ast_test_status_update(test, "Cannot open timer!\n");
return AST_TEST_FAIL;
}
do {
pfd.fd = handle;
if (kqueue_timer_set_rate(handle, 1000)) {
ast_test_status_update(test, "Cannot set timer rate to 1000/s\n");
res = AST_TEST_FAIL;
break;
}
if (ast_poll(&pfd, 1, 1000) < 1) {
ast_test_status_update(test, "Polling on a kqueue doesn't work\n");
res = AST_TEST_FAIL;
break;
}
if (pfd.revents != POLLIN) {
ast_test_status_update(test, "poll() should have returned POLLIN, but instead returned %hd\n", pfd.revents);
res = AST_TEST_FAIL;
break;
}
if (!(kt = lookup_timer(handle))) {
ast_test_status_update(test, "Could not find timer structure in container?!!\n");
res = AST_TEST_FAIL;
break;
}
if (kqueue_timer_get_event(handle) <= 0) {
ast_test_status_update(test, "No events generated after a poll returned successfully?!!\n");
res = AST_TEST_FAIL;
break;
}
if (kt->unacked == 0) {
ast_test_status_update(test, "Unacked events is 0, but there should be at least 1.\n");
res = AST_TEST_FAIL;
break;
}
kqueue_timer_enable_continuous(handle);
start = ast_tvnow();
for (i = 0; i < 100; i++) {
if (ast_poll(&pfd, 1, 1000) < 1) {
ast_test_status_update(test, "Polling on a kqueue doesn't work\n");
res = AST_TEST_FAIL;
break;
}
if (kqueue_timer_get_event(handle) <= 0) {
ast_test_status_update(test, "No events generated in continuous mode after 1 microsecond?!!\n");
res = AST_TEST_FAIL;
break;
}
}
diff = ast_tvdiff_us(ast_tvnow(), start);
ast_test_status_update(test, "diff is %llu\n", diff);
/*
if (abs(diff - kt->unacked) == 0) {
ast_test_status_update(test, "Unacked events should be around 1000, not %llu\n", kt->unacked);
res = AST_TEST_FAIL;
}
*/
} while (0);
kqueue_timer_close(handle);
return res;
}
#endif
static int load_module(void)
{
if (!(kqueue_timers = ao2_container_alloc(563, kqueue_timer_hash, kqueue_timer_cmp))) {
return AST_MODULE_LOAD_DECLINE;
}
if (!(timing_funcs_handle = ast_register_timing_interface(&kqueue_timing))) {
ao2_ref(kqueue_timers, -1);
return AST_MODULE_LOAD_DECLINE;
}
AST_TEST_REGISTER(test_kqueue_timing);
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
int res;
AST_TEST_UNREGISTER(test_kqueue_timing);
if (!(res = ast_unregister_timing_interface(timing_funcs_handle))) {
ao2_ref(kqueue_timers, -1);
kqueue_timers = NULL;
}
return res;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "KQueue Timing Interface",
.load = load_module,
.unload = unload_module,
.load_pri = 10,
);

View File

@ -1,7 +1,7 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2009, Digium, Inc.
* Copyright (C) 2010, Digium, Inc.
*
* Tilghman Lesher <tlesher AT digium DOT com>
*
@ -48,10 +48,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
AST_TEST_DEFINE(test_timezone_watch)
{
const char *zones[2] = { "America/Chicago", "America/New_York" };
const char *zones[] = { "America/Chicago", "America/New_York" };
int type, i, res = AST_TEST_PASS;
struct timeval tv = ast_tvnow();
struct ast_tm atm[2];
struct ast_tm atm[ARRAY_LEN(zones)];
char tmpdir[] = "/tmp/timezone.XXXXXX";
char tzfile[50], syscmd[256];
@ -73,15 +73,12 @@ AST_TEST_DEFINE(test_timezone_watch)
}
snprintf(tzfile, sizeof(tzfile), "%s/test", tmpdir);
/* Allow system(3) to function correctly */
ast_replace_sigchld();
for (type = 0; type < 2; type++) {
ast_test_status_update(test, "Executing %s test...\n", type == 0 ? "deletion" : "symlink");
for (i = 0; i < ARRAY_LEN(zones); i++) {
int system_res;
snprintf(syscmd, sizeof(syscmd), "%s " TZDIR "/%s %s", type == 0 ? "cp" : "ln -sf", zones[i], tzfile);
if ((system_res = system(syscmd))) {
if ((system_res = ast_safe_system(syscmd))) {
ast_log(LOG_WARNING, "system(%s) returned non-zero: %d\n", syscmd, system_res);
}
ast_localtime_wakeup_monitor();
@ -93,19 +90,18 @@ AST_TEST_DEFINE(test_timezone_watch)
}
}
/* stat(2) only has resolution to 1 second - must wait, or the mtime is the same */
usleep(1100000);
if (i + 1 != ARRAY_LEN(zones)) {
/* stat(2) only has resolution to 1 second - must wait, or the mtime is the same */
usleep(1100000);
}
}
}
snprintf(syscmd, sizeof(syscmd), "rm -rf %s", tmpdir);
if (system(syscmd)) {
if (ast_safe_system(syscmd)) {
ast_log(LOG_WARNING, "system(%s) returned non-zero.\n", syscmd);
}
/* Restore SIGCHLD handler */
ast_unreplace_sigchld();
return res;
}