Команда командной строки для автоматического уничтожения команды через определенное время - PullRequest
52 голосов
/ 02 марта 2009

Я бы хотел автоматически убить команду через определенное время. Я имею в виду такой интерфейс:

% constrain 300 ./foo args

Который запускает «./foo» с «args», но автоматически убивает его, если он все еще работает через 5 минут.

Может быть полезно обобщить идею для других ограничений, таких как автоматическое уничтожение процесса, если он использует слишком много памяти.

Существуют ли какие-либо инструменты, которые это делают, или кто-нибудь написал такое?

ДОБАВЛЕНО: решение Джонатана - именно то, что я имел в виду, и оно работает как прелесть в Linux, но я не могу заставить его работать на Mac OSX. Я избавился от SIGRTMIN, который позволяет нормально его компилировать, но сигнал просто не отправляется дочернему процессу. Кто-нибудь знает, как заставить это работать на Mac?

[Добавлено: обратите внимание, что от Джонатана доступно обновление, которое работает на Mac и в других местах.]

Ответы [ 15 ]

45 голосов
/ 24 августа 2011

GNU Coreutils включает в себя команду timeout , установленную по умолчанию во многих системах.

https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html

Чтобы посмотреть free -m в течение одной минуты, затем убейте его, отправив сигнал TERM:

timeout 1m watch free -m
30 голосов
/ 12 марта 2009

Может быть, я не понимаю вопроса, но это звучит выполнимо, по крайней мере, в bash:

( /path/to/slow command with options ) & sleep 5 ; kill $!

Эта команда запускает первую команду в круглых скобках в течение пяти секунд, а затем убивает ее. Вся операция выполняется синхронно, то есть вы не сможете использовать вашу оболочку, пока она занята ожиданием медленной команды. Если это не то, что вы хотели, можно добавить еще один &.

Переменная $! - это встроенная функция Bash, которая содержит идентификатор процесса последней запущенной подоболочки. Важно, чтобы в скобках не было символа &, так как при этом теряется идентификатор процесса.

27 голосов
/ 11 июля 2010

Я приехал довольно поздно на эту вечеринку, но я не вижу своего любимого трюка, перечисленного в ответах.

В * NIX alarm(2) наследуется через execve(2), а SIGALRM является фатальным по умолчанию. Таким образом, вы часто можете просто:

$ doalarm () { perl -e 'alarm shift; exec @ARGV' "$@"; } # define a helper function

$ doalarm 300 ./foo.sh args

или установите тривиальную оболочку C , чтобы сделать это для вас.

Преимущества Используется только один PID, и механизм прост. Вы не убьете неправильный процесс, если, например, ./foo.sh завершил работу «слишком быстро» и его PID был повторно использован. Вам не нужно, чтобы несколько подпроцессов оболочки работали согласованно, что можно сделать правильно, но скорее склонно к гонкам.

Недостатки Процесс, ограниченный во времени, не может управлять своим будильником (например, alarm(2), ualarm(2), setitimer(2)), поскольку это, вероятно, очистит унаследованный сигнал тревоги. Очевидно, что он также не может блокировать или игнорировать SIGALRM, хотя то же самое можно сказать о SIGINT, SIGTERM и т. Д. Для некоторых других подходов.

Некоторые (я думаю, очень старые) системы реализуют sleep(2) в терминах alarm(2), и даже сегодня некоторые программисты используют alarm(2) в качестве грубого внутреннего механизма тайм-аута для операций ввода-вывода и других операций. Однако, по моему опыту, эта техника применима к подавляющему большинству процессов, которые вы хотите ограничить по времени.

12 голосов
/ 02 марта 2009

У меня есть программа под названием timeout, которая делает это - написана на C, первоначально в 1989 году, но с тех пор периодически обновляется.


Обновление: этот код не компилируется в MacOS X, потому что SIGRTMIN не определен, и не работает с таймаутом при запуске в MacOS X, потому что функция signal() возобновляет wait() после истечения времени тревоги - что не является обязательным поведением. У меня есть новая версия timeout.c, которая решает обе эти проблемы (используя sigaction() вместо signal()). Как и прежде, свяжитесь со мной для получения 10K gzip-файла tar с исходным кодом и страницей руководства (см. Мой профиль).
/*
@(#)File:           $RCSfile: timeout.c,v $
@(#)Version:        $Revision: 4.6 $
@(#)Last changed:   $Date: 2007/03/01 22:23:02 $
@(#)Purpose:        Run command with timeout monitor
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1989,1997,2003,2005-07
*/

#define _POSIX_SOURCE       /* Enable kill() in <unistd.h> on Solaris 7 */
#define _XOPEN_SOURCE 500

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "stderr.h"

#define CHILD       0
#define FORKFAIL    -1

static const char usestr[] = "[-vV] -t time [-s signal] cmd [arg ...]";

#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_timeout_c[] = "@(#)$Id: timeout.c,v 4.6 2007/03/01 22:23:02 jleffler Exp $";
#endif /* lint */

static void catcher(int signum)
{
    return;
}

