Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH] use monotonic clock where possible
- X-seq: zsh-workers 53257
- From: dana <dana@xxxxxxx>
- To: zsh-workers@xxxxxxx
- Subject: [PATCH] use monotonic clock where possible
- Date: Mon, 16 Dec 2024 16:51:25 -0600
- Archived-at: <https://zsh.org/workers/53257>
- Feedback-id: i9be146f9:Fastmail
- List-id: <zsh-workers.zsh.org>
several shell features use wall time to calculate deltas and intervals, which
makes them unreliable if ntp or something else updates the system clock. this
change makes most of them use the monotonic clock instead
features updated:
* MAILCHECK parameter
* PERIOD parameter
* SECONDS parameter
* %(nS.t.f) prompt-expansion sequence
* time built-in's elapsed time and cpu % values
* zsh/zftp ZFTP_TMOUT parameter
* zsh/zprof timings
these changes also give us (nominally) nanosecond-precise SECONDS and `time`
elapsed times on most platforms
features that arguably should use monotonic time but still don't after this
change:
* TMOUT parameter. this is currently implemented by checking tty access time
via the file system
* history command durations. i was going to add new monotonic fields to struct
histent for calculating the duration, but it felt messy and i would need to
study it more to understand when it's ok to use wall vs mono for some of the
comparisons we do
* timed functions and zsh/sched commands. i think it should be possible to set
these based on either monotonic or wall time (with the sched built-in using
monotonic for relative times), but idk how to square that with the current
expectation that timedfn/schedcmd times be comparable with each other
* zsh/watch checks. the check time is used both for the LOGCHECK interval and
to compare against the utmp log-in times
related changes included:
* zgettime_monotonic_if_available() now uses CLOCK_MONOTONIC_RAW instead of
CLOCK_MONOTONIC on macos. it's explained why in the source
* added a zmonotime() convenience function to replace time(2) where applicable
* updated TIMEFMT to allow %nU, %nS, %nE. note that %U and %S remain limited
to microsecond precision (if that) on all platforms by the getrusage(2) and
times(2) apis
almost none of the features i changed had existing tests and i had trouble
coming up with any myself. some of the ones i made may be brittle. please lmk
if you have better ideas
ps: i think it would be pleasing to eliminate gettimeofday(2) and timeval from
the code base (nearly) entirely so that nobody has to think about the
distinction or worry about conversion. replacing select(2) by pselect(2) would
address most of the remaining uses. but i didn't do that here
dana
diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 02ce796a9..69298855f 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -926,7 +926,9 @@ referenced or seeded in the parent shell in between subshell invocations.
)
vindex(SECONDS)
item(tt(SECONDS) <S>)(
-The number of seconds since shell invocation. If this parameter
+The number of seconds since shell invocation. On most platforms, this
+is a monotonic value, so it is not affected by NTP time jumps or other
+clock changes (though it may be affected by slewing). If this parameter
is assigned a value, then the value returned upon reference
will be the value that was assigned plus the number of seconds
since the assignment.
@@ -936,8 +938,10 @@ be changed using the tt(typeset) command. The type may be changed only
to one of the floating point types or back to integer. For example,
`tt(typeset -F SECONDS)'
causes the value to be reported as a floating point number. The
-value is available to microsecond accuracy, although the shell may
-show more or fewer digits depending on the use of tt(typeset). See
+value is nominally available to nanosecond precision, although this
+varies by platform (and probably isn't accurate to 1 ns regardless),
+and the shell may show more or fewer digits depending on the
+use of tt(typeset). See
the documentation for the builtin tt(typeset) in
ifzman(zmanref(zshbuiltins))\
ifnzman(noderef(Shell Builtin Commands)) for more details.
@@ -1735,8 +1739,12 @@ A star may be inserted between the percent sign and flags printing time
(e.g., `tt(%*E)'); this causes the time to be printed in
`var(hh)tt(:)var(mm)tt(:)var(ss)tt(.)var(ttt)'
format (hours and minutes are only printed if they are not zero).
-Alternatively, `tt(m)' or `tt(u)' may be used (e.g., `tt(%mE)') to produce
-time output in milliseconds or microseconds, respectively.
+Alternatively, `tt(m)', `tt(u)', or `tt(n)' may be used (e.g.,
+`tt(%mE)') to produce time output in milliseconds, microseconds, or
+nanoseconds, respectively. Note that some timings on some platforms
+are not actually nanosecond-precise (nor accurate to 1 ns when
+they are); in fact on many systems user and kernel times are not
+even microsecond-precise.
)
vindex(TMOUT)
item(tt(TMOUT))(
diff --git a/Doc/Zsh/prompt.yo b/Doc/Zsh/prompt.yo
index de988ab7c..108cb62e5 100644
--- a/Doc/Zsh/prompt.yo
+++ b/Doc/Zsh/prompt.yo
@@ -195,7 +195,8 @@ sitem(tt(%K))(the hour of the day on the 24-hour clock)
sitem(tt(%L))(the hour of the day on the 12-hour clock)
endsitem()
-In addition, if the system supports the POSIX tt(gettimeofday) system
+In addition, if the system supports the POSIX tt(clock_gettime)
+or tt(gettimeofday) system
call, tt(%.) provides decimal fractions of a second since the epoch with
leading zeroes. By default three decimal places are provided, but a
number of digits up to 9 may be given following the tt(%); hence tt(%6.)
diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c
index b60e5bf31..230ad86f6 100644
--- a/Src/Modules/zftp.c
+++ b/Src/Modules/zftp.c
@@ -397,7 +397,7 @@ zfalarm(int tmout)
signal(SIGALRM, zfhandler);
oalremain = alarm(tmout);
if (oalremain)
- oaltime = time(NULL);
+ oaltime = zmonotime(NULL);
/*
* We'll leave sigtrapped, sigfuncs and TRAPXXX as they are; since the
* shell's handler doesn't get the signal, they don't matter.
@@ -431,7 +431,7 @@ zfunalarm(void)
* I love the way alarm() uses unsigned int while time_t
* is probably something completely different.
*/
- unsigned int tdiff = time(NULL) - oaltime;
+ time_t tdiff = zmonotime(NULL) - oaltime;
alarm(oalremain < tdiff ? 1 : oalremain - tdiff);
} else
alarm(0);
diff --git a/Src/Modules/zprof.c b/Src/Modules/zprof.c
index 171a15b90..f5a50effc 100644
--- a/Src/Modules/zprof.c
+++ b/Src/Modules/zprof.c
@@ -239,8 +239,7 @@ zprof_wrapper(Eprog prog, FuncWrap w, char *name)
struct sfunc sf, *sp;
Pfunc f = NULL;
Parc a = NULL;
- struct timeval tv;
- struct timezone dummy;
+ struct timespec ts;
double prev = 0, now;
char *name_for_lookups;
@@ -278,19 +277,19 @@ zprof_wrapper(Eprog prog, FuncWrap w, char *name)
stack = &sf;
f->calls++;
- tv.tv_sec = tv.tv_usec = 0;
- gettimeofday(&tv, &dummy);
- sf.beg = prev = ((((double) tv.tv_sec) * 1000.0) +
- (((double) tv.tv_usec) / 1000.0));
+ ts.tv_sec = ts.tv_nsec = 0;
+ zgettime_monotonic_if_available(&ts);
+ sf.beg = prev = ((((double) ts.tv_sec) * 1000.0) +
+ (((double) ts.tv_nsec) / 1000000.0));
}
runshfunc(prog, w, name);
if (active) {
if (zprof_module && !(zprof_module->node.flags & MOD_UNLOAD)) {
- tv.tv_sec = tv.tv_usec = 0;
- gettimeofday(&tv, &dummy);
+ ts.tv_sec = ts.tv_nsec = 0;
+ zgettime_monotonic_if_available(&ts);
- now = ((((double) tv.tv_sec) * 1000.0) +
- (((double) tv.tv_usec) / 1000.0));
+ now = ((((double) ts.tv_sec) * 1000.0) +
+ (((double) ts.tv_nsec) / 1000000.0));
f->self += now - sf.beg;
for (sp = sf.prev; sp && sp->p != f; sp = sp->prev);
if (!sp)
diff --git a/Src/compat.c b/Src/compat.c
index 8b31ad9f4..918d98e69 100644
--- a/Src/compat.c
+++ b/Src/compat.c
@@ -136,7 +136,19 @@ zgettime_monotonic_if_available(struct timespec *ts)
#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
struct timespec dts;
+
+/*
+ * On at least some versions of macOS it appears that CLOCK_MONOTONIC is not
+ * actually monotonic -- there are reports that it can go backwards.
+ * CLOCK_MONOTONIC_RAW does not have this problem. On top of that, it is faster
+ * to read and it has nanosecond precision. We could use it on other systems
+ * too, but on Linux at least it seems that CLOCK_MONOTONIC is preferred
+ */
+#if defined(__APPLE__) && defined(CLOCK_MONOTONIC_RAW)
+ if (clock_gettime(CLOCK_MONOTONIC_RAW, &dts) < 0) {
+#else
if (clock_gettime(CLOCK_MONOTONIC, &dts) < 0) {
+#endif
zwarn("unable to retrieve CLOCK_MONOTONIC time: %e", errno);
ret--;
} else {
diff --git a/Src/exec.c b/Src/exec.c
index bc07e8c39..874ff41f7 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -348,10 +348,9 @@ setlimits(char *nam)
/**/
static pid_t
-zfork(struct timeval *tv)
+zfork(struct timespec *ts)
{
pid_t pid;
- struct timezone dummy_tz;
/*
* Is anybody willing to explain this test?
@@ -360,8 +359,8 @@ zfork(struct timeval *tv)
zerr("job table full");
return -1;
}
- if (tv)
- gettimeofday(tv, &dummy_tz);
+ if (ts)
+ zgettime_monotonic_if_available(ts);
/*
* Queueing signals is necessary on Linux because fork()
* manipulates mutexes, leading to deadlock in memory
@@ -460,7 +459,7 @@ zfork(struct timeval *tv)
int list_pipe = 0, simple_pline = 0;
static pid_t list_pipe_pid;
-static struct timeval list_pipe_start;
+static struct timespec list_pipe_start;
static int nowait, pline_level = 0;
static int list_pipe_child = 0, list_pipe_job;
static char list_pipe_text[JOBTEXTSIZE];
@@ -1863,7 +1862,7 @@ execpline(Estate state, wordcode slcode, int how, int last1)
(jobtab[list_pipe_job].stat & STAT_STOPPED)))) {
pid_t pid = 0;
int synch[2];
- struct timeval bgtime;
+ struct timespec bgtime;
/*
* A pipeline with the shell handling the right
@@ -2284,7 +2283,7 @@ closemn(struct multio **mfds, int fd, int type)
char buf[TCBUFSIZE];
int len, i;
pid_t pid;
- struct timeval bgtime;
+ struct timespec bgtime;
/*
* We need to block SIGCHLD in case the process
@@ -2829,7 +2828,7 @@ execcmd_fork(Estate state, int how, int type, Wordcode varspc,
pid_t pid;
int synch[2], flags;
struct entersubsh_ret esret;
- struct timeval bgtime;
+ struct timespec bgtime;
child_block();
esret.gleader = -1;
@@ -2947,7 +2946,7 @@ execcmd_exec(Estate state, Execcmd_params eparams,
* for the "time" keyword
*/
child_times_t shti, chti;
- struct timeval then;
+ struct timespec then;
if (how & Z_TIMED)
shelltime(&shti, &chti, &then, 0);
@@ -5060,7 +5059,7 @@ getproc(char *cmd, char **eptr)
int out = *cmd == Inang;
char *pnam;
pid_t pid;
- struct timeval bgtime;
+ struct timespec bgtime;
#ifndef PATH_DEV_FD
int fd;
@@ -5149,7 +5148,7 @@ getpipe(char *cmd, int nullexec)
Eprog prog;
int pipes[2], out = *cmd == Inang;
pid_t pid;
- struct timeval bgtime;
+ struct timespec bgtime;
char *ends;
if (!(prog = parsecmd(cmd, &ends)))
diff --git a/Src/hist.c b/Src/hist.c
index 1a00c30ed..d0960a284 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -2891,9 +2891,9 @@ flockhistfile(char *fn, int keep_trying)
/*
* Timeout is ten seconds.
*/
- end_time = time(NULL) + (time_t)10;
+ end_time = zmonotime(NULL) + (time_t)10;
while (fcntl(flock_fd, F_SETLKW, &lck) == -1) {
- if (!keep_trying || time(NULL) >= end_time ||
+ if (!keep_trying || zmonotime(NULL) >= end_time ||
/*
* Randomise wait to minimise clashes with shells exiting at
* the same time.
@@ -3137,7 +3137,7 @@ static int lockhistct;
static int
checklocktime(char *lockfile, long *sleep_usp, time_t then)
{
- time_t now = time(NULL);
+ time_t now = zmonotime(NULL);
if (now + 10 < then) {
/* File is more than 10 seconds in the future? */
diff --git a/Src/init.c b/Src/init.c
index 61f759ded..378aee348 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -1022,7 +1022,6 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
#ifdef USE_GETPWUID
struct passwd *pswd;
#endif
- struct timezone dummy_tz;
char *ptr;
int i, j;
#if defined(SITEFPATH_DIR) || defined(FPATH_DIR) || defined (ADDITIONAL_FPATH) || defined(FIXED_FPATH_DIR)
@@ -1109,8 +1108,8 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
hatchar = '^';
termflags = TERM_UNKNOWN;
curjob = prevjob = coprocin = coprocout = -1;
- gettimeofday(&shtimer, &dummy_tz); /* init $SECONDS */
- srand((unsigned int)(shtimer.tv_sec + shtimer.tv_usec)); /* seed $RANDOM */
+ zgettime_monotonic_if_available(&shtimer); /* init $SECONDS */
+ srand((unsigned int)(shtimer.tv_sec + shtimer.tv_nsec)); /* seed $RANDOM */
/* Set default path */
path = (char **) zalloc(sizeof(*path) * 5);
@@ -1297,7 +1296,7 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
#endif
breaks = loops = 0;
- lastmailcheck = time(NULL);
+ lastmailcheck = zmonotime(NULL);
locallevel = sourcelevel = 0;
sfcontext = SFC_NONE;
trap_return = 0;
diff --git a/Src/jobs.c b/Src/jobs.c
index 39c664388..ad14f6312 100644
--- a/Src/jobs.c
+++ b/Src/jobs.c
@@ -136,7 +136,7 @@ int numpipestats, pipestats[MAX_PIPESTATS];
/**/
static struct timeval *
-dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2)
+dtime_tv(struct timeval *dt, struct timeval *t1, struct timeval *t2)
{
dt->tv_sec = t2->tv_sec - t1->tv_sec;
dt->tv_usec = t2->tv_usec - t1->tv_usec;
@@ -147,6 +147,21 @@ dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2)
return dt;
}
+/* As above, but with timespecs */
+
+/**/
+static struct timespec *
+dtime_ts(struct timespec *dt, struct timespec *t1, struct timespec *t2)
+{
+ dt->tv_sec = t2->tv_sec - t1->tv_sec;
+ dt->tv_nsec = t2->tv_nsec - t1->tv_nsec;
+ if (dt->tv_nsec < 0) {
+ dt->tv_nsec += 1000000000.0;
+ dt->tv_sec -= 1.0;
+ }
+ return dt;
+}
+
/* change job table entry from stopped to running */
/**/
@@ -349,7 +364,6 @@ get_usage(void)
void
update_process(Process pn, int status)
{
- struct timezone dummy_tz;
#ifdef HAVE_GETRUSAGE
struct timeval childs = child_usage.ru_stime;
struct timeval childu = child_usage.ru_utime;
@@ -360,12 +374,12 @@ update_process(Process pn, int status)
/* get time-accounting info */
get_usage();
- gettimeofday(&pn->endtime, &dummy_tz); /* record time process exited */
+ zgettime_monotonic_if_available(&pn->endtime); /* record time process exited */
pn->status = status; /* save the status returned by WAIT */
#ifdef HAVE_GETRUSAGE
- dtime(&pn->ti.ru_stime, &childs, &child_usage.ru_stime);
- dtime(&pn->ti.ru_utime, &childu, &child_usage.ru_utime);
+ dtime_tv(&pn->ti.ru_stime, &childs, &child_usage.ru_stime);
+ dtime_tv(&pn->ti.ru_utime, &childu, &child_usage.ru_utime);
#else
pn->ti.st = shtms.tms_cstime - childs; /* compute process system space time */
pn->ti.ut = shtms.tms_cutime - childu; /* compute process user space time */
@@ -753,7 +767,7 @@ printhhmmss(double secs)
}
static void
-printtime(struct timeval *real, child_times_t *ti, char *desc)
+printtime(struct timespec *real, child_times_t *ti, char *desc)
{
char *s;
double elapsed_time, user_time, system_time;
@@ -774,21 +788,21 @@ printtime(struct timeval *real, child_times_t *ti, char *desc)
}
/* go ahead and compute these, since almost every TIMEFMT will have them */
- elapsed_time = real->tv_sec + real->tv_usec / 1000000.0;
+ elapsed_time = real->tv_sec + real->tv_nsec / 1000000000.0;
#ifdef HAVE_GETRUSAGE
user_time = ti->ru_utime.tv_sec + ti->ru_utime.tv_usec / 1000000.0;
system_time = ti->ru_stime.tv_sec + ti->ru_stime.tv_usec / 1000000.0;
total_time = user_time + system_time;
percent = 100.0 * total_time
- / (real->tv_sec + real->tv_usec / 1000000.0);
+ / (real->tv_sec + real->tv_nsec / 1000000000.0);
#else
{
long clktck = get_clktck();
user_time = ti->ut / (double) clktck;
system_time = ti->st / (double) clktck;
percent = 100.0 * (ti->ut + ti->st)
- / (clktck * real->tv_sec + clktck * real->tv_usec / 1000000.0);
+ / (clktck * real->tv_sec + clktck * real->tv_nsec / 1000000000.0);
}
#endif
@@ -844,6 +858,23 @@ printtime(struct timeval *real, child_times_t *ti, char *desc)
break;
}
break;
+ case 'n':
+ switch (*++s) {
+ case 'E':
+ fprintf(stderr, "%0.fns", elapsed_time * 1000000000.0);
+ break;
+ case 'U':
+ fprintf(stderr, "%0.fns", user_time * 1000000000.0);
+ break;
+ case 'S':
+ fprintf(stderr, "%0.fns", system_time * 1000000000.0);
+ break;
+ default:
+ fprintf(stderr, "%%n");
+ s--;
+ break;
+ }
+ break;
case '*':
switch (*++s) {
case 'E':
@@ -991,12 +1022,12 @@ static void
dumptime(Job jn)
{
Process pn;
- struct timeval dtimeval;
+ struct timespec dtimespec;
if (!jn->procs)
return;
for (pn = jn->procs; pn; pn = pn->next)
- printtime(dtime(&dtimeval, &pn->bgtime, &pn->endtime), &pn->ti,
+ printtime(dtime_ts(&dtimespec, &pn->bgtime, &pn->endtime), &pn->ti,
pn->text);
}
@@ -1506,7 +1537,7 @@ deletejob(Job jn, int disowning)
/**/
void
-addproc(pid_t pid, char *text, int aux, struct timeval *bgtime,
+addproc(pid_t pid, char *text, int aux, struct timespec *bgtime,
int gleader, int list_pipe_job_used)
{
Process pn, *pnlist;
@@ -1894,16 +1925,15 @@ spawnjob(void)
/**/
void
-shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int delta)
+shelltime(child_times_t *shell, child_times_t *kids, struct timespec *then, int delta)
{
- struct timezone dummy_tz;
- struct timeval dtimeval, now;
+ struct timespec dtimespec, now;
child_times_t ti;
#ifndef HAVE_GETRUSAGE
struct tms buf;
#endif
- gettimeofday(&now, &dummy_tz);
+ zgettime_monotonic_if_available(&now);
#ifdef HAVE_GETRUSAGE
getrusage(RUSAGE_SELF, &ti);
@@ -1916,8 +1946,8 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
if (shell) {
if (delta) {
#ifdef HAVE_GETRUSAGE
- dtime(&ti.ru_utime, &shell->ru_utime, &ti.ru_utime);
- dtime(&ti.ru_stime, &shell->ru_stime, &ti.ru_stime);
+ dtime_tv(&ti.ru_utime, &shell->ru_utime, &ti.ru_utime);
+ dtime_tv(&ti.ru_stime, &shell->ru_stime, &ti.ru_stime);
#else
ti.ut -= shell->ut;
ti.st -= shell->st;
@@ -1926,15 +1956,15 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
*shell = ti;
}
if (delta)
- dtime(&dtimeval, then, &now);
+ dtime_ts(&dtimespec, then, &now);
else {
if (then)
*then = now;
- dtime(&dtimeval, &shtimer, &now);
+ dtime_ts(&dtimespec, &shtimer, &now);
}
if (!delta == !shell)
- printtime(&dtimeval, &ti, "shell");
+ printtime(&dtimespec, &ti, "shell");
#ifdef HAVE_GETRUSAGE
getrusage(RUSAGE_CHILDREN, &ti);
@@ -1945,8 +1975,8 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
if (kids) {
if (delta) {
#ifdef HAVE_GETRUSAGE
- dtime(&ti.ru_utime, &kids->ru_utime, &ti.ru_utime);
- dtime(&ti.ru_stime, &kids->ru_stime, &ti.ru_stime);
+ dtime_tv(&ti.ru_utime, &kids->ru_utime, &ti.ru_utime);
+ dtime_tv(&ti.ru_stime, &kids->ru_stime, &ti.ru_stime);
#else
ti.ut -= shell->ut;
ti.st -= shell->st;
@@ -1955,7 +1985,7 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
*kids = ti;
}
if (!delta == !kids)
- printtime(&dtimeval, &ti, "children");
+ printtime(&dtimespec, &ti, "children");
}
/* see if jobs need printing */
diff --git a/Src/params.c b/Src/params.c
index 6f137585b..d1c06b893 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -137,11 +137,11 @@ unsigned char hatchar, hashchar;
unsigned char keyboardhackchar = '\0';
/* $SECONDS = now.tv_sec - shtimer.tv_sec
- * + (now.tv_usec - shtimer.tv_usec) / 1000000.0
+ * + (now.tv_nsec - shtimer.tv_nsec) / 1000000000.0
* (rounded to an integer if the parameter is not set to float) */
/**/
-struct timeval shtimer;
+struct timespec shtimer;
/* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
@@ -4496,13 +4496,12 @@ randomsetfn(UNUSED(Param pm), zlong v)
zlong
intsecondsgetfn(UNUSED(Param pm))
{
- struct timeval now;
- struct timezone dummy_tz;
+ struct timespec now;
- gettimeofday(&now, &dummy_tz);
+ zgettime_monotonic_if_available(&now);
return (zlong)(now.tv_sec - shtimer.tv_sec -
- (now.tv_usec < shtimer.tv_usec ? 1 : 0));
+ (now.tv_nsec < shtimer.tv_nsec ? 1 : 0));
}
/* Function to set value of special parameter `SECONDS' */
@@ -4511,48 +4510,47 @@ intsecondsgetfn(UNUSED(Param pm))
void
intsecondssetfn(UNUSED(Param pm), zlong x)
{
- struct timeval now;
- struct timezone dummy_tz;
+ struct timespec now;
zlong diff;
- gettimeofday(&now, &dummy_tz);
+ zgettime_monotonic_if_available(&now);
+
diff = (zlong)now.tv_sec - x;
shtimer.tv_sec = diff;
if ((zlong)shtimer.tv_sec != diff)
zwarn("SECONDS truncated on assignment");
- shtimer.tv_usec = now.tv_usec;
+ shtimer.tv_nsec = now.tv_nsec;
}
/**/
double
floatsecondsgetfn(UNUSED(Param pm))
{
- struct timeval now;
- struct timezone dummy_tz;
+ struct timespec now;
- gettimeofday(&now, &dummy_tz);
+ zgettime_monotonic_if_available(&now);
return (double)(now.tv_sec - shtimer.tv_sec) +
- (double)(now.tv_usec - shtimer.tv_usec) / 1000000.0;
+ (double)(now.tv_nsec - shtimer.tv_nsec) / 1000000000.0;
}
/**/
void
floatsecondssetfn(UNUSED(Param pm), double x)
{
- struct timeval now;
- struct timezone dummy_tz;
+ struct timespec now;
+
+ zgettime_monotonic_if_available(&now);
- gettimeofday(&now, &dummy_tz);
shtimer.tv_sec = now.tv_sec - (zlong)x;
- shtimer.tv_usec = now.tv_usec - (zlong)((x - (zlong)x) * 1000000.0);
+ shtimer.tv_nsec = now.tv_nsec - (zlong)((x - (zlong)x) * 1000000000.0);
}
/**/
double
getrawseconds(void)
{
- return (double)shtimer.tv_sec + (double)shtimer.tv_usec / 1000000.0;
+ return (double)shtimer.tv_sec + (double)shtimer.tv_nsec / 1000000000.0;
}
/**/
@@ -4560,7 +4558,7 @@ void
setrawseconds(double x)
{
shtimer.tv_sec = (zlong)x;
- shtimer.tv_usec = (zlong)((x - (zlong)x) * 1000000.0);
+ shtimer.tv_nsec = (zlong)((x - (zlong)x) * 1000000000.0);
}
/**/
diff --git a/Src/prompt.c b/Src/prompt.c
index e10b05215..f36aba9d3 100644
--- a/Src/prompt.c
+++ b/Src/prompt.c
@@ -469,7 +469,7 @@ putpromptchar(int doprint, int endchar)
test = 1;
break;
case 'S':
- if (time(NULL) - shtimer.tv_sec >= arg)
+ if (zmonotime(NULL) - shtimer.tv_sec >= arg)
test = 1;
break;
case 'v':
diff --git a/Src/signals.c b/Src/signals.c
index 86f1a49f6..de42f302d 100644
--- a/Src/signals.c
+++ b/Src/signals.c
@@ -342,8 +342,7 @@ wait_for_processes(void)
zwarn("job can't be suspended");
} else {
#if defined(HAVE_WAIT3) && defined(HAVE_GETRUSAGE)
- struct timezone dummy_tz;
- gettimeofday(&pn->endtime, &dummy_tz);
+ zgettime_monotonic_if_available(&pn->endtime);
#ifdef WIFCONTINUED
if (WIFCONTINUED(status))
pn->status = SP_RUNNING;
diff --git a/Src/utils.c b/Src/utils.c
index ce4e875fd..5c91dfcda 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -1570,14 +1570,14 @@ preprompt(void)
/* If 1) the parameter PERIOD exists, 2) a hook function for *
* "periodic" exists, 3) it's been greater than PERIOD since we *
* executed any such hook, then execute it now. */
- if (period && ((zlong)time(NULL) > (zlong)lastperiodic + period) &&
+ if (period && ((zlong)zmonotime(NULL) > (zlong)lastperiodic + period) &&
!callhookfunc("periodic", NULL, 1, NULL))
- lastperiodic = time(NULL);
+ lastperiodic = zmonotime(NULL);
if (errflag)
return;
/* Check mail */
- currentmailcheck = time(NULL);
+ currentmailcheck = zmonotime(NULL);
if (mailcheck &&
(zlong) difftime(currentmailcheck, lastmailcheck) > mailcheck) {
char *mailfile;
@@ -2761,6 +2761,19 @@ timespec_diff_us(const struct timespec *t1, const struct timespec *t2)
return (reverse ? LONG_MIN : LONG_MAX);
}
+/* Like time(), but uses the monotonic clock */
+
+/**/
+mod_export int
+zmonotime(time_t *tloc)
+{
+ struct timespec ts;
+ zgettime_monotonic_if_available(&ts);
+ if (tloc)
+ *tloc = ts.tv_sec;
+ return ts.tv_sec;
+}
+
/*
* Sleep for the given number of microseconds --- must be within
* range of a long at the moment, but this is only used for
@@ -2794,7 +2807,9 @@ zsleep(long us)
/**
* Sleep for time (fairly) randomly up to max_us microseconds.
- * Don't let the wallclock time extend beyond end_time.
+ * Don't let the time extend beyond end_time. end_time is compared to
+ * the current *monotonic* clock time, so do NOT base it on e.g. time(2);
+ * use zmonotime() or zgettime_monotonic_if_available().
* Return 1 if that seemed to work, else 0.
*
* For best results max_us should be a multiple of 2**16 or large
@@ -2806,7 +2821,7 @@ int
zsleep_random(long max_us, time_t end_time)
{
long r;
- time_t now = time(NULL);
+ time_t now = zmonotime(NULL);
/*
* Randomish backoff. Doesn't need to be fundamentally
diff --git a/Src/zsh.h b/Src/zsh.h
index 090abf8f5..85b5c9bdc 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -1115,8 +1115,8 @@ struct process {
char text[JOBTEXTSIZE]; /* text to print when 'jobs' is run */
int status; /* return code from waitpid/wait3() */
child_times_t ti;
- struct timeval bgtime; /* time job was spawned */
- struct timeval endtime; /* time job exited */
+ struct timespec bgtime; /* time job was spawned */
+ struct timespec endtime; /* time job exited */
};
struct execstack {
diff --git a/Test/A08time.ztst b/Test/A08time.ztst
index 22a460f5e..4a41cc76a 100644
--- a/Test/A08time.ztst
+++ b/Test/A08time.ztst
@@ -11,9 +11,44 @@
(time cat) >&/dev/null
0:`time' keyword (status only)
- ( TIMEFMT='%E %mE %uE %* %m%mm %u%uu'; time (:) )
+ ( TIMEFMT='%E %mE %uE %nE %* %m%mm %u%uu %n%nn'; time (:) )
0:`time' keyword with custom TIMEFMT
-*?[0-9]##.[0-9](#c2)s [0-9]##ms [0-9]##us %\* %m%mm %u%uu
+*?[0-9]##.[0-9](#c2)s [0-9]##ms [0-9]##us [0-9]##ns %\* %m%mm %u%uu %n%nn
+
+ ( TIMEFMT='x %U %S %E'; time (:) )
+0:TIMEFMT %[USE] use centisecond precision
+*?x( <0-9>.<00-99>s)(#c3)
+
+ ( TIMEFMT='x %*U %*S %*E'; time (:) )
+0:TIMEFMT %*[USE] use millisecond precision
+*?x( <0-9>.<000-999>)(#c3)
+
+ ( TIMEFMT='%nU %nS'; time (read -k3 -t0.1) )
+1:TIMEFMT %nU and %nS are limited to microsecond precision
+*?[1-9][0-9]#000ns [1-9][0-9]#000ns
+
+# SECONDS (after - before) must be greater than the elapsed time, but not much
+# greater. 25% was picked arbitrarily as something that hopefully will prevent
+# the test from failing on slow machines
+ (
+ typeset -F SECONDS
+ TIMEFMT=%nE
+ a=$SECONDS
+ t=$( (time (read -k3 -t0.1)) 2>&1 )
+ b=$SECONDS
+ s=$(( b - a ))
+ t=$(( ${t%ns}.0 / 10**9 ))
+ echo $s $t $(( s > t )) $(( t > s - (s * 0.25) ))
+ )
+0:`time' elapsed time matches SECONDS
+*>[0-9.]## [0-9.]## 1 1
+
+# Again, the wide range here is an attempt to prevent this test from failing on
+# slow machines. We don't care about the exact time, just that it's vaguely sane
+# and that each representation has the same basis
+ ( TIMEFMT='%E %mE %uE %nE %*E'; time (read -k3 -t0.1) )
+1:TIMEFMT elapsed time values
+*?0.<10-50>s <10-500>ms <100000-500000>us <100000000-500000000>ns 0.<100-500>
time x=1
0:`time' simple assignment
diff --git a/Test/D01prompt.ztst b/Test/D01prompt.ztst
index 55861cca1..f42e19714 100644
--- a/Test/D01prompt.ztst
+++ b/Test/D01prompt.ztst
@@ -68,6 +68,13 @@
print -P '%(?.true.false)'
0:ternary prompt escapes
>true
+>false
+
+ sec=$SECONDS
+ eval "print -P '%(${sec}S.true.false)'"
+ eval "print -P '%($((sec+30))S.true.false)'"
+0:ternary prompt escape with test character S
+>true
>false
print -P 'start %10<...<truncated at 10%<< Not truncated%3< ...<Not shown'
diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst
index 7953827d6..ed25fd7a9 100644
--- a/Test/D04parameter.ztst
+++ b/Test/D04parameter.ztst
@@ -1550,6 +1550,28 @@
>1
>1
+ # Integer
+ a=$SECONDS
+ sleep 1
+ b=$SECONDS
+ print -r - $a $b $(( b > a ))
+ # Float
+ typeset -F SECONDS
+ a=$SECONDS
+ repeat 10 :
+ b=$SECONDS
+ print -r - $a $b $(( b > a ))
+ # Assignment
+ a=$SECONDS
+ SECONDS=8888
+ repeat 10 :
+ b=$SECONDS
+ print -r - $(( a < 8888 )) $(( b > 8888 ))
+0:SECONDS
+*>[0-9]## [0-9]## 1
+*>[0-9]##.[0-9]## [0-9]##.[0-9]## 1
+*>1 1
+
foo=("|" "?")
[[ "|" = ${(j.|.)foo} ]] && print yes || print no
[[ "|" = ${(j.|.)~foo} ]] && print yes || print no
Messages sorted by:
Reverse Date,
Date,
Thread,
Author