Discussion:
WebService/SOAP stabiler machen
(zu alt für eine Antwort)
Matthias Hanft
2007-02-14 16:39:02 UTC
Permalink
Hallo,

ich habe so ungefähr, wie es auf
http://cc.codegear.com/Item.aspx?id=16364
oder
http://thecoadletter.com/article/0,1410,27513,00.html
beschrieben ist, mit Indy (9) und Delphi (7 professional) einen
Stand-Alone-WebService programmiert.

Der funktioniert eigentlich ganz prima - lediglich nach drei
Wochen Uptime (oder so) werden keine neuen Verbindungen mehr
angenommen; bzw. angenommen werden sie schon, aber der Server
gibt nur noch aus "Die maximale Anzahl Verbindungen wurde
erreicht, bitte versuchen Sie es später noch einmal" (oder so
ähnlich). Wo kommt dieser Text genau her (von mir jedenfalls
nicht)?

Es genügt dann auch, einfach das Serverprogramm zu beenden und
neu zu starten, dann klappt's wieder.

Es liegt auch nur an _diesem_ Programm - denn ich habe auf der-
selben Maschine noch weitere Standalone-Indy-Server laufen (ein-
fach nur mit dem TIdHTTPServer ohne diese SOAP-Geschichten; auf
anderen IP-Adressen und/oder Ports); die laufen offensichtlich
auch jahrelang problemlos durch.

Wie könnte man da vorgehen, das ganze etwas stabiler zu machen?
Ist evtl. die SOAP-Verarbeitung in D7 ohnehin buggy? Oder gibt
es sinnvolle Debug-Möglichkeiten meines Servers? Hülfe es, das
ganze nach BDS2006 (vorhanden) zu portieren? Als VCL, WinForms
oder .NET? Gibt's dort andere/bessere/stabilere Komponenten?

Oder sollte ich der Windows- und Borland-Plattform diesbezüglich
überhaupt den Rücken kehren und das ganze unter Linux mit Apache
(ebenso vorhanden) aufziehen? Allerdings kenne ich da keine SOAP-
Verarbeitungsschicht (und den XML-Request manuell parsen kann's
ja auch irgendwie nicht sein).

Danke schon mal für alle Vorschläge...

Gruß Matthias.
Marian Aldenhövel
2007-02-14 16:57:48 UTC
Permalink
Hallo,
Post by Matthias Hanft
ich habe so ungefähr, wie es auf
http://cc.codegear.com/Item.aspx?id=16364
oder
http://thecoadletter.com/article/0,1410,27513,00.html
beschrieben ist, mit Indy (9) und Delphi (7 professional) einen
Stand-Alone-WebService programmiert.
Ich habe so was in den letzten Monaten ein paar Mal mit IndySOAP
gemacht. Das baut in D5 und D7 einheitlich.

Aber das Ergebnis ist noch keinem Streß unterlegen, also kann ich
schlecht sagen ob es hält.
Post by Matthias Hanft
aber der Server gibt nur noch aus "Die maximale Anzahl
Verbindungen wurde erreicht, bitte versuchen Sie es später noch
einmal" (oder so ähnlich). Wo kommt dieser Text genau her (von mir
jedenfalls nicht)?
Kenn ich auch nicht. Wohin gibt er das denn aus? Als "5xx Internal
Server Error"?
Post by Matthias Hanft
Oder gibt es sinnvolle Debug-Möglichkeiten meines Servers?
Die Fehlermeldung klingt ja nach offengehaltenen Verbindungen. Was sagt
denn netstat -a?
Post by Matthias Hanft
Oder sollte ich der Windows- und Borland-Plattform diesbezüglich
überhaupt den Rücken kehren und das ganze unter Linux mit Apache
(ebenso vorhanden) aufziehen? Allerdings kenne ich da keine SOAP-
Verarbeitungsschicht (und den XML-Request manuell parsen kann's
ja auch irgendwie nicht sein).
Apache Axis: http://ws.apache.org/axis/

Du baust die Server in Java, auch dann standalone oder irgendwo eingebettet.
In letzterem Modus meistens innerhalb eines Tomcat, den Du wahlweise wieder
hinter einen Apachen plazieren kannst, falls Du auf irgendwelche
besonderen Features desselben scharf bist und/oder parallel auch Massen
von statischem oder anderweitig erzeugtem Content ausliefern willst.

Windows brauchst Du dafür noch nicht wegzugeben :-).

Ciao, MM
--
Marian Aldenhövel, Rosenhain 23, 53123 Bonn
http://www.marian-aldenhoevel.de
"Success is the happy feeling you get between the time you
do something and the time you tell a woman what you did."
Matthias Hanft
2007-02-14 18:00:56 UTC
Permalink
Marian Aldenhövel schrieb:
[Indy-WebService]
Post by Marian Aldenhövel
Ich habe so was in den letzten Monaten ein paar Mal mit IndySOAP
gemacht. Das baut in D5 und D7 einheitlich.
Klingt interessant. Hast Du eine _ungefähre_ Abschätzung, wieviel
Aufwand ein Umbau von Delphi's SOAP-Komponenten auf IndySOAP viel-
leicht sein könnte?
Post by Marian Aldenhövel
Kenn ich auch nicht. Wohin gibt er das denn aus? Als "5xx Internal
Server Error"?
Ja, ich glaube 500. Ich seh's ja nie, denn wenn einer meiner Kunden
mir das meldet (was eh selten genug ist, die warten anscheinend immer
erst drei Tage?!), schau' ich lieber, daß ich das Zeug wieder hoch
kriege...
Post by Marian Aldenhövel
Die Fehlermeldung klingt ja nach offengehaltenen Verbindungen. Was sagt
denn netstat -a?
Müßte ich im Fehlerfall mal checken. Momentan (im normal laufenden
Zustand) gibts ein paar HTTP "WARTEND" oder "SCHLIESSEND" von außen,
und dann noch gaaaanz viele lokale auf den Firebird-Port (der Web-
Service verbindet sich über "local IP" mit einem Firebird-Server
auf derselben Maschine; das sollte aber wohl eher nicht "zählen",
oder?).

