oberon index <- ^ -> mail ?
Weiter: Verteilte Briefkasten-Objekte, davor: Namensräume als Informationsträger, darüber: Verteilte Anwendungen.

Erzeugung von Knoten in fremdem Auftrag

Das Modul PersMesgMaintenance

Wenden wir uns zunächst dem Problem zu, in einem fremden Prozeß neue Knoten zu erzeugen. Die Lokation eines Objektes ist im Ulmer Oberon-System im Prinzip weder zu sehen, noch spielt sie für dessen Verwendung eine Rolle. Erst die möglichen Konsequenzen für die Lebensdauer des Objekts rücken diesen Aspekt doch wieder ins Blickfeld.

Im konkreten Fall ist es keineswegs notwendig, daß ein Objekt nach seiner Entstehung beliebig migrieren kann -- was mit Persistenz zu erreichen wäre --, sondern nur, daß gesteuert werden kann, wo es stationär eingerichtet wird. Das ist nicht weiter schwierig, denn es wird ohne besondere Vorkehrungen genau dort bleiben, wo es erzeugt wird. Wenn dies also durch eine Methode eines anderen Objekts veranlaßt wird, teilt das neue Objekt anschließend dessen Adreßraum. Daher ist nichts weiter zu tun, als eine solche Methode zu realisieren, und zwar am zweckmäßigsten in Form eines zusätzlichen persistenten Auftrags an ein Objekt, das bereits exportierbar, d.h. mit Aufträgen aus anderen Prozessen erreichbar ist.

Wir wollen einen neuen Knoten, der uneingeschränkt neue Kanten aufnimmt und alle anderen Operationen durch eine bestimmte Berechtigung schützt, in einen Namensraum einfügen lassen. Es ist naheliegend, genau diesen Vorgang als zusätzliche Methode für den Knoten, von dem die Kante ausgehen soll, zu realisieren. Dieses Programmbeispiel zeigt die vollständige Definition des Moduls PersMesgMaintenance, das diese Methode implementiert. Weil nicht alle Knoten um diese Methode erweitert werden sollen, sondern kooperationswillige Server explizit einen ihrer Knoten dazu bestimmen sollen, wird für die Einrichtung des Auftragsbearbeiters eine eigene Prozedur angeboten, statt dies mit Hilfe von Services zu automatisieren.

Realisierung

In diesem Abschnitt soll Schritt für Schritt die Implementierung des Moduls PersMesgMaintenance gezeigt werden. Machen wir uns zunächst klar, was die Methode PersMesgMaintenance.SupplyBox intern im Server eigentlich tun soll (ein eigener Name ist notwendig, um die lokale Version von der im gleichen Modul angesiedelten allgemeingültigen zu unterscheiden):

PROCEDURE DoSupplyBox(msgsys: Names.Node;
                      name: Names.Name; perm: Shards.Pot;
                      VAR box: Names.Node): BOOLEAN;
   VAR
      status: Names.Status;
      amode: Names.AccessMode;
BEGIN
   NEW(status);
   amode := 0;
   WHILE amode < Names.accessmodes DO
      status.perm[amode] := perm;
      INC(amode);
   END;
   Shards.CreateSimplePot(status.perm[Names.insert], TRUE);
   Names.CreateNode(box, status, Names.allCaps);
   IF Names.Insert(msgsys, box, name, NIL) THEN
      RETURN TRUE
   END;
   box := NIL;
   RETURN FALSE
END DoSupplyBox;

Der angeforderte Briefkasten wird als Standardknoten erzeugt, dessen sämtliche Berechtigungen durch den angegebenen Autorisierungsagenten bestimmt werden, mit Ausnahme der Zugriffsart insert, die uneingeschränkt zugelassen wird. Wenn sich eine auf diesen Knoten verweisende Kante in den Verwaltungsknoten einfügen läßt, wird die Funktion erfolgreich beendet, andernfalls der neue Briefkasten wieder gelöscht und Mißerfolg signalisiert. Es wäre üblich, dem Verwaltungsknoten dazu ein entsprechendes Fehlerereignis mitzugeben, was hier der Einfachheit halber ausgespart wird. Mindestens ein Ereignis wird im Fehlerfall auch bereits von Names.Insert beigesteuert.

Es folgen die Definition eines persistenten Auftragstyps und ein Bearbeiter, der die interne Implementierung aufruft. Man beachte die temporäre Umlenkung von Fehlerereignissen und die Art, wie das Funktionsresultat im Auftragsverbund vermerkt wird.

