commit
bb145f83fb
@ -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;
|
||||
}
|
Loading…
Reference in new issue