svn commit: r334817 - in head/usr.sbin/cron: cron crontab lib

Gleb Smirnoff glebius at FreeBSD.org
Thu Jun 7 22:38:42 UTC 2018


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
+ at 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 at 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;


More information about the svn-src-head mailing list