• 沒有找到結果。

1.4 Software Components

1.4.3 Modules

Not surprisingly, most of the modules described in this book perform some interac-tion with the corresponding modules on peer processes; after all, this is a book about distributed computing. It is, however, also possible to have modules that perform only local actions. As there may exist multiple copies of a module in the runtime system of one process concurrently, every instance of a module is identified by a corresponding identifier.

To illustrate the notion of modules, we describe a simple abstract job handler module. An application may submit a job to the handler abstraction and the job han-dler confirms that it has taken the responsibility for processing the job. Module1.1 describes its interface. The job handler confirms every submitted job. However, the interface explicitly leaves open whether or not the job has been processed at the time when the confirmation arrives.

Module 1.1: Interface and properties of a job handler Module:

Name: JobHandler, instance jh.

Events:

Request: ⟨ jh, Submit | job ⟩: Requests a job to be processed.

Indication:⟨ jh, Confirm | job ⟩: Confirms that the given job has been (or will be) processed.

Properties:

JH1: Guaranteed response: Every submitted job is eventually confirmed.

Algorithm1.1is a straightforward job-handler implementation, which confirms every job only after it has been processed. This implementation is synchronous because the application that submits a job learns when the job has been processed.

A second implementation of the job-handler abstraction is given in Algorithm1.2.

This implementation is asynchronous and confirms every submitted job immedi-ately; it saves the job in an unbounded buffer and processes buffered jobs at its own speed in the background.

Algorithm 1.2 illustrates two special elements of our notation for algorithms:

initialization events and internal events. To make the initialization of a component explicit, we assume that a special ⟨ Init ⟩ event is generated automatically by the

Algorithm 1.1: Synchronous Job Handler Implements:

JobHandler, instance jh.

upon eventjh,Submit|jobdo process(job);

triggerjh,Confirm|job;

runtime system when a component is created. This event may initialize some data structures used by the component and perform some setup actions. For instance, in the asynchronous job handler example, it is used to create an empty buffer. The last uponstatement of Algorithm 1.2 represents an event handler that responds to an internalevent, as introduced in the previous section.

Algorithm 1.2: Asynchronous Job Handler Implements:

JobHandler, instance jh.

upon eventjh,Initdo buffer:=;

upon eventjh,Submit|jobdo buffer:= buffer∪ {job}; triggerjh,Confirm|job; upon buffer̸= ∅do

job:=selectjob(buffer);

process(job);

buffer:= buffer\ {job};

To demonstrate how modules are composed, we use the job-handler module and extend it by a module that adds a layer on top; the layer may apply an arbitrary transformation to a job before invoking the job handler on it. The composition of the two modules is illustrated in Fig.1.3.

The interface of the job transformation layer adds an ⟨ Error ⟩ event, which occurs when the transformation fails, but not otherwise; the interface shown in Module1.2.

An example of a transformation is given in Algorithm1.3. The layer implements a bounded-length queue of jobs waiting to be processed. The jobs are stored in an array buffer of length M, which is initialized to the M-vector of ⊥-values, denoted by [⊥]M. Two variables top and bottom point into buffer such that the next arriving job is stored at index top and the next job to be removed is at index bottom. To keep the code simple, these variables are unbounded integers and they are reduced modulo M to access the array. The algorithm interacts synchronously

TransformationHandler (th)

<th,Submit >

<jh,Submit > <jh,Confirm >

<th,Confirm >

<th,Error >

JobHandler (jh)

Figure 1.3:A stack of job-transformation and job-handler modules Module 1.2: Interface and properties of a job transformation and processing abstraction Module:

Name: TransformationHandler, instance th.

Events:

Request: ⟨ th, Submit | job ⟩: Submits a job for transformation and for processing.

Indication:⟨ th, Confirm | job ⟩: Confirms that the given job has been (or will be) transformed and processed.

Indication:⟨ th, Error | job ⟩: Indicates that the transformation of the given job failed.

Properties:

TH1: Guaranteed response: Every submitted job is eventually confirmed or its transformation fails.

TH2: Soundness: A submitted job whose transformation fails is not processed.

with the underlying job handler and waits before submitting the next job until the previously submitted job has been confirmed. When Algorithm1.3is combined with the synchronous job handler (Algorithm1.1), the run-time system does not need any unbounded buffers.

Modules are usually instantiated statically; this happens only once and occurs implicitly when the implementation of another component includes the module among the list of its used modules. There is one static instance of every module, which may be shared by many modules. A protocol module can also be instantiated

Algorithm 1.3: Job-Transformation by Buffering Implements:

TransformationHandler, instance th.

Uses:

JobHandler, instance jh.

upon eventth,Initdo top:=1;

bottom:=1; handling:= FALSE; buffer:=[⊥]M;

upon eventth,Submit|jobdo if bottom+ M =topthen

triggerth,Error|job; else

buffer[topmod M + 1] :=job;

top:= top+ 1;

triggerth,Confirm|job; upon bottom<tophandling=FALSEdo

job:= buffer[bottommod M + 1]; bottom:= bottom+ 1;

handling:= TRUE;

triggerjh,Submit|job; upon eventjh,Confirm|jobdo

handling:= FALSE;

dynamically with an a-priori unknown number of instances. The initializations of dynamic instances are mentioned explicitly in the code of the algorithm that calls them.

All module abstractions in this book are presented as isolated instances, in order to keep their descriptions simple. Every instance has an identifier. When a higher-level algorithm invokes multiple instances of a lower-higher-level abstraction, we ensure that every instance is named by a unique identifier. Any application that uses the abstractions should respect the same rule.