Könnte ich diese "WARTEND" oder "SCHLIESSEND" denn irgendwie aktiv
beenden (falls die eine Rolle spielen)? In einem normalen TIdHTTP-
Server.OnGetEvent kann ich die TCP-Verbindung ja einfach schließen,
aber per WebService hab' ich ja nur meine extern aufgerufene Func-
tion, in der ich irgendwelche Ergebnisse zurückliefere - da könnte
ich zwar vom WebBroker den Request-Record nach unten durchreichen,
um Zugriff auf die TIdTCP-Komponente zu haben, um die Verbindung
aktiv schließen zu können, aber _wann_ sollte ich das tun?

(Es geht übrigens das Gerücht, daß eine Windows-"Nicht-Server"-
Version ohnehin auf maximal zehn gleichzeitige TCP/IP-Verbindungen
"kastriert" wäre. Ist da was dran? Ich habs noch nicht nachge-
prüft.)
Post by Marian Aldenhövel
Apache Axis: http://ws.apache.org/axis/
Du baust die Server in Java, auch dann standalone oder irgendwo eingebettet.
In letzterem Modus meistens innerhalb eines Tomcat, den Du wahlweise wieder
hinter einen Apachen plazieren kannst, falls Du auf irgendwelche
besonderen Features desselben scharf bist und/oder parallel auch Massen
von statischem oder anderweitig erzeugtem Content ausliefern willst.
Danke für den Link, habs mal kurz überflogen. Klingt interessant,
allerdings kommt da drin öfters "Java" und "Tomcat" vor, und das
ist sehr abschreckend für mich (ich kenne so eine Applikation von
woanders her, und was instabileres hab ich mein Lebtag noch nicht
gesehen - ok, das hängt sicher von der jeweiligen Anwendung ab,
aber sowas prägt irgendwie für alle Zukunft :-) ). Außerdem müßte
ich dann meinen WebService wieder bei nil anfangen; das hätte ich
aus Zeitgründen gerne vermieden...

Gruß Matthias.
Christian Gütter
2007-02-14 20:51:18 UTC
Permalink
Post by Matthias Hanft
(Es geht übrigens das Gerücht, daß eine Windows-"Nicht-Server"-
Version ohnehin auf maximal zehn gleichzeitige TCP/IP-Verbindungen
"kastriert" wäre. Ist da was dran? Ich habs noch nicht nachge-
prüft.)
Das Gerücht höre ich öfter, es stimmt aber nicht.
Windows XP ist limitiert auf 10 gleichzeitige Verbindungen,
was den Zugriff auf lokale Shares angeht. Du stößt also auf
dieses Limit, wenn Du ein XP-System als Fileserver einsetzen
willst und mehr als 10 User hast.


Gruß,
Christian
Marian Aldenhövel
2007-02-15 09:00:01 UTC
Permalink
Hallo,
Post by Matthias Hanft
Klingt interessant. Hast Du eine _ungefähre_ Abschätzung, wieviel
Aufwand ein Umbau von Delphi's SOAP-Komponenten auf IndySOAP viel-
leicht sein könnte?
Nein.

Sicher hängt es davon ab, wie Du implementiert hast. In IndySOAP
deklariert man das Interface mit Delphi. Serverseitig implementiert
man dieses Interface, wobei klassischerweise die Implementation der
Methoden einfach nur an ein Backend delegiert. Oder maximal das
Ergebnis eines Backend-Aufrufs anders einpacht.

Wenn Du so ähnlich gebaut hast, also die tatsächliche Implementation
Deines Service separat hast, dann wäre das nicht schwer.
Post by Matthias Hanft
Ja, ich glaube 500.
Das ist dann wahrscheinlich die Antwort, die er sendet wenn der
Server eine Exception geworfen hat. MadExcept einbauen und nachlesen
wo das passiert.
Post by Matthias Hanft
Müßte ich im Fehlerfall mal checken. Momentan (im normal laufenden
Zustand) gibts ein paar HTTP "WARTEND" oder "SCHLIESSEND" von außen,
und dann noch gaaaanz viele lokale auf den Firebird-Port (der Web-
Service verbindet sich über "local IP" mit einem Firebird-Server
auf derselben Maschine; das sollte aber wohl eher nicht "zählen",
oder?).
Warum sollten die nicht einen Fehler verursachen können, von dem wir
noch nicht wissen, wer ihn verursacht?

