Browse Source

Initial commit

tags/v1.0.0-rc1^0
Stefan Bühler 11 years ago
commit
bb145f83fb
9 changed files with 486 additions and 0 deletions
  1. +11
    -0
      .gitignore
  2. +73
    -0
      CMakeLists.txt
  3. +22
    -0
      COPYING
  4. +10
    -0
      Makefile.am
  5. +24
    -0
      autogen.sh
  6. +6
    -0
      config.h.cmake
  7. +57
    -0
      configure.ac
  8. +40
    -0
      multiwatch.1
  9. +243
    -0
      multiwatch.c

+ 11
- 0
.gitignore View File

@@ -0,0 +1,11 @@
*~
*.o
Makefile.in
aclocal.m4
autom4te.cache
autoscan.log
config.h.in
configure
depcomp
install-sh
missing

+ 73
- 0
CMakeLists.txt View File

@@ -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)

+ 22
- 0
COPYING View File

@@ -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.

+ 10
- 0
Makefile.am View File

@@ -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

+ 24
- 0
autogen.sh View File

@@ -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."

+ 6
- 0
config.h.cmake View File

@@ -0,0 +1,6 @@
/*
CMake autogenerated config.h file. Do not edit!
*/

#define PACKAGE_NAME "${PACKAGE_NAME}"
#define PACKAGE_VERSION "${PACKAGE_VERSION}"

+ 57
- 0
configure.ac View File

@@ -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

+ 40
- 0
multiwatch.1 View File

@@ -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>.

+ 243
- 0
multiwatch.c View File

@@ -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…
Cancel
Save