=encoding utf-8 =head1 IO::AIO - Asynchronous Input/Output =head2 Motivation In vielen Situationen wäre es schön (oder ist man sogar gezwungen), bestimmte Reaktionszeiten einzuhalten: Ein Web-Server, der viele Sockets bedient, sollte sich möglichst rechtzeitig um diese kümmern können. Ein interktives Spiel muss bestimmte Antwortzeiten einhalten. Und manchmal wäre es einfach nur hilfreich, wenn man etwas anderes tun könnte, während der Rechner auf die Festplatte wartet. Unix bietet herkömmlicherweise relativ wenig Unterstützung für derartige Anliegen. Daher habe ich das Modul IO::AIO entwickelt, mit dem sich alle wichtigen I/O-Operationen asynchron abwickeln lassen. =head2 Asynchron mit Synchronisation Asynchron bedeutet, daß die eigentliche Ein-/Ausgabe, z.B. das schreiben einer Datei, gleichzeitig mit anderen Aktivitäten eines Programmes geschieht. Dies unterscheidet asynchrone I/O von non-blocking I/O, bei letzterem wird das Programm zwar ebenfalls nicht behindert, die eigentliche Ein-/Ausgabe findet jedoch immer noch synchron statt. Beispiel: aio_readdir "/etc", sub { my $entries = shift or die "error while reading /etc: $!"; print "read /etc, entries: @$entries\n"; }; Dieser Aufruf erzeugt eine readdir-Anfrage, die asynchron bearbeitet wird, d.h. C kehrt sofort zurück. Sobald das Verzeichnis eingelesen wurde, wird der übergebene Callback mit den Einträgen aufgerufen. Da Perl nicht einfach jederzeit unterbrochen werden kann (bzw. man dies auch garnicht zu jedem Zeitpunkt möchte), geschieht die Verarbeitung von Resultaten in IO::AIO synchron, d.h. die eigentliche Ein-/Ausgabe findet asynchron statt, aber der Callback, der das Ende der Operation signalisiert, wird nur zu bestimmten Zeiten aufgerufen, und zwar immer dann, wenn das Perl-Programm IO::AIO::poll_cb aufruft. Wie man dies tut ist relativ unwichtig. Wann dagegen ist wichtig, und um die Integration von IO::AIO in bestehende Event-Modelle zu erleichtern, stellt IO::AIO einen filedeskriptor zur Verfügung: sobald dieser lesbar wird, sollte man C aufrufen. Für Gtk2 sieht das z.B. so aus: add_watch Glib::IO IO::AIO::poll_fileno, in => sub { IO::AIO::poll_cb; 1 }; Für AnyEvent dagegen etwas komplexer, da es einen Filehandle benötigt: open my $fh, "<&=" . IO::AIO::poll_fileno or die "$!"; my $w = AnyEvent->io (fh => $fh, poll => ’r’, cb => sub { IO::AIO::poll_cb }); =head2 The Works Hat man IO::AIO erst einmal integriert, kann man wild drauf loslegen. Fast alle Operationen, die mit Dateien zu tun haben, werden von IO::AIO unterstützt: aio_sendfile aio_read aio_write aio_open aio_close aio_stat aio_lstat aio_unlink aio_rmdir aio_readdir aio_symlink aio_readlink aio_fsync aio_fdatasync aio_rename aio_link aio_mknod aio_mkdir Allen ist gemeinsam, daß sie ein oder mehrere Parameter verlangen und als letztes einen Callback, der im Fehlerfall oder bei erfolgreichem Abschluß der Operation aufgerufen wird. Anders als die eingebauten Perl-Funktionen geben die meiste aio-Operationen direkt den Status des entsprechenden Syscalls zurück: # Perl stat $path or die ... # IO::AIO aio_stat $path, # vvv and statt or sub { $_[0] and die "..." } =head2 The Features IO::AIO bietet auch eine Reihe von zusammengesetzten Anfragen. So verschieben bzw. kopieren C C einzelne Dateien. Mit C kann man eine Datei komplett einlesen, C löscht Verzeichnisbäume, C liest eine Datei in den OS-Cache (pre-caching) und C liefert alle Einträge eines Verzeichnisses in zwei Gruppen sortiert zurück: Verzeichnisse und Sonstige. Der Algorithmus von C ist recht komplex aber dafür sehr, sehr schnell. =head2 The Specialties Die meisten der etwas spezielleren Anfragen (sogenannte I) bestehen aus mehreren Einzelanfragen. C z.B. öffnet die Datei und liest dann deren Inhalt mit C. Damit diese Anfragen nach aussen wie eine Anfrage aussehen, kann man Anfragen mit C zusammengruppieren. Die Implementation von C sieht Beispielsweise so aus: sub aio_load($$;$) { aio_block { my ($path, undef, $cb) = @_; my $data = \$_[1]; my $pri = aioreq_pri; my $grp = aio_group $cb; aioreq_pri $pri; add $grp aio_open $path, O_RDONLY, 0, sub { my $fh = shift or return $grp->result (-1); aioreq_pri $pri; add $grp aio_read $fh, 0, (-s $fh), $$data, 0, sub { $grp->result ($_[0]); }; }; $grp } } Zur Erläuterung: C ist eine Art Locking-Mechanismus, der benötigt wird, wenn man asynchone Rückmeldungen wünscht. Das ist nicht Normalbetrieb und darf hier ignoriert werden. Zuest wird daher eine Gruppe erzeugt (C dient dazu, Anfragen untereinander zu prioritisieren). In diese Gruppe wird eine C-Anfrage gesteckt und - falls erfolgreich - gleich noch das C. =head2 Request-Objekte Alle Anfragen liefern (auf Wunsch :) ein Objekt zurück: my $buffer; my $loader = aio_load $path, $buffer, sub { # $buffer voll, hoffentlich }; Mit diesem Objekt kann man jede Operation unter andere, abbrechen, falls diese nicht schon bearbeitet wird: $loader->cancel; # wills nicht mehr haben Dies ist vor allem für interaktive Programme nützlich, die auf Benutzerwunsch eine Operationen starten, diese aber abbrechen, falls der Benutzer nicht mehr daran interessiert ist. =head2 Anwendungsbeispiel IO::AIO wird, unter anderem, von dne folgendne drei Programmen benutzt: =head3 Gtk2::CV Ein interaktives Bildbetrachtungsprogramm. Geschrieben wurde es, um die wichtigsten Features von XV in das 21te Jahrhundert zu retten, bzw. schnell und effizient große Bildverzeichnisse zu durchforsten. Es benutzt IO::AIO extensiv, um z.B. Dateien für den Thumbnailprozess zu lesen (es ist wahrscheinlich einer der schnellsten indexer "wo gibt"), Dateien zu verschieben, zu löschen und vieles mehr. =head3 myhttpd Ist ein single-Prozess-Webserver, der schon vor fast 10 Jahren 1400 Downloader gleichzeitig mit 80MBit/s von nur zwei Festplatten bedienen konnte. Damals noch mit Linux::AIO, benutzt er inzwischen IO::AIO für fats alle Dateiperationen, insbesondere natürlich für das Serven großer Dateien. =head3 Crossfire+ Crossfire+ ist ein grafisches Multiplayer-Online-RPG (bzw. eines der ältesten). Perl und IO::AIO haben ihm vor kurzer Zeit beigebracht, Karten/Levels und andere Spieldaten im Hintergrund zu Laden und zu Speichern, so daß auch bei vielen Spielern und einem laufenden Backup im Hintergrund keine lästigen Pausen mehr entstehen. =head2 Coro::AIO - IO::AIO linearisiert Mit Coro::AIO kann man alle IO::AIO-Operationen wieder "linear" benutzen. Crossfire+ z.B. benutzt IO::AIO meistens nicht direkt, sondern über Coro::AIO. Eine Map oder andere Daten werden z.B. so geschrieben: if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) { chmod SAVE_MODE, $fh; aio_write $fh, 0, (length $$rdata), $$rdata, 0; aio_fsync $fh; close $fh; aio_rename "$filename~", $filename; } else { warn "FATAL: $filename~: $!\n"; } Den Callback läßt man weg, stattdessen werden die Ergebnisse an den Aufrufer zurückgeliefert. Die aktuelle Coroutine wird zwar blockiert, aber andere Coroutinen (z.B. der Server selbst, der Map-Updates an die Spieler liefert) läuft aber ungehindert weiter. =head2 Autor Marc Lehmann