28 votes

Comment rendre les méthodes externes interruptibles?

Le Problème

Je suis à cours multiples appels de certains méthode externe via un ExecutorService. Je voudrais être capable d'interrompre ces méthodes, mais malheureusement, ils ne vérifient pas l'interruption indicateur par eux-mêmes. Est il possible que je puisse la force d'une exception soulevée par ces méthodes?

Je suis conscient que de jeter une exception à partir d'un emplacement arbitraire est potentiellement dangereux, dans mon cas précis, je suis prêt à prendre cette chance et prêt à faire face aux conséquences.

Détails

Par "méthode externe", je veux dire une méthode(s) qui viennent de l'extérieur de la bibliothèque, et je ne peux pas modifier son code (bien que je le peux, mais qui fera d'elle un entretien cauchemar à chaque fois qu'une nouvelle version est disponible).

Les externes sont les méthodes de calcul coûteux, pas IO-lié, de sorte qu'ils ne répondent pas aux interruptions standard et je ne peux pas avec force près d'un canal ou d'un socket ou quelque chose. Comme je l'ai mentionné avant, ils ont également ne pas vérifier l'interruption du pavillon.

Le code est conceptuellement quelque chose comme:

// my code
public void myMethod() {
    Object o = externalMethod(x);
}

// External code
public class ExternalLibrary {
    public Object externalMethod(Object) {
        innerMethod1();
        innerMethod1();
        innerMethod1();
    }

    private void innerMethod1() {
        innerMethod2();
        // computationally intensive operations
    }

    private void innerMethod2() {
        // computationally intensive operations
    }
}

Ce que j'ai Essayé

Thread.stop() sera théoriquement faire ce que je veux, mais elle n'est pas seulement obsolète, mais il est également disponible seulement pour les réels fils, alors que je travaille avec exécuteur de tâches (qui peuvent également partager des discussions avec les tâches futures, par exemple lorsque vous travaillez dans un pool de threads). Néanmoins, si pas de meilleure solution est trouvée, je vais convertir mon code à utiliser l'ancienne Threads au lieu et à l'utilisation de cette méthode.

Une autre option, que j'ai essayé c'est pour marquer myMethod() et des méthodes similaires avec un spécial "Interruptable" annotation et ensuite utiliser AspectJ (dont je suis certes un néophyte) pour la capture de tous les appels de méthode là - quelque chose comme:

@Before("call(* *.*(..)) && withincode(@Interruptable * *.*(..))")
public void checkInterrupt(JoinPoint thisJoinPoint) {
    if (Thread.interrupted()) throw new ForcefulInterruption();
}

Mais withincode n'est pas récursif pour les méthodes appelées par des méthodes d'appariement, si je dois modifier cette annotation dans le code externe.

Enfin, ce qui est similaire à la précédente question de la mienne - mais la différence notable est que maintenant, je fais face à une bibliothèque externe.

5voto

mhaller Points 10002

La suite des idées étranges viennent à mon esprit:

  • À l'aide d'un octet code de la modification de la bibliothèque, comme Javassist, d'introduire l'interruption des vérifications à différents points dans le bytecode. Juste au début de méthodes peuvent ne pas être suffisant, puisque vous mentionner que ces méthodes ne sont pas récursives, vous voudrez peut-être à force de les arrêter à tout moment. Le faire lors de l'octet au niveau du code, il serait aussi très sensible, par exemple, même si le code s'exécute dans une boucle ou quelque chose, il serait possible d'introduire de l'interruption des contrôles. Cependant, cela va ajouter des frais généraux, de sorte que la performance globale sera plus lent.
  • Le lancement de processus distincts (par exemple, les ordinateurs virtuels distincts) pour le code externe. L'abandon de processus peut être beaucoup plus facile de code que les autres solutions. L'inconvénient est que vous aurait besoin d'une sorte de canal de communication entre le code externe et de votre code, par exemple, de l'IPC, les prises etc. Le deuxième désavantage est que vous avez besoin de beaucoup plus de ressources (CPU, mémoire) pour le démarrage de nouvelles VMs et il peut être un environnement spécifique. Cela ne peut fonctionner que si vous commencez à un couple de tâches en utilisant le code externe, mais pas des centaines de tâches. Aussi, le rendement aurait à souffrir, mais le calcul lui-même serait aussi rapide que l'original. Processus peut être arrêté avec force à l'aide de java.lang.Processus.destroy()
  • En utilisant une mesure SecurityManager, qui effectue l'interruption vérifier sur chacun des checkXXX méthodes. Si le code externe en quelque sorte des appels privilégié méthodes, il peut être suffisant pour vous d'abandonner à ces endroits. Un exemple serait de java.lang.SecurityManager.checkPropertyAccess(String) si le code externe lit régulièrement un système de propriété.