int main(int argc, char **argv)
{
    pid_t   pid;
    int     tm_out;
    int     kill_signal;
    pid_t   corpse;
    int     status;
    int     opt;
    int     vflag = 0;

    err_setarg0(argv[0]);

    opterr = 0;
    tm_out = 0;
    kill_signal = SIGTERM;
    while ((opt = getopt(argc, argv, "vVt:s:")) != -1)
    {
        switch(opt)
        {
        case 'V':
            err_version("TIMEOUT", &"@(#)$Revision: 4.6 $ ($Date: 2007/03/01 22:23:02 $)"[4]);
            break;
        case 's':
            kill_signal = atoi(optarg);
            if (kill_signal <= 0 || kill_signal >= SIGRTMIN)
                err_error("signal number must be between 1 and %d\n", SIGRTMIN - 1);
            break;
        case 't':
            tm_out = atoi(optarg);
            if (tm_out <= 0)
                err_error("time must be greater than zero (%s)\n", optarg);
            break;
        case 'v':
            vflag = 1;
            break;
        default:
            err_usage(usestr);
            break;
        }
    }

    if (optind >= argc || tm_out == 0)
        err_usage(usestr);

    if ((pid = fork()) == FORKFAIL)
        err_syserr("failed to fork\n");
    else if (pid == CHILD)
    {
        execvp(argv[optind], &argv[optind]);
        err_syserr("failed to exec command %s\n", argv[optind]);
    }

    /* Must be parent -- wait for child to die */
    if (vflag)
        err_remark("time %d, signal %d, child PID %u\n", tm_out, kill_signal, (unsigned)pid);
    signal(SIGALRM, catcher);
    alarm((unsigned int)tm_out);
    while ((corpse = wait(&status)) != pid && errno != ECHILD)
    {
        if (errno == EINTR)
        {
            /* Timed out -- kill child */
            if (vflag)
                err_remark("timed out - send signal %d to process %d\n", (int)kill_signal, (int)pid);
            if (kill(pid, kill_signal) != 0)
                err_syserr("sending signal %d to PID %d - ", kill_signal, pid);
            corpse = wait(&status);
            break;
        }
    }

    alarm(0);
    if (vflag)
    {
        if (corpse == (pid_t) -1)
            err_syserr("no valid PID from waiting - ");
        else
            err_remark("child PID %u status 0x%04X\n", (unsigned)corpse, (unsigned)status);
    }

    if (corpse != pid)
        status = 2; /* Dunno what happened! */
    else if (WIFEXITED(status))
        status = WEXITSTATUS(status);
    else if (WIFSIGNALED(status))
        status = WTERMSIG(status);
    else
        status = 2; /* Dunno what happened! */

    return(status);
}

Если вы хотите получить «официальный» код для «stderr.h» и «stderr.c», свяжитесь со мной (см. Мой профиль).

11 голосов
/ 02 марта 2009

Существует также ulimit, который можно использовать для ограничения времени выполнения, доступного подпроцессам.

ulimit -t 10

Ограничивает процесс до 10 секунд процессорного времени.

Чтобы фактически использовать его для ограничения нового процесса, а не текущего процесса, вы можете использовать скрипт-обертку:

#! /usr/bin/env python

import os
os.system("ulimit -t 10; other-command-here")

other-command может быть любым инструментом. Я запускал версии различных алгоритмов сортировки на Java, Python, C и Scheme и записывал, сколько времени они занимали, в то время как время выполнения ограничивалось 30 секундами. Приложение Какао-Python сгенерировало различные командные строки - включая аргументы - и сопоставило времена в CSV-файл, но на самом деле это была просто запутанная команда, приведенная выше.

4 голосов
/ 06 октября 2010

Моя вариация на perl one-liner дает вам статус выхода без использования muck с помощью fork () и wait () и без риска убить неправильный процесс:

#!/bin/sh
# Usage: timelimit.sh secs cmd [ arg ... ]
exec perl -MPOSIX -e '$SIG{ALRM} = sub { print "timeout: @ARGV\n"; kill(SIGTERM, -$$); }; alarm shift; $exit = system @ARGV; exit(WIFEXITED($exit) ? WEXITSTATUS($exit) : WTERMSIG($exit));' "$@"

Обычно fork () и wait () скрыты внутри системы (). SIGALRM доставляется родительскому процессу, который затем убивает себя и своего потомка, отправляя SIGTERM всей группе процессов (- $$). В маловероятном случае выхода дочернего элемента и повторного использования pid дочернего элемента до того, как произойдет kill (), это НЕ уничтожит неправильный процесс, поскольку новый процесс с pid старого дочернего элемента не будет находиться в той же группе процессов родительского процесса perl .

В качестве дополнительного преимущества скрипт также завершает работу с тем, что , вероятно, является правильным статусом выхода.

4 голосов
/ 22 сентября 2010

Команда timeout из Ubuntu / Debian при компиляции из исходного кода для работы на Mac. Дарвин

10.4. *

http://packages.ubuntu.com/lucid/timeout

4 голосов
/ 11 марта 2009

Perl один лайнер, только для ударов:

perl -e '$s = shift; $SIG{ALRM} = sub { print STDERR "Timeout!\n"; kill INT => $p }; exec(@ARGV) unless $p = fork; alarm $s; waitpid $p, 0' 10 yes foo

Это печатает 'foo' в течение десяти секунд, затем время ожидания. Замените '10' на любое количество секунд, а 'yes foo' - любой командой.

2 голосов
/ 15 июня 2012
#!/bin/sh
( some_slow_task ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

Наблюдатель убивает медленное задание после заданного времени ожидания; скрипт ожидает медленной задачи и завершает работу наблюдателя.

Примеры:

  • Медленное задание выполнялось более 2 секунд и было остановлено

Медленная задача прервана

( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "Slow task finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "Slow task interrupted"
fi
  • Это медленное задание завершено до истечения заданного времени ожидания

Медленное задание выполнено

( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "Slow task finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "Slow task interrupted"
fi
2 голосов
/ 15 августа 2010

Я использую "timelimit", пакет, доступный в репозитории debian.

http://devel.ringlet.net/sysutils/timelimit/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...