Da mir im Moment der Sinn nicht ganz nach irgendeinem Politikum steht, worüber ich meinen Senf ausgießen müsste, werde ich vorerst noch mehr Softwareentwickler-Kram posten, mit dem ich die geneigten, aber uninteressierten Leser erfolgreich abschrecken kann. Wir danken für Ihr Verständnis.
Wer sich im Internet gezielt über aktives Rendering mittels Canvas in Java informiert, also etwas, das direkt im Zusammenhang mit Java-Spieleentwicklung steht, der wird wahrscheinlich irgendwann über die Methode sync() in java.awt.Toolkit stolpern, also meistens in der Form Toolkit.getDefaultToolkit().sync(). Nun gehe ich stark davon aus, dass die meisten Entwickler diese Codeschnipsel als gegeben ansehen und 1:1 in ihre eigene Klasse einfügen, da sie wahrscheinlich vielfach erprobt sind. So war das bei mir anfangs leider auch. In einem eigenen Render-Thread habe ich Bilder gerendert und anschließend brav das Toolkit „synchronisiert“, was laut Dokumentation irgendwie „gut für Animationen“ ist.
Seit der Umstellung auf aktives Rendering jedoch hatte ich plötzlich Schwierigkeiten damit, den Render-Thread mittels interrupt() vorzeitig zu stoppen, also etwa wenn der Nutzer das Rendering über das Menü beendet oder wenn ein bestimmtes Ereignis auftritt. Zuvor war das nie ein Problem gewesen. Neuerdings läuft der Thread in drei von vier Fällen einfach weiter, sobald ich ihn gezielt unterbrechen will. Das war eine mittlere Katastrophe, denn ich konnte mich jetzt nicht mehr darauf verlassen, dass das Programm das macht, was ich davon erwartete. Mein Programm funktionierte so nicht mehr, und ich wusste nicht, was ich falsch gemacht haben könnte.
Etwa einen halben Tag hat es gedauert, bis ich den Fehler gefunden hatte: java.awt.Toolkit.sync() verschluckt ganz frech das Interrupted-Flag. Es scheint leider wirklich so zu sein. Die Implementation der Java Virtual Machine von Oracle ist hier offenbar fehlerhaft, denn alles funktioniert einwandfrei wenn ich auf sync() verzichte. Sobald ich die Zeile verwende, klappt das Unterbrechen des Threads nicht mehr. Meine Vermutung ist daher, dass das blockierende sync() das Interrupted-Flag mittels interrupted() abfragt (und dadurch löscht!), und dann leider nicht mehr setzt und leider auch keine Exception wirft. Es verschluckt die Thread-Unterbrechung völlig. So wird dieser Mechanismus sogar ganz offiziell in der Java-Doku erklärt:
if (Thread.interrupted()) // Clears interrupted status!
Nun hält sich Oracle selbst nicht daran. Ich habe anschließend in meinem Code keine Möglichkeit mehr, diese Unterbrechung zu erkennen und gegebenenfalls selbst eine InterruptedException auszulösen. Oracle hat hier eindeutig Scheiße gebaut, denn eigentlich ist das Konsens, das Interrupted-Flag nach dem Löschen erneut zu setzen, damit die Unterbrechung nach oben eskaliert werden kann, so dass auch andere Programmteile auf die Unterbrechung korrekt reagieren können. Hier wurde ganz einfach geschlampt.
Und was macht sync() eigentlich? Tja, keine Ahnung. So ganz scheint das nämlich niemand zu wissen. Ich liebe es, wenn die Java-Dokumentation von Oracle so explizit ist, dass man trotzdem keine Ahnung hat, was genau vor sich geht. So auch zur Methode sync() der Klasse Toolkit:
void java.awt.Toolkit.sync()
Synchronizes this toolkit’s graphics state. Some window systems may do buffering of graphics events.
This method ensures that the display is up-to-date. It is useful for animation.
Wenn jemand mal im Internet in einschlägigen Entwicklerforen versucht danach zu fragen, was das genau bedeutet, wird die Frage mit „synchronizes the graphics state“ „beantwortet“, und anschließend als Off-Topic geschlossen. Zurecht wurde wenigstens noch darauf hingewiesen, dass das die Frage kaum beantwortet.
Da ich weder sagen kann, was die Methode macht, in welchen Fällen ich sie brauchen könnte, und welche Vorteile oder Nachteile damit zusammenhängen, werde ich auf sync() gerne verzichten, schon da ich mit dem Interrupt-Problem einen ziemlich schwerwiegenden Fehler entdeckt habe, den ich nicht bereit bin zu akzeptieren – ganz egal wie toll die Funktion ansonsten arbeiten möge.
Hast Du den Fehler mal an Oracle gemeldet? Wenn man sich mal anschaut was sync() so macht dann ist es allerdings auch kein Wunder, dass der Interrupt verloren geht :-D
Zum Beispiel beim Flush der OGL Render Queue:
while(this.needsFlush) {
try {
this.wait();
} catch (InterruptedException var2) {
;
}
}
Ist natürlich sehr ärgerlich wenn man über sowas stolpert und unnötig Zeit in die Fehlersuche investiert. Ich hatte auch mal einen fiesen Bug in Swing entdeckt, der mich ebenfalls gut einen Tag an Debugging gekostet hat.
Ich habe damals mit dem Gedanken gespielt, den Fehler zu melden, dann bin ich jedoch zu dem Schluss gekommen, dass sich der Aufwand für mich nicht lohnt. Wenn ICH derjenige bin, der den Fehler findet, dann stehen die Chancen gut, dass es außer mir niemanden juckt.
Ich hätte mir zu gerne angesehen was sync() macht, habe jedoch keine Implementierung von Toolkit gefunden. In der Klasse HeadlessToolkit ist sync() offenbar leer.
Ja, ich hab leider auch keine so gute Erfahrung mit Oracle gemacht, was Bugfixes angeht. Einen Versuch wärs vielleicht wert.
Die Toolkit-Klasse unter Windows müsste diese hier sein (hab ich jetzt allerdings nicht geprüft):
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/awt/windows/WToolkit.java#WToolkit.nativeSync%28%29