CodeGym /Corsi /JAVA 25 SELF /Gestione del ciclo di vita di un processo

Gestione del ciclo di vita di un processo

JAVA 25 SELF
Livello 61 , Lezione 2
Disponibile

1. Attendere la terminazione del processo: waitFor()

Quando avviate un processo esterno da Java, vive di vita propria: fa dei calcoli, scrive in console, a volte si blocca (ciao, ping con un milione di pacchetti). Ma a volte abbiamo bisogno di sapere quando quel processo è terminato, come è terminato (successo o fallimento) e — se qualcosa va storto — saperlo «uccidere». Insomma, come nella vita reale: se affidate a qualcuno un compito, è bene sapere se l’ha portato a termine e avere la possibilità di fermarlo se all’improvviso inizia a fare caffè all’infinito.

Quando avviate un processo tramite ProcessBuilder, ottenete un oggetto di tipo Process. Questo oggetto è come un telecomando: tramite esso potete attendere la terminazione del processo, ottenere il codice di uscita, terminare il processo e gestire i flussi di input/output.

Come attendere la terminazione del processo?

Per questo c’è il metodo waitFor():

ProcessBuilder builder = new ProcessBuilder("ping", "google.com", "-c", "3");
Process process = builder.start();

System.out.println("Attendiamo la terminazione del processo...");
int exitCode = process.waitFor(); // Blocca il thread corrente fino al termine del processo
System.out.println("Il processo è terminato con codice: " + exitCode);

Cosa succede?

  • Il programma avvia un processo esterno (ping).
  • Dopo di ciò, il thread in cui è stato chiamato waitFor() «si addormenta» finché il processo esterno non termina.
  • Non appena il processo esterno ha finito il lavoro, waitFor() restituisce il codice di uscita (di solito 0 = successo, diverso da 0 = errore).

Come ottenere il codice di uscita?

Il metodo waitFor() restituisce questo codice. È anche possibile ottenerlo separatamente tramite exitValue():

int code = process.exitValue();

Ma fate attenzione: se il processo non è ancora terminato, la chiamata a exitValue() lancerà l’eccezione IllegalThreadStateException. Per questo, in genere si attende prima la terminazione tramite waitFor(), e poi si guarda il codice.

2. Interrompere un processo: destroy() e destroyForcibly()

A volte un processo esterno inizia a durare troppo o si blocca. Per esempio, qualcuno ha avviato ping con 1000 pacchetti e avete una deadline tra 5 secondi. Serve il «pulsante rosso».

Arresto morbido: destroy()

Il metodo destroy() invia al processo un segnale di terminazione (SIGTERM su Unix, CTRL-BREAK su Windows), dandogli la possibilità di chiudersi correttamente.

Process process = new ProcessBuilder("ping", "google.com").start();

Thread.sleep(2000); // Lasciamolo lavorare per 2 secondi
process.destroy(); // Chiediamo di terminare

Arresto forzato: destroyForcibly()

Se il processo non reagisce alle richieste gentili, si può usare il metodo destroyForcibly(). È come staccare la spina dalla presa.

process.destroyForcibly();

Importante: Dopo aver chiamato destroy() o destroyForcibly() bisogna attendere la terminazione del processo tramite waitFor(). A volte al processo serve tempo per «accorgersi» della propria fine.

3. Attesa del processo con timeout

In Java 8+ è apparso un metodo con timeout:

boolean finished = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS);
if (finished) {
    System.out.println("Il processo è terminato in tempo!");
} else {
    System.out.println("Il processo non è terminato, lo interrompiamo...");
    process.destroy();
}
  • Se il processo termina entro 5 secondi, il metodo restituisce true.
  • Se no — restituisce false, e potete prendere provvedimenti (per esempio, terminare il processo).

Esempio: Avviamo un comando lungo e proviamo a «ucciderlo»:

ProcessBuilder builder = new ProcessBuilder("ping", "google.com", "-c", "10");
Process process = builder.start();

boolean finished = process.waitFor(2, java.util.concurrent.TimeUnit.SECONDS);
if (!finished) {
    System.out.println("Il processo sta impiegando troppo tempo, lo interrompiamo...");
    process.destroy();
    // Si può attendere la terminazione completa
    process.waitFor();
}
System.out.println("Codice di uscita: " + process.exitValue());

4. Pratica: mini-utility per avviare un comando con timeout

Scriviamo un semplice programma che avvia un comando esterno con timeout, ne attende la terminazione e, allo scadere del tempo, termina il processo.

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

public class ProcessTimeoutDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        Scanner scanner = new Scanner(System.in);

        System.out.println("Inserisci il comando da eseguire (ad esempio, ping google.com):");
        String commandLine = scanner.nextLine();
        String[] command = commandLine.split(" ");

        ProcessBuilder builder = new ProcessBuilder(command);
        Process process = builder.start();

        System.out.println("Quanti secondi attendere la terminazione?");
        int timeout = scanner.nextInt();

        boolean finished = process.waitFor(timeout, TimeUnit.SECONDS);
        if (finished) {
            System.out.println("Il processo è terminato da solo. Codice di uscita: " + process.exitValue());
        } else {
            System.out.println("Tempo scaduto! Terminiamo il processo...");
            process.destroy();
            process.waitFor();
            System.out.println("Il processo è stato terminato forzatamente. Codice di uscita: " + process.exitValue());
        }
    }
}

