Blog

Integrationstest von CloudBees RUN@cloud

Andrew Phillips

Aktualisiert Oktober 22, 2025
4 Minuten

Im Rahmen der Vorbereitungen für die jüngste Version 1.0.0 von jclouds habe ich die bestehende Tweetstore-Demoanwendung aufgeräumt und sie auf die Tomcat-basierte RUN@cloud-Plattform von CloudBees portiert. Ein wichtiger Bestandteil des Test-Harness für die Originalversionen von Tweetstore, die auf der Google App Engine laufen, ist die hübsche Klasse GoogleDevServe. Im Grunde handelt es sich dabei um einen cleveren Wrapper um die KickStart-Klasse, die von den GAE-SDK-Tools verwendet wird und die es Ihnen ermöglicht, den SDK-Standort, die Adresse, den Port und die WAR-Datei (oder das erweiterte WAR-Verzeichnis) anzugeben, die ausgeführt werden sollen.Noch besser ist, dass sie programmatisch sauber heruntergefahren werden kann, was sie ideal für Integrationstests macht1.Für CloudBees wollte ich daher einen ähnlichen RunAtCloudServer zusammenstellen. Das erwies sich als schwieriger als erwartet...2

Einfach schon einen Container spawnen?

Sowohl das maven-gae-plugin als auch das bees-maven-plugin ermöglichen es Ihnen natürlich, eine lokale GAE- oder RUN@cloud-Server-Simulation zu starten. Diese können leicht in Ihr Maven-Build integriert werden, zum Beispiel als Teil Ihrer Pre-Integrationstest-Einrichtung. Warum also nicht einfach dabei bleiben? Nun, für einige Szenarien kann das vollkommen ausreichend sein. Für Tweetstore können wir mit einem Inline-Server, der sauber herunterfährt, Folgendes tun:

  • Integrationstests neben Unit-Tests aus einer IDE heraus ausführen, ohne daran denken zu müssen, einen externen Prozess zu starten
  • die auf dem Server laufende Anwendung bei Bedarf zu debuggen, ohne sich mit Remote-Debuggern herumschlagen zu müssen oder einen Weg zu finden, die Maven-Plugins manchmal in den Debug-Modus zu versetzen
  • mit den Server-Argumenten ein wenig bedingte Magie anwenden

Wenn die Anforderungen Ihres Projekts ähnlich sind, könnte ein Inline-Server eine gute Option sein. Und zum Glück wurde der Code bereits geschrieben!

Herumlungern

Die KickStart-Version des RUN@cloud SDK ist der StaxSdkAppServer3. Es war nicht allzu schwer, die richtigen Argumente für die launchServer-Fabrikmethode herauszufinden, also schloss ich einen StaxSdkAppServer an und startete den Test. Es lief wie am Schnürchen! Dann wurde der Test beendet und... blieb einfach stehen. Im zuverlässigen Debugger4 konnte ich nicht nur einen, sondern zwei Zombie-Threads sehen, die noch liefen. Der Entwurf basierte auf einem "Shutdown-by-JVM-kill"-Szenario, nehme ich an.

Meuchelmörder und andere Ninja-Tricks

Der erste der beiden Threads ist ein Timer, den etwas namens WebAppEngine verwendet, um... nun, wer weiß? Auf jeden Fall können wir mit einer ordentlichen Portion Reflexion einen Verweis auf ihn erhalten und ihn abbrechen. Das ist ganz einfach.Unser verbleibender Zombie ist leider etwas schwieriger zu finden. Es handelt sich um einen Thread, der von einer Instanz der Klasse RequestMonitorValve in ähnlicher Weise erzeugt wird wie [java] new Thread(idleTimer, "requestMonitor").start(); [/java] Also keine Referenz und keine Möglichkeit, ihn zu unterbrechen. Die idleTimer Runnable wird jedoch referenziert und ruft als Teil seiner Ausführung wiederholt einen Handler auf. Wie wäre es, wenn Sie einen Handler einfügen, der eine Ausnahme auslöst, um den Thread zu beenden? Nette Idee, aber es stellt sich heraus, dass der Thread so nicht sterben will: [java] while(true) { try { // Dinge tun if(callbackClient != null) callbackClient.updateStatus(state); } catch(Exception e) { // einige Fehlerzähler erhöhen } try { Thread.sleep(statusIntervalSecs * 1000); } catch(InterruptedException interruptedexception) { } } [/java] Ich musste also noch einen draufsetzen und stattdessen einen ThreadDeath-Fehler auslösen. Urgh.

Jetzt sehen Sie es, jetzt nicht mehr?

Das verbleibende Problem besteht darin, den eingängig benannten KillerCallback in den idleTimer zu injizieren . Selbst bei Verwendung von Reflection ist dies ein elementarer Gleichzeitigkeitsfehler, denn ohne eine Synchronized-With-Beziehung5 zwischen der Injektion des neuen Handlers und dem Aufruf des Handlers durch idleTimer gibt es keine Garantie dafür, dass der neue Handler tatsächlich für den Thread sichtbar ist, den wir zu stoppen versuchen. Eine solche Beziehung könnte hergestellt werden, wenn der "Attentäter" injiziert würde , bevor der Thread, der idleTimer ausführt, startet. Wenn wir nicht die Bibliotheksklassen überschreiben wollen, würde das bedeuten, dass wir ihn nach der Erstellung, aber vor dem Start des StaxSdkAppServer6 setzen müssten... was leider auch nicht funktioniert, da die Instanzen, die idleTimer referenzieren, erst beim Start des Servers erstellt werden.

Und?

Für den Moment ist das potenzielle Gleichzeitigkeitsproblem also dokumentiert und wir hoffen auf das Beste ;-) Bis jetzt wurden keine Probleme beobachtet...
Fußnoten
  1. Das maven-gae-plugin ist eine weitere gute Option, um die lokale GAE-Simulation des SDK zu starten. Der Vorteil von GoogleDevServer ist, dass er sich auch sauber beenden lässt, von Ihrer IDE aus ausgeführt werden kann und es Ihnen außerdem ermöglicht, der WAR vor dem Start zusätzliche Dateien hinzuzufügen. Tweetstore nutzt dies, um Anmeldedaten für verschiedene Cloud-Speicheranbieter hinzuzufügen - nicht gerade das, was Sie wahrscheinlich in Github speichern möchten ;-)
  2. aber siehe hier
  3. CloudBees übernahm Stax im Dezember 2010
  4. Hier kommt die Möglichkeit, von einer IDE aus zu arbeiten, voll zum Tragen!
  5. wie sie z.B. von flüchtig
  6. Tatsächlich verwendet der Code einen fast exakten Klon namens StaxSdkAppServer2, der einfach die Erstellung der Serverinstanz vom Start trennt.

Verfasst von

Andrew Phillips

Contact

Let’s discuss how we can support your journey.