Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > de.comp.lang.java > #13039

Re: JUnit-Tests funktionieren nur manchmal: Synchronisations-Problem

From "Christian H. Kuhn" <qno-news@qno.de>
Newsgroups de.comp.lang.java
Subject Re: JUnit-Tests funktionieren nur manchmal: Synchronisations-Problem
Date 2016-08-26 20:21 +0200
Message-ID <e2bfkkF7jgbU1@mid.individual.net> (permalink)
References <e25pnrFqe4gU1@mid.individual.net> <npmc9e$stq$1@gwaiyur.mb-net.net>

Show all headers | View raw


Am 25.08.2016 um 11:03 schrieb Marcel Mueller:
> On 24.08.16 16.36, Christian H. Kuhn wrote:
> - notified einmalig initialisieren (eigentlich sogar optional),
> - jetzt erst alle thread anstupsen, die notified setzen können,
> - und dann wait(),
> - notified löschen
> - und dann das Ereignis, das zu notified gehört, verarbeiten.

Ja. Eigentlich selbstverständlich. Mach ich sonst immer so. Hier hab ich
dem Oracle-Tutorial geglaubt, ohne selbst zu denken ...

> Wie gehören "changed" und "notified" zusammen? Ich habe jetzt keine
> Muße, den kompletten Code durchzulesen.

Dank dieser Frage fand ich den Fehler. Hoffe ich. Bislang sind keine
JUnit-Tests mehr gescheitert. Was eine notwendige, keine hinreichende
Bedingung ist, da bin ich inzwischen mißtrauisch ...

notified ist die Kontrollvariable in der /Test/-Klasse
de.qno.qchessclock.common.QChessClockTest. Nach den von dir angeregten
Änderungen wird sie in setUp() gelöscht, in update() gesetzt und in
waitForNotify() NACH der Endlosschleife wieder gelöscht. Das ist auch
der Sinn: Die Testklasse ist eine Mock-GUI, simuliert Benutzeraktionen
und muss nach den Aktionen auf eine Reaktion der zu testenden Klasse
warten, bevor die Asserts verarbeitet werden können.

changed ist die Kontrollvariable in der /zu testenden/ Klasse
de.qno.qchessclock.common.QChessClock. Hier wird alle 20 ms über einen
TimerTask notifyObservers() aufgerufen. notifyObservers() erstellt und
verschickt nur dann ein Status-Objekt (durch Aufruf von
QChessClockObservers.update()), wenn sich der sichtbare Zustand der Uhr
verändert hat.

changed wird gesetzt:
- durch Betätigen eines Knopfes (moveButtonPressed(), stopPressed(),
resetPressed()),
- durch Setzen einer neuen Bedenkzeit (setNewTimers()),
- durch Ändern der Bedenkzeit um mindestens eine Sekunde (wird in
notifyObservers() selbst festgestellt),
- durch Blättchenfall (wird in notifyObservers() selbst festgestellt).

changed wird an einer einzigen Stelle gelösch:
- in notifyObservers() (der Methode, die changed auch auswertet) nach
Erstellen und Verschicken des Status-Objekts.

notified kann also in update() nur gesetzt werden, wenn der Aufruf von
update durch gesetztes changed in notifyObservers() erlaubt wird. Dieses
Problem war in alten Versionen nicht sichtbar. Dort wurde changed nicht
verwendet, update() wurde alle 20 ms aufgerufen. Wurde ein Aufruf
verpasst, wirkte der nächste, und der Fehler wäre nur mit Zeitmessungen
feststellbar gewesen.

Der Fehler war folgender: Ich wollte Resourcen sparen, indem ich den
durch synchronized gelockten Code-Abschnitt kurz halten wollte.
Insbesondere ist es nicht nötig, die for-Schleife mit den
update()-Aufrufen im synchronized-Abschnitt zu haben, da hier nicht mehr
auf den Status von QChessClock zugegriffen wird. Das ist auch richtig.
Dabei ist mir allerding auch eine Abfrage von changed und das
abschließende setChanged(false) aus dem synchronized gerutscht.

Dadurch ist eine race condition entstanden. Folgende Event-Reihenfolge
wurde möglich:
1. der synchronized-Teil von notifyObservers() wird ausgeführt. changed
ist am Ende des Blocks true
2. die for-Schleife mit QChessClockObservers.update() wird begonnen
3. „gleichzeitig“ tritt ein Ereignis ein, das changed wieder auf true setzt
4. die for-Schleife in notifyObservers() wird beendet, changed wird
gelöscht.
5. jetzt erst startet der nächste getimte Lauf von notifyObservers().
changed sollte durch die Aktion in 3. true sein, ist aber in 4. ohne
Auswertung gelöscht worden.

Durch ein paar Zeilentranspositionen kam setChanged(false) in den
gelockten Bereich. Dadurch wurde diese race condition vermieden.

Snapshot:
https://www.qno.de/gitweb/?p=qchessclock.git;a=snapshot;h=304120d97a7ca850851b3c913d344494162b4597;sf=tgz

Eclipse, Gradle (lokal) und Jenkins/Gradle (remote) führten die Tests
wiederholt ohne Fehler aus. Das gibt Hoffnung.

Vielen Dank
QNo

Back to de.comp.lang.java | Previous | NextPrevious in thread | Find similar


Thread

JUnit-Tests funktionieren nur manchmal: Synchronisations-Problem "Christian H. Kuhn" <qno-news@qno.de> - 2016-08-24 16:36 +0200
  Re: JUnit-Tests funktionieren nur manchmal: Synchronisations-Problem Lothar Kimmeringer <news200709@kimmeringer.de> - 2016-08-24 22:30 +0200
    Re: JUnit-Tests funktionieren nur manchmal: Synchronisations-Problem "Christian H. Kuhn" <qno-news@qno.de> - 2016-08-26 19:10 +0200
  Re: JUnit-Tests funktionieren nur manchmal: Synchronisations-Problem Marcel Mueller <news.5.maazl@spamgourmet.org> - 2016-08-25 11:03 +0200
    Re: JUnit-Tests funktionieren nur manchmal: Synchronisations-Problem "Christian H. Kuhn" <qno-news@qno.de> - 2016-08-26 20:21 +0200

csiph-web