Wie ist es denn mit den anderen Services? Greifen die auch auf Firebird
zu? Wann schließt Dein Service die Verbindung? Werden die
Backend-Komponenten irgendwie gepoolt?
Post by Matthias Hanft
aber per WebService hab' ich ja nur meine extern aufgerufene Func-
tion, in der ich irgendwelche Ergebnisse zurückliefere - da könnte
ich zwar vom WebBroker den Request-Record nach unten durchreichen,
um Zugriff auf die TIdTCP-Komponente zu haben, um die Verbindung
aktiv schließen zu können, aber _wann_ sollte ich das tun?
Nein, das sollte das Framework behandeln. Falls da ein Fehler schlummert,
wirfst Du das Framework weg (oder reparierst es an der richtigen Stelle)
aber Du fängst nicht an irgendwas von außen zu flicken.
Post by Matthias Hanft
Post by Marian Aldenhövel
Apache Axis: http://ws.apache.org/axis/
und was instabileres hab ich mein Lebtag noch nicht gesehen
Das täuscht. Ich habe zwei produktive Tomcat-Anwendungen. Eine stirbt alle
eineinhalb Jahre weil irgendein Logrotate-Skript versagt und dann die Platte
mit Logdateien volläuft. Die andere hatte noch keine Gelegenheit zu versagen
weil der Server aus anderen Gründen regelmäßig neu gebootet werden muss.

Und das sind nur meine eigenen Erfahrungen. Das Zeug wird massiv eingesetzt
und läuft.

Ciao, MM
--
Marian Aldenhövel, Rosenhain 23, 53123 Bonn
http://www.marian-aldenhoevel.de
"Success is the happy feeling you get between the time you
do something and the time you tell a woman what you did."
Matthias Hanft
2007-02-15 10:29:01 UTC
Permalink
Marian Aldenhövel schrieb:
[Umbau WebBroker->IndySOAP]
Post by Marian Aldenhövel
Wenn Du so ähnlich gebaut hast, also die tatsächliche Implementation
Deines Service separat hast, dann wäre das nicht schwer.
Ja, im Prinzip schon. Da müßte man dann wohl nur "außenrum" ein
bißchen das Gerüst ändern. Zumindest wäre das einfacher als alles
auf Java/Tomcat/etc. umzubauen :-)

Hältst Du denn IndySOAP grundsätzlich für besser/stabiler etc. als
WebBroker?

[500]
Post by Marian Aldenhövel
Das ist dann wahrscheinlich die Antwort, die er sendet wenn der
Server eine Exception geworfen hat. MadExcept einbauen und nachlesen
wo das passiert.
Done. Ich geh' mal davon aus, daß selber mit try/except abgefangene
Exceptions nicht als Exceptions im Sinne von madExcept gelten, oder?
Post by Marian Aldenhövel
Wie ist es denn mit den anderen Services? Greifen die auch auf Firebird
zu? Wann schließt Dein Service die Verbindung? Werden die
Backend-Komponenten irgendwie gepoolt?
Aaaalso, es ist so: Auf der Kiste laufen insgesamt acht TIdHTTPServer
in vier getrennten .EXE, wobei jedes .EXE zwei TIdHTTPServer hat, einen
ohne SSL auf Port 80 und einen mit SSL auf Port 443.

Die "normalen" Web-Requests behandle ich selbst im TIdHTTPServer.OnGet-
Event und gebe die Antwortseite mit TIdHTTPResponse.WriteContent aus.
Das schließt die TCP-Verbindung nach der Ausgabe selbsttätig. Was aller-
dings passiert, wenn die Verbindung "unterwegs" schon abreißt, weiß ich
nicht. Vielleicht sollte ich an der Stelle noch sicherheitshalber die
TCP-Verbindung nach unten durchreichen und aktiv schließen...

Bei dem (einen) .EXE mit den beiden SOAP-Servern kümmere ich mich gar
nicht um die Verbindung, sondern habe lediglich die verfügbaren Services
(von TInvokable) definiert und implementiert - um den ganzen Rest küm-
mert sich Delphi ja selber :-)

Auf Firebird greifen _alle_ .EXEs zu, manche mehr, manche weniger, aber
alle mit der IBSQL-Komponente (dynamisch zur Laufzeit erzeugt, da mehr-
fach in verschiedenen Threads verwendet) - über "hostname:3050" (auch
wenns dieselbe Maschine ist).

(Da ich Firebird auch auf einem Linux-Rechner einen Meter weiter laufen
habe, erwäge ich, die Datenbanken dorthin umzuziehen, dann ist der Web-
server vermutlich etwas weniger belastet - schaden kann's wohl nicht.)

Die vermutete 500-Exception tritt _nur_ bei dem SOAP-EXE auf (und da
nach mehreren Wochen Uptime), obwohl der "Web-Verkehr" auf den anderen
Servern _wesentlich_ höher ist (und es da sicher auch mehr abgerissene
Verbindungen, Hackversuche und anderen Datenmüll gibt). Daher eben meine
Vermutung, daß möglicherweise tief drin in den Delphi-SOAP-Behandlungs-
routinen eine kleine Unsauberkeit bezüglich Verbindungs-Handling drin
sein könnte...

[Axis/Tomcat]
Post by Marian Aldenhövel
Das täuscht. Ich habe zwei produktive Tomcat-Anwendungen. Eine stirbt alle
eineinhalb Jahre weil irgendein Logrotate-Skript versagt und dann die Platte
mit Logdateien volläuft. Die andere hatte noch keine Gelegenheit zu versagen
weil der Server aus anderen Gründen regelmäßig neu gebootet werden muss.
Ok, danke für die Info. So ein Projekt fange ich dann aber erst beim
nächsten _kompletten_ Redesign an :-)

