Sperrkonzept

Verschiedene Aktionen im System schließen sich gegenseitig aus. Das betrifft beispielsweise die gleichzeitige Arbeit von zwei Nutzern an demselben Objekt. Damit nicht zwei Nutzer dasselbe Objekt gleichzeitig bearbeiten können, fordert ein Editor standardmäßig eine Sperre an, bevor er in den Bearbeiten-Modus schaltet. Hat ein Nutzer erfolgreich eine Sperre auf einem Objekt angefordert, kann ein zweiter Nutzer einen Editor auf demselben Objekt nicht mehr in den Bearbeiten-Modus schalten, da das Anfordern der zugehörigen Sperre solange scheitert, solange der erste Nutzer seine Bearbeitung nicht abgeschlossen hat.

Sperren

Eine logische Sperre besteht aus einer Menge von sog. Tokens. Eine Sperre kann dann erfolgreich gesetzt werden, wenn die Anforderung aller ihrer Tokens möglich ist. Für eine logische Operation werden eine Menge von Tokens bestimmt, die notwendig sind, um diese Operation konfliktfrei durchführen zu können.

Token

Ein Token beschreibt einen atomar sperrbaren Aspekt eines Objekts. Es besteht aus einem Fachobjekt und einem Aspektnamen, z.B. "Werte" von "Objekt A". Dabei ist "Werte" der zu sperrende Aspekt von dem referenzierten Fachobjekt "Objekt A".

Exklusiv und shared

Ein Token kann entweder "exklusiv" oder "shared" angefordert werden. Wenn ein exklusives Token angefordert wurde, kann ein zweites Token mit demselben Objekt und Aspekt nicht mehr angefordert werden, bis das ursprüngliche Token wieder freigegeben wurde. Hingegen können mehrere Token für denselben Objekt-Aspekt auch zur selben Zeit "shared" angefordert werden. Allerdings ist es nicht mehr möglich, ein exklusives Token für einen Objekt-Aspekt zu erhalten, wenn es schon "shared" Token für denselben Aspekt gibt. Ein solches exklusives Token kann erst dann wieder angefordert werden, wenn alle shared Token für denselben Aspekt wieder freigegeben sind.

Timeout

Eine Sperre hat prinzipiell eine begrenzte Laufzeit. Der Timeout für eine Sperre wird bei der Anforderung des Sperre festgelegt. Solange der Timeout einer Sperre noch nicht erreicht ist, kann die Laufzeit einer Sperre jederzeit verlängert werden.

LockService service = LockService.getInstance();
Lock lock = service.createLock(operation, object);
lock.lock(timeout);
try {
  ...
  lock.renew(newTimeout);
  ...
} finally {
  lock.unlock();
}

Persistenz

In einem Cluster-Deployment einer Anwendungen wird die Synchronisation der Token-Anforderung über die Datenbank realisiert. Für lokale Tests in der Entwicklungsumgebung kann das persistente Token-Handling abgeschaltet werden, damit beim Neustart der Platform keine Tokens übrig bleiben.

Um das persistente Token-Handling abzuschalten, kann das Konfigurationsfragment devel-ephemeral-locks.config.xml eingebunden werden.

Lock-Service

Der Lock-Service ist in der Anwendung dafür verantwortlich, für eine logische Operation auf einem Objekt, die Menge der Token auszurechen, die für die konfliktfreie Durchführung dieser Operation notwendig sind. Der Lock-Service ist konfigurierbar. In seiner Konfiguration werden für einen gegebenen Objekt-Typ und eine logische Operation eine Menge von Lock-Strategien angegeben, die ausgehend von dem Basisobjekt der Operation eine Menge von Token ausrechen.

Der Lock-Service wird in der Anwendungskonfiguration beschrieben. Der beim Service konfigurierte lock-timeout ist der Default-Timeout für alle Operationen, wenn dort kein eigener Timeout konfiguriert ist.

<config service-class="com.top_logic.base.locking.LockService">
  <instance lock-timeout="30min">
    <!-- Define lock concept for types -->
  </instance>
</config>

Das Sperrkonzept wird für Objekte basierend auf ihrem Typ beschrieben. Der Lock-Service kann ein Sperrkonzept entweder für Objekte eines Modell-Typs oder eines Java-Implementierungstyps definieren.

Modell-Typ:

<model type="tl.element:StructuredElement">
  <!-- Define lock strategies for operations -->
</model>

Java-Implementierungstyp:

<java impl="com.top_logic.knowledge.service.db2.PersistentObject">
  <!-- Define lock strategies for operations -->
</java>

Daneben können auch Lock-Strategien für globale Operationen definiert werden, die sich auf kein Objekt beziehen:

<global>
  <!-- Define lock strategies for global operations -->
</global>

Innerhalb einer solchen Typ-Sektion werden abstrakte Operationen und die zu ihrer Synchronisation notwendigen Sperren definiert. Eine abstrakte Operation ist in erster Linie ein Name, der auch so z.B. in der Konfiguration einer Edit-Komponente als lockOperation hinterlegt ist. Die Default-Operation eines Editors ist editValues (Editieren der direkten Eigenschaften eines Objektes). Die Default-Operation eines Struktur-Editors hingegen ist editStructure (Editieren der gesamten Unterstruktur eines Teilbaumes).

<operation name="editValues" lock-timeout="5min">
  <!-- Define tokens to acquire for performing this operation on a certain object -->
</operation>

