@ -0,0 +1,11 @@ | |||
*~ | |||
*.o | |||
Makefile.in | |||
aclocal.m4 | |||
autom4te.cache | |||
autoscan.log | |||
config.h.in | |||
configure | |||
depcomp | |||
install-sh | |||
missing |
@ -0,0 +1,73 @@ | |||
CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0 FATAL_ERROR) | |||
cmake_policy(VERSION 2.6.0) | |||
INCLUDE(CheckIncludeFiles) | |||
INCLUDE(CheckLibraryExists) | |||
INCLUDE(FindPkgConfig) | |||
MACRO(ADD_TARGET_PROPERTIES _target _name _properties) | |||
SET(_properties ${ARGV}) | |||
LIST(REMOVE_AT _properties 0) | |||
LIST(REMOVE_AT _properties 0) | |||
GET_TARGET_PROPERTY(_old_properties ${_target} ${_name}) | |||
#MESSAGE("adding property to ${_target} ${_name}: ${_properties}") | |||
IF(NOT _old_properties) | |||
# in case it's NOTFOUND | |||
SET(_old_properties) | |||
ELSE(NOT _old_properties) | |||
SET(_old_properties "${_old_properties} ") | |||
ENDIF(NOT _old_properties) | |||
SET_TARGET_PROPERTIES(${_target} PROPERTIES ${_name} "${_old_properties}${_properties}") | |||
ENDMACRO(ADD_TARGET_PROPERTIES) | |||
PROJECT(multiwatch) | |||
SET(PACKAGE_VERSION 1.0.0) | |||
IF("${CMAKE_BUILD_TYPE}" STREQUAL "") | |||
SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." FORCE) | |||
ENDIF("${CMAKE_BUILD_TYPE}" STREQUAL "") | |||
# libev | |||
CHECK_INCLUDE_FILES(ev.h HAVE_EV_H) | |||
IF(HAVE_EV_H) | |||
CHECK_LIBRARY_EXISTS(ev ev_loop "" HAVE_LIBEV) | |||
IF(HAVE_LIBEV) | |||
SET(EV_LIBRARIES ev) | |||
SET(EV_STATIC_LIBRARIES ev;m) | |||
CHECK_LIBRARY_EXISTS(rt clock_gettime "" NEED_RT) | |||
IF(NEED_RT) | |||
SET(EV_STATIC_LIBRARIES ${EV_STATIC_LIBRARIES} rt) | |||
ENDIF(NEED_RT) | |||
ELSE(HAVE_LIBEV) | |||
MESSAGE(FATAL_ERROR "Couldn't find lib ev") | |||
ENDIF(HAVE_LIBEV) | |||
ELSE(HAVE_EV_H) | |||
MESSAGE(FATAL_ERROR "Couldn't find <ev.h>") | |||
ENDIF(HAVE_EV_H) | |||
# GLIB 2 | |||
pkg_check_modules (GLIB2 REQUIRED glib-2.0) | |||
SET(GLIB_INCLUDES ${GLIB2_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS}/glib-2.0/ ${GLIB2_INCLUDE_DIRS}/glib-2.0/include/) | |||
INCLUDE_DIRECTORIES(${GLIB_INCLUDES}) | |||
SET(MAIN_SOURCE multiwatch.c) | |||
SET(PACKAGE_NAME ${CMAKE_PROJECT_NAME}) | |||
SET(PACKAGE_VERSION ${PACKAGE_VERSION}) | |||
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_BINARY_DIR}/config.h ESCAPE_QUOTES) | |||
ADD_DEFINITIONS(-DHAVE_CONFIG_H) | |||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) | |||
add_executable(multiwatch ${MAIN_SOURCE}) | |||
ADD_TARGET_PROPERTIES(multiwatch COMPILE_FLAGS "-std=gnu99 -Wall -g -Wshadow -W -pedantic -fPIC -D_GNU_SOURCE") | |||
# libev | |||
TARGET_LINK_LIBRARIES(multiwatch "${EV_LIBRARIES}") | |||
# GLIB 2 | |||
ADD_TARGET_PROPERTIES(multiwatch LINK_FLAGS "${GLIB2_LDFLAGS}") | |||
ADD_TARGET_PROPERTIES(multiwatch COMPILE_FLAGS "${GLIB2_CFLAGS_OTHER}") | |||
INSTALL(TARGETS multiwatch DESTINATION bin) |
@ -0,0 +1,22 @@ | |||
The MIT License | |||
Copyright (c) 2008 Stefan Bühler | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in | |||
all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
THE SOFTWARE. |
@ -0,0 +1,10 @@ | |||
AM_CFLAGS=$(GLIB_CFLAGS) | |||
multiwatch_LDADD=$(GLIB_LIBS) | |||
EXTRA_DIST=autogen.sh multiwatch.1 CMakeLists.txt config.h.cmake | |||
man1_MANS=multiwatch.1 | |||
bin_PROGRAMS=multiwatch | |||
multiwatch_SOURCES=multiwatch.c |
@ -0,0 +1,24 @@ | |||
#!/bin/sh | |||
# Run this to generate all the initial makefiles, etc. | |||
ACLOCAL=${ACLOCAL:-aclocal} | |||
AUTOHEADER=${AUTOHEADER:-autoheader} | |||
AUTOMAKE=${AUTOMAKE:-automake} | |||
AUTOMAKE_FLAGS="--add-missing --copy" | |||
AUTOCONF=${AUTOCONF:-autoconf} | |||
ARGV0=$0 | |||
set -e | |||
run() { | |||
echo "$ARGV0: running \`$@'" | |||
$@ | |||
} | |||
run $ACLOCAL $ACLOCAL_FLAGS | |||
run $AUTOHEADER | |||
run $AUTOMAKE $AUTOMAKE_FLAGS | |||
run $AUTOCONF | |||
echo "Now type './configure ...' and 'make' to compile." |
@ -0,0 +1,6 @@ | |||
/* | |||
CMake autogenerated config.h file. Do not edit! | |||
*/ | |||
#define PACKAGE_NAME "${PACKAGE_NAME}" | |||
#define PACKAGE_VERSION "${PACKAGE_VERSION}" |
@ -0,0 +1,57 @@ | |||
# -*- Autoconf -*- | |||
# Process this file with autoconf to produce a configure script. | |||
AC_PREREQ(2.61) | |||
AC_INIT([multiwatch], [1.0.0], []) | |||
AC_CONFIG_SRCDIR([multiwatch.c]) | |||
AC_CONFIG_HEADER([config.h]) | |||
AM_INIT_AUTOMAKE([-Wall -Werror foreign]) | |||
# Checks for programs. | |||
AC_PROG_CC | |||
AC_PROG_MAKE_SET | |||
AC_PROG_INSTALL | |||
# Checks for libraries. | |||
# glib-2.0 | |||
PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.16.0, [ | |||
AC_DEFINE([HAVE_GLIB_H], [1], [glib.h]) | |||
],[AC_MSG_ERROR("glib-2.0 >= 2.16.0 not found")]) | |||
# lib ev | |||
AC_CHECK_HEADERS([ev.h], [], [AC_MSG_ERROR("ev.h not found")]) | |||
AC_CHECK_LIB([ev], [ev_loop], [ | |||
LIBS="-lev ${LIBS}" | |||
AC_DEFINE([HAVE_LIBEV], [1], [ev_loop in -lev]) | |||
], [AC_MSG_ERROR("libev not found")]) | |||
# Checks for header files. | |||
AC_CHECK_HEADERS([stdlib.h unistd.h], []) | |||
# Checks for typedefs, structures, and compiler characteristics. | |||
AC_TYPE_PID_T | |||
# Checks for library functions. | |||
AC_FUNC_FORK | |||
# check for extra compiler options (warning options) | |||
if test "${GCC}" = "yes"; then | |||
CFLAGS="${CFLAGS} -Wall -W -Wshadow -pedantic -std=gnu99" | |||
fi | |||
AC_ARG_ENABLE(extra-warnings, | |||
AC_HELP_STRING([--enable-extra-warnings],[enable extra warnings (gcc specific)]), | |||
[case "${enableval}" in | |||
yes) extrawarnings=true ;; | |||
no) extrawarnings=false ;; | |||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-extra-warnings) ;; | |||
esac],[extrawarnings=false]) | |||
if test x$extrawarnings = xtrue; then | |||
CFLAGS="${CFLAGS} -g -O2 -g2 -Wall -Wmissing-declarations -Wdeclaration-after-statement -Wno-pointer-sign -Wcast-align -Winline -Wsign-compare -Wnested-externs -Wpointer-arith -Wl,--as-needed -Wformat-security" | |||
fi | |||
AC_CONFIG_FILES([Makefile]) | |||
AC_OUTPUT |
@ -0,0 +1,40 @@ | |||
.TH multiwatch 1 "March 24, 2009" | |||
.SH NAME | |||
multiwatch \- forks and watches multiple instances of a program in the same environment | |||
.SH SYNOPSIS | |||
.B multiwatch | |||
[options] -- <application> [app arguments] | |||
.B multiwatch | |||
\-v | |||
.B multiwatch | |||
\-\-help | \-? | |||
.SH DESCRIPTION | |||
\fImultiwatch\fP is used to fork and watch multiple fastcgi backends. | |||
.SH OPTIONS | |||
.TP 8 | |||
.B \-f, \-\-forks=childs | |||
Number of childs to fork and watch(default 1) | |||
.TP 8 | |||
.B \-r, --retry=retries | |||
Number of retries to fork a single child | |||
.TP 8 | |||
.B \-t, --timeout=msecs | |||
Retry timeout in ms; if the child dies after the timeout the retry counter is reset | |||
.TP 8 | |||
.B \-?, --help | |||
General usage instructions | |||
.TP 8 | |||
.B \-v, --version | |||
Show version | |||
.SH EXAMPLE | |||
.TP 8 | |||
Spawn 2 rails instances on the same fastcgi socket (and supervise them): | |||
.RS 8 | |||
.B spawn-fcgi -s /tmp/fastcgi-php.sock -C 0 -n /usr/bin/multiwatch -f 2 /home/rails/public/dispatch.fcgi | |||
.RE | |||
.SH SEE ALSO | |||
spawn-fcgi(1) | |||
.SH AUTHOR | |||
Stefan Buehler <stbuehler@web.de>. |
@ -0,0 +1,243 @@ | |||
#include <glib.h> | |||
#include <ev.h> | |||
#include <signal.h> | |||
#include <stdlib.h> | |||
#include <sys/types.h> | |||
#include <sys/wait.h> | |||
#include <unistd.h> | |||
#include <errno.h> | |||
#ifdef HAVE_CONFIG_H | |||
#include "config.h" | |||
#endif | |||
#define UNUSED(x) ((void)(x)) | |||
#define PACKAGE_DESC (PACKAGE_NAME "-" PACKAGE_VERSION " - forks and watches multiple instances of a program in the same environment") | |||
typedef struct { | |||
gchar **app; | |||
gint forks; | |||
/* how many times we try to spawn a child */ | |||
gint retry; | |||
/* time within a dieing child is handled as "spawn failed" | |||
* if it dies after the timeout, the retry counter is reset and | |||
* we try to get it up again | |||
*/ | |||
gint retry_timeout_ms; | |||
gboolean show_version; | |||
} options; | |||
struct data; | |||
typedef struct data data; | |||
struct child; | |||
typedef struct child child; | |||
struct child { | |||
data *d; | |||
int id; | |||
pid_t pid; | |||
gint tries; | |||
ev_tstamp last_spawn; | |||
ev_child watcher; | |||
}; | |||
struct data { | |||
child *childs; | |||
guint running; | |||
gboolean shutdown; | |||
struct ev_loop *loop; | |||
ev_signal sigHUP, sigINT, sigQUIT, sigTERM, sigUSR1, sigUSR2; | |||
gint return_status; | |||
}; | |||
static options opts = { | |||
NULL, | |||
1, | |||
3, | |||
10000, | |||
FALSE | |||
}; | |||
static void forward_sig_cb(struct ev_loop *loop, ev_signal *w, int revents) { | |||
data *d = (data*) w->data; | |||
UNUSED(loop); | |||
UNUSED(revents); | |||
for (gint i = 0; i < opts.forks; i++) { | |||
if (d->childs[i].pid != -1) { | |||
kill(d->childs[i].pid, w->signum); | |||
} | |||
} | |||
} | |||
static void terminate_forward_sig_cb(struct ev_loop *loop, ev_signal *w, int revents) { | |||
data *d = (data*) w->data; | |||
UNUSED(loop); | |||
UNUSED(revents); | |||
d->shutdown = TRUE; | |||
for (gint i = 0; i < opts.forks; i++) { | |||
if (d->childs[i].pid != -1) { | |||
kill(d->childs[i].pid, w->signum); | |||
} | |||
} | |||
} | |||
static void spawn(child* c) { | |||
pid_t pid; | |||
if (c->tries++ > opts.retry) { | |||
g_printerr("Child[%i] died to often, not forking again\n", c->id); | |||
return; | |||
} | |||
switch (pid = fork()) { | |||
case -1: | |||
g_printerr("Fatal Error: Couldn't fork child[%i]: %s\n", c->id, g_strerror(errno)); | |||
if (0 == c->d->running) { | |||
g_printerr("No child running and fork failed -> exit\n"); | |||
c->d->return_status = -100; | |||
ev_unloop(c->d->loop, EVUNLOOP_ALL); | |||
} | |||
/* Do not retry... */ | |||
break; | |||
case 0: | |||
/* child */ | |||
execv(opts.app[0], opts.app); | |||
g_printerr("Exec failed: %s\n", g_strerror(errno)); | |||
exit(errno); | |||
break; | |||
default: | |||
c->pid = pid; | |||
c->d->running++; | |||
c->last_spawn = ev_now(c->d->loop); | |||
ev_child_set(&c->watcher, c->pid, 0); | |||
ev_child_start(c->d->loop, &c->watcher); | |||
break; | |||
} | |||
} | |||
static void child_died(struct ev_loop *loop, ev_child *w, int revents) { | |||
child *c = (child*) w->data; | |||
UNUSED(revents); | |||
ev_child_stop(loop, w); | |||
c->d->running--; | |||
c->pid = -1; | |||
if (c->d->shutdown) return; | |||
if (ev_now(c->d->loop) - c->last_spawn > (opts.retry_timeout_ms / (ev_tstamp) 1000)) { | |||
g_printerr("Child[%i] died, respawn\n", c->id); | |||
c->tries = 0; | |||
} else { | |||
g_printerr("Spawing child[%i] failed, next try\n", c->id); | |||
} | |||
spawn(c); | |||
} | |||
static const GOptionEntry entries[] = { | |||
{ "forks", 'f', 0, G_OPTION_ARG_INT, &opts.forks, "Number of childs to fork and watch(default 1)", "childs" }, | |||
{ "retry", 'r', 0, G_OPTION_ARG_INT, &opts.retry, "Number of retries to fork a single child", "retries" }, | |||
{ "timeout", 't', 0, G_OPTION_ARG_INT, &opts.retry_timeout_ms, "Retry timeout in ms; if the child dies after the timeout the retry counter is reset", "ms" }, | |||
{ "version", 'v', 0, G_OPTION_ARG_NONE, &opts.show_version, "Show version", NULL }, | |||
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &opts.app, "<application> [app arguments]", NULL }, | |||
{ NULL, 0, 0, 0, NULL, NULL, NULL } | |||
}; | |||
int main(int argc, char **argv) { | |||
GOptionContext *context; | |||
GError *error = NULL; | |||
gint res; | |||
context = g_option_context_new("<application> [app arguments]"); | |||
g_option_context_add_main_entries(context, entries, NULL); | |||
g_option_context_set_summary(context, PACKAGE_DESC); | |||
if (!g_option_context_parse (context, &argc, &argv, &error)) { | |||
g_printerr("Option parsing failed: %s\n", error->message); | |||
return -1; | |||
} | |||
if (opts.show_version) { | |||
g_printerr(PACKAGE_DESC); | |||
g_printerr("\nBuild-Date: " __DATE__ " " __TIME__ "\n"); | |||
return 0; | |||
} | |||
if (!opts.app || !opts.app[0]) { | |||
g_printerr("Missing application\n"); | |||
return -2; | |||
} | |||
if (opts.forks < 1) { | |||
g_printerr("Invalid forks argument: %i\n", opts.forks); | |||
return -3; | |||
} | |||
if (opts.retry < 1) { | |||
g_printerr("Invalid retry argument: %i\n", opts.retry); | |||
return -4; | |||
} | |||
if (opts.retry_timeout_ms < 0) { | |||
g_printerr("Invalid timeout argument: %i\n", opts.retry_timeout_ms); | |||
return -5; | |||
} | |||
data *d = g_slice_new0(data); | |||
d->childs = (child*) g_slice_alloc0(sizeof(child) * opts.forks); | |||
d->running = 0; | |||
d->shutdown = FALSE; | |||
d->return_status = 0; | |||
d->loop = ev_default_loop(0); | |||
#define WATCH_SIG(x) do { ev_signal_init(&d->sig##x, forward_sig_cb, SIG##x); d->sig##x.data = d; ev_signal_start(d->loop, &d->sig##x); ev_unref(d->loop); } while (0) | |||
#define WATCH_TERM_SIG(x) do { ev_signal_init(&d->sig##x, terminate_forward_sig_cb, SIG##x); d->sig##x.data = d; ev_signal_start(d->loop, &d->sig##x); ev_unref(d->loop); } while (0) | |||
#define UNWATCH_SIG(x) do { ev_ref(d->loop); ev_signal_stop(d->loop, &d->sig##x); } while (0) | |||
WATCH_TERM_SIG(HUP); | |||
WATCH_TERM_SIG(INT); | |||
WATCH_TERM_SIG(QUIT); | |||
WATCH_TERM_SIG(TERM); | |||
WATCH_SIG(USR1); | |||
WATCH_SIG(USR2); | |||
for (gint i = 0; i < opts.forks; i++) { | |||
d->childs[i].d = d; | |||
d->childs[i].id = i; | |||
d->childs[i].pid = -1; | |||
d->childs[i].tries = 0; | |||
d->childs[i].watcher.data = &d->childs[i]; | |||
ev_child_init(&d->childs[i].watcher, child_died, -1, 0); | |||
spawn(&d->childs[i]); | |||
} | |||
ev_loop(d->loop, 0); | |||
res = d->return_status; | |||
g_slice_free1(sizeof(child) * opts.forks, d->childs); | |||
g_slice_free(data, d); | |||
UNWATCH_SIG(HUP); | |||
UNWATCH_SIG(INT); | |||
UNWATCH_SIG(QUIT); | |||
UNWATCH_SIG(TERM); | |||
UNWATCH_SIG(USR1); | |||
UNWATCH_SIG(USR2); | |||
return res; | |||
} |