Git Product home page Git Product logo

daemonproxy's Introduction

daemonproxy

A tight efficient process manager driven by external scripts

philosophy

This process manager is written in the spirit of daemontools, much like runit, s6, perp, and others.

Their common philosophy is that the one true way to monitor a daemon is for the daemon to be a child of a process manager, such that the process manager receives SIGCHLD the moment the daemon exits, sees the exit status of the daemon, and can take appropriate steps to restart the daemon.

The other semi-common philosophy is that the process manager should be able to restart a daemon connected to the same output pipe (for logging) that it was originally connected to, and also give it an identical environment each time it is started. (this is a thing that is hard to guarantee with init scripts, which could be executed from any context)

The point at which daemonproxy diverges from the rest is that it generates a stream of events which can be piped to an external script that does the actual service-management logic, and is in turn controlled by this script. Thus, it is a proxy for daemon management.

configuration

While the filesystem-based configuration of daemontools and friends is a nice simple and unix-friendly way to configure services, there are times when setting up a tree of special scripts, directories, and control files with special filesystem flags is something of a hassle. Particularly when you want to commit them to version control (losing the special permission flags), or want them to live on a read-only filesystem.

While each of those tools have various workarounds available, nothing beats a plain old config file, which can be committed to version control, stored on read-only media, or even generated by a script on the fly. daemonproxy makes this especially easy by even allowing the configuration to come from stdin.

In fact, daemonproxy's config file isn't really even a config file; it is just a stream of commands that might happen to be read from a file (or stdin, or a variety of other inputs). These commands might configure new services and they might start some of them.

But, the best feature of all is that commands can configure a "controller" service which can then issue further commands to daemonproxy over a socket. The controller script can be written in whatever language you like with whatever fancy libraries you like, without needing to add all those details to daemonproxy itself.

daemonproxy takes over the important role of the parent process, and can act as a watchdog for your script, while maintaining the state of all the services and file handles that connect them to their loggers. It also condenses signals and other events into a nice event stream (tab-delimited text) so your script doesn't have to deal with the complications of signal handling, nonblocking I/O, or waiting for children. All you have to do is read stdin and write stdout!

advantages over other supervisors

Daemonproxy is really just a platform for your supervisor, requiring some additional scripting effort to build a complete program. What makes the effort worthwhile?

Here's a quick list:

  • The more RAM a supervisor uses, the more likely it is to get OOM-killed by the kernel, and the slower it forks. (because of all the page table entries that need to change) Daemonproxy uses about 8M address space and 400K resident (on a system where 'sleep' uses 4M address space and 300K resident)

  • If a supervisor makes use of lots of fun/powerful libraries, it adds lots of potential failure points. If the supervisor dies, all the child processes become orphaned, and it can be a mess to clean up. By splitting the supervisor into a reliable parent and a controller script, you can take more risks on library usage without worrying about catastrophic failures. If anything goes wrong with the controller, daemonproxy can restart it and resynchronize, so the controller picks up where it left off.

  • If you want to start a service with a few extra pipes connected to things, other supervision programs' narrow designs won't let you. Daemonproxy will let you create any number of file/pipe/socket handles and connect them between services however you like. Want to set up 8 services which all pipe to the same logger who receives the pipes on FD 3 through 10? no problem! Want to create a service which has a port-80 bound socket on fd 3, a read-only secret key file on fd 4, and a pipe to an authentication server on fd 5? easy! By setting up pipes and socketpairs within daemonproxy, you avoid the security hassle of creating user accounts and managing directory permissions of sockets in the filesystem.

  • daemonproxy is more suitable for small embedded systems than larger supervisors like systemd or upstart. And while daemonproxy is well-suited for replacing init, it can be used for any number of supervision roles, and doesn't need to be system-wide or run as root.

  • daemonproxy is more flexible than simpler supervisors like daemontools, runit, etc. since it gives you the ability to script your own supervision logic, which could load service definitions from a YAML file or from a database.

init replacement

Daemonproxy is intended for lots of purposes, but I added a few special features for acting as process 1 on embedded systems:

  • command-line options to allocate a fixed number of objects of a fixed size at startup, so it never needs to call malloc again.
  • a "terminate-guard" feature that prevents it from accidentally exiting
  • an "exec-on-exit" feature that can exec into an emergency cleanup program on fatal errors (or just when you want to shut down the system).