Spiegazione:

  • L’utente inserisce il comando da avviare.
  • Il programma attende per il tempo indicato.
  • Se il processo non termina — lo interrompe.

Suggerimento: In Windows ping per impostazione predefinita invia 4 richieste, mentre in Linux — all’infinito. Per Linux/Mac aggiungete il parametro -c 5 (ad esempio, ping google.com -c 5).

5. Ottenere il codice di uscita: che cos’è e a cosa serve?

Nel mondo delle righe di comando vige la regola: se il comando è terminato con successo, restituisce il codice 0. Se qualcosa è andato storto — qualsiasi altro codice.

  • Nei sistemi Unix-like: 0 = successo, 1 e superiori = errore.
  • In Windows — più o meno allo stesso modo.

Potete usare questo codice per prendere decisioni nel programma Java:

int exitCode = process.waitFor();
if (exitCode == 0) {
    System.out.println("Successo!");
} else {
    System.out.println("Errore! Codice: " + exitCode);
}

6. Terminare i processi bloccati

A volte un processo può semplicemente bloccarsi — per esempio, attende un vostro input o non riesce a connettersi alla rete. Se lo lasciate senza attenzione, rimarrà in esecuzione nel sistema consumando risorse.

Per evitarlo, conviene dargli un limite di pazienza. Il metodo waitFor(timeout, unit) consente di attendere la sua terminazione solo per un tempo determinato. Se il processo non fa in tempo — interrompetelo senza esitazioni.

Dopo la chiamata a destroy() è importante non voltare semplicemente pagina, ma assicurarsi che il processo sia davvero terminato. Per questo si invoca di nuovo waitFor(). E se non basta — c’è la misura estrema, destroyForcibly(). Interrompe tutto senza troppi complimenti.

Esempio con doppia interruzione

boolean finished = process.waitFor(5, TimeUnit.SECONDS);
if (!finished) {
    process.destroy();
    if (!process.waitFor(2, TimeUnit.SECONDS)) {
        process.destroyForcibly();
    }
}

Particolarità di Process: cosa è importante ricordare

Quando il processo è terminato, non abbiate fretta di dimenticarvene. Date un’occhiata al suo output — sia quello standard sia quello degli errori. Potrebbe esserci qualcosa di importante: un messaggio di guasto, un avvertimento o semplicemente la conferma che tutto è andato a buon fine.

Dopodiché chiudete obbligatoriamente tutti i flussi associati — InputStream, OutputStream e ErrorStream. È come spegnere la luce quando uscite: risparmia risorse e mantiene l’ordine nel sistema.

E ricordate: se avete interrotto un processo forzatamente, potrebbe non riuscire a scrivere correttamente il proprio output. Nulla di grave — è un comportamento naturale in questi casi.

7. Esempio: avvio di un comando lungo e sua terminazione

Supponiamo di voler avviare un processo lungo (per esempio, ping con un gran numero di pacchetti) e terminarlo dopo 3 secondi.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;

public class KillLongProcessDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        String[] command = {"ping", "google.com"};
        // Per Linux/Mac: {"ping", "google.com", "-c", "100"}
        ProcessBuilder builder = new ProcessBuilder(command);
        Process process = builder.start();

        // Leggiamo l'output del processo in un thread separato
        Thread readerThread = new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                // Ignoriamo
            }
        });
        readerThread.start();

        // Attendiamo 3 secondi
        if (!process.waitFor(3, TimeUnit.SECONDS)) {
            System.out.println("Il processo sta impiegando troppo tempo, lo interrompiamo...");
            process.destroy();
            process.waitFor();
        }
        readerThread.join(); // Attendere la lettura dell'output
        System.out.println("Processo terminato. Codice di uscita: " + process.exitValue());
    }
}

8. Errori tipici nella gestione dei processi

Errore n. 1: Non attendere la terminazione del processo. Se avviate un processo e poi ve ne dimenticate (non chiamate waitFor()), il processo può rimanere «appeso» nel sistema anche dopo la fine del vostro programma Java. I flussi di input/output potrebbero non essere chiusi, con conseguenti perdite di memoria e di descrittori di file.

Errore n. 2: Terminare il processo in modo scorretto. Avete chiamato destroy(), ma non avete atteso la terminazione del processo tramite waitFor(). Di conseguenza, il processo può rimanere «zombie» (soprattutto nei sistemi Unix).

Errore n. 3: Attendere il codice di uscita prima della terminazione del processo. Chiamare exitValue() prima della fine del processo porta all’eccezione IllegalThreadStateException.

Errore n. 4: Non gestire le eccezioni. I metodi di lavoro con i processi possono lanciare IOException e InterruptedException. Non dimenticate di catturarle o dichiararle nella firma del metodo.

Errore n. 5: Non leggere l’output del processo. Se non leggete stdout e stderr, il processo può bloccarsi a causa del riempimento del buffer di output.

1
Compito
JAVA 25 SELF, livello 61, lezione 2
Bloccato
Arresto di emergenza del sistema 🚨
Arresto di emergenza del sistema 🚨
1
Compito
JAVA 25 SELF, livello 61, lezione 2
Bloccato
Controllo del Lancio del Razzo 🛰️
Controllo del Lancio del Razzo 🛰️
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION