From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-1.0 required=5.0 tests=MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham autolearn_force=no version=3.4.2 Received: from primenet.com.au (ns1.primenet.com.au [203.24.36.2]) by inbox.vuxu.org (OpenSMTPD) with ESMTP id 9333b489 for ; Mon, 29 Jul 2019 20:36:04 +0000 (UTC) Received: (qmail 9805 invoked by alias); 29 Jul 2019 20:35:57 -0000 Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm Precedence: bulk X-No-Archive: yes List-Id: Zsh Workers List List-Post: List-Help: List-Unsubscribe: X-Seq: 44584 Received: (qmail 10708 invoked by uid 1010); 29 Jul 2019 20:35:57 -0000 X-Qmail-Scanner-Diagnostics: from nef2.ens.fr by f.primenet.com.au (envelope-from , uid 7791) with qmail-scanner-2.11 (clamdscan: 0.101.2/25524. spamassassin: 3.4.2. Clear:RC:0(129.199.96.40):SA:0(-4.2/5.0):. Processed in 0.899549 secs); 29 Jul 2019 20:35:57 -0000 X-Envelope-From: ware@phare.normalesup.org X-Qmail-Scanner-Mime-Attachments: | X-Qmail-Scanner-Zip-Files: | Received-SPF: pass (ns1.primenet.com.au: SPF record at ens.fr designates 129.199.96.40 as permitted sender) X-ENS-nef-client: 129.199.129.80 Date: Mon, 29 Jul 2019 22:35:21 +0200 From: Cedric Ware To: zsh-workers@zsh.org Subject: [PATCH] Enable sub-second timeout in zsystem flock Message-ID: <20190729203521.upp5ku3bsr3hsnxq@phare.normalesup.org> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="lv3ojic7ed4er5xi" Content-Disposition: inline User-Agent: NeoMutt/20170113 (1.7.2) X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Mon, 29 Jul 2019 22:35:22 +0200 (CEST) --lv3ojic7ed4er5xi Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hello, I'm interested in using the zsystem flock function in the zsh/system module to lock files, with a timeout if the lock is already taken, but currently the timeout value has a whole-second granularity and I'd like to specify sub-second timeouts. The attached patch (against zsh 5.7.1) modifies the bin_zsystem_flock() function, allowing the -t option to take floating-point parameters, and handles the timeout with microsecond granularity. It also adds option -p to control the retrying period, which was previously hardcoded to 1 second. I kept the same function's logic of periodically retrying to acquire the lock in non-blocking mode. However, there may be a better way to implement this timeout: set a timer such as setitimer(2) which sends a SIGALRM on timeout, trap SIGALRM without SA_RESTART, then acquire the lock in blocking mode; fcntl() will then block until SIGALRM is caught, after which errno will be EINTR. Unfortunately, lacking knowledge of zsh internals especially signal handling, I'm not confident that I can reimplement it this way by myself. The patch modifies Src/Modules/system.c and also Src/utils.c (to add a function akin to time(NULL) but that returns microseconds, using clock_gettime(CLOCK_MONOTONIC) if supported, or else gettimeofday()). I did not modify the documentation yet, not being sure of people being interested in this feature, nor of whether I should edit just the Doc/Zsh/mod_system.yo file, or also Doc/{zshmodules.1,zsh.texi} or others. Also, I'm afraid that I only did minimal testing. I checked that invoking zsystem flock with various values for -t and -p seems to do what I intended. Is there a more rigorous test framework for this kind of thing? Thanks, best regards, Cedric Ware. --lv3ojic7ed4er5xi Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="zsh-5.7.1-flock-timeout.patch" diff -ur zsh-5.7.1.orig/Src/Modules/system.c zsh-5.7.1/Src/Modules/system.c --- zsh-5.7.1.orig/Src/Modules/system.c 2018-12-14 08:50:17.000000000 +0100 +++ zsh-5.7.1/Src/Modules/system.c 2019-07-29 20:19:48.835060428 +0200 @@ -532,6 +532,8 @@ { int cloexec = 1, unlock = 0, readlock = 0; zlong timeout = -1; + long timeout_retry = 1e6; + mnumber timeout_param; char *fdvar = NULL; #ifdef HAVE_FCNTL_H struct flock lck; @@ -583,7 +585,35 @@ } else { optarg = *args++; } - timeout = mathevali(optarg); + timeout_param = matheval(optarg); + if (!(timeout_param.type & MN_FLOAT)) { + timeout_param.type = MN_FLOAT; + timeout_param.u.d = (double)timeout_param.u.l; + } + timeout = (zlong)(timeout_param.u.d * 1e6); + break; + + case 'p': + /* retry period in seconds */ + if (optptr[1]) { + optarg = optptr + 1; + optptr += strlen(optarg) - 1; + } else if (!*args) { + zwarnnam(nam, + "flock: option %c requires a numeric retry period", + opt); + return 1; + } else { + optarg = *args++; + } + timeout_param = matheval(optarg); + if (!(timeout_param.type & MN_FLOAT)) { + timeout_param.type = MN_FLOAT; + timeout_param.u.d = (double)timeout_param.u.l; + } + zlong timeout_retry_tmp = timeout_param.u.d * 1e6; + timeout_retry = (timeout_retry_tmp > LONG_MAX) ? + LONG_MAX : timeout_retry_tmp; break; case 'u': @@ -647,7 +677,7 @@ lck.l_len = 0; /* lock the whole file */ if (timeout > 0) { - time_t end = time(NULL) + (time_t)timeout; + zlong end = time_clock_us() + timeout; while (fcntl(flock_fd, F_SETLK, &lck) < 0) { if (errflag) { zclose(flock_fd); @@ -658,11 +688,15 @@ zwarnnam(nam, "failed to lock file %s: %e", args[0], errno); return 1; } - if (time(NULL) >= end) { + zlong now = time_clock_us(); + if (now >= end) { zclose(flock_fd); return 2; } - sleep(1); + if (now + timeout_retry > end) { + timeout_retry = end - now; + } + zsleep(timeout_retry); } } else { while (fcntl(flock_fd, timeout == 0 ? F_SETLK : F_SETLKW, &lck) < 0) { diff -ur zsh-5.7.1.orig/Src/utils.c zsh-5.7.1/Src/utils.c --- zsh-5.7.1.orig/Src/utils.c 2019-02-01 01:37:34.000000000 +0100 +++ zsh-5.7.1/Src/utils.c 2019-07-29 20:24:57.827073902 +0200 @@ -2724,6 +2724,26 @@ } /* + * Return the current time in microseconds, using the system's + * monotonic clock if supported, the wall clock if not. + */ + +/**/ +zlong +time_clock_us(void) +{ +#if defined(HAS_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * (zlong)1e6 + ts.tv_nsec / 1000; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * (zlong)1e6 + tv.tv_usec; +#endif +} + +/* * Sleep for the given number of microseconds --- must be within * range of a long at the moment, but this is only used for * limited internal purposes. --lv3ojic7ed4er5xi--