And design-wise, it is a single-thread process written in non-blocking style as a collection of state machines, and it has no external library dependencies, so it is a natural fit for static compilation. It also has relatively few lines of code.

daemonproxy's People

Contributors

nrdvana avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

daemonproxy's Issues

Write 'echo' command, for finding response boundaries

Because of the peer-to-peer nature of control communication, it is useful to have a "ping" or "echo" command that lets you know you've received all responses up to the current command's response.

command: echo\t$message
response: $message

Example Config?

To get quickly going it would be super helpful to have an example config in the repo or in the manfile

Print message about end of input, for interactive mode

Interactive mode exits as soon as it sees end of file on STDIN. It should print a message telling the user why it is exiting. Perhaps it should also print the PIDs of services that were still running when it exited...

Special case for the "control.event"/"control.cmd" file handles

If a service is defined which uses filehandles "control.event" or "control.cmd", these should be attached as a new Controller instance. This is how the service which manages the other services gets defined. (in absence of connecting to a unix socket)

Add service.signal command

Should take a signal (by name or number) and only perform the kill() if the service is running. Else, print an error.

Will require adding signal-by-name lookup function.

Parse meta tags to get "auto-restart" flag

init-frame needs to know which services to auto-restart. I'm planning to use the meta tags for this. It needs to parse "auto-restart=1" and set the corresponding flag in the service.

Option for behavior in response to SIGTERM and SIGQUIT

If a controller is actively responding to SIGTERM and SIGQUIT, then these signals need to be caught and forwarded. But if there isn't a controller, there should be some option to handle them in a normal sensible way, like terminating all services and exiting.

Env getting clobbered?

This is really weird. So as you know I'm trying to get offlineimap under
daemonproxy. Here's my current config:

  fd.pipe ol-log.r        ol-log.w
  service.args    ol-log  /usr/sbin/tinylog       -k      1       log/offlineimap
  service.fds     ol-log  ol-log.r        stderr  stderr
  service.auto_up ol-log  2       always

  service.args    offlineimap     offlineimap
  service.fds     offlineimap     null    ol-log.w        ol-log.w
  service.auto_up offlineimap     1       always

Here's the output I get from running offlineimap from my shell:

  OfflineIMAP 6.5.4
    Licensed under the GNU GPL v2+ (v2 or any later version)
  Account sync Mail:
   *** Processing account Mail
   Establishing connection to imap.gmail.com:993
  Folder INBOX [acc: Mail]:
   Syncing INBOX: Gmail -> Maildir
  Folder mitsi [acc: Mail]:
   Syncing mitsi: Gmail -> Maildir

Now here's the output of the logger when I run it under daemonproxy:

  OfflineIMAP 6.5.4
    Licensed under the GNU GPL v2+ (v2 or any later version)
  Account sync Mail:
   *** Processing account Mail
   Establishing connection to imap.gmail.com:993
   ERROR: While attempting to sync account 'Mail'
    Could not find .netrc: $HOME is not set
   *** Finished account 'Mail' in 0:00
   Next refresh in 1.0 minutes

I suspect that the environment does not correctly get cloned into the
subprocess. That would explain why I needed to include the path for tinylog.

Thoughts?

Example scripts

I think it would be really cool, expecially for casual" users like myself, if
you had a set of control scripts for a starting point for creating the external
scripts.

Obviously this is not my forté, but a fun thing to do might be to make a
daemonproxy based perp (similar commands), so maybe

  • derpctl
  • derphup
  • derpls
  • derpok
  • derpstat

I might try my hand at this, but I'm honestly not really sure where to start; I
guess if I were using perl I'd want to have an event loop watching for events on
STDIN and on some domain socket for external control? I'll let you know if I
come up with anything.

Oh also, I'd understand if you don't think that stuff like this should be
bundled with daemonproxy, but at least linking to examples would be nice.

Implement fd.pipe and fd.open commands

The backend for opening pipes and files is implemented, but not the command interface. These are needed to set up logging for services. Also fd.close should be implemented.

Doesn't check that args has at least one arg

I was trying to get a really basic config set up and I tried this:

  service.args    offlineimap
  service.fds     offlineimap     null    stdout  stdout
  service.auto_up offlineimap     1       always

The problem, obviously, is that I'm not actually picking a program to exec.

It might be nice to check that sooner instead of when the command is actually
being run.

Write "terminate" command