Gruß Matthias.
Marian Aldenhövel
2007-02-15 12:52:01 UTC
Permalink
Hallo,
Post by Matthias Hanft
Hältst Du denn IndySOAP grundsätzlich für besser/stabiler etc. als
WebBroker?
Kann ich nicht sagen. Ich habe keines von beiden unter Last erlebt.
Post by Matthias Hanft
Post by Marian Aldenhövel
Wann schließt Dein Service die Verbindung?
Ich meinte die DB-Verbindung, nicht TCP/IP. Du nimmst eine auf, wann?
Und wie und wann schließt Du sie wieder?

Üblicherweise instanziiert man ja eine Backend-Implementation entweder
ein oder mehrfach. Bei der einfachen Version serialisiert man Zugriffe
darauf aus den verschiedenen Web-Requests beantwortenden Threads. Bei
der mit mehreren Instanzen holt sich jeder Thread eine aus einem Pool,
verwendet sie und wirft sie wieder rein. Dann gibt es eine Entscheidung
zu treffen was bei leerem Pool passiert. Man kann warten, bis wieder
eine Instanz des Backends frei ist, oder man kann den Pool dynamisch
wachsen lassen.

Wenn da irgendwo was klemmt und immer wieder neu DB-Verbindungen
aufgenommen werden, dann könnte es auf der Verbindung zwischen
SOAP-Server und DB-Server "zu viele Verbindungen geben".

Ciao, MM
--
Marian Aldenhövel, Rosenhain 23, 53123 Bonn
http://www.marian-aldenhoevel.de
"Success is the happy feeling you get between the time you
do something and the time you tell a woman what you did."
Matthias Hanft
2007-02-15 14:18:13 UTC
Permalink
Post by Marian Aldenhövel
Ich meinte die DB-Verbindung, nicht TCP/IP. Du nimmst eine auf, wann?
Und wie und wann schließt Du sie wieder?
Jegliche Datenbankverbindung geht über ein (dynamisch instantiiertes)
Datenmodul, das eine TIBDatabase enthält (mit Connected=True, damit
die Verbindung beim Erzeugen gleich hergestellt und - hoffentlich?! -
beim Vernichten wieder beendet wird).

Während des eigentlichen WebService-Aufrufes passiert dann das hier:

unit DingsImpl;

interface

uses
InvokeRegistry, Types, XSBuiltIns, DingsIntf;

type
TDings = class(TInvokableClass, IDings)
public
procedure TuWas(const...; out...); stdcall;
end;

implementation

uses
Classes, HTTPApp, WebBrokerSOAP, SysUtils, IndyHTTP...;

procedure TDings.TuWas(const...; out...); stdcall;
var
myDingsDataModule: TDingsDataModule;
begin
myDingsDataModule:=TDingsDataModule.Create(nil); // mit TIBDatabase+TIBSQL
try
myDingsDataModule.SetAccountFromParameters(...);
// hier wird die ganze Berechnung zwischen Eingangs-
// und Ausgangsparametern gemacht [1]
myDingsDataModule.AddLogEntry(...)
finally
myDingsDataModule.Free
end
end;

Unter der Voraussetzung, daß eine TIBDatabase auf einem TDataModule,
die im Objektinspektor mit Connected=True eingerichtet wurde, beim
Modul.OnCreate die Verbindung herstellt und im .OnDestroy wieder
trennt, sollte das doch gehen, oder habe ich da irgendein Prinzip
grundsätzlich falsch verstanden?

(BTW, meine "Non-SOAP"-Server machen genau das gleiche, und da tritt
dieser Fehler _nie_ auf...)
Post by Marian Aldenhövel
Üblicherweise instanziiert man ja eine Backend-Implementation entweder
ein oder mehrfach. Bei der einfachen Version serialisiert man Zugriffe
darauf aus den verschiedenen Web-Requests beantwortenden Threads. Bei
der mit mehreren Instanzen holt sich jeder Thread eine aus einem Pool,
verwendet sie und wirft sie wieder rein. Dann gibt es eine Entscheidung
zu treffen was bei leerem Pool passiert. Man kann warten, bis wieder
eine Instanz des Backends frei ist, oder man kann den Pool dynamisch
wachsen lassen.
Die Pool-Lösung habe ich in der Tat in einer späteren, leicht abge-
wandelten Version programmiert. Derartige Fehler sind mir von dort
(läuft anderswo) zwar noch nicht zu Ohren gekommen, aber der Server
dort wird auch lange nicht so intensiv benutzt wie mein eigener hier.

Die hier vorliegende Version ist etwas älter, daher wird das Daten-
modul (und damit die Datenbankverbindung) einfach immer neu erzeugt.
Vielleicht ist das nicht so günstig bezüglich Heap-Fragmentierung
und/oder DB-Traffic o.ä., aber wenn's schon dieselbe Maschine ist,
muß ja wenigstens nix übers Ethernet-Kabel flutschen.

Geh' ich überhaupt recht in der Annahme, daß ein lokal im Thread
erzeugtes Datenmodul (mit einer eigenen Connection zur DB) gar
nicht serialisiert werden muß? In der DB steht jedenfalls das
richtige drin, auch bei "gleichzeitigen" WebService-Zugriffen.
Post by Marian Aldenhövel
Wenn da irgendwo was klemmt und immer wieder neu DB-Verbindungen
aufgenommen werden, dann könnte es auf der Verbindung zwischen
SOAP-Server und DB-Server "zu viele Verbindungen geben".
Auch wenn das dieselbe Maschine mit "local IP" ist?

