ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/docs/corodebug.pod
Revision: 1.9
Committed: Mon Jan 21 11:12:49 2008 UTC (16 years, 4 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.8: +1 -1 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 Debuggen mit Coroutinen
2
3 =head1 Einführung
4
5 Im Laufe der Jahre habe ich eine ganz eigene Methode zum Debuggen
6 meiner Programme entwickelt. Diese Methode kombiniert einige Konzepte
7 (interkative Shell in jedem Programm, Coroutinen), die mehr als reines
8 Debugging ermöglichen.
9
10 Diese Methode möchte ich hiermit vorstellen, vielleicht bringt sie
11 den einen oder anderen auf Gedanken oder stellt sich gar als nützlich
12 heraus...
13
14 =head1 "Traditionelle" Debugging-Methoden
15
16 Nun, es gibt den Perl-Debugger; über diesen wird tatsächlich viel
17 erzählt und geschrieben, und ich nehme an, er wird wirklich häufig
18 benutzt. Aber aus welchen Gründen auch immer, ich konnte mich mit ihm
19 (anders als mit gdb) nie wirklich anfreunden: die einzige Funktion, die
20 ich mit einiger Regelmäßigkeit benutze, ist die Trace-Funktion. Das kann
21 ich sogar als "Fest im Gehirn eingebautes Makro" sofort tippen: "perl -d
22 xxx, dann t, dann c", andere Debugger-Befehle kenne ich nicht.
23
24 Diese Trace-Funktion hat viele Nachteile: sie macht das Programm sehr
25 langsam, sie funktioniert nur, während man im Debugger ist, und häufig
26 hängt mein Programm aus nicht nachvollziehbaren Gründen, und die Ausgabe
27 ist zu umfangreich. Die Hauptnachteile sind aber, daß man den Debugger
28 nicht ein- oder ausschalten kann und daß er interaktiv arbeitet.
29
30 Die Mehrheit meiner Programme sind langlebige Hintergrundprogramme. Diese
31 sind immerhin so gut getestet, daß sie in Produktion selten Probleme
32 entwickeln, aber manchmal kommt das natürlich vor. Das erklärt
33 wahrscheinlich, weshalb ich den Perl-Debugger nicht benutze: man kann den
34 Perl-Debugger (meines Wissens) nicht an bestehende Prozesse attachen, man
35 kann die Programme nicht dauerhaft unter dem Debugger starten und man hat
36 im Allgemeinen auch keinen interaktiven Zugang.
37
38 Startet man das Programm neu (z.B. nach Einbau einiger C<warn>-statements
39 oder um es unter dem Debugger laufen zu lassen) tritt das Problem
40 natürlich nicht mehr auf, oder man müsste zu lange warten bis der
41 entscheidende "Trigger" kommt.
42
43 Hinzu kommt, daß viele meiner Programme stark ereignisgesteuert
44 arbeiten: Hält man das Programm an, gibt es unerwünschte Timeouts;
45 erstellt man einen Trace, springt dieser wild zwischen Programmteilen hin
46 und her, die nichts miteinander zu tun haben.
47
48 Als einzige Möglichkeit (die auch häufig angewendet wird), bleibt meist
49 nur, extrem viel mitzuloggen, so daß man im Fehlerfall zumindest auf eine
50 Art Trace zurückgreifen kann. Natürlich sind die schweren Fehler selten
51 dort, wo man gerade viel mitloggt.
52
53 =head1 Die Entwicklung eines anderen Ansatzes
54
55 =head2 Die Anfänge: ein Webserver
56
57 Um das Jahr 2000 herum schrieb ich einen Web-Server, der vollkommen
58 Event-gesteuert war (buchstäblich: er benutzte das Event-Modul dazu).
59
60 Weil es so einfach war, bekam er bald eine interaktive Shell verpasst:
61
62 sub shell {
63 my $fh = shift;
64
65 while (defined (print $fh "cmd> "), $_ = <$fh>) {
66 s/\015?\012$//;
67
68 if (/^info/) {
69 # bearbeite Kommandos
70 ...
71 }
72 }
73
74 my $port = new Coro::Socket
75 LocalPort => $CMDSHELL_PORT,
76 ReuseAddr => 1,
77 Listen => 1,
78 or die "unable to bind cmdshell port: $!";
79
80 push @listen_sockets, $port;
81
82 async {
83 while () {
84 async \&shell, scalar $port->accept;
85 }
86 };
87
88 Der Code benutzt Coroutinen, sollte aber einfach verständlich
89 sein: C<Coro::Socket> ist das Pendant zu C<IO::Socket>, bei dem Aufrufe
90 wie C<accept> die anderen Coroutinen nicht blockieren; die Funktion
91 C<async> startet eine neue ("asynchrone") Coroutine für jede neue
92 Verbindung, und die C<shell>-Funktion schließlich liest in einer Schleife
93 Befehle von der Socket und führt sie aus.
94
95 Ursprünglich dazu gedacht, eine Liste von aktiven Verbindungen zu
96 erhalten bzw. bisweilen IP-Adressen zu blockieren, hatte ich irgendwann
97 eine offensichtliche aber doch nicht so offensichtliche idee (*:
98
99 } elsif (s/^print//) {
100 my @res = eval $_;
101 print $fh "eval: $@\n" if $@;
102 print $fh "RES = ", (join " : ", @res), "\n";
103
104 Mit dem "print"-Kommando (eigentlich wäre "eval" ein besserer Name)
105 lassen sich erstaunlich viele Dinge erledigen:
106
107 Erlaube 200 gleichzeitige Verbindungen mehr:
108
109 print $conn::connections->adjust (200)
110
111 Setze die Downloadrate auf 1MB/s:
112
113 print $conn::tbf_top->{rate} = 1e6
114
115 Erlaube 20 gleichzeitige Downloads mehr:
116
117 print $conn::queue_file->{slots} += 20
118
119 Das erste Beispiel benutzt die korrekte "API" zum Anpassen einer
120 Semaphore, die beiden letzteren Beispiele sind eigentlich "böse
121 Hacks" weil der Code diese Möglichkeit der Anpassung nicht explizit
122 unterstützt, sie funktionieren aber trotzdem.
123
124 Auf diese Weise läßt sich bisweilen auch Debuggen: wenn z.B. eine
125 Verbindung hängt, kann man versuchen, sie zu finden und dann versuchen,
126 herauszufinden, woran es liegt, indem man sich verschiedene globale
127 Variablen anschaut oder kleine "Suchprogramme" mit C<print do "dateiname">
128 ausführt.
129
130 Das kann eine extreme Hilfe sein, ist aber immer noch recht umständlich.
131
132 =head2 Perl ist besser als jede Shell: Deliantra
133
134 Der Deliantra-Server (ein MORPG) ist ebenfalls vollkommen
135 ereignisgesteuert und besitzt ebenfalls eine Shell, die (fast) ohne
136 Coroutinen auskommt:
137
138 sub tcp_serve($) {
139 my ($fh) = @_;
140
141 binmode $fh, ":raw:perlio:utf8";
142 print $fh "\n> ";
143
144 my $iow; $iow = EV::io $fh, EV::READ, sub {
145 if (defined (my $cmd = <$fh>)) {
146 $cmd =~ s/\s+$//;
147
148 if ($cmd =~ /^\s*exit\b/i) {
149 print $fh "will not exit() server.\n";
150 } elsif (...
151 # andere befehle
152 ...
153
154 }
155 };
156 }
157
158 our $LISTENER;
159
160 # now a shell listening on a tcp-port - let the firewall decide access rights
161 if ($cf::CFG{perl_shell}) {
162 if (my $listen = new IO::Socket::INET LocalAddr => $cf::CFG{perl_shell}, Listen => 1, ReuseAddr => 1, Blocking => 0) {
163 $LISTENER = EV::io $listen, EV::READ, sub { tcp_serve $listen->accept };
164 }
165 }
166
167 Deliantra benutzt EV als Event-Bibliothek, ansonsten habe ich aus meinen
168 früheren Versuchen gelernt und implementiere keine Kommandos mehr direkt,
169 sondern erlaube direkt die Eingabe von Perl-Ausdrücken (und stelle
170 stattdessen einfach ein paar Funktionen im Namensraum zur Verfügung):
171
172 ...
173 } else {
174 my $sub = sub {
175 package cf;
176 select $fh;
177
178 # compile first, then execute, as Coro does not support switching in eval string
179 my $cb = eval "sub { $cmd \n}";
180
181 my $t1 = Time::HiRes::time;
182 my @res = $@ ? () : eval { $cb->() };
183 my $t2 = Time::HiRes::time;
184
185 print "\n",
186 "command: '$cmd'\n",
187 "execution time: ", $t2 - $t1, "\n";
188 warn "evaluation error: $@" if $@;
189 print "evaluation error: $@\n" if $@;
190 print "result:\n", cf::dumpval @res > 1 ? \@res : $res[0] if @res;
191 print "\n> ";
192
193 select STDOUT;
194 };
195
196 if ($cmd =~ s/\s*&$//) {
197 cf::async {
198 $Coro::current->desc ($cmd);
199 $sub->()
200 };
201 } else {
202 $sub->();
203 }
204
205 Die Befehlsausführung ist weitaus komplexer: Zuerst wird die eingegebene
206 Zeile kompiliert und dann C<eval>uiert. Danach wird das Ergebnis, etwaige
207 Laufzeitfehler und die Ausführungszeit ausgegeben.
208
209 Da man als Administrator manchmal Befehle im "Hauptprogramm" (die
210 Coroutine, die die Event-Schleife ausführt) ausführen muss, der Server
211 aber all 120ms ein update generieren muss, muss man lang dauernde Befehle
212 in den "Hintergrund" (eine weitere Coroutine) schieben, was mit einem
213 angehängten "&" geschieht.
214
215 Eine Beispielsession sieht so aus:
216
217 # dmshell
218 Welcome!
219
220 ext::help::reload &
221 ext::books::reload &
222 ext::map_tags::reload &
223 ext::map_world::reload &
224 print JSON::XS->new->pretty->encode({cf::mallinfo})
225
226 > ext::map_world::reload &
227
228 >
229 command: 'ext::map_world::reload'
230 execution time: 0.0744819641113281
231
232 > ext::map_tags::reload &
233
234 >
235 command: 'ext::map_tags::reload'
236 execution time: 1.14403510093689
237
238 > $cf::PLAYER-{schmorp}
239
240 command: '$cf::PLAYER-{schmorp}'
241 execution time: 5.00679016113281e-06
242 evaluation error: Bareword "schmorp" not allowed while "strict subs" in use at (eval 180) line 1, <GEN63> line 6.
243
244 > $cf::PLAYER{schmorp}
245
246 command: '$cf::PLAYER{schmorp}'
247 execution time: 1.09672546386719e-05
248 result:
249 bless( {
250 log_told => {},
251 last_save => "32009728.5217925",
252 rent => {
253 last_online_check => "1200189659",
254 last_offline_check => "1200189659",
255 balance => "-0.737118053715676",
256 apartment => {
257 "/brest/apartments/brest_town_house" => undef,
258 "/scorn/apartment/apartments" => undef
259 }
260 },
261 hintmode => 0,
262 npc_dialog_active => {}
263 }, 'cf::player::wrap' )
264
265 >
266 > "schmorp"->cf::player::find->ob->stats->hp
267
268 command: '"schmorp"->cf::player::find->ob->stats->hp'
269 execution time: 4.79221343994141e-05
270 result:
271 520
272
273 und so weiter... Das ist natürlich eine große Hilfe beim Administrieren
274 oder Debuggen, weil man sich die aktuellen Daten, die im Server geladen
275 sind, direkt ansehen kann.
276
277 Sehr angenehm ist es auch, direkt im Spielbetrieb Bugs zu fixen, indem man
278 einzelne Routinen direkt überschreibt:
279
280 # cat /tmp/bugfix
281 package cf;
282 sub _can_merge {
283 # neue merge-logik, vielleicht mit printf-style-debugging
284 }
285 1
286
287 # dmshell
288 > do "/tmp/bugfix"
289
290 So kann man im laufenden Betrieb schon sehr angenehm am Server arbeiten,
291 aber man muss sich jedesmal eine Shell ausdenken, sie implementieren, und
292 kann dennoch nur herumstochern.
293
294 Doch mit Coroutinen kann mehr wesentlich mehr...
295
296
297 =head1 Coroutinen
298
299 Mit Perl hat man prinzipiell zwei Methoden der
300 "Parallelverarbeitung": Coroutinen (teilen sich einen gemeinsamen
301 Adressraum, laufen aber nicht wirklich parallel) und Prozesse (haben
302 getrennte Adressräume, laufen aber parallel (mit entsprechender
303 Hardware)). Threads (gemeinsamer Adressraum und echte Parallelität)
304 werden von Perl nicht angeboten.
305
306 Gegenüber Prozessen hat man den Vorteil extrem einfacher Kommunikation
307 zwischen den einzelnen Instanzen. Beispielsweise implementiert der schon
308 genannte Webserver einen Schutz gegen segmentierte Downloads, indem er die
309 gerade heruntergeladenen URLs pro Klient in einem globalen Hash speichert:
310
311 if ($DOWNLOADS{$url}{$clientid} >= 4) {
312 # abort, zu viele Verbindungen
313 return;
314 }
315
316 ++$DOWNLOADS{$url}{$clientid};
317
318 Mit mehreren Prozessen ist dies natürlich nicht so einfach.
319
320 Ein weiterer Vorteil von Coroutinen ist das stark vereinfachte Locking: es
321 gibt praktisch keine Race-Conditions, denn solange man nicht absichtlich
322 Rechenzeit abgibt, wird man auch nicht unterbrochen.
323
324 Aber wie hilft das beim Debuggen?
325
326 In einem ereignisgesteuerten Programm hat man meistens wenige Watcher
327 (z.B. einen pro TCP-Verbindung). Der aktuelle Zustand einer Verbindung
328 steht in irgendwelchen Variablen serialisiert. Das kann entweder eine
329 Zustandsmaschine sein (entweder Ad-Hoc oder z.B. mit POE) oder auch
330 per Continuation-Style durch den auf den Watcher gebundenen Callback,
331 mit einigen lexikalischen Variablen, in die man garnicht so einfach
332 reinschauen kann.
333
334 Die ausgeführten Programmzeilen sind relativ uninteressant, das Programm
335 verbringt wohl die meiste Zeit in der Hauptschleife (z.B. C<EV::loop> oder
336 C<< Gtk->main >>), und Backtraces helfen garnicht ("steckt das Programm in
337 C<lese_von_socket> weil es noch auf den Header wartet oder ist es schon
338 beim Lesen des Request-Bodies? In welcher Variable steht der aktuelle
339 Zustand nochmal?").
340
341 Bei einem "herkömmlichen" - nicht ereignisgesteuertem - Programm ist
342 es viel einfacher: "Programm steckt in Zeile 231, da liest er gerade den
343 Header ein".
344
345 Und genau diese einfache Relation "Ort entspricht Zustand" erhält man mit
346 Coroutinen. Natürlich braucht man Unterstützung vom Laufzeitsystem, und diese
347 Hilfe kommt von...
348
349 =head1 Coro::Debug
350
351 Seit Version 4.0 gibt es in der Coro-Distribution das
352 C<Coro::Debug>-Modul.
353
354 Dieses implementiert nicht nur eine interaktive Shell (bzw. auch
355 Einzelteile, mit denen man das selbst tun kann), sondern auch eine
356 Übersicht über die Coroutinen, Backtraces und Aufruftraces.
357
358 =head2 Benutzung
359
360 Die einfachste Methode, um an eine Shell zu kommen, ist es, einen "UNIX
361 Socket Server" zu starten:
362
363 use Coro::Debug;
364
365 $CORO_DEBUGGER = new_unix_server Coro::Debug "/tmp/debug";
366
367 Und schon kann man sich mit F</tmp/debug> verbinden... Oder auch
368 nicht: Die bisherigen Beispiele benutzten TCP-Sockets, da konnte man
369 C<telnet> benutzen, aber wie macht man das mit UNIX Sockets, und
370 warum?
371
372 An dieser Stelle möchte ich herzlichst ein feines Werkzeug namens
373 C<socat> empfehlen, mit dem man sehr einfach Verbindungen zwischen zwei
374 Punkten schaffen kann, z.B. zwischen der Readline-Bibliothek und einer
375 UNIX Socket:
376
377 socat readline unix:/tmp/debug
378
379 Und man hat - im Gegensatz zum Perl-Debugger - auch noch echtes Readline
380 :-> C<socat> kann übrigens auch anders, z.B. C<netcat> ersetzen: C<socat
381 readline tcp:127.0.0.1:80>. Oder einen UDP-Server implementieren: C<socat
382 system:mcookie udp-listen:676>. Oder einen einfachen Proxy, oder... es ist
383 sehr hilfreich!
384
385 Der Grund, eine UNIX Socket zu benutzena, ist der Sicherheitsaspekt: Um einen
386 TCP-Port zu schützen (die Shell macht ja keinerlei Authentifizierung)
387 braucht man schon einen Firewall, um eine UNIX Socket zu schützen
388 muss man sie nur in ein Verzeichnis legen auf das nur legitime
389 Benutzer/Programme Zugriff besitzen (F</tmp/debug> ist also ein schlechtes
390 Beispiel). Auf diese Weise kann man diese Shell relativ unbedenklich
391 immer aktiviert lassen (auch z.B. in der Produktionsumgebung):
392
393 system "rm -rf /tmp/myprog";
394 mkdir "/tmp/myprog", 0700 or die "/tmp/myprog: $!";
395 $CORO_DEBUGGER = new_unix_server Coro::Debug "/tmp/myprog/debug";
396
397 Auf diese Weise haben nur diejenigen Zugriff auf den Prozess, die sowieso
398 Zugriff haben.
399
400 Und nun zu einigen der eingebaute Befehle (es gibt auch das
401 C<help>-Kommando, und alles, was nicht ein bekanntes Kommando ist, wird
402 als Perl-Ausdruck interpretiert):
403
404
405 =head2 "Prozessliste"
406
407 Mit dem C<ps>-Kommando erhält man eine Übersicht aller laufenden
408 Coroutinen, hier als Beispiel in einem laufenden Deliantra-Server:
409
410 > ps
411 PID SS RSS USES Description Where
412 10289200 US 865k 830k [main::] [/deliantra/ext/dm-support.ext:47]
413 10289392 -- 2508 66 [coro manager] [/opt/perl/lib/perl5/Coro.pm:177]
414 10289712 -- 2508 2094 [unblock_sub scheduler] [/opt/perl/lib/perl5/Coro.pm:589]
415 13776976 -- 2548 13 [EV idle process] [/opt/perl/lib/perl5/Coro/EV.pm:65]
416 18176656 -- 2964 53k timeslot manager [/deliantra/cf.pm:397]
417 23103792 -- 19k 42k player scheduler [/deliantra/ext/login.ext:510]
418 46912554817888 -- 2980 1 follow handler [/deliantra/ext/follow.ext:50]
419 19457280 -- 138k 432k map scheduler [/deliantra/ext/map-scheduler.ext:65]
420 18681088 -- 3228 6312 music scheduler [/deliantra/ext/player-env.ext:77]
421 26391616 -- 2980 40k worldmap updater [/deliantra/ext/item-worldmap.ext:114]
422 140130800 -- 16k 3746 [async_pool idle] [/opt/perl/lib/perl5/Coro.pm:258]
423 286210960 -- 16k 22k [async_pool idle] [/opt/perl/lib/perl5/Coro.pm:258]
424 196084816 -- 10k 1251 [async_pool idle] [/opt/perl/lib/perl5/Coro.pm:258]
425 439518192 -- 3268 2 addme init [/deliantra/ext/login.ext:21]
426
427 Die C<PID> ist nichts anderes als die Addresse des Perl-Coroutinenobjektes
428 (C<$obj+0>). Die C<SS>-Spalte (State/Stack) gibt an, ob eine Coroutine
429 gerade läuft (rB<U>nning, B<R>eady oder weder noch), bzw. ob sie einen
430 eigenen C-Stack hat (B<S>) oder nicht (B<->), oder gerade getraced wird
431 (C<T>), mehr dazu später. C<RSS> ist der Speicherverbrauch der Coroutine
432 in Bytes. C<Uses> gibt an, wie oft die Coroutine Rechenzeit zugeteilt
433 bekommen bzw. wieder abgegeben hat.
434
435 Die C<Description>-Spalte gibt lediglich den Inhalt des C<desc>-Members
436 der Coroutinenstruktur wieder, den man einfach setzen kann:
437
438 $Coro::current->{desc} = "login phase 1";
439 ... tue etwas
440 $Coro::current->{desc} = "login phase 2";
441 ...
442
443 Die meisten Coroutinen setzen einfach einen Namen, manche ändern den
444 Namen je nach Ort, so daß man sofort sehen kann, in welchen "Zustand" die
445 Coroutine ist.
446
447 Und C<Where> schließlich gibt an, wo im Programm sich die Coroutine
448 gerade befindet (dabei nimmt sich C<Coro> selbst aus, sonst würde dort
449 fast immer nur C<Coro::Semaphore>, C<Coro::Signal> usw. auftauchen).
450
451 Der Server benutzt eine Menge Coroutinen (die meisten sind sehr
452 kurzlebig): Spieler regelmäßig speichern erledigt z.B. der C<player
453 scheduler>, der hier gerade in Zeile C<510> von C<login.ext> steckt:
454
455 Coro::EV::timer_once $SCHEDULE_INTERVAL;
456
457 Das macht Sinn, da er nur alle paar Sekunden aktiv wird und den Rest der
458 Zeit schläft (in C<timer_once>).
459
460 Ein anderes Beispiel ist C<addme init>, was einen Teil des Login-Prozesses
461 darstellt. Zeile C<21> ist in einer Funktion C<query>, die den Benutzer
462 etwas fragt (naheligenderweise Name oder Passwort). Um mehr zu erfahren,
463 braucht man einen Backtrace:
464
465 =head2 Backtraces
466
467 Backtraces erhält man mit dem C<bt>-Kommando und der "PID" als Argument:
468
469 > bt 439518192
470 coroutine is at /deliantra/ext/login.ext line 21
471 ext::login::query('cf::client::wrap=HASH(0x3d7dfb0)',
472 0, 'What is your name?\x{a}:')
473 called at /deliantra/ext/login.ext line 208
474 ext::login::__ANON__ called at -e line 0
475 Coro::_run_coro called at -e line 0
476
477 Nicht nur sieht man sofort, was den Benutzer gefragt wird (weil C<query>
478 den Fragetext als Parameter übergeben bekommt), sondern man sieht auch,
479 wo im Login-Prozess man sich gerade befindet, nämlich in Zeile C<208>
480 (was innerhalb des C<on_addme>-Callbacks von Deliantra ist).
481
482 Noch mehr Informationen erhält man mit einem Aufruftrace:
483
484 =head2 Aufruftraces/Ablaufverfolgung
485
486 Zuerst sollte man den "Logging Level" in seiner Shell auf 5 oder höher schrauben:
487
488 > loglevel 5
489
490 Dann kann man Aufruftraces mit C<tr> und C<ut> starten und stoppen:
491
492 > tr 439518192
493 2008-01-13Z04:03:28.1954 (5) [439518192] tracing enabled
494 (timestanmps usf. im Folgenden gekürzt)
495 .8374 (5) [pid] enter Coro::State::_cctx_init with (277569264)
496 .8375 (5) [pid] leave Coro::State::_cctx_init returning ()
497 .8375 (5) [pid] leave ext::login::query returning (schmorp)
498 .8376 (5) [pid] enter ext::login::check_playing with (cf::client::wrap=HASH(0x3d7dfb0),name)
499 .8376 (5) [pid] enter cf::player::find_active with (schmorp)
500 .8376 (5) [pid] leave cf::player::find_active returning ()
501 .8378 (5) [pid] enter cf::client::send_drawinfo with
502 (cf::client::wrap=HASH(0x3d7dfb0),Welcome name, please enter your password...,5)
503 .8378 (5) [pid] leave cf::client::send_drawinfo returning ()
504 .8379 (5) [pid] enter ext::login::query with (
505 cf::client::wrap=HASH(0x3d7dfb0),4,What is your password?\x{0a}:)
506 .8380 (5) [pid] enter cf::client::query with (
507 cf::client::wrap=HASH(0x3d7dfb0),4,What is your password?\x{0a}:,CODE(0x6fca040))
508 .8380 (5) [pid] leave cf::client::query returning ()
509
510 Bei diesem Tracing wird bei jedem Funktionsaufruf bzw. bei jeder Rückkehr
511 aus einer Funktion eine Zeile ausgegeben die Funktionsnamen, Argumente
512 bzw. Resultatsliste ausgibt.
513
514 Im Beispiel wurde die Ablaufverfolgung aktiviert, während der
515 Spiel-Klient noch in der Namensabfrage steckte (der Aufruf von
516 C<_cctx_init> ist ein Implementationsdetail von Coro, der das Starten
517 eines neuen Interpreters anzeigt, mehr dazu später). Konsequenterweise
518 sieht man daher nur die Rückkehr aus dem C<query>-Aufruf, mit dem Namen
519 als Resultat (C<schmorp>).
520
521 Danach schaut der Server nach, ob der User gerade spielt
522 (C<check_playing>), was wiederum C<find_active>) aufruft). Weil er nicht
523 spielt kann man mit dem Login fortfahren und nach dem Passwort fragen,
524 usf.
525
526 Es ist gut möglich, daß der Server während der Ausführung einer
527 Coroutine viele andere Dinge tut (wenn der Login-Callback den Spieler von
528 der Platte lädt, läuft der Server erstmal weiter, denn der Zugriff kann
529 durchaus mal eine Sekunde oder länger dauern, wenn z.B. gerade viel I/O
530 stattfindet). Da die Ablaufverfolgung nur für eine (die interessante)
531 Coroutine aktiviert ist, bekommt man auch nur für diese die Meldungen,
532 was ungemein hilfreich ist.
533
534 C<Coro> selbst unterstützt auch eine zeilenweise Ablaufverfolgung
535 ähnlich wie im Perl-Debugger, es gibt jedoch noch kein Kommando um dieses
536 zu aktivieren (ich hab's schlicht noch nie vermisst). Wenn man sich das
537 C<Coro::Debug>-Modul ansieht, sieht man such, daß man seine eigenen
538 Trace-Callbacks definieren kann und damit recht viele Tricks möglich
539 sind.
540
541 =head3 "printf-debugging"
542
543 Die Ablaufverfolgung benutzt die C<Coro::Debug::log $loglevel,
544 $msg>-Funktion zum ausgeben der Meldungen, mit der man auch eigene Meldungen ausgeben kann,
545 wahlweise auch nur wenn Tracing aktiviert ist:
546
547 Coro::Debug::log 6, "some log message"
548 if $Coro::current->is_traced;
549
550 (Vielleicht sollte die nächste Version von Coro::Debug einen
551 coroutinen/shellabhängigen Loglevel anbieten...)
552
553 =head3 Automatische Aktivierung
554
555 Die Ablaufverfolgung kann man auch progrrammatisch aktivieren, z.B. wenn
556 die Coroutine kurzlebig ist oder man unbedingt den Anfang mitkriegen
557 möchte:
558
559 Coro::Debug::trace;
560 # ab hier wird getraced
561
562 Coro::Debug::untrace;
563 # ab hier nicht mehr, boa!
564
565 =head3 Problemchen
566
567 Ablaufverfolgung geht nicht mit allen Coroutinen:
568
569 PID SS RSS USES Description Where
570 10289200 US 865k 926k [main::] [/deliantra/ext/dm-support.ext:47]
571
572 > coro tr 10289200
573 2008-01-13Z04:33:58.7787 (5) [123282976] unable to enable tracing:
574 cannot enable tracing on coroutine with custom stack
575 at /opt/perl/lib/perl5/Coro/Debug.pm line 205, <GEN88> line 2.
576
577 Der Grund liegt in der Implementation: Sowohl der Perl-Debugger als auch
578 Coro müssen eine andere Implementation des Interpreters verwenden, der
579 langsamer läuft, aber den Ablauf verfolgen kann. Wie also kann man diese
580 mit Coro an- oder abschalten?
581
582 Coro arbeitet, indem jede Coroutine quasi ihren eigenen (abgespeckten)
583 Interpreter verpasst bekommt. Damit der Speicherverbrauch gering und
584 vor allem die Geschwindigkeit hoch sind, teilt Coro verschiedenen
585 Coroutinen nach Möglichkeit den gleichen Interpreter zu. Dies ist
586 genau dann möglich, wenn sich die Coroutine in der äußersten
587 Interpreterschleife befindet (d.h. keine rekursiven Aufrufe gemacht
588 wurden. Ein rekursiver Aufruf findet statt, wenn man eine C/XS-Funktion
589 aufruft und diese wiederum Perl, was z.B. bei fast allen Callback-Systemen
590 vorkommt). Außerdem hat das Hautprogramm immer seinen eigenen
591 Interpreter.
592
593 Genau dies sieht man im rechten Teil der C<SS>-Spalte: Tracing kann
594 nur dann aktiviert werden, wenn dort ein C<-> steht, und auch nur dann
595 deaktiviert werden, wenn die Coroutine während des Tracens keine
596 rekursiven Mätzchen macht.
597
598 In der Praxis ist das kein Problem: man kann ja einfach alles in eine
599 Coroutine verlegen und das Hauptprogramm schlafenlegen, oder das Tracing
600 aktivieren (z.b. programmatisch), bevor man beispielsweise in die
601 Event-Schleife springt: Das verlangsamt die Ausführung zwar, aber nicht
602 sehr (einige Prozent).
603
604 =head2 Code-Injection
605
606 Manchmal kann es recht hilfreich sein, einer Coroutine etwas Code
607 unterzujubeln. Auf diese Weise kann man z.B. Backtraces erhalten:
608
609 > eval 277575488 Carp::cluck "holladrio"
610
611 Oder man kann auf lokale Variablen zugreifen (z.B. mit dem
612 C<PadWalker>-Modul).
613
614 Das Ganze geht auch programmatisch:
615
616 $coro->eval ("string");
617 $coro->call (sub { ... });
618
619 Und es funktioniert sogar, wenn die Coroutine sich in einem XS-Aufruf
620 befinden sollte.
621
622
623 =head1 Ausblick
624
625 C<Coro::Debug> ist recht neu und die sich ergebenden Möglichkeiten noch
626 nicht wirklich ausgelotet. Ich denke, daß es aber jetzt schon sehr
627 hilfreiche Mittel zum Debugging zur Verfügung stellt.
628
629 Ich hoffe auch, ich konnte ein paar Anregungen zum Thema "jedes
630 Perl-Programm braucht eine interaktive Shell" zu liefern: Es ist
631 wirklich hilfreich, und mit Perl (mit oder ohne Coro) sehr einfach zu
632 implementieren.
633
634 =head1 Autor
635
636 Marc Lehmann <schmorp@schmorp.de>.
637
638 =head1 Anhang: dumpval
639
640 Wen's interessiert, ich verwende die folgende "dumpval"-Funktion schon
641 seit vielen Jahren, und sie gibt recht lesbare Debug-Dumps:
642
643 # dumpval $ref
644
645 sub dumpval {
646 eval {
647 local $SIG{__DIE__};
648 my $d;
649 $d = new Data::Dumper([$_[0]], ["*var"]);
650 $d->Terse(1);
651 $d->Indent(2);
652 $d->Quotekeys(0);
653 $d->Useqq(1);
654 #$d->Bless(...);
655 $d->Seen($_[1]) if @_ > 1;
656 $d = $d->Dump();
657 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge;
658 $d
659 } || "[unable to dump $_[0]: '$@']";
660 }
661
662 Früher habe ich mal Dumpvalue benutzt, ist auch nicht schlecht (das
663 PApp::Catch_STDOUT ist ein Hack, den man in neueren Perls durch PerlIO
664 ersetzen kann):
665
666 sub dumpval {
667 eval {
668 local $SIG{__DIE__};
669 my $d;
670 local *STDOUT;
671 local *PApp::output;
672 tie *STDOUT, PApp::Catch_STDOUT;
673
674 require Dumpvalue;
675 $d = new Dumpvalue globPrint => 1, compactDump => 0, veryCompact => 0;
676 $d->dumpValue($_[0]);
677 $d = $PApp::output;
678 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge;
679 $d;
680 } || "[unable to dump $_[0]: '$@']";
681 }
682