Domanda

Vorrei uccidere automaticamente un comando dopo un certo periodo di tempo. Ho in mente un'interfaccia come questa:

% constrain 300 ./foo args

Che eseguirà " ./ foo " con "args" ma uccidilo automaticamente se continua a funzionare dopo 5 minuti.

Potrebbe essere utile generalizzare l'idea ad altri vincoli, come il completamento automatico di un processo se utilizza troppa memoria.

Esistono strumenti esistenti che lo fanno o qualcuno ha scritto una cosa del genere?

AGGIUNTO: La soluzione di Jonathan è esattamente ciò che avevo in mente e funziona come un incantesimo su Linux, ma non riesco a farlo funzionare su Mac OSX. Mi sono sbarazzato di SIGRTMIN che consente di compilare correttamente, ma il segnale non viene inviato al processo figlio. Qualcuno sa come farlo funzionare su Mac?

[Aggiunto: si noti che è disponibile un aggiornamento da Jonathan che funziona su Mac e altrove.]

È stato utile?

Soluzione

Sono arrivato piuttosto tardi a questa festa, ma non vedo il mio trucco preferito elencato nelle risposte.

In * NIX, un allarme (2) è ereditato da un execve (2) e SIGALRM è fatale per impostazione predefinita. Quindi, spesso puoi semplicemente:

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

$ doalarm 300 ./foo.sh args

o installa un involucro C banale per farlo per te.

Vantaggi È coinvolto solo un PID e il meccanismo è semplice. Non ucciderai il processo sbagliato se, ad esempio, ./foo.sh è uscito " troppo rapidamente " e il suo PID è stato riutilizzato. Non sono necessari diversi sottoprocessi della shell che lavorano in concerto, cosa che può essere eseguita correttamente ma è piuttosto soggetta a razza.

Svantaggi Il processo a tempo limitato non può manipolare la sua sveglia (ad es. alarm (2) , ualarm (2) , setitimer (2) ), poiché probabilmente questo cancellerebbe l'allarme ereditato. Ovviamente, né può bloccare o ignorare SIGALRM, sebbene lo stesso si possa dire di SIGINT, SIGTERM, ecc. Per alcuni altri approcci.

Alcuni sistemi (molto vecchi, credo) implementano sleep (2) in termini di alarm (2) e, ancora oggi, alcuni programmatori usano alarm (2) come meccanismo di timeout interno grezzo per I / O e altre operazioni. Nella mia esperienza, tuttavia, questa tecnica è applicabile alla stragrande maggioranza dei processi che si desidera limitare nel tempo.

Altri suggerimenti

GNU Coreutils include il comando timeout , installato di default su molti sistemi.

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

Per guardare free -m per un minuto, quindi uccidilo inviando un segnale TERM:

timeout 1m watch free -m

Forse non capisco la domanda, ma questo sembra fattibile direttamente, almeno in bash:

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

Questo esegue il primo comando, tra parentesi, per cinque secondi, e poi lo uccide. L'intera operazione viene eseguita in modo sincrono, ovvero non sarà possibile utilizzare la shell mentre è occupata in attesa del comando lento. Se non è quello che volevi, dovrebbe essere possibile aggiungere un altro & amp ;.

La variabile $! è un built-in di Bash che contiene l'ID di processo della subshell avviata più di recente. È importante non avere & amp; all'interno della parentesi, così facendo si perde l'ID del processo.

Ho un programma chiamato timeout che lo fa - scritto in C, originariamente nel 1989 ma aggiornato periodicamente da allora.


Aggiornamento: questo codice non può essere compilato su MacOS X perché SIGRTMIN non è definito e non riesce a scadere quando viene eseguito su MacOS X perché la funzione signal () riprende il wait () dopo il timeout della sveglia - che non è il comportamento richiesto. Ho una nuova versione di timeout.c che affronta entrambi questi problemi (usando sigaction () invece di signal () ). Come prima, contattami per un file tar compresso con 10 KB con il codice sorgente e una pagina di manuale (vedi il mio profilo).