There needs to be a command which controllers can ask init-frame to terminate. While the command is simple, semantics for what should be done when terminating need to be thought through.

Write testcase for pipe overflow

Write a controller script that requests a bunch of data without reading any responses. Verify that the overflow gets set and then the overflow event gets delivered when script starts reading stdin again. Verify that commands work again after that.

Also write a script that sends lots of signals to daemonproxy, causing an overflow.

Also write a script that writes longer and longer lines of output and verify that daemonproxy correctly reports them as errors. Ensure that long comment lines don't trigger the error message. Ensure that command processing still works afterward.

Add service.cancel or "service.start NAME -" or something

Need a command to cancel a previously scheduled service start. Without this, there is no way to "down" a service without waiting for the countdown, if the delayed start or restart interval feature was used.

With this, the prescribed sequence to down a service will be:

  service.auto_up NAME -
  service.start NAME -
  service.signal NAME TERM

(wait for service.state of 'down', or...)

  service.signal NAME KILL

Write testcases for process management

Should test that it runs a service and correctly identifies

  • execution time
  • exit status (test exit codes 0, 1, 255, sigterm, sigsegv, sighup)
  • timestamps for exec and reap (when compared to MONOTONIC of another process)

Implement "terminate shutdown" command

This command should perform the standard cleanup procedure of sending signals to children, until all have exited, then waiting until I/O has been flushed, then exiting.

Implement "failsafe" command

This command should enable/disable the failsafe flag. It should take an option of +/-, and an integer argument that must be repeated when disabling the flag.

Write testcases for exec-on-exit feature

Use an exec-on-exit script to verify that daemonproxy passes control to it under a variety of error scenarios, including

  • Incorrect arguments following the exec-on-exit argument
  • segfault (need some way to link a "buggy" function that reads a null pointer)
  • malloc failure (link in bogus malloc function)
  • 'terminate' command

Log errors in config file

Daemonproxy doesn't emit any error messages for commands that come from a config file. These clearly ought to go to the log.

Piping (or even general?) DSL

Having a logger that is only used for a single service feels really "heavy". I
wouldn't mind it so much if my services were sharing the logger, but given that
they aren't it ends up being pretty noisy when %50 of my config is for logging.

For example, this is normal for me:

  fd.pipe offlineimap-log.r offlineimap-log.w
  service.args offlineimap-log /usr/sbin/tinylog -t -k 1 log/offlineimap
  service.fds offlineimap-log offlineimap-log.r stderr stderr
  service.auto_up offlineimap-log 1 always

  service.args offlineimap env HOME=/home/frew offlineimap
  service.fds offlineimap null offlineimap-log.w offlineimap-log.w
  service.auto_up offlineimap 1 always

I was thinking it would be nice if (and maybe this is a sugar layer that
generates a config file, or a script that talks to daemonproxy) you could do
something like this:

  service.args offlineimap env HOME=/home/frew offlineimap
  service.pipe offlineimap 0 1 1 /usr/sbin/tinylog -t -k 1 log/offlineimap
  service.auto_up offlineimap 1 always

I see how that's ghetto; maybe once I use daemonproxy enough I'll know how to
make it it scriptable. An example of how to script this might be nice.

Implement signal number/name mapping using table

In order to keep the code smaller, redesign the signal-by-name and signal-by-number to iterate a small table. The locality of the data should make up for not using switch statements or hash tables.

Make logging destination configurable

All log messages go to stdout currently, but it should be possible to redirect logging into a service maintained by daemonproxy itself. (i.e. direct log output into a pipe attached to a service.)

New functions for altering service.meta fields

It would be more convenient to have "service.meta.apply (changes)" than to have to add/remove values from the client. Also this avoids race conditions. Implement this method.

Also, "service.meta" should be the method to query the state, not set it. Change "service.args", "service.fds", "service.meta" into "service.args.set", "service.fd.set", "service.meta.set".

Write man page

Write a synopsis of features and protocol, and have makefile 'install' target install it.

Add protocol for timestamping signals and clearing them

Currently signals just get delivered to controllers as they arrive. If the controller were in the process of restarting or had a full pipe, the signal is lost.

In order to have proper handling for signals, they need to be added to a queue such that the controller can clear them once they've been dealt with.

There needs to be a sensible interface to handle the accumulation of duplicate signals and containing them in a fixed memory buffer.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.