Gruß Matthias.

[1] an dieser Stelle wird auch noch ein COM-Objekt vom Typ IBla
per myBla:=CoBla.Create erzeugt und verwendet und nicht mehr
freigegeben, weil letzteres ja automagisch passiert, oder?
Marian Aldenhövel
2007-02-15 19:12:20 UTC
Permalink
Hallo,
Post by Matthias Hanft
Jegliche Datenbankverbindung geht über ein (dynamisch instantiiertes)
Datenmodul, das eine TIBDatabase enthält (mit Connected=True, damit
die Verbindung beim Erzeugen gleich hergestellt und - hoffentlich?! -
beim Vernichten wieder beendet wird).
Normalerweise ja.
Post by Matthias Hanft
myDingsDataModule:=TDingsDataModule.Create(nil); // mit TIBDatabase+TIBSQL
try
myDingsDataModule.SetAccountFromParameters(...);
// hier wird die ganze Berechnung zwischen Eingangs-
// und Ausgangsparametern gemacht [1]
myDingsDataModule.AddLogEntry(...)
finally
myDingsDataModule.Free
end
Du erzeugst also für jede neue Anfrage eine neue Instanz des Datenmoduls, mit
dem Overhead der Verbindungsaufnahme. Das war, was ich sehen wollte.

Da kann man nicht viel falsch machen.
Post by Matthias Hanft
Geh' ich überhaupt recht in der Annahme, daß ein lokal im Thread
erzeugtes Datenmodul (mit einer eigenen Connection zur DB) gar
nicht serialisiert werden muß? In der DB steht jedenfalls das
richtige drin, auch bei "gleichzeitigen" WebService-Zugriffen.
Das hat weniger damit zu tun welcher Thread das erzeugt, als vielmehr damit
welche es benutzen. Bei Dir ist (schon wegen der lokalen Variablen)
weitgehend sichergestellt, daß das Ding nur in dem einen Threadkontext
verwendet wird. Und damit ist es recht sicher.

"recht sicher" weil es auch noch ein paar Globalitäten gibt, die da beteiligt
sind. Aber das wären andere Fehler.
Post by Matthias Hanft
Auch wenn das dieselbe Maschine mit "local IP" ist?
Ja. das ist trotzdem eine TCP/IP-Verbindung.
Post by Matthias Hanft
[1] an dieser Stelle wird auch noch ein COM-Objekt vom Typ IBla
per myBla:=CoBla.Create erzeugt und verwendet und nicht mehr
freigegeben, weil letzteres ja automagisch passiert, oder?
Auch das stimmt.

Ich war nur nicht sicher, ob das Äquivalent von "myDingsDataModule.Free"
kommt. Das ist bei Dir sicher. Beim Pooling kann man da Fehler machen, zum
Beispiel die Instanzen nicht wieder zurückgeben.

Ciao, MM
--
Marian Aldenhövel, Rosenhain 23, 53123 Bonn
http://www.marian-aldenhoevel.de
"Success is the happy feeling you get between the time you
do something and the time you tell a woman what you did."
Arno Garrels
2007-02-15 19:53:57 UTC
Permalink
Post by Marian Aldenhövel
Post by Matthias Hanft
Auch wenn das dieselbe Maschine mit "local IP" ist?
Ja. das ist trotzdem eine TCP/IP-Verbindung.
Lokal ist sogar noch schlechter als remote, weil jede Verbindung
zwei Sockets 'verbraucht'. Wie viele offenen Verbindungen sind
denn da vorhanden? Die Anzahl der maximal möglichen Sockets ist
begrenzt und auch abhängig von der Grösse des RAMs und der
Windowsversion (praktisch sollten es aber wenigstens einige
tausend sein). Also mal mit TcpView von SysInternals verfolgen,
welche Verbindungen da nicht geschlossen werden.

Arno Garrels
Matthias Hanft
2007-02-16 07:56:37 UTC
Permalink
Post by Arno Garrels
Lokal ist sogar noch schlechter als remote, weil jede Verbindung
zwei Sockets 'verbraucht'. Wie viele offenen Verbindungen sind
denn da vorhanden? Die Anzahl der maximal möglichen Sockets ist
begrenzt und auch abhängig von der Grösse des RAMs und der
Windowsversion (praktisch sollten es aber wenigstens einige
tausend sein). Also mal mit TcpView von SysInternals verfolgen,
welche Verbindungen da nicht geschlossen werden.
Naja, einige tausend würden vermutlich schon ausreichen :-)

