diff --git a/manage-procs.md b/manage-procs.md index f0b6877..b29c6cb 100644 --- a/manage-procs.md +++ b/manage-procs.md @@ -60,7 +60,7 @@ Increase verbosity level. (may be specified multiple times) # COMMANDS -**manage-procs add** \[-h\] \[-f\] \[-A\] \[-C *dir*\] \[-P *port*\] \[-U *user*\] \[-G *group*\] *name* *command*…​ +**manage-procs add** \[-h\] \[-f\] \[-A\] \[-C *dir*\] \[-P *port*\] \[-U *user*\] \[-G *group*\] *name* *command*…​ Create a new procServ instance. **-h, --help** @@ -86,14 +86,19 @@ User name for instance to run as. **-G, --group** *groupname* Group name for instance to run as. -**name** +**--grace-period** *n* +Grace period (in seconds) that procServ will wait for the child to shut +down after receiving a termination signal. (See procServ(1) for more +details.) + +**name** Instance name. -**command…​** +**command…​** The remaining line is interpreted as the command (with arguments) to run inside the procServ instance. -**manage-procs remove** \[-h\] \[-f\] *name* +**manage-procs remove** \[-h\] \[-f\] *name* Remove an existing procServ instance from the configuration. **-h, --help** @@ -102,30 +107,30 @@ Show a help message and exit. **-f, --force** Remove without asking for confirmation. -**name** +**name** Instance name. -**manage-procs start** \[-h\] \[*pattern*\] +**manage-procs start** \[-h\] \[*pattern*\] Start procServ instances. **-h, --help** Show a help message and exit. -**pattern** +**pattern** Pattern to match existing instance names against. (default: "\*" = start all procServ instances) -**manage-procs stop** \[-h\] \[*pattern*\] +**manage-procs stop** \[-h\] \[*pattern*\] Stop procServ instances. **-h, --help** Show a help message and exit. -**pattern** +**pattern** Pattern to match existing instance names against. (default: "\*" = stop all procServ instances) -**manage-procs attach** \[-h\] *name* +**manage-procs attach** \[-h\] *name* Attach to the control port of a running procServ instance. For this, manage-procs is using one of two existing CLI client @@ -137,10 +142,10 @@ For both connection types, press `^D` to detach from the session. **-h, --help** Show a help message and exit. -**name** +**name** Instance name. -**manage-procs list** \[-h\] \[--all\] +**manage-procs list** \[-h\] \[--all\] List all procServ instances. **-h, --help** @@ -149,7 +154,7 @@ Show a help message and exit. **--all** Also list inactive instances. -**manage-procs status** \[-h\] +**manage-procs status** \[-h\] Report the status of all procServ instances. **-h, --help** diff --git a/procServ.cc b/procServ.cc index c8d04a2..04fb210 100644 --- a/procServ.cc +++ b/procServ.cc @@ -35,6 +35,7 @@ #endif /* __CYGWIN__ */ #include "procServ.h" +#include "processClass.h" // Wrapper to ignore return values template @@ -174,6 +175,7 @@ void printHelp() " -d --debug debug mode (keeps child in foreground)\n" " -e --exec specify child executable (default: arg0 of )\n" " -f --foreground keep child in foreground (interactive)\n" + " -G --grace-period wait seconds for child to shut down\n" " -h --help print this message\n" " --holdoff set holdoff time [sec] between child restarts\n" " -i --ignore ignore all chars in (^ for ctrl)\n" @@ -214,6 +216,7 @@ int main(int argc,char * argv[]) const size_t BUFLEN = 512; char buff[BUFLEN]; std::string infofile; + unsigned int gracePeriod = 0; time(&procServStart); // remember start time procservName = argv[0]; @@ -235,6 +238,7 @@ int main(int argc,char * argv[]) {"debug", no_argument, 0, 'd'}, {"exec", required_argument, 0, 'e'}, {"foreground", no_argument, 0, 'f'}, + {"grace-period", required_argument, 0, 'G'}, {"help", no_argument, 0, 'h'}, {"holdoff", required_argument, 0, 'H'}, {"ignore", required_argument, 0, 'i'}, @@ -261,7 +265,7 @@ int main(int argc,char * argv[]) /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long (argc, argv, "+c:de:fhi:I:k:l:L:n:op:P:qVwx:", + c = getopt_long (argc, argv, "+c:de:fG:hi:I:k:l:L:n:op:P:qVwx:", long_options, &option_index); /* Detect the end of the options. */ @@ -310,6 +314,14 @@ int main(int argc,char * argv[]) stampFormat = strdup(optarg); break; + case 'G': + k = atoi(optarg); + if (k < 0) { + k = 0; + } + gracePeriod = (unsigned int) k; + break; + case 'h': // Help printHelp(); exit(0); @@ -594,6 +606,8 @@ int main(int argc,char * argv[]) strncat(infoMessage1, buff, INFO1LEN-strlen(infoMessage1)-1); } + time_t stopAt = 0; + firstRun = true; // Run here until something makes it die while ( ! shutdownServer ) @@ -621,8 +635,27 @@ int main(int argc,char * argv[]) timeout.tv_sec = 0; // select() timeout: 0.5 sec timeout.tv_nsec = 500000000l; + if (stopAt != 0) { + time_t now = time(0); + if (now < stopAt) { + long remaining = (long)(stopAt - now); + if (remaining < 1) { + timeout.tv_sec = 0; + timeout.tv_nsec = 500000000l; + } else if (remaining < timeout.tv_sec || (remaining == timeout.tv_sec && 0 < timeout.tv_nsec)) { + timeout.tv_sec = remaining; + timeout.tv_nsec = 0; + } + } else { + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + } + } + ready = pselect(nFd, &fdset, NULL, NULL, &timeout, &sigset_pselect); + time_t now = time(0); + // Handle signals for which signal handlers were called while in pselect. if (sigPipeSet) { @@ -635,7 +668,14 @@ int main(int argc,char * argv[]) sigTermSet = 0; PRINTF("SigTerm received\n"); processFactorySendSignal(killSig); - shutdownServer = true; + if(killSig==SIGKILL || gracePeriod<=0) { + shutdownServer = true; + + } else if(!stopAt) { + restartMode = oneshot; // prevent restart + stopAt = now + gracePeriod; // wait a bit for child to stop + PRINTF("Start child cleanup timer %u sec.\n", gracePeriod); + } } if (sigHupSet) { @@ -651,8 +691,9 @@ int main(int argc,char * argv[]) // Pick up the process item if it dies // This call returns NULL if the process item lives - if (processFactoryNeedsRestart()) + if (stopAt==0 && processFactoryNeedsRestart()) { + if ((restartMode == oneshot) && !firstRun) { PRINTF("Option oneshot is set... exiting\n"); shutdownServer = true; @@ -663,7 +704,7 @@ int main(int argc,char * argv[]) npi= processFactory(childExec, childArgv); if (npi) AddConnection(npi); if (firstRun) { - firstRun = false; + firstRun = false; } } } @@ -680,6 +721,18 @@ int main(int argc,char * argv[]) } OnPollTimeout(); } + + if(stopAt!=0) { + if(!processClass::hasRunning()) { + PRINTF("child exits\n"); + shutdownServer = true; + + } else if(now >= stopAt) { + PRINTF("child cleanup timer expires %ld, %ld\n", stopAt, now); + shutdownServer = true; + // connectionItem dtor will KILL + } + } } ttySetCharNoEcho(false); diff --git a/procServ.md b/procServ.md index 261ec1f..267bb40 100644 --- a/procServ.md +++ b/procServ.md @@ -75,6 +75,13 @@ procServ server after the child exits. In that mode, the system daemon must handle restarts (if required), and all clients will have to reconnect. +When receiving a termination signal, the server will by default shut +down immediately and kill the child process. Using the **-G** +(**--grace-period**) option, the server can be made to wait up to a +specified number of seconds for the child process to shut down +gracefully before the server itself exits and kills the child. In this +mode, the server will stop restarting the child. + Any connection (control or log) can be disconnected using the client’s disconnect sequence. Control connections can also be disconnected by sending the logout command character that can be specified using the @@ -100,25 +107,25 @@ plus additional debug messages to stdout. Both control and log endpoints may be bound to either TCP or UNIX sockets (where supported). Allowed endpoint specifications are: -**\** +**\** Bind to either 0.0.0.0:*\* (any) or 127.0.0.1:*\* (localhost) depending on the type of endpoint and the setting of **-r** (**--restrict**) and **--allow** options. -**\:\** +**\:\** Bind to the specified interface address and *\*. The interface IP address *\* must be given in numeric form. Uses 127.0.0.1 (localhost) for security reasons unless the **--allow** option is also used. -**unix:\** +**unix:\** Bind to a named unix domain socket that will be created at the specified absolute or relative path. The server process must have permission to create files in the enclosing directory. The socket file will be owned by the uid and primary gid of the procServ server process with permissions 0666 (equivalent to a TCP socket bound to localhost). -**unix:\:\:\:\** +**unix:\:\:\:\** Bind to a named unix domain socket that will be created at the specified absolute or relative path. The server process must have permission to create files in the enclosing directory. The socket file will be owned @@ -128,7 +135,7 @@ Any of *\*, *\*, and/or *\* may be omitted. E.g. socket with 0660 permissions and allow the "grp" group connect to it. This requires that procServ be run as root or a member of "grp". -**unix:@\** +**unix:@\** Bind to an abstract unix domain socket (Linux specific). Abstract sockets do not exist on the filesystem, and have no permissions checks. They are functionally similar to a TCP socket bound to localhost, but @@ -170,6 +177,12 @@ Run *file* as executable for child. Default is *command*. Keep the server process in the foreground and connected to the controlling terminal. +**-G, --grace-period**=*n* +Wait at most *n* seconds for the child process to shut down after +receiving a termination signal. If the child does not exit within this +period, it will be killed. (Default is 0: the server shuts down and +kills the child immediately.) + **-h, --help** Print help message. @@ -303,11 +316,11 @@ file or through a console access and logging facility (such as # ENVIRONMENT VARIABLES -**PROCSERV_PID** +**PROCSERV_PID** Sets the file name to write the PID of the server process into. (See **-p** option.) -**PROCSERV_DEBUG** +**PROCSERV_DEBUG** If set, procServ starts in debug mode. (See **-d** option.) # KNOWN PROBLEMS diff --git a/procServUtils/conf.py b/procServUtils/conf.py index b2b60fb..24bbda2 100644 --- a/procServUtils/conf.py +++ b/procServUtils/conf.py @@ -58,6 +58,7 @@ def getconffiles(user=False): 'group':'nogroup', 'chdir':'/', 'port':'0', + 'grace-period':'0', 'instance':'1', } diff --git a/procServUtils/launch.py b/procServUtils/launch.py index 97f72e7..bff4d75 100644 --- a/procServUtils/launch.py +++ b/procServUtils/launch.py @@ -38,6 +38,7 @@ def main(args): chdir = conf.get(name, 'chdir') cmd = conf.get(name, 'command') port = conf.get(name, 'port') + grace = conf.get(name, 'grace-period') rundir = getrundir(user=user) @@ -57,6 +58,7 @@ def main(args): '--chdir',chdir, '--info-file',os.path.join(rundir, 'procserv-%s'%name, 'info'), #/run/procserv-$NAME/info '--port', port if port != "0" else 'unix:%s/procserv-%s/control'%(rundir,name), + '--grace-period', grace, ] if args.debug>1: diff --git a/procServUtils/manage.py b/procServUtils/manage.py index e984015..50e66a2 100644 --- a/procServUtils/manage.py +++ b/procServUtils/manage.py @@ -144,6 +144,7 @@ def addproc(conf, args): if args.username: F.write("user = %s\n"%args.username) if args.group: F.write("group = %s\n"%args.group) if args.port: F.write("port = %s\n"%args.port) + if args.grace_period: F.write("grace-period = %s\n"%args.grace_period) if args.environment: env_to_string = ' '.join("\"%s\""%e for e in args.environment) F.write("environment = %s\n"%env_to_string) @@ -174,7 +175,10 @@ def delproc(conf, args): with open(cfile) as F: C = ConfigParser({'instance':'1'}) - C.readfp(F) + if hasattr(C, 'read_file'): + C.read_file(F) + else: + C.readfp(F) if not C.has_section(args.name): continue @@ -276,6 +280,7 @@ def getargs(args=None): S.add_argument('-P','--port', help='telnet port') S.add_argument('-U','--user', dest='username') S.add_argument('-G','--group') + S.add_argument('--grace-period', help='grace period for child shutdown') S.add_argument('-e','--environment', action='append', help='Add an environment variable') S.add_argument('-E','--env-file', help='Environment file path') S.add_argument('-f','--force', action='store_true', default=False) diff --git a/processClass.h b/processClass.h index 0beea2c..e640d3f 100644 --- a/processClass.h +++ b/processClass.h @@ -29,6 +29,8 @@ friend void processFactorySendSignal(int signal); static void restartOnce (); static bool exists() { return _runningItem ? true : false; } virtual ~processClass(); + static inline + bool hasRunning() { return _runningItem; } protected: pid_t _pid; static processClass * _runningItem;