From tgl@sss.pgh.pa.us Sun Jun 16 03:34:01 1996 To: Mills@huey.udel.edu cc: ken@sdd.hp.com Subject: Re: Proposed change to adjtimed for HPUX In-reply-to: Your message of Fri, 1 Mar 96 14:09:29 EST <9603011409.aa22376@huey.udel.edu> Date: Sun, 24 Mar 1996 21:14:55 -0500 From: Tom Lane I finally figured out why I was still getting occasional "Previous time adjustment didn't complete" messages even with my revisions to the adjtimed daemon. Should've been obvious. The daemon process, of course, is subject to scheduling delays. If a near-maximum time slew is requested at the top of the second, but the daemon is not dispatched to begin it for a few ticks, then the slew will not be complete at the top of the next second. The old version of the daemon was extremely prone to this problem because it would always schedule the slew to occur over one full second. (It varied the tickadj delta rather than the time interval over which the delta is applied.) So even a one-tick scheduling delay would trigger a complaint. The patch attached below makes adjtimed work more like the customary BSD-ish flavor of adjtime(2): constant slew rate of 5 microsec/tick applied over as long a period as needed to get the requested delta. This is much less likely to trigger a didn't-complete complaint for slews less than the maximum possible slew. Also, the patch increases the process priority (decreases nice setting) to reduce the chances that the daemon will not be executed immediately when it needs to run. In addition to fixing the daemon, the attached patch changes ntp_unixclock.c to reduce the maxslew value a little bit when using this daemon. This is the same tweak applied when SLEWALWAYS is defined, and it gives further defense against didn't-complete gripes. Although the patched code can still get the didn't-complete complaint, it is much less likely to do so than the original code. It has a couple of other advantages, I think: it works much more like BSD adjtime than the old code, so should create fewer compatibility problems; and it works correctly for slews exceeding 1 second, which is not an issue for ntpd but is an issue for other applications that might like to use this emulation of adjtime(). regards, tom lane Note: these are diffs against ntpd 3.5a; haven't gotten around to downloading the newer releases yet. *** adjtime/adjtimed.c.orig Mon Oct 3 06:02:20 1994 --- adjtime/adjtimed.c Sat Mar 23 15:43:08 1996 *************** *** 18,29 **** /* * Adjust time daemon. ! * This deamon adjusts the rate of the system clock a la BSD's adjtime(). * The adjtime() routine uses SYSV messages to communicate with this daemon. * * Caveat: This emulation uses an undocumented kernel variable. As such, it ! * cannot be guaranteed to work in future HP-UX releases. Perhaps a real ! * adjtime(2) will be supported in the future. */ #include --- 18,29 ---- /* * Adjust time daemon. ! * This daemon adjusts the rate of the system clock a la BSD's adjtime(). * The adjtime() routine uses SYSV messages to communicate with this daemon. * * Caveat: This emulation uses an undocumented kernel variable. As such, it ! * cannot be guaranteed to work in future HP-UX releases. Fortunately, ! * it will no longer be needed in HPUX 10.01 and later. */ #include *************** *** 37,42 **** --- 37,43 ---- #include #include #include + #include #include "ntp_syslog.h" #include "adjtime.h" *************** *** 196,208 **** perror("adjtimed: get message queue id"); Exit(1); } ! if (plock(PROCLOCK)) { syslog(LOG_ERR, "plock: %m"); perror("adjtimed: plock"); Cleanup(); } for (;;) { if (msgrcv(mqid, &msg.msgp, MSGSIZE, CLIENT, 0) == -1) { if (errno == EINTR) continue; --- 197,220 ---- perror("adjtimed: get message queue id"); Exit(1); } ! ! /* Lock process in memory to improve response time */ if (plock(PROCLOCK)) { syslog(LOG_ERR, "plock: %m"); perror("adjtimed: plock"); Cleanup(); } + /* Also raise process priority. + * If we do not get run when we want, this leads to bad timekeeping + * and "Previous time adjustment didn't complete" gripes from xntpd. + */ + if (nice(-10) == -1) { + syslog(LOG_ERR, "nice: %m"); + perror("adjtimed: nice"); + Cleanup(); + } + for (;;) { if (msgrcv(mqid, &msg.msgp, MSGSIZE, CLIENT, 0) == -1) { if (errno == EINTR) continue; *************** *** 252,317 **** */ #define DEFAULT_RATE (MILLION / HZ) #define UNKNOWN_RATE 0L ! #define SLEW_RATE (MILLION / DEFAULT_RATE) ! #define MIN_DELTA SLEW_RATE static long default_rate = DEFAULT_RATE; ! static long slew_rate = SLEW_RATE; AdjustClockRate(delta, olddelta) register struct timeval *delta, *olddelta; { register long rate, dt; struct itimerval period, remains; - static long leftover = 0; - /* - * rate of change - */ - dt = (delta->tv_sec * MILLION) + delta->tv_usec + leftover; - - if (dt < MIN_DELTA && dt > -MIN_DELTA) { - leftover += delta->tv_usec; - - if (olddelta) { - getitimer(ITIMER_REAL, &remains); - dt = ((remains.it_value.tv_sec * MILLION) + remains.it_value.tv_usec) * - oldrate; - olddelta->tv_sec = dt / MILLION; - olddelta->tv_usec = dt - (olddelta->tv_sec * MILLION); - } ! if (verbose > 2) printf("adjtimed: delta is too small: %dus\n", dt); ! if (sysdebug > 2) syslog(LOG_INFO, "delta is too small: %dus", dt); ! return (1); ! } ! ! leftover = dt % MIN_DELTA; ! dt -= leftover; if (verbose) printf("adjtimed: new correction %.6fs\n", (double)dt / (double)MILLION); if (sysdebug) syslog(LOG_INFO, "new correction %.6fs", (double)dt / (double)MILLION); - if (verbose > 2) printf("adjtimed: leftover %dus\n", leftover); - if (sysdebug > 2) syslog(LOG_INFO, "leftover %dus", leftover); - rate = dt; /* ! * The adjustment will always be a multiple of the minimum adjustment. ! * So the period will always be a whole second value. */ - period.it_value.tv_sec = 1l; - period.it_value.tv_usec = 0; - if (verbose > 1) ! printf("adjtimed: will be complete in %ds\n", period.it_value.tv_sec); if (sysdebug > 1) ! syslog(LOG_INFO, "will be complete in %ds", period.it_value.tv_sec); /* * adjust the clock rate */ ! if (SetClockRate((rate / slew_rate) + default_rate) == -1) { ! syslog(LOG_ERR, "set clock rate: %m"); ! perror("adjtimed: set clock rate"); } /* * start the timer --- 264,319 ---- */ #define DEFAULT_RATE (MILLION / HZ) #define UNKNOWN_RATE 0L ! #define TICK_ADJ 5 /* standard adjustment rate, microsec/tick */ ! static long default_rate = DEFAULT_RATE; ! static long tick_rate = HZ; /* ticks per sec */ ! static long slew_rate = TICK_ADJ * HZ; /* in microsec/sec */ AdjustClockRate(delta, olddelta) register struct timeval *delta, *olddelta; { register long rate, dt; struct itimerval period, remains; ! dt = (delta->tv_sec * MILLION) + delta->tv_usec; if (verbose) printf("adjtimed: new correction %.6fs\n", (double)dt / (double)MILLION); if (sysdebug) syslog(LOG_INFO, "new correction %.6fs", (double)dt / (double)MILLION); /* ! * Apply a slew rate of slew_rate over a period of dt/slew_rate seconds. ! */ ! if (dt > 0) { ! rate = slew_rate; ! } else { ! rate = -slew_rate; ! dt = -dt; ! } ! period.it_value.tv_sec = dt / slew_rate; ! period.it_value.tv_usec = (dt % slew_rate) * (MILLION / slew_rate); ! /* ! * Note: we assume the kernel will convert the specified period into ticks ! * using the modified clock rate rather than an assumed nominal clock rate, ! * and therefore will generate the timer interrupt after the specified ! * number of true seconds, not skewed seconds. */ if (verbose > 1) ! printf("adjtimed: will be complete in %lds %ldus\n", ! period.it_value.tv_sec, period.it_value.tv_usec); if (sysdebug > 1) ! syslog(LOG_INFO, "will be complete in %lds %ldus", ! period.it_value.tv_sec, period.it_value.tv_usec); /* * adjust the clock rate */ ! if (dt) { ! if (SetClockRate((rate / tick_rate) + default_rate) == -1) { ! syslog(LOG_ERR, "set clock rate: %m"); ! perror("adjtimed: set clock rate"); ! } } /* * start the timer *************** *** 390,400 **** if (rate != default_rate) { if (verbose > 3) { printf("adjtimed: clock rate (%lu) %ldus/s\n", rate, ! (rate - default_rate) * slew_rate); } if (sysdebug > 3) { syslog(LOG_INFO, "clock rate (%lu) %ldus/s", rate, ! (rate - default_rate) * slew_rate); } } --- 392,402 ---- if (rate != default_rate) { if (verbose > 3) { printf("adjtimed: clock rate (%lu) %ldus/s\n", rate, ! (rate - default_rate) * tick_rate); } if (sysdebug > 3) { syslog(LOG_INFO, "clock rate (%lu) %ldus/s", rate, ! (rate - default_rate) * tick_rate); } } *************** *** 421,427 **** */ default_rate = GetClockRate(); if (default_rate == UNKNOWN_RATE) default_rate = DEFAULT_RATE; ! slew_rate = (MILLION / default_rate); return (0); } /* InitClockRate */ --- 423,430 ---- */ default_rate = GetClockRate(); if (default_rate == UNKNOWN_RATE) default_rate = DEFAULT_RATE; ! tick_rate = (MILLION / default_rate); ! slew_rate = TICK_ADJ * tick_rate; return (0); } /* InitClockRate */ *** xntpd/ntp_unixclock.c.orig Mon Feb 5 17:58:30 1996 --- xntpd/ntp_unixclock.c Sun Mar 24 16:41:51 1996 *************** *** 180,185 **** --- 180,192 ---- adj_precision = (1< 999990) { /* * Don't let the maximum slew exceed 1 second in 4. This