/*
@(#)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 <*>quot;;
#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);
}

Se vuoi il codice 'ufficiale' per 'stderr.h' e 'stderr.c', contattami (vedi il mio profilo).

Esiste anche ulimit, che può essere utilizzato per limitare il tempo di esecuzione disponibile per i sottoprocessi.

ulimit -t 10

Limita il processo a 10 secondi di tempo CPU.

Per usarlo effettivamente per limitare un nuovo processo, piuttosto che il processo corrente, potresti voler usare uno script wrapper:

#! /usr/bin/env python

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

altro comando può essere qualsiasi strumento. Stavo eseguendo una versione Java, Python, C e Scheme di diversi algoritmi di ordinamento e registrando il tempo impiegato, limitando il tempo di esecuzione a 30 secondi. Un'applicazione Cocoa-Python ha generato le varie righe di comando - inclusi gli argomenti - e ha fascicolato i tempi in un file CSV, ma in realtà era solo un fluff in cima al comando fornito sopra.

Perl una fodera, solo per calci:

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

Questo stampa 'pippo' per dieci secondi, quindi scade. Sostituisci "10" con un numero qualsiasi di secondi e "yes foo" con qualsiasi comando.

Il comando di timeout di Ubuntu / Debian quando compilato dal sorgente per funzionare su Mac. Darwin

10.4. *

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

La mia variazione sul perl one-liner ti dà lo stato di uscita senza confondere con fork () e wait () e senza il rischio di uccidere il processo sbagliato:

#!/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));' "$@"

Fondamentalmente fork () e wait () sono nascosti all'interno di system (). SIGALRM viene consegnato al processo genitore che quindi uccide se stesso e il suo figlio inviando SIGTERM all'intero gruppo di processi (- $$). Nel caso improbabile che il bambino esca e il pid del bambino venga riutilizzato prima che si verifichi il kill (), questo NON ucciderà il processo sbagliato perché il nuovo processo con il pid del bambino vecchio non sarà nello stesso gruppo di processo del processo perl genitore .

Come ulteriore vantaggio, lo script esce anche con quello che è probabilmente lo stato di uscita corretto.

Prova qualcosa del tipo:

# This function is called with a timeout (in seconds) and a pid.
# After the timeout expires, if the process still exists, it attempts
# to kill it.
function timeout() {
    sleep $1
    # kill -0 tests whether the process exists
    if kill -0 $2 > /dev/null 2>&1 ; then
        echo "killing process $2"
        kill $2 > /dev/null 2>&1
    else
        echo "process $2 already completed"
    fi
}

<your command> &
cpid=$!
timeout 3 $cpid
wait $cpid > /dev/null 2>&
exit $?

Ha il rovescio della medaglia che se il pid del tuo processo viene riutilizzato entro il timeout, potrebbe uccidere il processo sbagliato. Questo è altamente improbabile, ma potresti iniziare oltre 20000 processi al secondo. Questo potrebbe essere risolto.

Uso " timelimit " ;, che è un pacchetto disponibile nel repository debian.

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

#!/bin/sh
( some_slow_task ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

L'osservatore uccide l'attività lenta dopo un determinato timeout; lo script attende l'attività lenta e termina l'osservatore.

Esempi:

  • L'attività lenta è stata eseguita per più di 2 secondi ed è stata terminata
  

Attività lenta interrotta

( 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
  • Questa attività lenta è terminata prima del timeout specificato
  

Attività lenta terminata

( 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

Una leggera modifica del perl one-liner renderà lo stato di uscita corretto.

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

Fondamentalmente, exit ($? > > 8) inoltrerà lo stato di uscita del sottoprocesso. Ho appena scelto 77 nello stato di uscita per il timeout.

Che ne dici di usare lo strumento aspetta?

## run a command, aborting if timeout exceeded, e.g. timed-run 20 CMD ARGS ...
timed-run() {
  # timeout in seconds
  local tmout="$1"
  shift
  env CMD_TIMEOUT="$tmout" expect -f - "$@" <<"EOF"
# expect script follows
eval spawn -noecho $argv
set timeout $env(CMD_TIMEOUT)
expect {
   timeout {
      send_error "error: operation timed out\n"
      exit 1
   }
   eof
}
EOF
}

pure bash:


#!/bin/bash

if [[ $# < 2 ]]; then
  echo "Usage: <*> timeout cmd [options]"
  exit 1
fi

TIMEOUT="$1"
shift

BOSSPID=$

(
  sleep $TIMEOUT
  kill -9 -$BOSSPID
)&
TIMERPID=$!

trap "kill -9 $TIMERPID" EXIT

eval "$@"

Non c'è un modo per impostare un orario specifico con " a " per fare questo?

$ at 05:00 PM kill -9 $pid

Sembra molto più semplice.

Se non sai quale sarà il numero pid, suppongo che ci sia un modo per copiarlo leggendolo con ps aux e grep, ma non sono sicuro di come implementarlo.

$   | grep someprogram
tony     11585  0.0  0.0   3116   720 pts/1    S+   11:39   0:00 grep someprogram
tony     22532  0.0  0.9  27344 14136 ?        S    Aug25   1:23 someprogram

Il tuo script dovrebbe leggere il pid e assegnargli una variabile. Non sono troppo esperto, ma suppongo che sia fattibile.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top