Der an einer Operation konfigurierte Lock-Timeout gilt für Locks, die für diese Operation angefordert werden. Wenn kein spezieller Lock-Timeout an einer Operation konfiguriert ist, gilt der globale Timeout der Service-Konfiguration.

Welche Token für eine bestimte Operation notwendig sind, wird über eine Liste sog. LockStrategy-Konfigurationen angegeben. Eine LockStrategy ist im wesentlichen eine Funktion, die das Basisobjekt der Operation auf eine Menge von notwendigen Tokens abbildet.

Es existieren die folgenden vordefinierten LockStrategy-Funktionen, die in der Konfiguration für eine abstrakte Operation angegeben werden können:

Ein Token, das (exclusiv oder shared) genau auf dem Basisobjekt der Operation (z.B. dem Modell des Editors) angefordert wird:

<local aspect="[aspect-name]" kind="[exclusive|shared]"/>

Ein globales Token, das sich auf kein Objekt bezieht und daher unabhängig von dem Basisobjekt der Operation ist:

<global aspect="[aspect-name]" kind="[exclusive|shared]"/>

Token auf allen Vorgänger-Objekten des Basisobjektes in seiner Struktur (vorausgesetzt, das Basisobjekt ist ein StructuredElement-Objekt):

<ancestors aspect="[aspect-name]" kind="[exclusive|shared]"/>

Token auf dem Basisobjekt selbst und allen seinen Vorgänger-Objekten in seiner Struktur:

<ancestors-or-self aspect="[aspect-name]" kind="[exclusive|shared]"/>

Token auf einer frei definierbaren Menge von Objekten, die über eine TL-Script-Funktion ausgerechnet werden kann. Die Funktion erhält das Basisobjekt der Operation als einziges Argument:

<tokens aspect="[aspect-name]" kind="[exclusive|shared]" objects="[function-computing-lock-objects]"/>

Insgesamt könnte eine Lock-Service-Konfiguration wie folgt aussehen:

<config service-class="com.top_logic.base.locking.LockService">
  <instance>
    <java impl="com.top_logic.knowledge.service.db2.PersistentObject">
      <operation name="editValues">
        <local aspect="values" kind="exclusive"/>
      </operation>
    </java>

    <model type="tl.element:StructuredElement">
      <operation name="editValues">
        <local aspect="values" kind="exclusive"/>
        <ancestors-or-self aspect="structure" kind="shared" />
      </operation>
    
      <operation name="editStructure">
        <local aspect="structure" kind="exclusive"/>
        <ancestors aspect="structure" kind="shared" />
      </operation>
    </model>
    
    <global>
      <operation name="startup">
        <global aspect="com.top_logic.util.AbstractStartStopListener"/>
      </operation>
    </global>
  </instance>
</config>

Diese Konfiguration definiert, dass für die Bearbeitung aller persistenten Objekte jeweils ein exklusives Token für den Sperraspekt values genau des bearbeiteten Objektes angefordert wird. Damit können verschiedene Objekte gleichzeitig von verschiedenen Nutzern bearbeitet werden, aber ein und dasselbe Objekt kann immer nur von höchstens einem Nutzer geleichzeitig bearbeitet werden.

Weiter definiert die Konfiguration ein Sperrkonzept für Strukturen. Hier werden für eine einfache Bearbeitung eines Strukturknotens in einem Editor ein exklusives Token für den Sperraspekt values des bearbeiteten Objektes anfgefordert und gleichzeitig shared Tokens für den Sperraspekt structure auf allen Struktur-Vorgängern des bearbeiteten Objektes.

Werden nur Knoten in normalen Editoren mit der Operation editValues bearbeitet, haben die angeforderten Shared-Token für den Sperraspekt structure keine Bedeutung, da sie weder einen Konflikt mit anderen Shared-Token erzeugen noch mit dem exklusiven Token für den Sperraspekt values (da es sich um einen anderen Sperraspekt handelt).

Die Bedeutung der Shared-Token wird erst deutlich, wenn Strukturknoten in Struktureditoren mit der Operation editStructure bearbeitet werden. Diese Operation fordert ein exklusives Token für den Sperraspekt structure auf dem Wurzelobjekt der bearbeiteten Teilstruktur an. Dieses Token steht im Konflikt mit anderen shared Token für denselben Sperraspekt, die in der Operation editValues angefordert werden. In Kombination können also Knoten eines im Struktureditor bearbeiteten Teilbaumes nicht gleichzeitig in einem normalen Editor bearbeitet werden, weil hierfür shared Token auf allen Objekten auf dem Pfad zur Strukturwurzel für den structure-Aspekt notwendig wären. Von diesen steht aber eines im Konflikt mit dem exklusiven structure-Token für die Bearbeitung im Struktureditor.

Aus entsprechenden Gründen ist es auch nicht mehr möglich, eine Ober- oder Unter-Struktur einer Teilstruktur in einem Struktureditor zu bearbeiten, die schon in einem anderen Struktureditor bearbeitet wird. Genausowenig ist es möglich, eine Teilstruktur in einem Struktureditor zu bearbeiten, wenn bereits ein einzelner Knoten dieser Teilstruktur in einem normalen Editor bearbeitet wird.

In-App-Konfiguration

Während der Lock-Service globale Defaults für das Sperrkonzept vorgibt, kann ein Sperrkonzept für neue Fachobjekttypen auch direkt am Typ annotiert werden:

Administrative Sicht

Die aktuell gesetzten Sperren können in der Sicht  Sperren eingesehen und notfalls manuell freigegeben werden.