TYPE
   SupplyBoxMsg = POINTER TO SupplyBoxMsgRec;
   SupplyBoxMsgRec =
      RECORD
         (Messages.MessageRec)
         name: Names.Name;   (* IN *)
         perm: Shards.Pot;   (* IN *)
         box:  Names.Node;   (* OUT *)
      END;

PROCEDURE Handler(obj: Messages.Object;
                  VAR msg: Messages.Message);
BEGIN
   IF (msg IS SupplyBoxMsg) & (obj IS Names.Node) THEN
      WITH msg: SupplyBoxMsg DO
         WITH obj: Names.Node DO
            RelatedEvents.Save(obj);
            RelatedEvents.Forward(obj, msg.errors);
            msg.done :=
               DoSupplyBox(obj, msg.name, msg.perm, msg.box);
            RelatedEvents.Restore(obj);
            msg.processed := TRUE;
         END;
      END;
   END;
END Handler;

Die Konstruktor-, Schreib- und Leseprozeduren, die den Auftragsverbund persistent machen, CreateSupplyBoxMsg, WriteSupplyBoxMsg und ReadSupplyBoxMsg, ergeben sich ohne Schwierigkeiten aus der Typdefinition:

PROCEDURE CreateSupplyBoxMsg(VAR obj: PersistentObjects.Object);
   VAR
      msg: SupplyBoxMsg;
BEGIN
   NEW(msg);
   PersistentObjects.Init(msg, supplyBoxMsgType);
   Messages.Init(msg);
   obj := msg;
END CreateSupplyBoxMsg;

PROCEDURE WriteSupplyBoxMsg(s: Streams.Stream;
                          msg: PersistentObjects.Object): BOOLEAN;
BEGIN
   WITH msg: SupplyBoxMsg DO
      IF msg.processed THEN
         RETURN ~msg.done OR RemoteObjects.Export(s, msg.box)
      END;
      RETURN
         NetIO.WriteConstString(s, msg.name) &
         PersistentObjects.Write(s, msg.perm)
   END;
END WriteSupplyBoxMsg;

PROCEDURE ReadSupplyBoxMsg(s: Streams.Stream;
                          msg: PersistentObjects.Object): BOOLEAN;
BEGIN
   WITH msg: SupplyBoxMsg DO
      IF msg.processed THEN
         RETURN ~msg.done OR RemoteObjects.Import(s, msg.box)
      END;
      RETURN
         NetIO.ReadConstString(s, msg.name) &
         PersistentObjects.Read(s, msg.perm)
   END;
END ReadSupplyBoxMsg;

Beim Lesen und Schreiben der Komponenten wird anhand des Erledigungskennzeichens msg.processed die Transportrichtung unterschieden, um nur die jeweils relevanten Komponenten übertragen zu müssen. Der neue Knoten wird über RemoteObjects weitergegeben.

Man beachte, daß für das Versenden des Auftrags selbst weder in diesem Modul noch in Messages explizit Bezug auf RemoteObjects genommen wird; vielmehr ist es eine Eigenschaft von Stellvertreter-Objekten, Aufträge über Netzverbindungen hinweg zu delegieren. Sollte sich ein Auftrag zufällig an ein Objekt im eigenen Adreßraum richten, ist kein Stellvertreter-Objekt zwischengeschaltet und die Persistenz des Auftrags bleibt unerheblich; stattdessen aktiviert er direkt den lokalen Bearbeiter.

In CreateSupplyBoxMsg wird auf die Typvariable supplyBoxMsgType Bezug genommen, die global definiert ist und anfangs ein einziges Mal mit PersistentObjects.RegisterType initialisiert wird:

VAR
   supplyBoxMsgType: Services.Type;

PROCEDURE InitInterface;
   VAR
      if: PersistentObjects.Interface;
BEGIN
   NEW(if);
   if.create := CreateSupplyBoxMsg;
   if.read := ReadSupplyBoxMsg;
   if.write := WriteSupplyBoxMsg;
   if.createAndRead := NIL;
   PersistentObjects.RegisterType(supplyBoxMsgType,
      "PersMesgMaintenance.SupplyBoxMsg", "Messages.Message", if);
END InitInterface;

Nun sind alle Voraussetzungen gegeben, um die von PersMesgMaintenance exportierten Prozeduren zu implementieren:

PROCEDURE InitServer(msgsys: Names.Node);
BEGIN
   Messages.InstallHandler(msgsys, Handler);
