6
votes

Peut-il être acceptable dans Java d'utiliser le thread # STOP () pour tuer un fil qui fonctionne à l'état sauvage?

Malheureusement, il n'est pas moyen de spécifier un délai d'attente lors de l'utilisation d'une expression régulière sur une chaîne en Java. Donc, si vous n'avez aucun contrôle strict sur quels modèles sont appliqués à quelle entrée, vous risquez de vous retrouver sur des threads qui consomment beaucoup de CPU tout en essayant sans cesse de faire correspondre (pas si bien conçu) des motifs (malveillants?).

Je suis au courant des raisons pour lesquelles le thread # STOP () est obsolète (voir http://download.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitedepeprecation.html ). Ils sont centrés autour d'objets qui pourraient être endommagés en cas de filetage, et qui polluent ensuite votre environnement JVM en cours d'exécution et peuvent conduire à des erreurs subtiles. P>

Ma question à quiconque a une perspicacité plus profonde que moi dans le fonctionnement de la JVM est la suivante: Si le fil à arrêter de ne pas être arrêté ne contient aucun surveille (évident) sur ou références à des objets utilisés par le reste du programme, peut-il être acceptable d'utiliser le thread # STOP # () Néanmoins? strong> p>

J'ai créé une solution plutôt défensive pour pouvoir traiter la correspondance d'expression régulière avec un délai d'attente. Je serais heureux que tout commentaire ou remarque, surtout sur les problèmes que cette approche peut causer malgré mes efforts pour les éviter. P>

Merci! P>

import java.util.concurrent.Callable;

public class SafeRegularExpressionMatcher {

