aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore11
-rw-r--r--CMakeLists.txt73
-rw-r--r--COPYING22
-rw-r--r--Makefile.am10
-rwxr-xr-xautogen.sh24
-rw-r--r--config.h.cmake6
-rw-r--r--configure.ac57
-rw-r--r--multiwatch.140
-rw-r--r--multiwatch.c243
9 files changed, 486 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6214f89
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+*~
+*.o
+Makefile.in
+aclocal.m4
+autom4te.cache
+autoscan.log
+config.h.in
+configure
+depcomp
+install-sh
+missing
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..c05f387
--- /dev/null
+++ b/CMakeLists.txt
@@ -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)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..2838e22
--- /dev/null
+++ b/COPYING
@@ -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.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..973e2c7
--- /dev/null
+++ b/Makefile.am
@@ -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
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..7673deb
--- /dev/null
+++ b/autogen.sh
@@ -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."
diff --git a/config.h.cmake b/config.h.cmake
new file mode 100644
index 0000000..69bb180
--- /dev/null
+++ b/config.h.cmake
@@ -0,0 +1,6 @@
+/*
+ CMake autogenerated config.h file. Do not edit!
+*/
+
+#define PACKAGE_NAME "${PACKAGE_NAME}"
+#define PACKAGE_VERSION "${PACKAGE_VERSION}"
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..779b79c
--- /dev/null
+++ b/configure.ac
@@ -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
diff --git a/multiwatch.1 b/multiwatch.1
new file mode 100644
index 0000000..5f58fc9
--- /dev/null
+++ b/multiwatch.1
@@ -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>.
diff --git a/multiwatch.c b/multiwatch.c
new file mode 100644
index 0000000..887ce98
--- /dev/null
+++ b/multiwatch.c
@@ -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;
+}