nsxiv49d11f0
spawn(): improve performance and simplify API
posix_spawn() is designed especially for this purpose, and thus it's
much more lightweight and efficient than manually fork/dup/exec-ing.
on my system, it improves the performance of spawn() by about 10x. given
that we make frequent calls to potentially multiple scripts, the
increased efficiency will add up overtime.
using posix_spawn() also simplifies the logic quite a bit, despite the
very verbose function names. however it does make cleanup a bit more
complicated.
this patch uses the linux kernel style cleanup strategy [0] (which I'm
personally not a huge fan of, but it fits this situation quite nicely)
with a "stack-like" unwinding via `goto`-s.
additionally simplify the spawn() API by taking in {read,write}fd
pointers and returning the pid instead of using some custom struct.
this coincidently also fixes #299
[0]: https://www.kernel.org/doc/html/v4.10/process/coding-style.html?highlight=goto#centralized-exiting-of-functions
+67 −79
3 files changed
| @@ -277,9 +277,9 @@ static bool check_timeouts(int *t) | |||
| 277 | 277 | static size_t get_win_title(char *buf, size_t len) | |
| 278 | 278 | { | |
| 279 | 279 | char *argv[8]; | |
| 280 | − | spawn_t pfd; | |
| 281 | 280 | char w[12] = "", h[12] = "", z[12] = "", fidx[12], fcnt[12]; | |
| 282 | 281 | ssize_t n = -1; | |
| 282 | + | int readfd; | |
| 283 | 283 | ||
| 284 | 284 | if (wintitle.f.err || buf == NULL || len == 0) | |
| 285 | 285 | return 0; | |
| @@ -293,11 +293,10 @@ static size_t get_win_title(char *buf, size_t len) | |||
| 293 | 293 | snprintf(fcnt, ARRLEN(fcnt), "%d", filecnt); | |
| 294 | 294 | construct_argv(argv, ARRLEN(argv), wintitle.f.cmd, files[fileidx].path, | |
| 295 | 295 | fidx, fcnt, w, h, z, NULL); | |
| 296 | − | pfd = spawn(wintitle.f.cmd, argv, X_READ); | |
| 297 | − | if (pfd.readfd >= 0) { | |
| 298 | − | if ((n = read(pfd.readfd, buf, len-1)) > 0) | |
| 296 | + | if (spawn(&readfd, NULL, argv) > 0) { | |
| 297 | + | if ((n = read(readfd, buf, len-1)) > 0) | |
| 299 | 298 | buf[n] = '\0'; | |
| 300 | − | close(pfd.readfd); | |
| 299 | + | close(readfd); | |
| 301 | 300 | } | |
| 302 | 301 | ||
| 303 | 302 | return MAX(0, n); | |
| @@ -314,9 +313,7 @@ void close_info(void) | |||
| 314 | 313 | ||
| 315 | 314 | void open_info(void) | |
| 316 | 315 | { | |
| 317 | − | spawn_t pfd; | |
| 318 | − | char w[12] = "", h[12] = ""; | |
| 319 | − | char *argv[6]; | |
| 316 | + | char *argv[6], w[12] = "", h[12] = ""; | |
| 320 | 317 | char *cmd = mode == MODE_IMAGE ? info.f.cmd : info.ft.cmd; | |
| 321 | 318 | bool ferr = mode == MODE_IMAGE ? info.f.err : info.ft.err; | |
| 322 | 319 | ||
| @@ -329,12 +326,8 @@ void open_info(void) | |||
| 329 | 326 | } | |
| 330 | 327 | construct_argv(argv, ARRLEN(argv), cmd, files[fileidx].name, w, h, | |
| 331 | 328 | files[fileidx].path, NULL); | |
| 332 | − | pfd = spawn(cmd, argv, X_READ); | |
| 333 | − | if (pfd.readfd >= 0) { | |
| 334 | − | fcntl(pfd.readfd, F_SETFL, O_NONBLOCK); | |
| 335 | − | info.fd = pfd.readfd; | |
| 336 | − | info.pid = pfd.pid; | |
| 337 | − | } | |
| 329 | + | if ((info.pid = spawn(&info.fd, NULL, argv)) > 0) | |
| 330 | + | fcntl(info.fd, F_SETFL, O_NONBLOCK); | |
| 338 | 331 | } | |
| 339 | 332 | ||
| 340 | 333 | static void read_info(void) | |
| @@ -579,13 +572,13 @@ static bool run_key_handler(const char *key, unsigned int mask) | |||
| 579 | 572 | FILE *pfs; | |
| 580 | 573 | bool marked = mode == MODE_THUMB && markcnt > 0; | |
| 581 | 574 | bool changed = false; | |
| 582 | − | int f, i; | |
| 575 | + | pid_t pid; | |
| 576 | + | int writefd, f, i; | |
| 583 | 577 | int fcnt = marked ? markcnt : 1; | |
| 584 | 578 | char kstr[32]; | |
| 585 | 579 | struct stat *oldst, st; | |
| 586 | 580 | XEvent dump; | |
| 587 | 581 | char *argv[3]; | |
| 588 | − | spawn_t pfd; | |
| 589 | 582 | ||
| 590 | 583 | if (keyhandler.f.err) { | |
| 591 | 584 | if (!keyhandler.warned) { | |
| @@ -608,12 +601,11 @@ static bool run_key_handler(const char *key, unsigned int mask) | |||
| 608 | 601 | mask & Mod1Mask ? "M-" : "", | |
| 609 | 602 | mask & ShiftMask ? "S-" : "", key); | |
| 610 | 603 | construct_argv(argv, ARRLEN(argv), keyhandler.f.cmd, kstr, NULL); | |
| 611 | − | pfd = spawn(keyhandler.f.cmd, argv, X_WRITE); | |
| 612 | − | if (pfd.writefd < 0) | |
| 604 | + | if ((pid = spawn(NULL, &writefd, argv)) < 0) | |
| 613 | 605 | return false; | |
| 614 | − | if ((pfs = fdopen(pfd.writefd, "w")) == NULL) { | |
| 606 | + | if ((pfs = fdopen(writefd, "w")) == NULL) { | |
| 615 | 607 | error(0, errno, "open pipe"); | |
| 616 | − | close(pfd.writefd); | |
| 608 | + | close(writefd); | |
| 617 | 609 | return false; | |
| 618 | 610 | } | |
| 619 | 611 | ||
| @@ -626,7 +618,7 @@ static bool run_key_handler(const char *key, unsigned int mask) | |||
| 626 | 618 | } | |
| 627 | 619 | } | |
| 628 | 620 | fclose(pfs); | |
| 629 | − | while (waitpid(pfd.pid, NULL, 0) == -1 && errno == EINTR); | |
| 621 | + | while (waitpid(pid, NULL, 0) == -1 && errno == EINTR); | |
| 630 | 622 | ||
| 631 | 623 | for (f = i = 0; f < fcnt; i++) { | |
| 632 | 624 | if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { | |
| @@ -326,17 +326,6 @@ typedef struct { | |||
| 326 | 326 | int stlen; | |
| 327 | 327 | } r_dir_t; | |
| 328 | 328 | ||
| 329 | − | typedef struct { | |
| 330 | − | int readfd; | |
| 331 | − | int writefd; | |
| 332 | − | pid_t pid; | |
| 333 | − | } spawn_t; | |
| 334 | − | ||
| 335 | − | enum { | |
| 336 | − | X_READ = (1 << 0), | |
| 337 | − | X_WRITE = (1 << 1) | |
| 338 | − | }; | |
| 339 | − | ||
| 340 | 329 | extern const char *progname; | |
| 341 | 330 | ||
| 342 | 331 | void* emalloc(size_t); | |
| @@ -349,7 +338,7 @@ int r_closedir(r_dir_t*); | |||
| 349 | 338 | char* r_readdir(r_dir_t*, bool); | |
| 350 | 339 | int r_mkdir(char*); | |
| 351 | 340 | void construct_argv(char**, unsigned int, ...); | |
| 352 | − | spawn_t spawn(const char*, char *const [], unsigned int); | |
| 341 | + | pid_t spawn(int*, int*, char *const []); | |
| 353 | 342 | ||
| 354 | 343 | ||
| 355 | 344 | /* window.c */ | |
| @@ -21,6 +21,7 @@ | |||
| 21 | 21 | ||
| 22 | 22 | #include <assert.h> | |
| 23 | 23 | #include <errno.h> | |
| 24 | + | #include <spawn.h> | |
| 24 | 25 | #include <stdarg.h> | |
| 25 | 26 | #include <stdio.h> | |
| 26 | 27 | #include <stdlib.h> | |
| @@ -28,6 +29,7 @@ | |||
| 28 | 29 | #include <sys/stat.h> | |
| 29 | 30 | #include <unistd.h> | |
| 30 | 31 | ||
| 32 | + | extern char **environ; | |
| 31 | 33 | const char *progname = "nsxiv"; | |
| 32 | 34 | ||
| 33 | 35 | void* emalloc(size_t size) | |
| @@ -228,60 +230,65 @@ void construct_argv(char **argv, unsigned int len, ...) | |||
| 228 | 230 | error(EXIT_FAILURE, 0, "argv not NULL terminated"); | |
| 229 | 231 | } | |
| 230 | 232 | ||
| 231 | − | spawn_t spawn(const char *cmd, char *const argv[], unsigned int flags) | |
| 233 | + | static int mkspawn_pipe(posix_spawn_file_actions_t *fa, const char *cmd, int *pfd, int dupidx) | |
| 232 | 234 | { | |
| 233 | − | pid_t pid; | |
| 234 | − | spawn_t status = { -1, -1, -1 }; | |
| 235 | − | int pfd_read[2] = { -1, -1 }, pfd_write[2] = { -1, -1 }; | |
| 236 | − | const bool r = flags & X_READ; | |
| 237 | − | const bool w = flags & X_WRITE; | |
| 238 | − | ||
| 239 | − | if (cmd == NULL || argv == NULL || flags == 0) | |
| 240 | − | return status; | |
| 241 | − | ||
| 242 | − | if (r && pipe(pfd_read) < 0) { | |
| 235 | + | int err; | |
| 236 | + | if (pipe(pfd) < 0) { | |
| 243 | 237 | error(0, errno, "pipe: %s", cmd); | |
| 244 | − | return status; | |
| 238 | + | return -1; | |
| 245 | 239 | } | |
| 240 | + | err = posix_spawn_file_actions_adddup2(fa, pfd[dupidx], dupidx); | |
| 241 | + | err = err ? err : posix_spawn_file_actions_addclose(fa, pfd[0]); | |
| 242 | + | err = err ? err : posix_spawn_file_actions_addclose(fa, pfd[1]); | |
| 243 | + | if (err) { | |
| 244 | + | error(0, err, "posix_spawn_file_actions: %s", cmd); | |
| 245 | + | close(pfd[0]); | |
| 246 | + | close(pfd[1]); | |
| 247 | + | } | |
| 248 | + | return err ? -1 : 0; | |
| 249 | + | } | |
| 246 | 250 | ||
| 247 | − | if (w && pipe(pfd_write) < 0) { | |
| 248 | − | error(0, errno, "pipe: %s", cmd); | |
| 249 | − | if (r) { | |
| 250 | − | close(pfd_read[0]); | |
| 251 | − | close(pfd_read[1]); | |
| 252 | − | } | |
| 253 | − | return status; | |
| 251 | + | pid_t spawn(int *readfd, int *writefd, char *const argv[]) | |
| 252 | + | { | |
| 253 | + | pid_t pid = -1; | |
| 254 | + | const char *cmd; | |
| 255 | + | int err, pfd_read[2], pfd_write[2]; | |
| 256 | + | posix_spawn_file_actions_t fa; | |
| 257 | + | ||
| 258 | + | assert(argv != NULL && argv[0] != NULL); | |
| 259 | + | cmd = argv[0]; | |
| 260 | + | ||
| 261 | + | if ((err = posix_spawn_file_actions_init(&fa)) != 0) { | |
| 262 | + | error(0, err, "spawn: %s", cmd); | |
| 263 | + | return pid; | |
| 254 | 264 | } | |
| 255 | 265 | ||
| 256 | − | if ((pid = fork()) == 0) { /* in child */ | |
| 257 | − | if ((r && dup2(pfd_read[1], 1) < 0) || (w && dup2(pfd_write[0], 0) < 0)) | |
| 258 | − | error(EXIT_FAILURE, errno, "dup2: %s", cmd); | |
| 266 | + | if (readfd != NULL && mkspawn_pipe(&fa, cmd, pfd_read, 1) < 0) | |
| 267 | + | goto err_destroy_fa; | |
| 268 | + | if (writefd != NULL && mkspawn_pipe(&fa, cmd, pfd_write, 0) < 0) | |
| 269 | + | goto err_close_readfd; | |
| 270 | + | ||
| 271 | + | if ((err = posix_spawn(&pid, cmd, &fa, NULL, argv, environ)) != 0) { | |
| 272 | + | error(0, err, "spawn: %s", cmd); | |
| 273 | + | } else { | |
| 274 | + | if (readfd != NULL) | |
| 275 | + | *readfd = pfd_read[0]; | |
| 276 | + | if (writefd != NULL) | |
| 277 | + | *writefd = pfd_write[1]; | |
| 278 | + | } | |
| 259 | 279 | ||
| 260 | − | if (r) { | |
| 261 | − | close(pfd_read[0]); | |
| 262 | − | close(pfd_read[1]); | |
| 263 | − | } | |
| 264 | − | if (w) { | |
| 265 | − | close(pfd_write[0]); | |
| 266 | − | close(pfd_write[1]); | |
| 267 | − | } | |
| 268 | − | execv(cmd, argv); | |
| 269 | − | error(EXIT_FAILURE, errno, "exec: %s", cmd); | |
| 270 | − | } else if (pid < 0) { /* fork failed */ | |
| 271 | − | error(0, errno, "fork: %s", cmd); | |
| 272 | − | if (r) | |
| 273 | − | close(pfd_read[0]); | |
| 274 | − | if (w) | |
| 280 | + | if (writefd != NULL) { | |
| 281 | + | close(pfd_write[0]); | |
| 282 | + | if (pid < 0) | |
| 275 | 283 | close(pfd_write[1]); | |
| 276 | − | } else { /* in parent */ | |
| 277 | − | status.pid = pid; | |
| 278 | − | status.readfd = pfd_read[0]; | |
| 279 | − | status.writefd = pfd_write[1]; | |
| 280 | 284 | } | |
| 281 | − | ||
| 282 | − | if (r) | |
| 285 | + | err_close_readfd: | |
| 286 | + | if (readfd != NULL) { | |
| 287 | + | if (pid < 0) | |
| 288 | + | close(pfd_read[0]); | |
| 283 | 289 | close(pfd_read[1]); | |
| 284 | − | if (w) | |
| 285 | − | close(pfd_write[0]); | |
| 286 | − | return status; | |
| 290 | + | } | |
| 291 | + | err_destroy_fa: | |
| 292 | + | posix_spawn_file_actions_destroy(&fa); | |
| 293 | + | return pid; | |
| 287 | 294 | } | |