Mit "netstat -a | grep -w -c TCP" werden 70 Verbindungen gemeldet,
von denen die meisten "WARTEND" oder "ABHÖREN" sind (nur 7 "HER-
GESTELLT"). Da bräuchte man wohl einfach ab und zu mal eine
"Garbage Collection" für TCP-Ports - gibt's sowas?!

Die meisten "WARTEND" sind in der Tat die lokalen Firebird-Ver-
bindungen - ist das ein Indiz dafür, daß ich da irgendwas falsch
programmiert habe, oder "liegt das am System"? Würde das besser
aussehen, wenn der Firebird-Server auf einer anderen Maschine
liegen würde?

Gruß Matthias.
Arno Garrels
2007-02-16 09:03:54 UTC
Permalink
Post by Matthias Hanft
Naja, einige tausend würden vermutlich schon ausreichen :-)
Mit "netstat -a | grep -w -c TCP" werden 70 Verbindungen gemeldet,
Aus Sicht von Winsock ist das nicht viel.
Post by Matthias Hanft
von denen die meisten "WARTEND" oder "ABHÖREN" sind (nur 7 "HER-
GESTELLT").
Benutze mal TcpView, denn mit den eingedeutschten Socketstati kann
ich zumindest nichts anfangen. Wartend kann 3 Zustände haben FIN_WAIT_1,
FIN_WAIT_2 und TIME_WAIT. Unter Last sind 70 wartenden Sockets
jedenfalls nix besonderes, die sollten aber nach einiger Zeit wieder
verschwinden, TIME_WAIT bleibt eine Socke üblicherweise mehrere
Minuten nach Beenden der Verbindung.
Wichtig wäre auch die Winsock-Fehlernummer zu kennen. Vieleicht ist
aber auch nur der Webserver/Firebird? für eine sehr geringe Zahl von
gleichzeitigen Verbindungen eingerichtet, oder es sind irgendwelche
(zu kleinen) Bereiche für die Bindung an local ports konfiguriert?

Arno Garrels
Matthias Hanft
2007-02-16 17:34:46 UTC
Permalink
Post by Arno Garrels
Benutze mal TcpView, denn mit den eingedeutschten Socketstati kann
ich zumindest nichts anfangen. Wartend kann 3 Zustände haben FIN_WAIT_1,
FIN_WAIT_2 und TIME_WAIT. Unter Last sind 70 wartenden Sockets
jedenfalls nix besonderes, die sollten aber nach einiger Zeit wieder
verschwinden, TIME_WAIT bleibt eine Socke üblicherweise mehrere
Minuten nach Beenden der Verbindung.
Naja, die verschwinden dann wohl auch, denn die 70 bleiben ja einiger-
maßen konstant über die Zeit. Also vielleicht ist das dann doch alles
ganz normal. Die weitaus meisten Verbindungen sehen im TcpView wie
folgt aus (meiner=mein Server, extern=irgendjemand von draußen):

Process Protocol Local Address Remote Address State

[System Process]:0 TCP meiner.de:http extern.de:55409 TIME_WAIT
[System Process]:0 TCP meiner.de:3050 meiner.de:3294 TIME_WAIT

Wenn also "TIME_WAIT" der normale Zustand nach dem Beenden einer
Verbindung ist, sieht das da oben doch ganz normal aus - ein externer
Benutzer hat auf meinen HTTP-Server zugegriffen, und ebendieser hat
dann auf den FBServer auf der lokalen Maschine zugegriffen.

Also kein Grund, sich deswegen Sorgen zu machen?!
Post by Arno Garrels
Wichtig wäre auch die Winsock-Fehlernummer zu kennen. Vieleicht ist
aber auch nur der Webserver/Firebird? für eine sehr geringe Zahl von
gleichzeitigen Verbindungen eingerichtet, oder es sind irgendwelche
(zu kleinen) Bereiche für die Bindung an local ports konfiguriert?
Das einzige, was ich da zum Einstellen kenne, sind die MaxConnections
des TIdHTTPServers, aber die stehen (standardmäßig und bei mir) auf 0.

Da die Exception auch nur bei _dem_ TIdHTTPServer auftritt, wo drunter
die ganze SOAP-/WebBroker-Architektur liegt (und er die Exception ja
auch noch über den HTTP-Port an den Client weitermelden kann; die TCP-
Verbindung an sich funktioniert ja!), habe ich daher irgendwas in der
SOAP-Verarbeitung in Verdacht... aber ich werde jetzt nicht den Delphi-
Quälcode Zeile für Zeile prüfen :-/

Gerade war der Fehler übrigens wieder aufgetreten: Ich teste den Web-
Service alle fünf Minuten von meinem Linux-Rechner aus, und von diesem
aus waren gerade noch zehn "ESTABLISHED"-Verbindungen zum SOAP-Server
offen. Anscheinend macht im Fehlerfall niemand mehr die TCP-Verbindung
zu?!

Ich konnte den Server dann auch erst nach einigen Minuten wieder starten,
weil die Ports 80 und 443 anscheinend noch einige Zeit lang belegt
waren. Höchst eigenartig...

Gruß Matthias.
Arno Garrels
2007-02-17 10:38:10 UTC
Permalink
Post by Matthias Hanft
Wenn also "TIME_WAIT" der normale Zustand nach dem Beenden einer
Verbindung ist, sieht das da oben doch ganz normal aus - ein externer
Benutzer hat auf meinen HTTP-Server zugegriffen, und ebendieser hat
dann auf den FBServer auf der lokalen Maschine zugegriffen.
Also kein Grund, sich deswegen Sorgen zu machen?!
Sehe ich auch so.
Post by Matthias Hanft
Da die Exception auch nur bei _dem_ TIdHTTPServer auftritt, wo drunter
die ganze SOAP-/WebBroker-Architektur liegt (und er die Exception ja
auch noch über den HTTP-Port an den Client weitermelden kann; die TCP-
Verbindung an sich funktioniert ja!), habe ich daher irgendwas in der
SOAP-Verarbeitung in Verdacht... aber ich werde jetzt nicht den
Delphi- Quälcode Zeile für Zeile prüfen :-/
Mit SOAP sowie Indy kenne ich mich absolut nicht aus, hab aber gerade
mal den Quelltext durchsucht. Deine Fehlermeldung kommt wohl vom Webbroker,
der ist standardmäßig auf 32 Verbindungen max. eingestellt.
WebReq.pas, function TWebRequestHandler.ActivateWebModules: TWebModuleList;
EWebBrokerException.CreateRes(@1sTooManyActiveConnections);

Arno Garrels
Matthias Hanft
2007-02-17 11:42:08 UTC
Permalink
Post by Arno Garrels
Mit SOAP sowie Indy kenne ich mich absolut nicht aus, hab aber gerade
mal den Quelltext durchsucht. Deine Fehlermeldung kommt wohl vom Webbroker,
der ist standardmäßig auf 32 Verbindungen max. eingestellt.
WebReq.pas, function TWebRequestHandler.ActivateWebModules: TWebModuleList;
Ups, tatsächlich, da steht im .Create einfach so drin:
FMaxConnections := 32;

Ist allerdings eine "offizielle" Property - also sollte man vielleicht
nach dem Erzeugen mal .MaxConnections höher setzen (und auch .ActiveCount
und .InactiveCount im Auge behalten)?!

Wobei auf _diesem_ Server aber nicht _so_ wahnsinnig viel los ist, als
daß ich mir da ernsthaft 32 _gleichzeitige_ Connections vorstellen
könnte (es sei denn, die "TIME_WAIT" würden da noch mit dazuzählen).

Da werde ich mal eine tiefergehende Untersuchung einleiten :-) Vielen
Dank für den Hinweis - jetzt weiß ich wenigstens, wo's herkommt!