END InitServer;

PROCEDURE SupplyBox( msgsys: Names.Node;
                     name: Names.Name; perm: Shards.Pot;
                     VAR box: Names.Node): BOOLEAN;
   VAR
      msg: SupplyBoxMsg;
BEGIN
   CreateSupplyBoxMsg(msg);
   msg.name := name;
   msg.perm := perm;
   Messages.Send(msgsys, msg);
   box := msg.box;
   RETURN msg.done;
END SupplyBox;

Das Serverprogramm

Damit andere Programme die neue Dienstleistung nutzen können, muß mindestens ein Serverprogramm diese zur Verfügung stellen, d.h. ein Objekt einrichten, das die Methode PersMesgMaintenance.SupplyBox versteht und für die potentiellen Klienten auch auffindbar ist. Für die besagte Methode vorbereitet wird ein Knoten bekanntlich mit der Prozedur PersMesgMaintenance.InitServer. Ein probates Mittel, ihn allgemein erreichbar zu machen, ist es, einen Namen zu vergeben, unter dem er in einen prozeßübergreifenden Namensraum eingebunden wird. Dies könnte im einfachsten Fall etwa so formuliert werden:

CONST
   myname = "/etc/msg";

PROCEDURE StartService;
   VAR
      msgsys: Names.Node;
      etype: Events.EventType;
BEGIN
   Names.CreateNode(msgsys, NIL, Names.allCaps);
   PersMesgMaintenance.InitServer(msgsys);
   IF ~Paths.Insert(myname, msgsys, NIL, msgsys) THEN
      Conclusions.Conclude(
         msgsys, Errors.fatal, "can't install msgsys")
   END;
END StartService;

Ein Prozeß muß seine Bereitschaft, mit anderen Prozessen in Kontakt zu treten, ausdrücklich bekunden, indem er einen oder mehrere Netzwerkanschlüsse als ports für RemoteObjects registriert. Dadurch wird unter anderem für jeden dieser Anschlüsse eine Task aufgesetzt, die auf Verbindungen wartet. Ein "Kochrezept", um alle verfügbaren Verbindungsarten ausfindig und für RemoteObjects nutzbar zu machen, stellt die folgende Prozedur dar:

PROCEDURE OpenAllKindsOfPorts;
   VAR
      network: Networks.Network;
      it: Iterators.Iterator;
      port: Networks.Socket;
      address: Networks.Address;
      errors: RelatedEvents.Object;
BEGIN
   NEW(errors); RelatedEvents.QueueEvents(errors);
   Networks.GetNetworks(it);
   WHILE Iterators.Get(it, network) DO
      IF ~Networks.CreateSomeSocket(port, network, address,
	    errors) OR
            ~RemoteObjects.AddPort(port, address, errors) THEN
         Conclusions.Conclude(errors, Errors.error, "open ports");
      END;
   END;
END OpenAllKindsOfPorts;

Die beiden gezeigten Prozeduren sind alles, was ein Programm aufzurufen braucht, um zu einem minimalen Server für unser Mitteilungs-System zu werden. Weil von RemoteObjects Tasks ins Leben gerufen werden, wird es auch nicht mehr von selbst terminieren. Zum guten Benehmen eines Servers gehörte allerdings ein Weiteres, und zwar sollte gerade seine Beendigung in angemessener Weise steuerbar sein. Naheliegend wäre es z.B., auf die Termination des exportierten Objektes mit der Termination des Prozesses als Ganzem zu reagieren. In der Prozedur StartService wäre dies etwa mit folgender Anweisungsfolge zu erreichen:

   Resources.TakeInterest(msgsys, etype);
   Events.Handler(etype, ExitHandler);

Hierbei bezeichnet etype eine Variable vom Typ Events.EventType und steht ExitHandler für einen geeigneten Ereignisbearbeiter, auf den hier aber nicht näher eingegangen wird. Es sei lediglich angemerkt, daß die von RemoteObjects erzeugten auf neue Verbindungen wartenden Tasks mit RemoteObjects.RemoveAllPorts und die anderen, für bestehende Verbindungen zuständigen Tasks dieses Moduls mit RemoteObjects.CloseAllConnections wieder beendet werden können.


oberon index <- ^ -> mail ?
Weiter: Verteilte Briefkasten-Objekte, davor: Namensräume als Informationsträger, darüber: Verteilte Anwendungen.
Martin Hasch, Oct 1996