2012-01-06 13 views
6

Nella mia applicazione ho un wrapper su un codice nativo, che viene chiamato tramite il bridge JNI. Questo codice nativo deve essere eseguito in thread separati (elaborazione parallela). Tuttavia il problema è che il codice a volte "si blocca", quindi il thread deve essere terminato "con la forza". Sfortunatamente non ho trovato alcun metodo "delicato" per farlo: il consiglio generale è di dire al codice in una discussione di uscire con garbo, ma non posso farlo con questo codice nativo (che è il codice di terze parti sopra).Termina una discussione che esegue un codice nativo

Io uso concomitante Java API per la presentazione compito:

Future<Integer> processFuture = taskExecutor.submit(callable); 

try { 
    result = processFuture.get(this.executionTimeout, TimeUnit.SECONDS).intValue(); 
} 
catch (TimeoutException e) { 
    // How to kill the thread here? 
    throw new ExecutionTimeoutException("Execution timed out (max " + this.executionTimeout/60 + "min)"); 
} 
catch (...) { 
    ... exception handling for other cases 
} 

Future#cancel() interromperà solo il filo, ma non terminerà esso. Così ho usato il seguente trucco:

class DestroyableCallable implements Callable<Integer> { 

    private Thread workerThread; 

    @Override 
    public Integer call() { 
     workerThread = Thread.currentThread(); 

     return Integer.valueOf(JniBridge.process(...)); 
    } 

    public void stopWorkerThread() { 
     if (workerThread != null) { 
      workerThread.stop(); 
     } 
    } 
} 

DestroyableCallable callable = new DestroyableCallable(); 

Future<Integer> processFuture = taskExecutor.submit(callable); 

try { 
    result = processFuture.get(this.executionTimeout, TimeUnit.SECONDS).intValue(); 
} 
catch (TimeoutException e) { 
    processFuture.cancel(true); 
    // Dirty: 
    callable.stopWorkerThread(); 

    ThreadPoolTaskExecutor threadPoolTaskExecutor = (ThreadPoolTaskExecutor) taskExecutor; 

    logger.debug("poolSize: " + threadPoolTaskExecutor.getPoolSize() + ", maxPoolSize:" 
        + threadPoolTaskExecutor.getMaxPoolSize() + ", activeCount:" 
        + threadPoolTaskExecutor.getActiveCount()); 
    } 

    throw new ...; 
} 
catch (...) { 
    ... exception handling for other cases 
} 

Le domande/problemi con questo codice:

  • E 'in generale, il modo giusto per farlo? Qualche altra alternativa più elegante?
  • activeCount sull'operazione esecutore non viene ridotta, così compito esecutore ancora "pensa" quel filo esegue
  • ho dovuto aggiungere workerThread != null assegno stopWorkerThread() metodo, come questa variabile rivelato essere null su alcuni casi. Non riesco a capire che cosa sono questi casi ...

Note:

  • codice nativo non consuma descrittori di file (prese). Tutto è passato ad esso come blocco di dati e restituito allo stesso modo.
  • Il codice nativo richiede un uso intensivo della CPU. Anche se garantisce di terminare, potrebbe volerci molto tempo.

Bounty modificare: L'approccio/suggerimento per rivisitare il codice nativo è chiaro, si prega di non offrire nella risposta. Ho bisogno di soluzioni/soluzioni in puro Java.

risposta

9

Java ha pure opzioni per la terminazione del thread di forza. È obsoleto e deprecato solo Thread.stop() (AFAIK). E no opzione per safe terminazione del thread (motivo per cui .stop() è stato dichiarato obsoleto e non può nemmeno essere implementato dagli implementatori JVM).

Il motivo è quello che tutte le discussioni all'interno app azioni memoria e risorse - quindi, se si forza la cessazione di filo in qualche punto arbitrario, non è possibile dimostrare con certezza che cosa terminati filo non ha lasciato un po 'di memoria condivisa/risorse in stato incoerente. E non puoi nemmeno (in generale) supporre che quali risorse siano (possibilmente) sporche (perché non sai esattamente a che punto il thread è stato interrotto).

