#include "conf.h"
#include "logger.h"
#include "responder.h"
#include "ipc.h"
#define IDENT_FMT "pyfcgi[%d]"
#define MAX_REQS 1024
#define EARLY_ERR(err_str) write(2, err_str, strlen(err_str))
extern pyfcgi_conf_t PyFCGI_conf;
pid_t pool_handler_pid;
pid_t monitor_serv_pid;
void sighandler(int signum)
{
int status, ret, i;
struct timespec req;
req.tv_sec = 0;
req.tv_nsec = 100000000; //0.1s
if(signum == SIGALRM)
{
pyfcgi_log(LOG_WARNING, "Master process received SIGALRM !");
return;
}
else if(signum == SIGINT)
{
pyfcgi_log(LOG_INFO,
"Master process received ctrl+c, exiting...");
}
else
{
pyfcgi_log(LOG_INFO,
"Master process received signal %s(%d), exiting...",
strsignal(signum), signum);
}
if(pool_handler_pid)
{
pyfcgi_log(LOG_INFO, "Killing pool_handler(%d)",
pool_handler_pid);
kill(pool_handler_pid, SIGTERM);
for(i=0; i<5; i++)
{
ret = waitpid(pool_handler_pid, &status, WNOHANG);
if(ret <= 0)
{
nanosleep(&req, NULL);
continue;
}
pool_handler_pid = 0;
break;
}
}
if(monitor_serv_pid)
{
pyfcgi_log(LOG_INFO, "Killing monitor(%d)",
monitor_serv_pid);
kill(monitor_serv_pid, SIGTERM);
for(i=0; i<5; i++)
{
ret = waitpid(monitor_serv_pid, &status, WNOHANG);
if(ret <= 0)
{
nanosleep(&req, NULL);
continue;
}
monitor_serv_pid = 0;
break;
}
}
if(pool_handler_pid)
{
pyfcgi_log(LOG_WARNING,
"pool_handler(%d) seems to freeze, sending SIGKILL",
pool_handler_pid);
kill(pool_handler_pid, SIGKILL);
}
if(monitor_serv_pid)
{
pyfcgi_log(LOG_WARNING,
"pool_handler(%d) seems to freeze, sending SIGKILL",
monitor_serv_pid);
kill(monitor_serv_pid, SIGKILL);
}
pyfcgi_log(LOG_INFO,
"Master process exiting.");
exit(0);
}
void debug_sighandler(int signum)
{
pyfcgi_log(LOG_WARNING, "Master process received signal %s(%d)",
strsignal(signum), signum);
return;
}
int main(int argc, char **argv)
{
int emerg_sleep = 3;
int child_ret;
pid_t rpid;
struct sigaction act;
short fails, need_wait;
//Sleeping on waitpid WNOHANG
struct timespec tsleep;
char *child_name;
act.sa_handler = sighandler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_restorer = NULL;
if(sigaction(SIGTERM, &act, NULL))
{
perror("Sigaction error");
exit(4);
}
if(sigaction(SIGINT, &act, NULL))
{
perror("Sigaction error");
exit(4);
}
act.sa_handler = debug_sighandler;
if(sigaction(SIGALRM, &act, NULL))
{
perror("Sigaction2 error");
exit(4);
}
pool_handler_pid = 0;
monitor_serv_pid = 0;
default_conf();
pyfcgi_name_IPC(getpid()); //name semaphore using master proc PID
pyfcgi_logger_init();
pyfcgi_logger_set_ident("MainProc");
if(parse_args(argc, argv))
{
return 1;
}
pyfcgi_log(LOG_INFO, "New server started");
while(1)
{
if(!pool_handler_pid)
{
pool_handler_pid = spawn_pool_handler();
if(pool_handler_pid < 0)
{
fails = 1;
pool_handler_pid = 0;
}
}
if(PyFCGI_conf.mon_socket && !monitor_serv_pid)
{
monitor_serv_pid = pyfcgi_spawn_monitor();
if(monitor_serv_pid < 0)
{
fails = 1;
monitor_serv_pid = 0;
}
}
if(fails)
{
if(emerg_sleep > 600)
{
pyfcgi_log(LOG_EMERG, "Abording...");
exit(PYFCGI_FATAL);
}
fails = 0;
pyfcgi_log(LOG_WARNING, "Sleeping %ds",
emerg_sleep);
emerg_sleep *= 2;
continue;
}
else
{
emerg_sleep = 3;
}
need_wait = ((!PyFCGI_conf.mon_socket || monitor_serv_pid)
&& pool_handler_pid);
rpid = waitpid(0, &child_ret, need_wait?0:WNOHANG);
if(rpid == pool_handler_pid)
{
child_name = "Pool handler";
pool_handler_pid = 0;
if(!child_ret)
{
pyfcgi_log(LOG_NOTICE,
"Restarting main process after %d requests",
MAX_REQS);
continue;
}
}
else if(rpid == monitor_serv_pid)
{
child_name = "Monitor server";
monitor_serv_pid = 0;
}
else if(rpid == 0 && !need_wait)
{
tsleep.tv_sec = 0;
tsleep.tv_nsec = 100000000;
nanosleep(&tsleep, NULL);
continue;
}
else if(rpid < 0)
{
pyfcgi_log(LOG_EMERG, "Unable to waitpid : %s",
strerror(errno));
exit(PYFCGI_FATAL);
}
else
{
child_name = "Unknown child";
pyfcgi_log(LOG_WARNING,
"Unknown child (PID %d) exited...", rpid);
}
if(child_ret)
{
pyfcgi_log(LOG_ERR, "%s exits with error code : %s",
child_name, status2str(WEXITSTATUS(child_ret)));
if(WIFSIGNALED(child_ret))
{
pyfcgi_log(LOG_ERR, "%s terminated by sig %s(%d)",
child_name,
strsignal(WTERMSIG(child_ret)),
WTERMSIG(child_ret));
}
}
else
{
pyfcgi_log(LOG_WARNING,
"%s exits with no error status 0",
child_name);
}
}
closelog();
}
/**@mainpage PyFCGI
* @section main_what What is PyFCGI ?
* PyFCGI is a simple python3 fastcgi runner.
*
* Usage : Usage : spawn-fcgi [OPTIONS] -- pyfcgi -e PYMODULE [-E PYFUN] [OPTIONS]
*
* To run foo.entrypoint() python function.
*
* @subsection main_how_use How to use it ?
*
* @warning For the moment PyFCGI is under heavy developpement and in early
* stage. Everything will change ;o)
*
* PyFCGI should be runned with spawn-fcgi (or something similar), allowing
* to configure & forward environnement variables to libFCGI.
*
* For the moment no configuration files exists. You have to pass arguments
* to pyfcgi using -- argument of spawnn-fcgi
*
* When called this function will have to send valid CGI data to the webserver
* using sys.stdin (the print() function for exemple) like :
*Content-type: text/html\\r\\n\\r\\nHello world !\\n
*
* The function will have access to updated CGI os.environ containing
* all informations about a request
*
* @subsubsection main_how_use_syslog Logging, using syslog
*
* Right now PyFCGI uses pyfcgi_log() to log stuff using a pyfcgi ident.
* PyFCGI logs can be filtered using /etc/rsyslog.d/pyfcgi.conf :
*
if ($programname contains 'pyfcgi') then {
-/var/log/pyfcgi/pyfcgi.log
stop
}
*
* @subsubsection main_how_use_hardcode Hardcoded stuffs
*
* Right now there is a lot of hardcodd stuff. Fortunatly a vast majority
* is in @ref main.c like the minimum and maximum number of workers, or
* the number of requests before a worker restart. Some timers and stuff
* are harcoded but should not in @ref pyworker.c & @ref responder.c
*
* @subsection main_how_works How it works ?
*
* - @ref processes
* - @ref main_proc
* - @ref work_master_proc
* - @ref worker_process
*
*/
/**@page pyfcgi
* @brief Python Fast CGI runner
* @section man_syn SYNOPSIS
* pyfcgi [OPTIONS]
* @section man_desc DESCRIPTION
* Run WSGI python application in a pool of worker.
*
* @section man_opt OPTIONS
* @subsection man_opt_gen General OPTIONS
* @par -h --help
* Display help and exit
* @par -V --version
* Display pyfcgi and Python version and exit
* @par -C --config=FILE
* load a configuration file
* @par -l --listen=SOCK_PATH
* fcgi listen socket path. For TCP socket use "IPv4:PORT" syntax (
* "127.0.0.1:9000" by default)
* @par -e --pymodule=MODULE_NAME
* python entrypoint module name
* @par -E --pyapp=FUNC_NAME
* python entrypoint function name
* @par -A --alt-io
* use stdout to communicate with web server instead of entrypoint return as
* specified in PEP333
* @par -P --pid-file=PATH
* Create a pidfile with master process PID
*
* @subsection man_opt_pool Worker pool OPTIONS
* @par -w --min-worker
* minimum worker in the pool
* @par -W --max-worker
* maximum worker in the pool
* @par -f --fast-spawn
* If not given there is at least 1s between two child creation. When given
* childs may be spawned in small burst.
* @par -t --timeout=SECONDS
* Request timeout. If the timeout expires the worker process is restarted.
*
* @subsection man_opt_log Logging & monitoring OPTIONS
* @par -L --log=LOGGERSPEC
* Add a logfile using syntax "LOGFILE[;FILT][;FMT]" See @ref man_logger_specs
* @par -S --syslog
* Use syslog for logging
* @par -v --verbose
* Send all loglines on STDERR
* @par -s --socket-server=SOCKURL
* Indicate a socket like "tcp://localhost:8765" to listen on, replying status
* and statistics. See @ref man_url_spec
*
* @section man_logger_specs Logfile format
* A logger specification use the following format : LOGILE[;FILT][;FMT] with
* - LOGFILE the logfile name
* - FILT a number (in decimal or hex 0xHH) indicating wich facility/level to
* log
* - FMT the logline format, following the markup format described bellow
* @subsection man_logger_fmt Logline format
* Logline's format is indicated using a simple format using fields between
* '{' and '}'. Supported fields are :
* - {datetime} {datetime:SIZE} {datetime:SIZE:FMT} defines a format and a
* constant length for a datetime field. Default : {datetime:25:%F %T%z}
* - {level} the loglevel
* - {facility} the log facility
* - {pid} the process PID
* - {ident} the process ident (friendly name)
* - {msg} the log message (can appear only once)
*
* Specials chars '{' & '}' can be escpaed using "{{" and "}}", and all field
* names can be abreviated to one character.
*
* @section man_url_spec Statistics socket URL format
* Statistics server listen on a socket specified by an URL like :
*
* PROT://HOST:[PORT] with PROT one of :
* - tcp with HOST a valid INET address
* - udp with HOST a valid INET address
* - unix with HOST a valid PATH
*
* @section EXAMPLES
* To run foo_pep333.entrypoint() PEP333 application :
*
* src/pyfcgi -l '127.0.0.1:9000' -S -e foo_pep333 -E entrypoint
*
* Logfile example :
*
* src/pyfcgi -l '127.0.0.1:9000' -S -e foo_pep333 -E entrypoint
* -L '/tmp/foo.log;0xff;{datetime} {msg} {ident}'
*
* @section AUTHOR
* Written by Yann Weber <yann.weber@member.fsf.org>
*
* @section COPYRIGHT
* Copyright @ 2019 Yann Weber License GPLv3+: GNU GPL version 3 or later
* <http://gnu.org/licences/gpl.html>.
*
* This is free software: you are free to change and redistribute it.
* There is NO WARRANTY, to extent permitted by law.
*
* PyFCGI git repositorie <https://git.yannweb.net/yannweb/pyfcgi>
*
*/