    // demonstrates behavior for regular expression running into catastrophic backtracking for given input
    public static void main(String[] args) {
        SafeRegularExpressionMatcher matcher = new SafeRegularExpressionMatcher(
                "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "(x+x+)+y", 2000);
        System.out.println(matcher.matches());
    }

    final String stringToMatch;

    final String regularExpression;

    final int timeoutMillis;

    public SafeRegularExpressionMatcher(String stringToMatch, String regularExpression, int timeoutMillis) {
        this.stringToMatch = stringToMatch;
        this.regularExpression = regularExpression;
        this.timeoutMillis = timeoutMillis;
    }

    public Boolean matches() {
        CallableThread<Boolean> thread = createSafeRegularExpressionMatchingThread();
        Boolean result = tryToGetResultFromThreadWithTimeout(thread);
        return result;
    }

    private CallableThread<Boolean> createSafeRegularExpressionMatchingThread() {
        final String stringToMatchForUseInThread = new String(stringToMatch);
        final String regularExpressionForUseInThread = new String(regularExpression);
        Callable<Boolean> callable = createRegularExpressionMatchingCallable(stringToMatchForUseInThread,
                regularExpressionForUseInThread);
        CallableThread<Boolean> thread = new CallableThread<Boolean>(callable);
        return thread;
    }

    private Callable<Boolean> createRegularExpressionMatchingCallable(final String stringToMatchForUseInThread,
            final String regularExpressionForUseInThread) {
        Callable<Boolean> callable = new Callable<Boolean>() {
            public Boolean call() throws Exception {
                return Boolean.valueOf(stringToMatchForUseInThread.matches(regularExpressionForUseInThread));
            }
        };
        return callable;
    }

    private Boolean tryToGetResultFromThreadWithTimeout(CallableThread<Boolean> thread) {
        startThreadAndApplyTimeout(thread);
        Boolean result = processThreadResult(thread);
        return result;
    }

    private void startThreadAndApplyTimeout(CallableThread<Boolean> thread) {
        thread.start();
        try {
            thread.join(timeoutMillis);
        } catch (InterruptedException e) {
            throwRuntimeException("Interrupt", e);
        }
    }

    private Boolean processThreadResult(CallableThread<Boolean> thread) {
        Boolean result = null;
        if (thread.isAlive()) {
            killThread(thread); // do not use anything from the thread anymore, objects may be damaged!
            throwRuntimeException("Timeout", null);
        } else {
            Exception exceptionOccurredInThread = thread.getException();
            if (exceptionOccurredInThread != null) {
                throwRuntimeException("Exception", exceptionOccurredInThread);
            } else {
                result = thread.getResult();
            }
        }
        return result;
    }

    private void throwRuntimeException(String situation, Exception e) {
        throw new RuntimeException(situation + " occured while applying pattern /" + regularExpression + "/ to input '"
                + stringToMatch + " after " + timeoutMillis + "ms!", e);
    }

    /**
     * This method uses {@link Thread#stop()} to kill a thread that is running wild. Although it is acknowledged that
     * {@link Thread#stop()} is inherently unsafe, the assumption is that the thread to kill does not hold any monitors on or
     * even references to objects referenced by the rest of the JVM, so it is acceptable to do this.
     * 
     * After calling this method nothing from the thread should be used anymore!
     * 
     * @param thread Thread to stop
     */
    @SuppressWarnings("deprecation")
    private static void killThread(CallableThread<Boolean> thread) {
        thread.stop();
    }

    private static class CallableThread<V> extends Thread {

        private final Callable<V> callable;

        private V result = null;

        private Exception exception = null;

        public CallableThread(Callable<V> callable) {
            this.callable = callable;
        }

        @Override
        public void run() {
            try {
                V result = compute();
                setResult(result);
            } catch (Exception e) {
                exception = e;
            } catch (ThreadDeath e) {
                cleanup();
            }
        }

        private V compute() throws Exception {
            return callable.call();
        }

        private synchronized void cleanup() {
            result = null;
        }

        private synchronized void setResult(V result) {
            this.result = result;
        }

        public synchronized V getResult() {
            return result;
        }

        public synchronized Exception getException() {
            return exception;
        }

    }

}


4 commentaires

Découvrez Cette solution pour faire thread.Interrupt travail...


@dacwe ressemble à un duplicate pour moi.


@assylias, correct, ferme (aurait été agréable de fusionner les deux ...).


@dawce: Merci pour la réponse, l'approche de la solution que vous référence est essentiellement la même que la mienne. Je sais déjà que cela fonctionne, ma question est de savoir s'il y aura des conséquences sous forme de bogues subtils que je ne peux pas prévoir ou si mes contre-mesures semblent prometteuses (ne transmettent que des copies d'objets dans le fil, évitez de l'y accéder au tout après l'arrêt ( )))).


4 Réponses :


2
votes

au lieu d'utiliser thread.stop () qui est obsolète, utilisez thread.Interrupt () qui va Soulevez le drapeau d'interruption qui Peut être vérifié via istinterrupted () ou interrompu () ou jette un interrompteException .

Mon modèle pour la construction de la classe de thread est comme celui-ci xxx

Je ne dis pas ma façon de manipuler c'est parfait, il peut y parier mieux, mais cela fonctionne pour moi.


2 commentaires

thread.Interrupt () n'arrête pas le fil, mais il augmente le drapeau qui peut être vérifié via isinterrupted () ou interrompu () , ou jette un interrompteException si le thread est en état bloqué.


Merci de votre réponse et, bien sûr, c'est l'approche que nous prenons pour notre propre code. Faire cela pour un modèle d'expression régulier correspondant à Core Java serait ma demande de fonctionnalité à Oracle.



9
votes

Vous pouvez utiliser thread.stop () si vous déterminez sa seule solution à votre disposition. Vous devrez peut-être arrêter et redémarrer votre application pour vous assurer que c'est dans un bon état.

Remarque: un thread peut capturer et ignorer threaddeath alors arrêtez-vous ne garantit pas tous les filets.

Un moyen alternatif d'arrêter un thread est de l'exécuter dans un processus différent. Cela peut être tué au besoin. Cela peut toujours laisser des ressources dans un état incosé (comme des fichiers de verrouillage), mais il est moins probable et plus facile à contrôler.

La meilleure solution bien sûr est de corriger le code de sorte qu'il ne le fait pas en premier lieu et respecte le thread.Interrupt () à la place.


6 commentaires

Le dernier bit de votre réponse est adressé joliment dans cette réponse .


Merci d'avoir répondu. Le redémarrage de l'application est exactement ce que j'essaie d'éviter, et j'espère que c'est dans un meilleur état après arrêt () d'un fil que lorsque le fil consomme 100% de la CPU.


Si sa CPU consommant 100%, vous pouvez prendre un dépotoir de pile avec Jstack ou Jvisalvm ou par programme si vous le souhaitez avec Thread.getstackTrace () et utilisez ces informations pour réparer la boucle qui est cassée.


J'ai déjà joint à la StackTrace et sais ce qui est cassé à ce sujet, il est suspendu dans un match d'expression régulier (qui souffre probablement d'une arrière-marche catastrophique). Étant donné que l'entrée de l'expression régulière est fournie, je ne peux pas garantir cela.


Pouvez-vous modifier l'expression régulière ou écrire le code sans une expression régulière?


Merci d'avoir séjourné avec moi et j'ai déjà considéré cela. Une discussion plus profonde nous éloignerait cependant du sujet de la question initiale.



2
votes

Si le fil qui doit être arrêté ne contient aucun surveille (évident) sur ou références aux objets utilisés par le reste du programme, peut-il alors être acceptable d'utiliser le thread # STOP () Néanmoins?

C'est à vous pour décider s'il est "acceptable". Tout ce que nous pouvons faire est de conseiller s'il est sûr. Et la réponse est que ce n'est pas.

  • Qu'en est-il des moniteurs et des références non évidents qu'il contient?

  • Qu'en est-il des notifications, etc. que cela ferait autrement?

  • Qu'en est-il des actions qu'il pourrait autrement rendre affectant la statique?

    Le problème est qu'il est difficile (dans la plupart des cas) de savoir avec certitude que vous avez considéré toutes les interactions possibles que le thread pourrait avoir avec le reste de l'application.


    Redémarrer l'application est exactement ce que j'essaie d'éviter ...

    Cela me frappe que que est la racine réelle de votre problème; C'est-à-dire que vous avez conçu un programme sans tenir compte du fait que les programmes de longue date doivent être redémarrés pour des raisons pragmatiques. Des plus compliqués qui ont des bugs potentiels.


4 commentaires

Merci pour votre réponse! Pouvez-vous perdre une lumière sur des moniteurs et des références «non évidents» potentiels? Avez-vous examiné l'exemple de mon code? Il a essayé d'éviter toute références et de faire attention à ne rien utiliser après l'arrêt (). L'application est une application Web conçue pour fonctionner pendant des semaines ou même des mois sans interruption. Et je n'ai aucune influence sur le code que j'ai besoin d'utiliser là-bas. Une autre option pourrait être de changer la bibliothèque d'expression régulière, mais ce serait une étape assez importante.


J'ai essayé de lire le code, mais ces noms et noms variables ridiculement longs font mal à la tête.


Merci pour l'effort et désolé pour la douleur. Les noms de méthodes longs sont destinés à exprimer quelles sont les méthodes et les variables contiennent (mon interprétation des enseignements de Robert C. "oncle Bob" Martin).


Eh bien, je ne pense pas que plus de 20 identifiants de caractère améliorent la lisibilité. Plutôt l'inverse, en fait. Et je pense que la plupart des professionnels Java auraient tendance à être d'accord avec moi à ce sujet.



1
votes

Si vous concevez spécifiquement votre code de thread pour ne pas maintenir les serrures, etc. (Oui, ce qui inclut les verrous non explicites. Par exemple, un verrou Malloc qui peut être utilisé lors de la modification de la taille de la chaîne), puis arrêtez le fil, oui . Sondage Un drapeau "interrompu" va bien, sauf que cela signifie interroger un drapeau "interrompu", c'est-à-dire. frais généraux au cours des 99,9999% du temps qu'il n'est pas réellement défini. Cela peut être un problème avec des boucles hautes performances et serrées.

Si le chèque peut être conservé hors d'une boucle la plus interne et toujours être vérifié raisonnablement fréquemment, c'est bien le meilleur moyen d'aller.

Si le drapeau ne peut pas être vérifié souvent (par exemple, en raison d'une boucle serrée dans un code de bibliothèque inaccessible), vous pouvez définir la priorité du fil au plus bas possible et l'oublier jusqu'à ce qu'il meure finalement.

Un autre corps à l'occasion possible est de détruire les données sur lesquelles le thread fonctionne de manière à ce que le code de la bibliothèque quitte normalement, provoque une exception à l'exception et de contrôler ainsi les bulles hors du code de la bibliothèque opaque ou des causes un gestionnaire d'onerror à appeler. Si la lib. fonctionne sur une chaîne, écaille la chaîne avec NULLS est sûr de faire quelque chose. Toute exception fera une exception - si vous pouvez organiser un AV / Segfault dans le fil, alors bien, tant que vous obtenez le contrôle.


0 commentaires