Quindi, se vuoi che alcuni thread della tua app siano in grado di interrompere, l'unica soluzione è fornire - in fase di progettazione - alcune notazioni di "punti di salvataggio" - posizioni nel codice del thread di destinazione, che sono garantite per non mutare lo stato condiviso, quindi è sicuro che il thread esca qui. Ed è esattamente ciò che i javadoc di Thread.stop() ti dicono: l'unico modo per interrompere il thread in modo sicuro è progettare il codice del thread in modo che possa da solo la risposta a qualche tipo di richiesta di interrupt. Qualche tipo di flag, che viene controllato di volta in volta da thread.

Ho provato a dirti: non puoi fare la cosa che ti viene richiesta sull'uso del threading/concorrenza Java. Il modo in cui potrei suggerirti (è stato dato presto qui) è fare il tuo lavoro in un processo separato. Il processo di uccisione forzata è molto più sicuro del thread poiché 1) i processi sono molto più separati l'uno dall'altro e 2) il sistema operativo si occupa di molte operazioni di pulizia dopo la chiusura del processo. Il processo di uccisione non è completamente sicuro, poiché esiste un qualche tipo di risorse (file, ad esempio) che non vengono cancellate dal sistema operativo per impostazione predefinita, ma nel tuo caso sembra essere sicuro.

Così si progetta una piccola applicazione separata (potrebbe essere anche in java - se la libreria di terze parti non fornisce altri collegamenti, o anche in script di shell) che solo il lavoro è quello di fare il calcolo. Avvia tale processo dall'app principale, dagli il compito e avvia il watchdog. Il cane da guardia rileva il timeout: uccide il processo con la forza.

Questa è l'unica bozza di soluzione. È possibile implementare un pool di processi di tipo, se si desidera migliorare le prestazioni (il processo di avvio potrebbe richiedere del tempo), e così via ...

+0

Grazie a un buon preavviso. Una piccola osservazione per il tuo post: poiché il thread avviato sta eseguendo il codice nativo, non ci sono blocchi (a meno che non lo si codi esplicitamente tramite JNI) che sono condivisi con Java-part. Quindi l'unica risorsa rischiosa sono i descrittori di file aperti (che non è il mio caso, poiché tutti i dati necessari vengono passati come blob e ripresi come stringa). L'unico problema è come dire a 'ThreadPoolTaskExecutor 'che il thread è veramente morto? Ho esaminato il codice: dovrebbe davvero catturare questo caso. –

+1

Nessuna serratura - ok, potrebbe essere. Ma per quanto riguarda le perdite di memoria nel codice nativo? Puoi essere sicuro che la lib nativa sia stata progettata e codificata sufficientemente sicura per elaborare con garbo la terminazione anomala? Rilascia la memoria allocata su tutti i percorsi di uscita, incluso quello anormale? Cosa farai con la memoria, trapelata nel codice nativo?Stessa domanda su alcune risorse a livello di sistema operativo: cosa succede se la lib nativa alloca il blocco a livello di kernel? Inizia diversi thread aggiuntivi? – BegemoT

+0