Gruß Matthias.
Arno Garrels
2007-02-17 12:58:35 UTC
Permalink
Post by Matthias Hanft
könnte (es sei denn, die "TIME_WAIT" würden da noch mit dazuzählen).
Bestimmt nicht, das Handle dieser TIME_WAIT Sockets ist bereits
geschlossen worden.
Post by Matthias Hanft
Da werde ich mal eine tiefergehende Untersuchung einleiten :-)
Viel Glück!

Arno Garrels
Matthias Hanft
2007-02-21 18:06:24 UTC
Permalink
Post by Arno Garrels
Viel Glück!
Danke. Allzuviel Glück hatte ich allerdings nicht:

- Ich lasse mir jetzt immer vom WebRequestHandler die aktuellen
Verbindungen ausgeben. Nach einer SOAP-Verbindung steht da:
MaxConnections=32, ActiveCount=0, InactiveCount=1, CacheConnections=True

- Irgendwann nach ein paar Tagen stand dann mal dort
MaxConnections=32, ActiveCount=0, InactiveCount=3, CacheConnections=True
(waren wohl mal drei Requests "gleichzeitig"; naja, warum
auch nicht)

- Und ohne daß das noch irgendwie gewachsen wäre, ist vorhin (nach
ein paar weiteren Tagen) der bekannte Fehler wieder aufgetreten.

Also zumindest an _diesem_ Connection-Pool liegt's anscheinend nicht.

Eine madExcept-Meldung habe ich auch nicht gekriegt; der schickt
anscheinend nix weg, wenn die Exception in einem Thread passiert
(mit einem kleinen Demo-Programm getestet: da passiert wirklich
nix; das muß ich noch rausfinden, warum nicht).

Dann habe ich das Programm beendet und neu gestartet und bin dabei
wohl versehentlich zweimal draufgekommen: Ein Exemplar wurde dann
ordnungsgemäß gestartet, und das zweite hat sich logischerweise
mit der Exception "Adresse/Socket wird bereits benutzt" verabschiedet
- da das Öffnen der HTTP-Server aber im Haupt-Thread passiert, hat
madExcept zugeschlagen und (wegen der Einstellung "Anwendung bei
Exception sofort neu starten") mir zehn Bugreports _pro_Sekunde_
gemailt... :-)

Ich konnte das dann abwürgen, indem ich das erste Exemplar einfach
beendet habe, so daß sich das zweite an Adresse+Socket binden konnte.
Aber da wird einem erst bewußt: Wie beendet man eigentlich so einen
Kreislauf, wenn's eine andere Exception wäre, die bei _jedem_ Start
passiert? Argh!

Die Suche geht also weiter...

Gruß Matthias.