4voto

Chochos Points 3364

Cette solution n'est pas facile, mais cela fonctionne: à l'Aide de Javassist ou CGLIB, vous pouvez insérer le code au début de chaque méthode interne (ceux probablement d'être appelé par la principale méthode run ()) pour vérifier si le thread est en vie, ou d'un autre indicateur (si c'est un autre drapeau, vous devez les ajouter ainsi, avec une méthode à définir).

Je propose Javassist/CGLIB au lieu de l'extension de la classe par le biais de code parce que vous le dites externes et vous ne voulez pas modifier le code source, et il peut changer dans l'avenir. Ainsi, l'ajout de l'interruption des contrôles lors de l'exécution de travail pour la version actuelle et également dans les versions futures, même si la méthode interne de changement des noms (ou leurs paramètres, les valeurs de retour, etc). Vous avez juste à prendre la classe et les ajouter interrompre les contrôles à effectuer au début de chaque méthode qui n'est pas la méthode run ().

2voto

kriegaex Points 6365

Vous avez écrit:

Une autre option, que j'ai essayé c'est pour marquer myMethod() et des méthodes similaires avec un spécial "Interruptable" annotation et ensuite utiliser AspectJ (qui Je suis certes un néophyte) pour la capture de tous les appels de méthode il n'y - quelque chose comme:

@Before("call(* *.*(..)) && withincode(@Interruptable * *.*(..))")
public void checkInterrupt(JoinPoint thisJoinPoint) {
    if (Thread.interrupted()) throw new ForcefulInterruption();
}

Mais withincode n'est pas récursif pour les méthodes appelées par le correspondant méthodes, si je dois modifier cette annotation dans l'externe code.

Le AspectJ idée est bonne, mais vous avez besoin de

  • utiliser cflow() ou cflowbelow() afin de récursivement correspondent à un certain contrôle de flux (par exemple, quelque chose comme @Before("cflow(execution(@Interruptable * *(..)))")).
  • assurez-vous également de tisser votre bibliothèque externe, et pas seulement vous-même le code. Cela peut être fait soit en utilisant des binaires de tissage, de instrumentant le fichier JAR de classes et de ré-emballage-le dans un nouveau fichier JAR, ou par l'application d'LTW (temps de charge de tissage) pendant l'application de démarrage (c'est à dire lors du chargement des classes).

Vous pourriez même pas besoin de votre marqueur d'annotation si votre bibliothèque externe a un nom de package, vous pouvez déterminer avec within(). AspectJ est vraiment puissant, et souvent il y a plus d'une façon de résoudre un problème. Je vous recommande de l'utiliser car il a été fait pour de tels projets comme le vôtre.

1voto

biziclop Points 21446

Une option est de:

  1. Faire de la VM se connecter à lui-même à l'aide de JDI.
  2. Regarder le thread en cours d'exécution de votre tâche. Ce n'est pas trivial, mais puisque vous avez accès à l'ensemble de la pile d'images, il est certainement faisable. (Si vous mettez un unique id champ dans votre tâche objets, vous serez en mesure d'identifier le thread qui l'exécute.)
  3. Arrêter le fil en mode asynchrone.

Bien que je ne pense pas qu'un arrêté du thread sérieusement interférer avec les exécuteurs testamentaires (ils doivent être fail-safe, après tout), il existe une autre solution qui n'implique pas l'arrêt du thread.

À condition que les tâches ne rien modifier dans d'autres parties du système (ce qui est une hypothèse juste, sinon vous ne seriez pas en essayant de tirer vers le bas), ce que vous pouvez faire est d'utiliser JDI pop indésirables de la pile hors cadres et à la sortie de la tâche normalement.

public class StoppableTask implements Runnable {

private boolean stopped;
private Runnable targetTask;
private volatile Thread runner;
private String id;

public StoppableTask(TestTask targetTask) {
    this.targetTask = targetTask;
    this.id = UUID.randomUUID().toString();
}

@Override
public void run() {
    if( !stopped ) {
        runner = Thread.currentThread();
        targetTask.run();
    } else {
        System.out.println( "Task "+id+" stopped.");
    }
}

public Thread getRunner() {
    return runner;
}

public String getId() {
    return id;
}
}

C'est l'exécutable qui encapsule tous vos autres runnables. Il stocke une référence à l'exécution du thread (sera important plus tard) et une pièce d'identité afin que nous puissions la trouver avec une JDI appel.

public class Main {

public static void main(String[] args) throws IOException, IllegalConnectorArgumentsException, InterruptedException, IncompatibleThreadStateException, InvalidTypeException, ClassNotLoadedException {
    //connect to the virtual machine
    VirtualMachineManager manager = Bootstrap.virtualMachineManager();
    VirtualMachine vm = null;
    for( AttachingConnector con : manager.attachingConnectors() ) {
        if( con instanceof SocketAttachingConnector ) {
            SocketAttachingConnector smac = (SocketAttachingConnector)con;
            Map<String,? extends Connector.Argument> arg = smac.defaultArguments();
            arg.get( "port" ).setValue( "8000");
            arg.get( "hostname" ).setValue( "localhost" );
            vm = smac.attach( arg );
        }
    }

    //start the test task
    ExecutorService service = Executors.newCachedThreadPool();
    StoppableTask task = new StoppableTask( new TestTask() );
    service.execute( task );
    Thread.sleep( 1000 );

    // iterate over all the threads
    for( ThreadReference thread : vm.allThreads() ) {
        //iterate over all the objects referencing the thread
        //could take a long time, limiting the number of referring
        //objects scanned is possible though, as not many objects will
        //reference our runner thread
        for( ObjectReference ob : thread.referringObjects( 0 ) ) {
            //this cast is safe, as no primitive values can reference a thread
            ReferenceType obType = (ReferenceType)ob.type();
            //if thread is referenced by a stoppable task
            if( obType.name().equals( StoppableTask.class.getName() ) ) {

                StringReference taskId = (StringReference)ob.getValue( obType.fieldByName( "id" ));

                if( task.getId().equals( taskId.value() ) ) {
                    //task with matching id found
                    System.out.println( "Task "+task.getId()+" found.");

                    //suspend thread
                    thread.suspend();

                    Iterator<StackFrame> it = thread.frames().iterator();
                    while( it.hasNext() ) {
                        StackFrame frame = it.next();
                        //find stack frame containing StoppableTask.run()
                        if( ob.equals( frame.thisObject() ) ) {
                            //pop all frames up to the frame below run()
                            thread.popFrames( it.next() );
                            //set stopped to true
                            ob.setValue( obType.fieldByName( "stopped") , vm.mirrorOf( true ) );
                            break;
                        }
                    }
                    //resume thread
                    thread.resume();

                }

            }
        }
    }

}
}

Et pour la référence, la "bibliothèque" appel je l'ai testé avec:

public class TestTask implements Runnable {

    @Override
    public void run() {
        long l = 0;
        while( true ) {
            l++;
            if( l % 1000000L == 0 )
                System.out.print( ".");
        }

    }
}

Vous pouvez l'essayer en lançant l' Main classe, avec l'option de ligne de commande -agentlib:jdwp=transport=dt_socket,server=y,address=localhost:8000,timeout=5000,suspend=n. Il fonctionne avec deux mises en garde. Tout d'abord, si il est natif de l'exécution d'un code (thisObject de un cadre est null), vous aurez à attendre jusqu'à ce qu'elle soit terminée. Deuxièmement, finally blocs ne sont pas invoqués, de sorte que diverses ressources peuvent potentiellement être une fuite.

0voto

vinga Points 521

Si les méthodes internes ont des noms similaires, vous pouvez utiliser la définition de pointcut en xml (spring / AspectJ) au lieu d'annotations, donc aucune modification de code de la bibliothèque externe ne devrait être nécessaire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X