Sono d'accordo riguardo alle perdite di memoria sull'heap (come [i thread condividono lo stesso heap] (http://stackoverflow.com/questions/1665419)). Non sono un esperto, ma non penso che il thread utente possa essere interrotto in modalità kernel, altrimenti uccidere il thread danneggerebbe ad es. filesystem nel caso in cui il thread sia stato ucciso durante la scrittura di qualcosa sul disco. Quindi i blocchi a livello di kernel non rilasciati sarebbero molto tristi. Per il resto sostengo pienamente la tua posizione: il processo è più sicuro. Più sul ponte JNI è stato fatto da me da processo autonomo, quindi non è uno sforzo da ripristinare. –

1

Poiché si tratta di codice di terze parti, suggerirei di creare un'applicazione shell nativa che gestisca il richiamo, il tracciamento e la chiusura di questi thread. Meglio ancora che questa terza parte lo faccia per te se il tuo accordo di licenza offre qualsiasi tipo di supporto.

http://java.sun.com/docs/books/jni/html/other.html

+0

Se ho capito correttamente, si intende che Java Threading si arrende per gestire un caso del genere? –

1

Sicuramente un brutto hack che avete qui ...

Prima di tutto, le discussioni pool di thread non sono destinate ad essere temperato con individualmente e deve essere generalmente lasciata a correre fino al completamento, soprattutto non fermato con Thread.stop() che non è raccomandato anche per i thread normali.

L'uso di Thread.stop(), come ho detto, non viene mai incoraggiato e in genere lascia il thread in uno stato incoerente, che è probabilmente la causa per il pool di thread che non vede il thread come "morto". Potrebbe anche non ucciderlo affatto.

Qualche idea sul perché il codice nativo si blocca? Penso che la radice del tuo problema sia qui, non il thread che ferma la parte. I thread dovrebbero normalmente essere eseguiti fino al completamento, quando possibile.Forse puoi trovare un'implementazione migliore che funzioni correttamente (o implementare qualcosa di diverso se lo hai scritto).

Edit: Per quanto riguarda il punto 3, è probabile che è necessario dichiarare il riferimento al thread corrente come volatile dal momento che si sta assegnando in un thread e la lettura in un altro:

private volatile Thread workerThread; 

Modifica 2: Sto iniziando a capire che il tuo codice JNI esegue solo calcoli numerici e non apre alcun handle che può rimanere in uno stato incoerente se il thread viene interrotto bruscamente. Puoi confermare questo?

In tal caso, consentitemi di andare contro il mio stesso consiglio e dirvi che in questo caso si può tranquillamente uccidere il thread con Thread.stop(). Tuttavia, ti consiglio di utilizzare un thread separato anziché un thread pool di thread per evitare di lasciare il pool di thread in uno stato incoerente (come hai detto, non vede il thread come morto). È anche più pratico, perché non devi fare tutti questi trucchi per far sì che il thread si fermi, perché puoi semplicemente chiamare stop() direttamente dal thread principale, diversamente dai thread del thread thread.

+0

** Tudor **, grazie per aver indicato il terzo punto, +1 per quello. Sì, ho l'idea del perché il codice nativo "si blocca": l'elaborazione dei dati richiede troppo tempo e in questo caso vorrei che i dati venissero saltati (nel mio caso è giusto perdere alcuni dati per motivi di velocità di elaborazione). Quello che suggerisci è di rivedere il codice nativo. Qualcos'altro * oltre a * quello? –

+0

@dma_k: hai qualche garanzia che il codice nativo finirà per terminare? – Tudor

+0

Sì, ho delle garanzie. Ma potrebbero volerci giorni :) –

0

Non ripeterò tutti i preziosi consigli forniti da Tudor .... Aggiungerò solo un punto di architettura alternativo a te mentre usi un meccanismo di tipo queing per gestire la comunicazione tra l'applicazione Java principale e il thread nativo lanciato .... Questo thread può essere client di un broker e essere avvisato se si verificano alcuni eventi speciali (terminazione) e agisce di conseguenza (interrompendo un lavoro di lunga durata) Naturalmente questo aggiunge un po 'di complessità ma è una soluzione piuttosto elegante .. Ovviamente se il thread nativo non è robusto non cambierà nulla a tutta la robustezza .. Un modo per gestire la comunicazione tra thread nativo e un broker sarebbe utilizzare un'interfaccia simile a STOMP (molti broker Apache activemq, MQ da Oracle e XPose tale interfaccia) ...

HTH Jerome

+0

Questo broker dovrebbe essere scritto in Java? Se sì, potresti fornire una meta-logica per questo? Da quello che hai detto, il broker dovrebbe essere eseguito in un altro thread separato per gestire gli eventi ... –

2

Si potrebbe avvolgere questa singola chiamata a metodo JNI in un'applicazione Java separato e poi sborsare un altro processo Java con java.lang.Process. Quindi è possibile chiamare Process.destroy() per distruggere tale processo a livello di sistema operativo.

A seconda del proprio ambiente e di altre considerazioni, potrebbe essere necessario fare alcuni trucchi per scoprire come trovare un eseguibile java, soprattutto se si sta costruendo un software ridistribuibile che potrebbe essere eseguito su piattaforme diverse. Un altro problema potrebbe essere l'IPC, ma ciò potrebbe essere fatto utilizzando i flussi di input/output di Process.

+0

L'uccisione forzata di un processo è ancora meno elegante dell'arresto di un thread. – Tudor

+3

L'uccisione forzata di un processo è sicura (il sistema operativo si occupa di esso), il che non è vero per il thread. Quindi è una soluzione più elegante, ed è, suppongo, l'unica soluzione, se è noto che la lib di JNI di terze parti non produca alcune risorse esterne, come i file, che non verranno cancellate dal sistema operativo al termine del processo. – BegemoT

Problemi correlati