Marian Aldenhövel
2007-02-16 09:56:08 UTC
Permalink
Hallo,
Post by Matthias Hanft
Mit "netstat -a | grep -w -c TCP" werden 70 Verbindungen gemeldet,
von denen die meisten "WARTEND" oder "ABHÖREN" sind (nur 7 "HER-
GESTELLT").
Die meisten "WARTEND" sind in der Tat die lokalen Firebird-Ver-
bindungen - ist das ein Indiz dafür, daß ich da irgendwas falsch
programmiert habe
Dein Code legt eigentlich nahe, daß eine DB-Verbindung nur kürzer leben
kann als der dazugehörige http-Request-Response-Zyklus dauert. Denn
Du stellst sie innerhalb der Bearbeitung her und beendest sie wieder
bevor die Antwort rausgeht.

Wenn Du nun mehr aktive Sockets zur DB zu http-Verbindungen beobachtest,
dann sieht das in der Tat falsch aus.

Verlege doch Deinen Server und die DB mal auf eine Maschine auf der Du
alleine bist (also wo Dir nicht externe Clients dazwischenfunken).
Teste was TcpView erzählt wenn Du einen Request ausführst. Logge ob die
DB-Verbindung wirklich getrennt wird (also das Disconnect() auf die
Komponente ausgeführt wird), Dein Code sieht zwar so aus, aber vielleicht
steckt ja noch irgendwo eine Erzeugung so eines Datenmoduls...
Post by Matthias Hanft
Würde das besser aussehen, wenn der Firebird-Server auf einer anderen
Maschine liegen würde?
Nein.

Ciao, MM
--
Marian Aldenhövel, Rosenhain 23, 53123 Bonn
http://www.marian-aldenhoevel.de
"Success is the happy feeling you get between the time you
do something and the time you tell a woman what you did."
Matthias Hanft
2007-02-16 17:37:29 UTC
Permalink
Post by Marian Aldenhövel
Verlege doch Deinen Server und die DB mal auf eine Maschine auf der Du
alleine bist (also wo Dir nicht externe Clients dazwischenfunken).
Teste was TcpView erzählt wenn Du einen Request ausführst. Logge ob die
DB-Verbindung wirklich getrennt wird (also das Disconnect() auf die
Komponente ausgeführt wird), Dein Code sieht zwar so aus, aber vielleicht
steckt ja noch irgendwo eine Erzeugung so eines Datenmoduls...
Das ist eine gute Idee, erfordert allerdings einige Vorbereitung. Habe
aber in der Tat noch eine "nackte" Maschine, auf der man das prima nach-
vollziehen können müßte. Am Wochenende müßte ich Zeit dazu haben...

Wobei nach Arnos Beitrag das aber anscheinend gar nicht so falsch ist,
wenn die "WARTEND"-Verbindungen zur DB in Wirklichkeit "TIME_WAIT" und
damit ganz normal nach dem Schließen sind?!

Gruß Matthias.
Matthias Hanft
2007-02-16 07:51:20 UTC
Permalink
Post by Marian Aldenhövel
Das hat weniger damit zu tun welcher Thread das erzeugt, als vielmehr damit
welche es benutzen. Bei Dir ist (schon wegen der lokalen Variablen)
weitgehend sichergestellt, daß das Ding nur in dem einen Threadkontext
verwendet wird. Und damit ist es recht sicher.
Ich meinte schon auch eher "benutzen" als "erzeugen". Ja, ich versuche
eigentlich immer und überall, möglichst "lokal" zu bleiben (insbesondere
wenn Threads mit im Spiel sind) - da hab ich hier und anderswo ja schon
genügend Horrormeldungen miterlebt, wenn man da zu "global" wird.

BTW, ich habe inzwischen mal ein kleines Testprogramm zum Ausprobieren
von madExcept gemacht - also wenn man in Form1.Button1Click eine Division
durch Null macht, funktioniert das auch schon so wie erwartet, aber wenn
so eine Exception im Thread von TIdHTTPServer.OnCommandGet auftritt,
passiert eigentlich gar nichts (außer daß der Thread - oder zumindest
die Prozedur, wo die Exception auftritt - offensichtlich beendet wird):

procedure TForm1.Button1Click(Sender: TObject);
var
myNull: Integer;
begin
myNull:=0;
try
Memo1.Lines.Append(IntToStr(1 div myNull))
except
Memo1.Lines.Append('Except!')
end;
Application.ProcessMessages; // hier wird das "Except!" ins Memo geschrieben
Memo1.Lines.Append(IntToStr(1 div myNull)) // hier greift madExcept ordnungsgemäß ein
end;

procedure TForm1.IdHTTPServer1CommandGet(AThread: TIdPeerThread;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
myNull: Integer;
begin
myNull:=0;
Memo1.Lines.Append(ARequestInfo.RemoteIP);
Application.ProcessMessages;
AResponseInfo.ContentText:='Hallo';
Memo1.Lines.Append('Vor Write');
Application.ProcessMessages;
AResponseInfo.WriteContent; // Web-Antwort "Hallo" wird korrekt zum Browser geschickt
Memo1.Lines.Append('zwischen Write und DIV');
Application.ProcessMessages; // bis hierhin wird alles ins Memo geschrieben
Memo1.Lines.Append(IntToStr(1 div myNull)); // Exception - passiert nix?!
Application.ProcessMessages;
Memo1.Lines.Append('zwischen DIV und END'); // das hier wird nicht mehr ausgeführt!
Application.ProcessMessages
end;

Ich will Dich natürlich nicht für madExcept-Support mißbrauchen, aber
vielleicht weißt Du ja auf Anhieb das Häkchen, das ich in der Konfi-
guration noch setzen muß :-)

Danke & Gruß Matthias.
Marian Aldenhövel
2007-02-16 09:57:57 UTC
Permalink
Hi,
Post by Matthias Hanft
Ich will Dich natürlich nicht für madExcept-Support mißbrauchen, aber
vielleicht weißt Du ja auf Anhieb das Häkchen, das ich in der Konfi-
guration noch setzen muß :-)
Nö. War da nicht was, daß Exceptions in Threads grundsätzlich behandelt
werden? Kann ich gerade nicht sagen.

Ciao, MM
--
Marian Aldenhövel, Rosenhain 23, 53123 Bonn
http://www.marian-aldenhoevel.de
"Success is the happy feeling you get between the time you
do something and the time you tell a woman what you did."
Loading...