Path: csiph.com!news.mixmin.net!news.albasani.net!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail From: "Christian H. Kuhn" Newsgroups: de.comp.lang.java Subject: JUnit-Tests funktionieren nur manchmal: Synchronisations-Problem Date: Wed, 24 Aug 2016 16:36:42 +0200 Lines: 119 Message-ID: Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Trace: individual.net NSkGbGyZKaBQofL5lkvDmgUN9dFu7qHqh5PriDhqfHZQFJ8zw= Cancel-Lock: sha1:45kC9qtTYmNmzOU+beFZes3NiiI= X-Mozilla-News-Host: snews://news.individual.net:563 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Thunderbird/45.2.0 Xref: csiph.com de.comp.lang.java:13035 Hallo Gemeinde, Immer noch die Schachuhr. Projekt: https://www.qno.de/gitweb/?p=qchessclock.git;a=summary Snapshot unter https://www.qno.de/gitweb/?p=qchessclock.git;a=snapshot;h=9ad77df87ad2dc11c8fa72816bec8c5e5459ea77;sf=tgz Alles lief, und da musste ich doch was ändern :-) Ich glaubte, die Multithreading-Probleme gelöst zu haben. Um die Systemlast ein klein wenig zu reduzieren, bin ich auf die Idee gekommen, in der QChessClock in notifyObservers() auf changed zu prüfen. changed wird gesetzt, wenn von außen ein Knopf gedrückt oder die Bedenkzeit verändert wird. Bei jedem Aufruf von notifyObservers wird außerdem überprüft, ob sich die anzuzeigende (also auf Sekunden gerundete) Bedenkzeit geändert hat oder ob ein Blättchen gefallen ist. Auch dann wird changed gesetzt. Nur wenn sich etwas geändert hat, wird das Objekt zum Übertragen des Status gefüllt, und nur dann werden die Observer mit diesem Objekt benachrichtigt. Dies führt wie erhofft dazu, dass Observer nicht mehr alle 20 ms, sondern nur bei einer Benutzeraktion bzw. beim Umspringen der Sekundenanzeige benachrichtigt werden. In der Swing-GUI funktioniert auch weiterhin alles. Schwierigkeiten machen die JUnit-Tests. Die Testklasse implementiert die Funktionen einer GUI, um die zu testende Klasse anzusprechen. Die Testklasse registriert sich als Observer. Bislang wurde alle 20 ms die Update-Funktion von notifyObservers() aufgerufen: @Override public final void update(final QChessClockSTO _sto) { synchronized (this) { state = _sto.getStatus(); (...) // weitere übertragene Daten notified = true; notifyAll(); } } Im Test wurde eine Aktion auf der zu testenden Klasse aufgerufen, mit waitForNotify() auf den eintreffenden update() gewartet und dann die entsprechenden asserts vorgenommen. Ob ein update() verpasst wurde oder nicht, war nicht wichtig, der nächste kam ja 20 ms später. private void waitForNotify() throws InterruptedException { synchronized (this) { notified = false; while (!notified) { wait(); } } } Durch den Test auf changed erfolgt jetzt nur noch ein einziger update() als Reaktion auf die Benutzeraktion. Wird der verpasst, kommt kein weiterer mehr, und waitForNotify hängt in der Endlosschleife. Also folgende Hilfsfunktion: private void pressButton(final Buttons _button) throws InterruptedException { synchronized (this) { notified = false; switch (_button) { case LEFT_: sut.moveButtonPressed(LEFT); break; (...) // weitere Knöpfe default: sut.resetPressed(); break; } while (!notified) { wait(); } } } Im Test wird pressButton() aufgerufen, danach die Asserts durchgeführt. Ich glaubte, folgendes erreicht zu haben: pressButton holt sich den Lock, löscht notified und führt die Benutzeraction an der Uhr aus. Danach wird die Endlosschleife betreten, der Lock freigegeben und auf notifyAll() gewartet. Irgendwann kommt der update(). Der braucht den Lock. Kommt der update, bevor wait() erreicht ist, ist der Lock noch belegt, und update() muss warten. Bekommt update() den Lock, werden die Daten aktualisiert, notified wird gesetzt, notifyAll() aufgerufen und der Lock freigegeben. wait() erkennt den notifyAll(), beendet das Warten, bekommt den Lock zurück. Die Endlosschleife wird beendet, die Daten sind aktuell und können mit Assert überprüft werden. Auf QChessClock benutzen die Benutzer-Zugriffsmethoden und notifyObservers den gleichen Lock. Wenn ein Knopf gedrückt wird, kann der Lock für die angeforderte Methode nur erhalten werden, wenn notifyObservers gerade nicht läuft. Während diese Methode ausgeführt wird, an deren Schluss setChanged(true) aufgerufen wird, kann notifyObservers nicht ausgeführt werden. Erst wenn die Benutzer-Zugriffsmethode beendet ist, erfolgt der nächste notifyObservers. Der sieht changed = true und ruft auf der Testklasse update() auf. Soweit die Theorie. Beim Ausführen der Tests kommt es unregelmäßig zu Fehlern. Tests bleiben einfach mal stehen, weil notified false bleibt. Da wird ein update() verschluckt. Im Debugger funktioniert alles immer bestens. Da ist das update schon am Warten, wenn das wait() erreicht ist, sobald wait() ausgeführt ist, ist notified true, und alles ist gut. Im echten Ablauf passiert das aus mir unbekannten Gründen anscheinend nicht. Diesen Fehler konnte ich nicht beobachten, wenn ich eine Testmethode einzeln ausführe. Führe ich die Testklasse aus, egal ob in Eclipse oder mit Gradle, tritt der Fehler an einer anscheinend zufälligen Testmethode auf. Ich halte das für ein deutliches Zeichen, dass da was mit der Nebenläufigkeit nicht stimmt. Ich finde den Fehler aber nicht. Wer winkt mit dem Zaunpfahl? TIA QNo