From owner-svn-src-head@freebsd.org Thu Jun 7 22:38:42 2018 Return-Path: Delivered-To: svn-src-head@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id C1DE7FF8869; Thu, 7 Jun 2018 22:38:41 +0000 (UTC) (envelope-from glebius@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 66A9C833A5; Thu, 7 Jun 2018 22:38:41 +0000 (UTC) (envelope-from glebius@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 48AB026593; Thu, 7 Jun 2018 22:38:41 +0000 (UTC) (envelope-from glebius@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id w57McfkS011817; Thu, 7 Jun 2018 22:38:41 GMT (envelope-from glebius@FreeBSD.org) Received: (from glebius@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id w57Mcehx011811; Thu, 7 Jun 2018 22:38:40 GMT (envelope-from glebius@FreeBSD.org) Message-Id: <201806072238.w57Mcehx011811@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: glebius set sender to glebius@FreeBSD.org using -f From: Gleb Smirnoff Date: Thu, 7 Jun 2018 22:38:40 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r334817 - in head/usr.sbin/cron: cron crontab lib X-SVN-Group: head X-SVN-Commit-Author: glebius X-SVN-Commit-Paths: in head/usr.sbin/cron: cron crontab lib X-SVN-Commit-Revision: 334817 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.26 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 07 Jun 2018 22:38:42 -0000 Author: glebius Date: Thu Jun 7 22:38:40 2018 New Revision: 334817 URL: https://svnweb.freebsd.org/changeset/base/334817 Log: Add new functionality and syntax to cron(1) to allow to run jobs at a given interval, which is counted in seconds since exit of the previous invocation of the job. Example user crontab entry: @25 sleep 10 The example will launch 'sleep 10' every 35 seconds. This is a rather useless example above, but clearly explains the functionality. The practical goal here is to avoid overlap of previous job invocation to a new one, or to avoid too short interval(s) for jobs that last long and doesn't have any point of immediate launch soon after previous run. Another useful effect of interval jobs can be noticed when a cluster of machines periodically communicates with a single node. Running the task time based creates too much load on the node. Running interval based spreads invocations across machines in cluster. Note that -j/-J won't help in this case. Sponsored by: Netflix Modified: head/usr.sbin/cron/cron/cron.c head/usr.sbin/cron/cron/cron.h head/usr.sbin/cron/cron/do_command.c head/usr.sbin/cron/crontab/crontab.5 head/usr.sbin/cron/lib/entry.c Modified: head/usr.sbin/cron/cron/cron.c ============================================================================== --- head/usr.sbin/cron/cron/cron.c Thu Jun 7 21:24:21 2018 (r334816) +++ head/usr.sbin/cron/cron/cron.c Thu Jun 7 22:38:40 2018 (r334817) @@ -46,7 +46,9 @@ static void usage(void), parse_args(int c, char *v[]); static int run_at_secres(cron_db *); +static void find_interval_entry(pid_t); +static cron_db database; static time_t last_time = 0; static int dst_enabled = 0; static int dont_daemonize = 0; @@ -100,7 +102,6 @@ main(argc, argv) int argc; char *argv[]; { - cron_db database; int runnum; int secres1, secres2; struct tm *tm; @@ -154,8 +155,8 @@ main(argc, argv) database.mtime = (time_t) 0; load_database(&database); secres1 = secres2 = run_at_secres(&database); - run_reboot_jobs(&database); cron_sync(secres1); + run_reboot_jobs(&database); runnum = 0; while (TRUE) { # if DEBUGGING @@ -210,6 +211,9 @@ run_reboot_jobs(db) if (e->flags & WHEN_REBOOT) { job_add(e, u); } + if (e->flags & INTERVAL) { + e->lastexit = TargetTime; + } } } (void) job_runqueue(); @@ -313,6 +317,13 @@ cron_tick(cron_db *db, int secres) env_get("LOGNAME", e->envp), e->uid, e->gid, e->cmd)) + if (e->flags & INTERVAL) { + if (e->lastexit > 0 && + TargetTime >= e->lastexit + e->interval) + job_add(e, u); + continue; + } + if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) { if (bit_test(e->second, otzsecond) && bit_test(e->minute, otzminute) @@ -489,6 +500,7 @@ sigchld_handler(int x) ("[%d] sigchld...no dead kids\n", getpid())) return; default: + find_interval_entry(pid); Debug(DPROC, ("[%d] sigchld...pid #%d died, stat=%d\n", getpid(), pid, WEXITSTATUS(waiter))) @@ -557,9 +569,26 @@ run_at_secres(cron_db *db) for (u = db->head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { - if ((e->flags & SEC_RES) != 0) + if ((e->flags & (SEC_RES | INTERVAL)) != 0) return 1; } } return 0; +} + +static void +find_interval_entry(pid_t pid) +{ + user *u; + entry *e; + + for (u = database.head; u != NULL; u = u->next) { + for (e = u->crontab; e != NULL; e = e->next) { + if ((e->flags & INTERVAL) && e->child == pid) { + e->lastexit = time(NULL); + e->child = 0; + break; + } + } + } } Modified: head/usr.sbin/cron/cron/cron.h ============================================================================== --- head/usr.sbin/cron/cron/cron.h Thu Jun 7 21:24:21 2018 (r334816) +++ head/usr.sbin/cron/cron/cron.h Thu Jun 7 22:38:40 2018 (r334817) @@ -168,19 +168,29 @@ typedef struct _entry { #endif char **envp; char *cmd; - bitstr_t bit_decl(second, SECOND_COUNT); - bitstr_t bit_decl(minute, MINUTE_COUNT); - bitstr_t bit_decl(hour, HOUR_COUNT); - bitstr_t bit_decl(dom, DOM_COUNT); - bitstr_t bit_decl(month, MONTH_COUNT); - bitstr_t bit_decl(dow, DOW_COUNT); + union { + struct { + bitstr_t bit_decl(second, SECOND_COUNT); + bitstr_t bit_decl(minute, MINUTE_COUNT); + bitstr_t bit_decl(hour, HOUR_COUNT); + bitstr_t bit_decl(dom, DOM_COUNT); + bitstr_t bit_decl(month, MONTH_COUNT); + bitstr_t bit_decl(dow, DOW_COUNT); + }; + struct { + time_t lastexit; + time_t interval; + pid_t child; + }; + }; int flags; #define DOM_STAR 0x01 #define DOW_STAR 0x02 #define WHEN_REBOOT 0x04 -#define RUN_AT 0x08 +#define RUN_AT 0x08 #define NOT_UNTIL 0x10 #define SEC_RES 0x20 +#define INTERVAL 0x40 time_t lastrun; } entry; Modified: head/usr.sbin/cron/cron/do_command.c ============================================================================== --- head/usr.sbin/cron/cron/do_command.c Thu Jun 7 21:24:21 2018 (r334816) +++ head/usr.sbin/cron/cron/do_command.c Thu Jun 7 22:38:40 2018 (r334817) @@ -47,6 +47,8 @@ do_command(e, u) entry *e; user *u; { + pid_t pid; + Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", getpid(), e->cmd, u->name, e->uid, e->gid)) @@ -57,9 +59,11 @@ do_command(e, u) * vfork() is unsuitable, since we have much to do, and the parent * needs to be able to run off and fork other processes. */ - switch (fork()) { + switch ((pid = fork())) { case -1: log_it("CRON",getpid(),"error","can't fork"); + if (e->flags & INTERVAL) + e->lastexit = time(NULL); break; case 0: /* child process */ @@ -70,6 +74,12 @@ do_command(e, u) break; default: /* parent process */ + Debug(DPROC, ("[%d] main process forked child #%d, " + "returning to work\n", getpid(), pid)) + if (e->flags & INTERVAL) { + e->lastexit = 0; + e->child = pid; + } break; } Debug(DPROC, ("[%d] main process returning to work\n", getpid())) Modified: head/usr.sbin/cron/crontab/crontab.5 ============================================================================== --- head/usr.sbin/cron/crontab/crontab.5 Thu Jun 7 21:24:21 2018 (r334816) +++ head/usr.sbin/cron/crontab/crontab.5 Thu Jun 7 22:38:40 2018 (r334817) @@ -17,7 +17,7 @@ .\" .\" $FreeBSD$ .\" -.Dd January 5, 2016 +.Dd June 6, 2018 .Dt CRONTAB 5 .Os .Sh NAME @@ -220,7 +220,10 @@ would cause a command to be run at 4:30 am on the 1st month, plus every Friday. .Pp Instead of the first five fields, -one of eight special strings may appear: +a line may start with +.Sq @ +symbol followed either by one of eight special strings or by a numeric value. +The recognized special strings are: .Bd -literal -offset indent string meaning ------ ------- @@ -235,6 +238,16 @@ string meaning @every_minute Run once a minute, "*/1 * * * *". @every_second Run once a second. .Ed +.Pp +The +.Sq @ +symbol followed by a numeric value has a special notion of running +a job that much seconds after completion of previous invocation of +the job. +Unlike regular syntax, it guarantees not to overlap two or more +invocations of the same job. +The first run is scheduled specified amount of seconds after cron +has started. .Sh EXAMPLE CRON FILE .Bd -literal @@ -251,6 +264,8 @@ MAILTO=paul 0 22 * * 1-5 mail -s "It's 10pm" joe%Joe,%%Where are your kids?% 23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday" 5 4 * * sun echo "run at 5 after 4 every sunday" +# run at 5 minutes intervals, no matter how long it takes +@300 svnlite up /usr/src .Ed .Sh SEE ALSO .Xr crontab 1 , @@ -292,7 +307,7 @@ either). .Pp All of the .Sq @ -commands that can appear in place of the first five fields +directives that can appear in place of the first five fields are extensions. .Sh AUTHORS .An Paul Vixie Aq Mt paul@vix.com Modified: head/usr.sbin/cron/lib/entry.c ============================================================================== --- head/usr.sbin/cron/lib/entry.c Thu Jun 7 21:24:21 2018 (r334816) +++ head/usr.sbin/cron/lib/entry.c Thu Jun 7 22:38:40 2018 (r334817) @@ -132,6 +132,9 @@ load_entry(file, error_func, pw, envp) } if (ch == '@') { + long interval; + char *endptr; + /* all of these should be flagged and load-limited; i.e., * instead of @hourly meaning "0 * * * *" it should mean * "close to the front of every hour but not 'til the @@ -209,6 +212,13 @@ load_entry(file, error_func, pw, envp) bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); + } else if (*cmd != '\0' && + (interval = strtol(cmd, &endptr, 10)) > 0 && + *endptr == '\0') { + Debug(DPARS, ("load_entry()... %ld seconds " + "since last run\n", interval)) + e->interval = interval; + e->flags = INTERVAL; } else { ecode = e_timespec; goto eof;