An Introduction to the Coordination Language Manifold
Kees Everaars & Farhad Arbab June 21, 2000
Contents 1 Manifold in a Nutshell: an Overview and an Example 1.1 1.2 1.3 1.4 1.5 1.6
Processes . . . . . . . . . . . . . Ports . . . . . . . . . . . . . . . . Streams . . . . . . . . . . . . . . Events and State Transitions . . An Arti cial Table Tennis Game Trying it Out . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. 6 . 7 . 7 . 7 . 8 . 11
Install and Check the Manifold Installation . . . . . . . . Unpack the Tar le of this Tutorial . . . . . . . . . . . . . Install DID (Delight in Disorder) . . . . . . . . . . . . . . Adapt the GNU Make les . . . . . . . . . . . . . . . . . . Initialization of the Underlying Communications Software 2.5.1 Starting Up the PVM Daemon . . . . . . . . . . . 2.5.2 Choose a Thread Package . . . . . . . . . . . . . . Running the Tennis Game . . . . . . . . . . . . . . . . . . Calling the Executable(s) by Name . . . . . . . . . . . . . The File Billboard . . . . . . . . . . . . . . . . . . . . . . 2.8.1 Billboard Examples . . . . . . . . . . . . . . . . . Scope of the Make le . . . . . . . . . . . . . . . . . . . . . Documentation of the Manifold system . . . . . . . . . . . Using Manifold Utilities Directly* . . . . . . . . . . . . . . From Getting Started to Getting Acquainted . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
2 Getting Started 2.1 2.2 2.3 2.4 2.5
2.6 2.7 2.8 2.9 2.10 2.11 2.12
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
5
3 Getting Acquainted
13
13 13 14 14 14 15 15 15 19 19 21 23 23 24 25
27
3.1 Producer and Consumer as Regular Manifolds . . . . . . . . . . . . . . . . . . . . . 30 3.2 Producer and Consumer as Internal Workers . . . . . . . . . . . . . . . . . . . . . 30 3.3 Producer and Consumer as External Workers . . . . . . . . . . . . . . . . . . . . . 31
4 The Managers
4.1 Manifolds Events Driven Nature . . . . . . . . . . . . . . . 4.1.1 ROMMEL HOEKJE . . . . . . . . . . . . . . . . . . 4.2 The Pipeline Concept . . . . . . . . . . . . . . . . . . . . . 4.2.1 Some Examples of States whose Bodies are Pipelines 4.2.2 Undeterministic Behaviour of a Pipeline . . . . . . . 4.3 The Block Concept . . . . . . . . . . . . . . . . . . . . . . . 4.4 The Group Concept . . . . . . . . . . . . . . . . . . . . . . 4.5 Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5.1 BK Streams . . . . . . . . . . . . . . . . . . . . . . . 4.5.2 KK Streams . . . . . . . . . . . . . . . . . . . . . . . 4.5.3 KB Streams . . . . . . . . . . . . . . . . . . . . . . . i
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
33
33 34 34 35 36 37 37 37 39 39 39
CONTENTS
ii 4.5.4 BB Streams . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5.5 Reconnectable BK Streams . . . . . . . . . . . . . . . . . 4.5.6 Reconnectable KB Streams . . . . . . . . . . . . . . . . . 4.6 Behaviour of p -> c under Dierent Circumstances . . . . . . . 4.7 Condition Constructs and Loop Constructs . . . . . . . . . . . . 4.8 The Primitive Actions . . . . . . . . . . . . . . . . . . . . . . . . 4.8.1 The Primitive Actions Creating Virtual Processes . . . . 4.8.2 Other Primitive Actions . . . . . . . . . . . . . . . . . . . 4.8.3 A Primitive Action as Parameters or Source of a Stream .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
39 39 39 40 40 40 41 42 44
5 The Internal Workers
45
6 The External Workers 7 Manner
47 49
8 Application Construction and Termination
51
9 Trace Messages
55
5.1 How to Write a Neat Atomic Process . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.1 Regular Manners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 7.2 Atomic Manners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 8.1 Application Construction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 8.2 Application Termination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 8.3 General Termination Protocols of Manifold Processes . . . . . . . . . . . . . . . . . 53
9.1 The who-what-where label . . . . . . . . . . . . . . . . . 9.2 Trace Messages in Managers . . . . . . . . . . . . . . . . 9.2.1 Trace Manners . . . . . . . . . . . . . . . . . . . 9.2.2 Trace Manifolds . . . . . . . . . . . . . . . . . . 9.2.3 Some Alternative Ways* . . . . . . . . . . . . . . 9.2.4 More Advanced Message Printing in Managers* . 9.2.5 Using Preprocessor Identi ers in Messages* . . . 9.3 Messages in Internal Workers . . . . . . . . . . . . . . . 9.3.1 Checking Return Values of AP Calls . . . . . . . 9.3.2 Making Your Own Messages . . . . . . . . . . . . 9.3.3 Manner Names in who-what-where label . . . . . 9.3.4 Disable Message Tracing . . . . . . . . . . . . . . 9.4 Messages from External Workers . . . . . . . . . . . . . 9.5 Application Development and Tracing . . . . . . . . . .
10 Debugging Manifold Applications
10.1 The Standard Way to Debug . . . . 10.1.1 Making Sandwiches . . . . . 10.2 Race Conditions and Deadlock . . . 10.3 Some Additional Guidelines & Tips . 10.3.1 Using Prototypes . . . . . . . 10.3.2 Handle the Error Events . . . 10.3.3 Setting Switches . . . . . . . 10.3.4 Problems with Stacksize . . . 10.3.5 Head breakers . . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
56 57 57 60 63 64 64 65 65 66 67 68 69 69
71
71 72 74 76 77 78 80 81 81
CONTENTS
11 Some Instructive Examples
11.1 Playing with variables . . . . . . . 11.1.1 Fibonacci Series . . . . . . 11.2 The Sieve of Eratothenes . . . . . 11.3 Bucket Sort . . . . . . . . . . . . . 11.4 Quick Sort . . . . . . . . . . . . . . 11.5 Domain Decomposition . . . . . . 11.6 The Dining Philosophers Problem . 11.7 A Water Installation . . . . . . . . 11.8 The 8-puzzle Problem . . . . . . .
iii . . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
83
84 85 90 95 101 106 107 110 112
12 Miscellaneous and Advanced Features 13 Multi Task Applications A Of All Sorts B Contents of the libdid.a library
113 115 117 119
C D E F
121 125 127 129
B.1 manner wam(process p) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 B.2 manifold black hole() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 B.3 manner id(process p) returns process . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Manifold Basics in Questions & Answers Format Some Remarks about the Make le Glossary Ideen
iv
CONTENTS
List of Figures 1.1 A pingpong game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.1 Three streams connected to stdout . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.1 The MANIFOLD process . . . . 3.2 The MANIFOLD application . . 3.3 Dierent MANIFOLD processes: worker . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 a) a manager b) an internal worker c) an external . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.1 State Transitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 4.2 Reconnectable BK stream: the hose of a vacuum cleaner . . . . . . . . . . . . . . . 40 4.3 Reconnectable KB stream: the hose of a fuel tank at a gas station . . . . . . . . . 40 11.1 An in nite loop using a variable . . . . . . . . . . . 11.2 The Fibonacci series . . . . . . . . . . . . . . . . . . 11.3 The sieve of Eratothenes . . . . . . . . . . . . . . . . 11.4 The network connection in the sieve of Eratothenes . 11.5 Bucket Sort . . . . . . . . . . . . . . . . . . . . . . . 11.6 Bucket sort . . . . . . . . . . . . . . . . . . . . . . . 11.7 The atomic processes at work . . . . . . . . . . . . . 11.8 Quick Sort . . . . . . . . . . . . . . . . . . . . . . . . 11.9 Domain Decomposition . . . . . . . . . . . . . . . . 11.10The Five Philosophers . . . . . . . . . . . . . . . . .
v
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
84 86 90 91 95 96 98 101 106 107
vi
LIST OF FIGURES
List of Tables 2.1 Scope of the make le . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.1 Scope of the make le . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 4.2 Behaviour of dierent stream types between p and c under dierent circumstances 41 4.3 Primitive Actions as Parameters or Source of a Stream . . . . . . . . . . . . . . . . 44 9.1 Manner macros that can be used in regular manifolds (managers) . . . . . . . . . . 57 9.2 Manifold macros that can be used in regular manifolds (managers) . . . . . . . . . 60 9.3 Macros that can be used in internal (atomic) workers . . . . . . . . . . . . . . . . . 65 C.1 Abbreviations used in the examples . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
1
2
LIST OF TABLES
Preface This document is an introduction to the coordination language MANIFOLD. 1 . This introduction does not replace the reference manual[2] but teaches you how to use many of the facilities summarized there. Although the reference manual[2] is the ultimate document for all kind of questions, it is not suited as an introduction to MANIFOLD. It is as with a magni er, in a reference manual we can see everything very clearly, especially the things we are not looking for. To use it comfortably we should rst learn to direct the magni er on what we are looking for. This introduction will help you do that. This introduction and the sources of the examples therein, are available on-line at http://www.cwi.nl/~ farhad/manifold.html/tut.tar.Z (see 2.2). This Introduction to MANIFOLD assumes that you have completed an introductory C programming text, or have equivalent experience. In Chapter 1 we give a very short introduction to the MANIFOLD coordination language. It gives you an idea of what kinds of things you can use MANIFOLD for. Using already some MANIFOLD terminology we can say that this Chapter could best best read by a manager. However, when you really want to work with MANIFOLD you should also read some other Chapters. In Chapter 2 we describe how to install Manifold, prepare your environment for running the MANIFOLD examples, and nally how to use the make le belonging to this tutorial, in order to produce the executables of the examples. In Chapter 3 we discuss three dierent ways to implement manifolds. The possibilities are: as a manager, as a internal worker, or as an external worker. In Chapters 4-6 we work this further out. In Chapter 4, we discuss how to write managers. A manager is coordinator module and is always written in the MANIFOLD language. In this Chapter, the emphasis is on the basics of the MANIFOLD language (syntax, concepts, and MANIFOLD terminology). In Chapter 4, we discuss how to write internal workers. An internal worker is a worker module that runs as thread in a multi threaded executable and is always written in the a conventional programming language (ANSI C or C++ in this case). Chapter 6 shows how to use external workers (i.e., heavy weight Unix executables) as in manifold processes. In MANIFOLD we can also write subroutines (in the MANIFOLD language or in ANSI C or C++). In MANIFOLD, a subroutines is called a \manner". In Chapter 7 we learn everything about writing manners. Chapter 8 deals with application construction and termination. In Chapter 9 we show a convenient way to produce trace messages. In Chapter 10 we explain how to debug a MANIFOLD application. In Chapter 11 we use the basics explained in the previous chapters and give a number of full examples. Chapter 12 includes the more advanced features of the MANIFOLD language. In Chapter 13 we discuss how to write (distributed) multi-task applications. Appendix A provides a summary of additional MANIFOLD programs. These programs mostly came into existence as by-products while we were working on bigger manifold applications. Their 1 Not all the Chapters are nished: nished are Chapter 1, Chapter 2, partly Chapter 3, partly Chapter 8, Chapter 9, Chapter 10, partly Chapter 11, Appendix B, Appendix C,
3
4
LIST OF TABLES
aim was mostly to explore how certain prede ned manifolds or manners could be used. We give only a short description of these programs. In appendix B we provide a complete summary of the contents of the libdid.a library. We use this library for producing trace messages and debugging. In appendix C we provide a complete summary of the basic manifold terminology and rules. This appendix is provided for reference purposes. The contents of this appendix must be known by heart. In appendix D we discuss the make le belonging to this tutorial and in appendix E we provide a glossary of MANIFOLD terminology. All sections with an asterisk (*) can in principle be skipped in a rst reading. One of the easiest ways to waste time and eort is to write a manifold that was already written by someone else. That is why we ask MANIFOLD users who have written MANIFOLD examples that can be instructive or useful to other users, to email them to Kees Everaars (
[email protected]). Then we will take them up in the Appendix A and make them available to other users. For more information about the MANIFOLD project, we refer to our html pages located at http://www.cwi.nl/cwi/projects/manifold.html and http://www.cwi.nl/ farhad/CWICoordina.html The cover of this booklet is a picture from \Lego Mindstorms", which is a Lego system that allows you to set up interprocess communication between dierent programmable Lego elements. With some fantasy, a MANIFOLD user can immediately recognize in this picture MANIFOLD's unit mechanism. Those among us with some more fantasy, can also see MANIFOLD's event mechanism in it by drawing antennas on the Lego bricks. Your comments about this text are very welcome, and can help guide its evolution. You can send them by electronic mail to Kees Everaars (
[email protected]).
Chapter 1
Manifold in a Nutshell: an Overview and an Example In this section, we brie y introduce MANIFOLD. MANIFOLD is used to develop concurrent software, regardless of whether it runs on a parallel or a distributed platform. MANIFOLD is not a parallel programming language: it is a coordination language as opposed to a computation language[7]. MANIFOLD is a complete language (as opposed to a language extension, like Linda[6]) for programming the cooperation protocols of concurrent systems. These protocols describe the routing of the information between various processes that comprise a concurrent application, and the dynamic changes that take place in such routing networks in reaction to events. MANIFOLD is based on the IWIM (Idealized Worker Idealized Manager) model of communication [1]. The basic concepts in the IWIM model (and thus also in MANIFOLD) are processes, events, ports, and channels (in MANIFOLD called streams) (see sections 1.1 through 1.4). In IWIM a process can be regarded as a worker process or a manager (or coordinator) process. In IWIM an application is built as a (dynamic) hierarchy of worker and manager processes. Lowest in the hierarchy are pure worker processes that don't do any coordinating activities. Highest in the hierachy is a pure coordinator. A process between the lowest and highest level may consider itself as a worker doing a task for a manager higher in the hierarchy or as a manager coordinating processes lower in the hierarchy. Programming in MANIFOLD is a game of dynamically creating process instances and (re)connecting the ports of some processes via streams (asynchronous channels), in reaction to observed event occurrences. Its style re ects the way one programmer might discuss his interprocess communication application with another programmer on telephone (let process a connect process b with process c so that c can get its input; when process b receives event e, broadcast by process c, react to that by doing this and that; etc.). As in this telephone call, processes in MANIFOLD (in this case b and c) do not explicitly send to or receive messages from other processes. Processes in MANIFOLD are treated as black-boxes that can only read or write through the openings (called ports) in their own bounding walls. It is the responsibility of a worker process to perform a (computational) task. A worker process is not responsible for the communication that is necessary for it to obtain the proper input it requires to perform its task (it simply reads this information from its own input port), nor is it responsible for the communication that is necessary to deliver the results it produces to their proper recipients (it simply writes this information to its own output port). In general, no process in IWIM is responsible for its own communication with other processes. It is always the responsibility of a third party - a coordinator process called a manager - to arrange for and to coordinate the necessary communications among a set of worker processes. This third party sets up the communication channel between the output port of one process and the input port of another process, so that data can ow through it. This setting up of the communication links from the outside is very typical for MANIFOLD and has several advantages. One important advantage is that it results in a clear separation between the modules responsible for computation 5
6
CHAPTER 1. MANIFOLD IN A NUTSHELL: AN OVERVIEW AND AN EXAMPLE
(the workers) and the modules responsible for coordination (the managers). This strengthens the modularity and enhances the re-usability of both types of modules (see [3, 1, 4]). A MANIFOLD application consists of a (potentially very large) number of (light- and/or heavyweight) processes running on a network of heterogeneous hosts, some of which may be parallel systems. Processes in the same application may be written in dierent programming languages. Some of them may not know anything about MANIFOLD, nor the fact that they are cooperating with other processes through MANIFOLD in a concurrent application. The MANIFOLD system consists of a compiler called (MC), a run-time system library, a number of utility programs, libraries of builtin and prede ned processes[2], a link le generator called MLINK and a run-time con gurator called CONFIG. MLINK uses the object les produced by the (MANIFOLD and other language) compilers to produce link les needed to compose the application executable les for each required platform. At run time of an application, CONFIG determines the actual host(s) where the processes which are created in the MANIFOLD application will run. The system has been ported to several dierent platforms (e.g., IBM RS60000 AIX, IBM SP1/2, Solaris, Linux, Cray, and SGI). The system was developed with emphasis on portability and support for heterogeneity of the execution environment. It can be ported with little or no eort to any platform that supports a thread facility functionally equivalent to a small subset of the Posix threads, plus an inter-process communication facility roughly equivalent to a small subset of PVM[5]. The MANIFOLD system automatically takes care of the data conversion necessary for communication in a heterogeneous environment. These conversions are only done when the receiving process really attempts to use the data. When data is simply to be passed on to another process on another machine, conversion is not necessary and does not take place. In sections 1.1 through 1.4 we brie y discuss the basic concepts in MANIFOLD and we end our MANIFOLD introduction in section 1.5 with a parallel/distributed \toy" example.
1.1 Processes In MANIFOLD, the atomic workers of the IWIM model are called atomic processes. Any operating system-level process can be used as an atomic process in MANIFOLD. Furthermore, a regular C function running as an independent thread [8] can be used as an atomic process too. Atomic processes can only produce and consume units through their ports, broadcast and receive events, and compute. They cannot set up streams between (ports of) processes. This is something only a coordinator process can do. In this way, the desired separation of computation and coordination is achieved. Coordination processes are written in the MANIFOLD language and are called manifolds. A manifold de nition de nes a process type and consists of a header and a body. The header of a manifold gives its name, the number and types of its parameters, and the names of its input and output ports. The body of a manifold de nition is a block. A block consists of a nite number of states. Each state has a label and a body. The label of a state de nes the condition under which a transition to that state is possible. It is an expression that can match observed event occurrences in the event memory of the manifold instance. The body of a simple state de nes the set of actions that are to be performed upon transition to that state. The body of a compound state is either a (nested) block, or a call to a parameterized subprogram known as a manner in MANIFOLD. A manner consists of a header and a body. As for the subprograms in other languages, the header of a manner essentially de nes its name and the types and the number of its parameters. A manner is either atomic or regular. The body of a regular manner is a block. The body of an atomic manner is a C function that can interface with the MANIFOLD world through an interface library.
1.2. PORTS
7
1.2 Ports A port is a regulated opening at the boundary of a process, through which the information produced and/or consumed by the process is exchanged with other processes. Regulated means that the information can ow in only one direction through a port: it either ows into or out of the process. The information exchanged between a process and other processes through its ports is quantized in discrete bundles called units. A unit is a packet containing an arbitrary number of bits that are produced, transferred, and consumed in an integral fashion; i.e., there are no partial units. Ports through which units ow into a process are called the input ports of the process. Similarly, ports through which units ow out of a process are called the output ports of the process.
1.3 Streams All communication in MANIFOLD is asynchronous. In MANIFOLD, the asynchronous IWIM channels are called streams. A stream is a communication link that transports units. A stream represents a reliable and directed ow of information from its source to its sink. As in the IWIM model, the constructor of a stream between two processes is, in general, a third process. Once a stream is established between (a port of) a producer process and (a port of) a consumer process, it operates autonomously and transfers the units from its source to its sink. When the process at the sink of a stream requires a unit through its port connected to that stream, it is suspended only if no units are available in any of the streams connected to the arrival side of that port. The suspended process resumes as soon as the next unit becomes available for its consumption. The source of a stream is never suspended because the in nite buer capacity of a stream is never lled. There are four basic stream types designated as BB, BK, KB, and KK, each behaving according to a slightly dierent protocol with regards to its automatic disconnection from its source or sink. Furthermore, in MANIFOLD, the BK and KB type streams can be declared to be reconnectable. See [1] or [2] for details.
1.4 Events and State Transitions In MANIFOLD, once an event is raised (broadcast) by a process, the latter continues, while the event occurrence propagates through the environment independently. Any receiver process that is interested in such an event occurrence will automatically receive it in its event memory. An observer process can react to an event occurrence in its event memory at its own leisure. In reaction to such an event occurrence, a manifold instance can make a transition from one labeled state to another. The only control structure in the MANIFOLD language is an event-driven state transition mechanism. More familiar control structures, such as the sequential ow of control represented by the connective \;" (as in Pascal and C), conditional (i.e., \if") constructs, and loop constructs can be built out of this event mechanism, and are also available in the MANIFOLD language as convenience features. Upon transition to a state, the primitive actions speci ed in its body are performed atomically in some non-deterministic order. Then, the state becomes pre-emptable: if the conditions for transition to another state are satis ed, the current state is pre-empted. Pre-empting a state can dismantle the streams (depending on their types) that were connected upon transition to that state. The most important primitive actions in a simple state body are (i) creating and activating processes, (ii) generating event occurrences, and (iii) connecting streams to the ports of various processes.
CHAPTER 1. MANIFOLD IN A NUTSHELL: AN OVERVIEW AND AN EXAMPLE
8
Figure 1.1: A pingpong game
1.5 An Arti cial Table Tennis Game We now illustrate MANIFOLD through an example that implements a game of table tennis between "you " and "me" (see gure 1.1). We give the MANIFOLD source le of this example below (lines numbers have been added). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
#include "MBL.h" #include "rdid.h" event ping, pong. /***************************************************/ manifold ping_pong_player(event e) { auto process v is variable. begin: v = input; MES(v); raise(e); output = v; post(begin). } /***************************************************/ manifold umpire { process you is ping_pong_player(ping). process me is ping_pong_player(pong). begin: (MES("Lets start ping-ponging"), activate(you, me), "ball" -> you, terminated(void)). ping: you -> me. pong: me -> you. } /***************************************************/ manifold Main { begin: umpire. }
This code describes three manifolds (i.e., process types) named ping pong player, umpire and Main (respectively lines 8-13, 16-27, 30-33). A manifold is a template from which we can make process instances. A process instance always has an event memory in which itself or other process instances can put event occurrences. MANIFOLD is an event driven language which means that once a process instance detects an event occurrence in its event memory, the process instance makes a transition out of its current state to the state that is labeled with the name of that event occurrence. Switching to a state also means that the streams that were constructed in the former state are broken down (see chapter 4 for the details). In MANIFOLD syntax, a state looks like \event_name: actions to be executed." and its semantic is \switch to this state when there is an event with this name in the event memory and execute the actions". The most important (primitive) actions are (1) creating and activating process instances, (2) broadcasting events (with the action raise) or putting them in a process' own event memory (with the action
1.5. AN ARTIFICIAL TABLE TENNIS GAME
9
post), (3) connecting processes to each other by setting up streams between their ports (by the action denoted by the arrow ->). A manifold written in the MANIFOLD languages (we can also write them in a traditional programming language such as C) always has the following structure. After the word manifold a name is given (line 8: ping pong player) followed by some optional parameters (line 8: event e). After this rst line, comes the body of the manifold (lines 9-13). The body of a manifold is a block enclosed in a pair of braces (for the ping-pong player, lines 9 and 13) and contains some optional local declarations (line 10 for the ping-pong player, lines 18 and 19 for the umpire) followed by one or more states to which an instance of the manifold can jump when there is a suitable event in its event memory. In our case, we see that the ping pong player and Main manifolds (and thus their instances) have only the begin state. The umpire, on the other hand, has three dierent states: the begin, ping, and pong states. A begin state in MANIFOLD is something special. Activation of a process instance automatically puts an occurrence of the special high priority begin event in the event memory of that process, which results in an initial state transition to the begin state. In our arti cial game of table tennis, we consider a ping-pong player as someone who sequentially performs in his begin state the following actions (sequential execution is syntactically denoted by the connective \;" between the actions): It reads (catches) a ball (line 12: v=input) from its input port and stores it in a variable (line 10). It shows the ball on the screen (line 12: MES(v)). It broadcasts the event it receives as a parameter (line 8) to signal the umpire that it intends to write (return) the ball to its output port (line 12: raise(e)). It puts the begin event in its own event memory (line 12: post(begin)) which results in another round of execution of the actions in the begin state. The umpire manifold can be described as follows: In its local declaration part, the umpire uses the syntactic construct \process x is y.", to create two processes named \you" and \me" as instances of the ping-pong player manifold (line 18, 19). Note that the actual parameters in the process creation \you" and \me" are respectively ping and pong so that \you" always raises ping and \me" always raises pong (line 12: raise(e)). In the begin state, the umpire shows the message \Lets start ping-ponging" on the screen (line 21), activates the two process instances \you" and \me" (line 22), gives a ball to \you" (line 22: "ball" -> you) and waits (denoted by terminated(void) on line 22) until it detects one of the global events (declared on line 5). Because \you" starts returning the ball and raises (broadcasts) the ping event (line 12), the rst event found in the event memory of the umpire is ping which causes a state transition from the begin state to the ping state. In this state a stream is created between \you" and \me" so that the ball can ow through this stream to \me" (line 24: you -> me). The process instance \me" behaves the same way as \you" except that it raises the event pong to signal to the umpire that the ball is written to its output port. In reaction to this event, the umpire makes a state transition out of the ping state, which results in breaking the connection between \you" and \me", and enters the pong state where a new connection between \me" and \you" (line 26: me -> you) is created through which the ball can ow back to \you". It is clear that this table tennis game never stops. The third manifold is Main. The name Main is indeed special in MANIFOLD: there must be a manifold with that name in every MANIFOLD application and an automatically created instance of this manifold called main is the rst process that is started up in an application. In our case
CHAPTER 1. MANIFOLD IN A NUTSHELL: AN OVERVIEW AND AN EXAMPLE
10
the Main manifold has only the begin state in which it automatically creates and activates an instance of the umpire manifold, just by calling the umpire by name (line 32). This implicit process instantiation is an alternative to the explicit creation of a process (as we did on line 18) and explicit activation (as we did on line 22). Our table tennis program is very arti cial and one might think of many other ways to implement it. For instance we can set up the stream connections between the ping-pong players in the umpire in just one state, in which case there is no need to do state transitions and thus there is no need to set up and break the stream connections between the ping-pong players again and again. We chose to implement it as we did because this scheme clearly shows the dynamic changing of connections on the beat of event occurrences which is an important aspect of MANIFOLD's event driven nature. Process instances in a MANIFOLD application always run as separate threads (light weight processes [8]) within an operating-system level process. This latter heavy weight process is called a task instance in MANIFOLD. The way process instances are bundled in task instances in uences the mechanism that is actually used for the data transport in streams (the ->). The bundling can be done automatically or under user control. When we ensure that all process instances of a MANIFOLD application run as threads in the same task instance, the eective data transport in the streams between these process instances is implemented in shared memory. In that case we in fact play \shared memory table tennis". We can also bundle the process instances in such a way that each ping-pong player and the umpire is housed in a separate task instance. In that case the eective data transport in streams between the process instances is implemented using Unix sockets. The mapping of process instances in task instances is considered to be a separate stage in the application construction. This mapping is described in a le which is the input for the MANIFOLD linker MLINK. An example of such input le is shown below (line numbers have been added). 1 2 3 4 5 6 7 8 9 10 11
# pingpong.mlink {task * {load 10} {weight ping_pong_player 10} {weight umpire 10} } {task pingpong {include pingpong.o} }
In this le we specify that a task instance is considered to be \full" when its load exceeds 10 (line 4) and that the weight of an instance of the ping-pong player or umpire is also 10 (line 5, 6). The net result of this is that each task instance will house only one thread and thus instances of ping-pong player and the umpire end up in dierent instances of the task named pingpong (line 9). The primary output of the manifold linker is a make le, plus a number of C source les necessary to provide the inter-task information. This make le is meant to be used as a black-box by recursive make commands in programmerde ned make les that nally create the executable les suitable for the appropriate platforms. With this the task composition stage comes to an end and the nal stage in application construction can start. This is the run-time con guration stage, in which we de ne the mapping of tasks to hosts. This mapping too is described in a le which is the input for the MANIFOLD run-time con gurator CONFIG. An example of such an input le is shown below. 1 2 3 4
{host host1 sampan} {host host2 pont} {host host3 opduwer} {locus pingpong $host1 $host2 $host3}
Here we de ne three variables host1, host2 and host3, which we set to respectively sampan.cwi.nl, pont.cwi.nl and opduwer.cwi.nl. These are the names of computers located at dierent places and connected via a network. The last line in the le states that the instances (in our case three) of the pingpong task can be started on any of these three machines. Note that the dierent mappings in the task composition stage and the run-time con guration stage do not aect the semantics of the MANIFOLD source code.
1.6. TRYING IT OUT
11
Running the ping-pong program using the task composition and run-time con guration described above forms a \distributed table tennis game" and gives the following output. sampan 262149 112 pingpong umpire() pingpong.m 21 -> Lets start ping-ponging opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball
Each of these output lines has the following structure. It starts with a long label followed by a -> before the actual message (the value of the argument of MES). The label shows respectively the machine on which the task instance runs, the identi cation of the task instance, the identi cation of the process instance, the name of the task, the name of the manifold, the name of the MANIFOLD source le, and the line number where MES is called. With such a label in front of the actual message, we always know who is printing what and where.
1.6 Trying it Out Trying out the tennis game example is very simple. After installing the MANIFOLD system and some other simple preparation of your computer environment (see in next chapter 2.1 through 2.5) the command gmake R=pingpong.m r will create and run the tennis game (see 2.6).
12
CHAPTER 1. MANIFOLD IN A NUTSHELL: AN OVERVIEW AND AN EXAMPLE
Chapter 2
Getting Started 2.1 Install and Check the Manifold Installation Before we can run the examples in this introduction we rst have to install the MANIFOLD system. Currently MANIFOLD runs on Unix-like platforms only. The tar le containing the MANIFOLD system is freely available after lling in the software license agreement. This license agreement is available from our web page http://www.cwi.nl/~ farhad/manifold.html. After receiving the signed license agreement we make a tar le available that contains the MANIFOLD system. Unpack this tar le and install the MANIFOLD system as explained in the README le in directory ./manifold/modules. If you did it correctly you now have something like the following lines in the your .profile (we assume you use the Korn shell ksh; if not, do something similar in the shell you use): PVM_ROOT=/hosts/manifold/V2/pvm3 PVM_DPATH=$PVM_ROOT/lib/pvmd MANIFDIR=/hosts/manifold/V2 export PVM_ROOT PVM_DPATH MANIFDIR
If you did not install the MANIFOLD system yourself, you must ask the person who did the installation, how to de ne the above environment variables, and take them up in your pro le. In this case it is not necessary to read the remainder of the section (so go to 2.2). Probably you did the installation yourself. The MANIFOLD installation is correct when you are also able to run the \hello World!" example (in the le hello.m) with the make le named Makefile. These les are in the directory $(MANIFDIR)/modules/machine_name/Test whereby machine name is the output of the \pvm" command $PVM ROOT/lib/pvmgetarch. Follow the instructions in the $(MANIFDIR)/modules/README le on how to run the \Hello World!" example. If \hello World!" does not work (don't forget to start up pvm!), rst install the MANIFOLD system properly before you continue. When you can't solve the problem send an email to Freek Burger (
[email protected]).
2.2 Unpack the Tar le of this Tutorial The tar le of this tutorial is available on-line http://www.cwi.nl/~ farhad/manifold.html/tut.tar.Z. Extract the les from this archive by typing: zcat tut.tar.Z | tar xf -. System V systems require the following tar command instead: zcat tut.tar.Z | tar xof -. If zcat is not available on your system, use separate uncompress and tar commands. The result of the extraction is a directory with the following structure: /BIN /DID /DOC /MAKEFILES /EXAMPLES. 13
CHAPTER 2. GETTING STARTED
14
2.3 Install DID (Delight in Disorder) In the DID directory of the tar le we nd a software package named DID (Delight in Disorder) which we use in the examples in this tutorial. With this package we can safely print messages from our independent running processes to the screen. Normally when you print messages out of independent running processes to the screen, the output is mixed up and is consequently dicult to understand. You might create this situation, for example, when you use \printf" in dierent processes you have written in C and run them in parallel in a MANIFOLD application. The problem is in \printf". \printf" is not always what is called thread-safe. This problem aside, it is also dicult to know from which process instance the message comes. The DID package solves all these problems for you. Using this package it is no \disaster" anymore when we print messages out of parallel or distributed running processes. This explains our choice of the name of this package. Go to the DID directory and type in gmake install. At the end of this gmake run, you are asked to take up in your .profile the DID environment variable. Set it to the advised value, and export it in your .profile. The result of the gmake call on a certain machine type is that we now have a library named libdid.a which contains the safe printing facilities for that machine type. The libdid.a is stored in the $DID/lib/machine_type whereby machine type is the output of the \pvm" command $PVM ROOT/lib/pvmgetarch.
2.4 Adapt the GNU Make les In the MAKEFILES directory of the tar le we nd a make le named makefile which we can use for compiling and running MANIFOLD applications. It is a GNU make le that includes some other xed make les (checker, heart and runner printer) and some other application dependent make les. Before we can use this make le we must verify if certain variables in it are set properly. Therefore do the following:
Check in the le heart if the following variables are properly set. { ECHO: The location of the Unix \echo" command { MAKEDEPEND: The location of the \makedepend" command. \makedepend" is public domain from GNU software and is available via ftp expo.lcs.mit.edu in subdirectory /pub and from numerous other sites { RM: The location of the \rm" command. { CC: The name of the Ansi C compiler for your system. See the enumeration in the le $(MANIFDIR)/modules/README. { GM4: The \gm4" macro package. \gm4" is public domain and is available via ftp expo.lcs.mit.edu in subdirectory /pub and from numerous other sites Check in the le runner printer if the following variables have the desired values. { PRINTER1: The printer where you want your output { PRINTER2: Another printer where you want your output
2.5 Initialization of the Underlying Communications Software In the MANIFOLD system, PVM[5] is used for the inter-task communication. That is why we rst have to start up a PVM daemon process on a host before we can start a MANIFOLD application there (see 2.5.1). For the internal task communication MANIFOLD uses a thread package (e.g.,
2.6. RUNNING THE TENNIS GAME
15
[8]). On some platforms there are various thread packages. In this case we must choose one, otherwise we get the default thread package on that platform (see 2.5.2).
2.5.1 Starting Up the PVM Daemon
safest way to start up an PVM daemon process on the current host is by typing in: $(MANIFDIR)/bin/machine_name/mf_cleanup. You can also do this by typing in $(PVM_ROOT)/lib/pvm or just pvm but then you must rst add the location of the pvm command to your path (for the Korn shell user take up in your .pro le the following: export PATH=$PVM_ROOT/lib:$PATH). The result of the pvm command will be probably that the pvm daemon is telling that it is already running. This is no surprise after the activities of section 2.1 or maybe you have already start up the daemon with the $(MANIFDIR)/bin/machine_name/mf_cleanup command in this section.
2.5.2 Choose a Thread Package
In the le $(MANIFDIR)/modules/README we can read how many and which thread packages are available on which platform. E.g we can read there that on SGI6 machines there are two thread packages named respectively \sproc" and \pthread" Selecting the desired package is done by creating certain environment variables. When we want e.g., \sproc" as thread package we set the environment variable SPROC (type in set SPROC) or when we want the \pthread" as thread package we take care that there is no SPROC variable in the environment (so the \pthread" package is the default on SGI6 platforms). To remove a variable SPROC out of your environment type in unset SPROC. When you always want to use \sproc" as thread package, take up in your .pro le the line export SPROC. Remark: Don't forget to re-link the whole MANIFOLD application when you change from one thread package to another one. Simply throw away your executables and link the application again with a gmake call (see section 2.6).
2.6 Running the Tennis Game everything has been prepared for running MANIFOLD examples. Let us try the tennis game example of chapter 1. The source le of this example is pingpong.m and can be found in the EXAMPLES directory (see 2.2). We can make and run the executable of pingpong.m in the EXAMPLES directory itself or make another working directory. If you want to work in another working directory, copy pingpong.* (i.e., all the les related to the tennis game) to it and also copy or link the make les (makefile, checker, heart, and runner printer) to it 1 . Whatever you do, go to the directory you want to work in, and call the parameterized make le named makefile as follow: gmake R=pingpong.m r
The R variable at the Unix command line in the gmake call is internally used in the make le as a placeholder for all the regular manifolds (i.e., manifolds written in the MANIFOLD language) used in an application. The r in the gmake call is the target in the make le that creates and run the executable. The rst result of the above gmake call is a billboard, with a survey of all the les that are involved in that particular MANIFOLD application (see 2.8 for a full explanation of the le billboard). 1 In order to make the make les available in the another working directory we can go to this working directory and type in tut makefiles (see section 2.11). This command will link the make les (makefile, checker, heart, and runner printer) to the current working directory. Linking is better than copying them, because in this way we are sure we have only one version of the make les.
16
CHAPTER 2. GETTING STARTED ****************************************************************************** ********* This manifold application consists of the following files ********** ****************************************************************************** Managers ==> pingpong.m Workers ==> not used Prototyping ==> not used Task map file ==> pingpong.mlink Config map file ==> pingpong.config Task Type(s) ==> pingpong Output ==> pingpong.out Sorted output ==> pingpong.out.spi Info file ==> pingpong.info ****************************************************************************** ******************************************************************************
From this billboard we can read the following: Managers (i.e., the manifold written in the MANIFOLD language) are stored in the le pingpong.m. Atomic workers (i.e., manifold wrappers around functions written in traditional languages like C, C++, Fortran) are not used. Prototyping .h les, used for storing the prototypes of the functions used in the atomic manifolds, are not used. The task composition stage (see 1.5 and 13) is described in the le pingpong.mlink. The mapping of tasks to hosts (which task run where) is described in the le pingpong.config. The name of the task type (i.e., the executable les) from which an task instance is made is pingpong. The output of the application, as seen on the screen, will be stored in the le pingpong.out. Mostly we want to sort the output of a MANIFOLD application in such a way that the output per process instance is collected together (we explain below the reason for this). This sorted output is stored in the le pingpong.out.spi (the sux spi stands for \sorted per instance"). application Some addition information about the application can be stored in the le pingpong.info. Note that the rst name (without the sux .m) we have typed in after the R in the gmake call, is used for naming some other les used in this MANIFOLD application (see 2.8). After this billboard we see how the executable named pingpong is made. Once the executable is formed, it starts running and we see the following output (which is also stored in the le pingpong.out) on the screen. ******************** OUTPUT STARTS ********************** sampan 262149 112 pingpong umpire() pingpong.m 21 -> Lets start ping-ponging opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball pont 524289 63 pingpong ping_pong_player(event) pingpong.m 12 -> ball
2.6. RUNNING THE TENNIS GAME opduwer 786433 pont 524289 63 opduwer 786433 pont 524289 63 opduwer 786433 pont 524289 63 opduwer 786433 pont 524289 63 opduwer 786433 pont 524289 63
63 pingpong ping_pong_player(event) pingpong.m pingpong ping_pong_player(event) pingpong.m 12 63 pingpong ping_pong_player(event) pingpong.m pingpong ping_pong_player(event) pingpong.m 12 63 pingpong ping_pong_player(event) pingpong.m pingpong ping_pong_player(event) pingpong.m 12 63 pingpong ping_pong_player(event) pingpong.m pingpong ping_pong_player(event) pingpong.m 12 63 pingpong ping_pong_player(event) pingpong.m pingpong ping_pong_player(event) pingpong.m 12
17 12 -> 12 -> 12 -> 12 -> 12 ->
-> ball ball -> ball ball -> ball ball -> ball ball -> ball ball
If you don't see the above output, check if you did all the things described in the sections 2.1 through 2.5, and try it again. If the problem can not be solved by yourself send an email to Kees Everaars (@
[email protected]). As already said in section 1.5, this application is a multi task application (three task instances) running in a (homogeneous) computer network consisting of the machines sampan.cwi.nl, pont.cwi.nl and opduwer.cwi.nl, and the application never stops. That why we have to stop it by ourself. In general, a simple CTRL-C on the machine where we start up the MANIFOLD application (the so called start-up machine), will only stop the rst task instance on that machine. Other tasks instances on this machine (we don't have them in our example) and task instances running on the other machines (we have two other task instances, each running on as dierent computer) have to stop too. Of course, we can go to these machines and kill (with the Unix command kill) the executables there. Easier is to use next gmake call with target clearit: gmake R=pingpong.m clearit
This results in killing all the executables that belongs to the MANIFOLD application. We can also use mf cleanup -a -m pingpong.config
for doing this (see 2.11). We already explained in 2.6 that each output line consists of a long label followed by a -> before the actual message (the value of the argument of MES). This label gives us information about who is printing what and where. What we did not tell is that the order, wherein we see those output lines appear on the screen, is not totally deterministic. Only the order of the lines that are printed to the screen, from within the same manifold is deterministic. Thus in the above output it is quite possible that we see the rst line somewhere later on the screen. However it is not possible that we see the second line later than the fourth line, because these are printed after each other (with MES) in the same manifold instance (they both have the identi cation 786433 63). The reason for this is that in each manifold instance the units read from the output port of the argument of Mes, are transported via one stream to a prede ned process stdout, that prints all the incoming units to the screen. In gure 2.1 we can see how it works. Just imagine that process x produce in succession three units, respectively x1 , x2 , and x3 and transports it via that one stream, which is connected to stdout. Process y transports in this way two units, respectively y1 and y2 , and nally process z transports only z1 . Thus in this case we have three incoming streams for stdout. In general, when we have in MANIFOLD, multiple connections to the same sink port (a "many to one" connection scheme), the order in which the reading process instance (in our case stdout) reads the dierent streams is non-deterministic. Thus it can start with every stream and after reading one or more units from this stream (it is not necessary to read the stream completely empty) it can start reading another stream. For units in a stream, the " fo" rule is applied (so units in a stream can not pass each other). From the above it follows that it is possible that we can see on the screen the sequence y1 , x1 , y2 , x2 , z1 , x3 or y1 , x1 , y2 , z1 , x2 , x3 , but the sequence y1 , x2 , y2 , x1 , z1 , x3 is not possible. Thus, the best thing we can do with the output of the tennis game (stored in pingpong.out), is sort it in such a way that the output of each process instance is collected together. We can do this with next gmake call (the s in the target is the s of sorting):
CHAPTER 2. GETTING STARTED
18 x3 x2 x1
Screen
y2 y1
y1 x1 y2 x2 z1 x3
stdout
z1
Figure 2.1: Three streams connected to stdout gmake R=pingpong.m s
This gives the following sorted output. ******************** SORTING OUTPUT STARTS ******************** sampan 262149 112 pingpong umpire() pingpong.m 21 -> Lets start ping-ponging pont pont pont pont pont pont pont pont pont
524289 524289 524289 524289 524289 524289 524289 524289 524289
63 63 63 63 63 63 63 63 63
pingpong pingpong pingpong pingpong pingpong pingpong pingpong pingpong pingpong
ping_pong_player(event) ping_pong_player(event) ping_pong_player(event) ping_pong_player(event) ping_pong_player(event) ping_pong_player(event) ping_pong_player(event) ping_pong_player(event) ping_pong_player(event)
pingpong.m pingpong.m pingpong.m pingpong.m pingpong.m pingpong.m pingpong.m pingpong.m pingpong.m
12 12 12 12 12 12 12 12 12
-> -> -> -> -> -> -> -> ->
opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 opduwer 786433 63 pingpong ping_pong_player(event) pingpong.m 12 ******************** SORTING OUTPUT FINISHES ******************
ball ball ball ball ball ball ball ball ball -> -> -> -> -> -> -> -> ->
ball ball ball ball ball ball ball ball ball
This sorted output is also stored in the le pingpong.out.spi (the sux spi stands for, sorted per instance). With the sorted output we can always easy reconstruct the overall logic of the MANIFOLD application. In chapter 10 we will see how we can use such output for debugging MANIFOLD applications. Printing all the information about the tennis game (pingpong.m, pingpong.mlink, pingpong.config, pingpong.out, pingpong.out.spi, and pingpong.info) is done with: gmake R=pingpong.m p
The output is send to the printer set in PRINTER1 or PRINTER2 (see 2.4). We can also do the sorting and printing in sequence with make le target sp, so
2.7. CALLING THE EXECUTABLE(S) BY NAME
19
gmake R=pingpong.m sp
For applications with a self terminating character (so not the tennis game) we can create, run, sort, and print, in one gmake call by using the rsp target.
2.7 Calling the Executable(s) by Name In previous section we saw, that we can run the tennis game by typing in: gmake R=pingpong.m r
You might think that this can also be realized by just calling the executable le by name. In principle it can be done in this way, as long as the MANIFOLD application consists of only one task instance. When more task instances are formed in a MANIFOLD application, the PVM daemon has to start up these additional task instances. In order to do this, the PVM daemon always looks in the directory /$HOME/pvm3/bin/machine_name (machine name is the output of the \pvm" command $PVM ROOT/lib/pvmgetarch) for the executable le(s) (in our example we only have one executable le). Therefore we have to make them available in that directory, so copy or better link the executable le(s) to /$HOME/pvm3/bin/machine_name. In the above gmake call this linking is automatically done so we don't have to worry about it. In general we never know in advance, how many task instances are involved in an MANIFOLD application. In the tennis game it was clear because the constrains in the task map le were chosen that way (see 1.5). However in MANIFOLD application where we do not specify constrains in a task map le, the MANIFOLD runtime system may nd it necessary to start up a new task instance (a new instance of the same executable le) because the number of process instances (i.e., threads) within a task instance exceeds a certain platform dependent limit. Calling the executable by name in that case without the necessary link will result in next message: Fatal error in _MaNiF_CreateManifInstIX : Spawn a new task failed
Therefore we always link the executable le(s) to that xed place where the PVM daemon expect to nd them, by using a gmake call like the one above.
2.8 The File Billboard The le billboard is a survey of all the les that are involved in the MANIFOLD application as speci ed in the gmake call. When the ll billboard is inconsistent in some way (see below what that means) then an error report is generated and the gmake call stops prematurely. In this way we always have a consistent set of les before we use the manifold compiler and linker. On the billboard we rst see the input les the MANIFOLD application consists of (the rst ve lines), followed by a number of output les (next four lines) (see billboard on page 19). In 2.8.1 we play a little with gmake calls and show the corresponding billboards. The les on the billboard are respectively:
The source les of the managers. Managers (also called regular manifolds) are implemented in the MANIFOLD language. Their source les must have the .m sux. In the gmake call it are the les after the R=. When you use more les for storing the regular manifold, use double quotes around the lenames in the gmake call (e.g., gmake R="x.m y.m z.m"). The rst .m le after the R= is used in naming some other les, as you will see below. The source les of the atomic workers. Atomic workers are written in traditional programming languages like C, C++, Fortran. We can not call such (e.g., C) functions directly in the source les of the managers. We have to write MANIFOLD wrappers around them as we will see this in the examples in next chapter 3
CHAPTER 2. GETTING STARTED
20
(e.g., fibo.m). In the gmake call the source les of the atomic workers are speci ed after the A=. Normally we don't specify these le names because our make le automatically derives their names from the les used for the managers. E.g., when you have used gmake R="x.m y.m z.m" our make le automatically checks if there are source les for the atomic workers named respectively x.ato.c, y.ato.c and z.ato.c. These automatically named source les always have the .ato.c sux (ato stands for atomic). It is not allowed (see 2.8.1) to have a common base name (i.e., what comes before the dot) for the source les of the atomic workers and source les of the managers ( 2.8.1). The .h les that contain the prototypes of the functions used in the atomic workers. If we use atomic workers then we also must supply the prototyping .h les. The default names of these les are automatically derived from the source les of the atomic workers (.c becomes .h). If we have e.g., the le names x.ato.c, y.ato.c and z.ato.c for the atomic workers, then the names for the .h les are x.ato.h, y.ato.h and z.ato.h. We can also name those les explicitly using the H variable on the command line in the gmake call. It is the users responsibility to include the right .h le in the corresponding .m (by using a pragma) and .c les (by using an include statement). Only when this is done properly, we are sure that a parameter mismatch will result in an syntax error issued by the C compiler. The input le for the MANIFOLD linker MLINK (the so called task map le). In this le the task composition stage (see 1.5 and 13) is described. The default name of this le is x.mlink where x is the base name of the rst le name after the R=. We can also name this le explicitly using the TASK MAP FILE variable on the command line in the gmake call. The input les for the MANIFOLD run-time con gurator CONFIG (the so called con g map le). In this le the mapping of tasks to hosts (which task run where) is described. The default name of this le is x.config where x is the base name of the rst le name after the R=. We can also name this le explicitly using the CONFIG MAP FILE variable on the command line in the gmake call. The name of the task type(s) (i.e., the executable les) from which the task instance(s) is (are) made. These names are speci ed in a task map le or otherwise (i.e., no task map is used or they are not speci ed in it) the name of task type is x where x is the base name of the rst le name after the R=. We can also name this le explicitly using the MANI APP NAME variable on the command line in the gmake call. The le used for storing the output of the application as seen on the screen . The default name of this le is x.out where x is the base name of the rst le name after the R=. We can also name this le explicitly using the MANI APP OUT variable on the command line in the gmake call. The le used for storing the sorted output of the application. Mostly we want to sort the output of a MANIFOLD application in such a way that the output per process instance is collected together (see 2.6). The default name of this le is x.out.spi where x is the base name of the rst le name after the R=. The sux spi stands for \sorted per instance". We can also name this le explicitly using the MANI APP OUT SPI variable on the command line in the gmake call. The le used for storing some extra info about the MANIFOLD application. This extra information can be: { The actual gmake call for that particular application. { Some additional remarks about the applications.
2.8. THE FILE BILLBOARD
21
{ References to relevant sections in MANIFOLD's reference manual or to other relevant
literature. { The name of the MANIFOLD user who was so kind to send us an instructive MANIFOLD source code he wants to share with other users. The default name of this le is x.out where x is the base name of the rst le name after the R=. We can also name this le explicitly using the MANI APP INFO variable on the command line in the gmake call.
2.8.1 Billboard Examples
In next section we do some experiments with the le billboard in your working directory Before we start experimenting, rst some remarks: The command you have to type in below, are in bold. The le billboard is seen on your screen (including an error report when the MANIFOLD application is not consistent) when using the gmake call with a .show target. When there is an error report about the billboard you see question-marks behind the les on the billboard that cause the problem(s) We assume in our experiment that none of the les used below, already exist. We start our experiment.
touch x.m y.m z.m gmake R="x.m y.m z.m" .show The billboard is: ****************************************************************************** ********* This manifold application consists of the following files ********** ****************************************************************************** Managers ==> x.m y.m z.m Workers ==> not used Prototyping ==> not used Task map file ==> virtual task map (because x.mlink does not exist) Config map file ==> virtual config map (because x.config does not exist) Task Type(s) ==> x Output ==> x.out Sorted output ==> x.out.spi Info file ==> not used ****************************************************************************** ******************************************************************************
touch x.ato.c z.ato.c gmake R="x.m y.m z.m" .show The billboard is: ****************************************************************************** ********* This manifold application consists of the following files ********** ****************************************************************************** Managers ==> x.m y.m z.m Workers ==> x.ato.c z.ato.c Prototyping ==> x.ato.h? z.ato.h?
CHAPTER 2. GETTING STARTED
22
Task map file ==> virtual task map (because x.mlink does not exist) Config map file ==> virtual config map (because x.config does not exist) Task Type(s) ==> x Output ==> x.out Sorted output ==> x.out.spi Info file ==> not used ****************************************************************************** ****************************************************************************** **************************** Error report started **************************** The following .h file(s) is (are) missing: x.ato.h z.ato.h. x.ato.h z.ato.h must be included in the appropriate .m files and in the atomic files. **************************** Error report finished ***************************
Note the question-marks behind the les names on the billboard that cause the problem. Simply making the .ato.h les available creates a consistent billboard as is shown in next commands.
touch x.ato.h z.ato.h gmake R="x.m y.m z.m" .show The billboard is: ****************************************************************************** ********* This manifold application consists of the following files ********** ****************************************************************************** Managers ==> x.m y.m z.m Workers ==> x.ato.c z.ato.c Prototyping ==> x.ato.h z.ato.h Task map file ==> virtual task map (because x.mlink does not exist) Config map file ==> virtual config map (because x.config does not exist) Task Type(s) ==> x Output ==> x.out Sorted output ==> x.out.spi Info file ==> not used ****************************************************************************** ******************************************************************************
touch x.con g x.mlink gmake R="x.m y.m z.m" .show The billboard is: ****************************************************************************** ********* This manifold application consists of the following files ********** ****************************************************************************** Managers ==> x.m y.m z.m Workers ==> x.ato.c z.ato.c Prototyping ==> x.ato.h z.ato.h Task map file ==> x.mlink Config map file ==> x.config Task Type(s) ==> x Output ==> x.out Sorted output ==> x.out.spi Info file ==> not used ****************************************************************************** ******************************************************************************
2.9. SCOPE OF THE MAKEFILE
23
Now the MANIFOLD application don't use the default con g and task map le anymore.
gmake R="x.m y.m z.m" A=x.c .show The billboard is: ****************************************************************************** ********* This manifold application consists of the following files ********** ****************************************************************************** Managers ==> y.m z.m x.m? Workers ==> x.c? Prototyping ==> x.h? Task map file ==> x.mlink Config map file ==> x.config Task Type(s) ==> x Output ==> x.out Sorted output ==> x.out.spi Info file ==> not used ****************************************************************************** ****************************************************************************** **************************** Error report started **************************** There is a common base-name in the regular(s) and atomic(s) => FORBIDDEN. The following atomic manifold file(s) do(es) exist: x.c. The following .h file(s) is (are) missing (see the Manifold tutorial section ...): x.h. x.h must be included in the appropriate .m files and in the atomic files. **************************** Error report finished ***************************
The reason why x.c is in this case not allowed as lename for atomic workers is that the MANIFOLD compiler already generates from the regular x.m le, a le with that name.
2.9 Scope of the Make le The make le (consisting of makefile, checker, heart, and runner printer) belonging to this tutorial is only suitable for making the executables for parallel (i.e., it consists of one task instance) or distributed (i.e., it consists of more task instances) MANIFOLD applications, whereby in this latest case we can only run on a homogeneous computer network (i.e., a network of computers consisting of the same type). The make le is not suitable (up to now) for making the executables network parallel distributed homogeneous Yes Yes heterogeneous see chapter 13 Table 2.1: Scope of the make le for a distributed applications that has to run on a heterogeneous computer network (see table 2.1). For this we refer to chapter 13 and/or to the documentation of MLINK (see 2.10).
2.10 Documentation of the Manifold system In $(MANIFDIR)/doc $(MANIFDIR)/doc we nd the following MANIFOLD documentation (compress .ps les). refman.ps.Z is the MANIFOLD reference manual.
24
CHAPTER 2. GETTING STARTED
mc.ps.Z is the manual page of the MANIFOLD compiler named MC. mlink.ps.Z is the manual page of the MANIFOLD linker named MLINK. con g.ps.Z is the manual page of the MANIFOLD run-time con gurator named CONFIG.
2.11 Using Manifold Utilities Directly* Although we can always use the make le, it is sometimes handy to use commands directly at the Unix command line. When you do not intend to do this, skip this section. Below we summarized the commands, give their location and explain their usage. (machine name is the output of the \pvm" command $PVM ROOT/lib/pvmgetarch). mc is the name of the MANIFOLD compiler. Location : $(MANIFDIR)/bin/machine_name Usage : see in the documentation ( 2.10) mlink is the name of the MANIFOLD linker. Location : $(MANIFDIR)/bin/machine_name Usage : see in the documentation ( 2.10) con g is the name of the MANIFOLD run-time con gurator. Location : $(MANIFDIR)/bin/machine_name Usage : see in the documentation ( 2.10) mf cleanup is a Unix script to start or cleanup a \virtual" PVM machine due to the default or speci ed con g.map le. Location : /$(MANIFDIR)/bin/machine_name/mf_cleanup. Usage : mf cleanup [-a] [-m config mapfile] [machine1] [machine2] ..... mf cleanup without arguments start up a PVM daemon. mf cleanup -m mapfile [machine1] [machine2] ....
cleanup a \virtual" PVM machine that consists of the machines mentioned in the config mapfile and/or the additional mentioned machines in the command line. The cleanup operation consists of a) killing the PVM daemon on those machine and b) removing the le pvm?.uid where uid is your numeric user identi cation.
mf cleanup -a -m mapfile [machine1] [machine2] ....
is doing the same things as previous command, but now also all tasks mentioned in the config mapfile are killed. tut make les makes the tutorial make les available in the current directory. Location : $DID/../BIN Usage : tut makefiles. gkill kills all processes on the local machine whose names contains a certain string. Location : $DID/../BIN Usage : gkill string. schoon removes the pvm?.uid le on the local machine where uid is your numeric user identi cation and performs gkill pvm (so that all the processes that have the string \pvm" in their name are killed). Location : $DID/../BIN Usage : schoon.
2.12. FROM GETTING STARTED TO GETTING ACQUAINTED
25
2.12 From Getting Started to Getting Acquainted In next chapters we present a number of examples to illustrate the features and the capabilities of the MANIFOLD language. As each new feature is presented, a small complete program example is usually provided to illustrate the feature. We have chosen to teach by example. Just as a picture is worth a thousand words, so is a properly chosen program example. If you have access to a computer facility that support the MANIFOLD coordination language, we invite you to enter and run each program presented in this tutorial and to compare the results obtained on your system to those shown in the text. By doing so, not only will you learn the language and its syntax, but you will also become familiar with the process of typing in, compiling, and running MANIFOLD programs. In the chapters 3 through 6, the material covered in each section is based on previous sections. Therefore we advice to read the sections of each chapter in succession. The other chapters in this booklet can be read in any order or even skipped when you prefer to learn MANIFOLD by using it immediately for your own application. The source code of each MANIFOLD example in this tutorial contains (as a comment) in the rst line the name of the le under which it can be found in the MANIFOLD EXAMPLES directory (see 2.2). At the end of a section we give, if necessary, under the heading \Further Reading", references to parts of the MANIFOLD's reference manual[2] that can be read to deepen your knowledge of MANIFOLD. So keep a copy of the reference manual at your side while reading this text. Now everything has been prepared, we invite you to copy the contents of the examples directory to your own working directory and explore the MANIFOLD language.
26
CHAPTER 2. GETTING STARTED
Chapter 3
Getting Acquainted is a coordination language base on the IWIM (Idealized Worker Idealized Manager) model of communication. We can use MANIFOLD to implement the cooperation protocols between dierent active processes that collaborate to achive a shared objective (probably because it is too dicult to do alone!). In MANIFOLD a process is an independent, autonomous, active entity that executes a procedure. Processes in MANIFOLD are treated as black-boxes that can only read or write through the openings in their own bounding walls. These openings are called ports and each MANIFOLD process has three standard ports named input, output, and error. input is the standard port through which units ow into a process. output and error are the standard output ports through which units ow out of a process. We can also de ned input and output ports with user de ned names. As is also known from daily life, cooperation necessarily involves communication. In MANIFOLD we can communicate in two ways via an event mechanism via an unit mechanism Communication via the MANIFOLD's event mechanism goes as follows. One process broadcasts an event in its environment. Other processes can pick up an occurrence of this event and can react to it by performing some actions. An event is an asynchronous, non-decomposable (atomic) message. A process that broadcasts the event is called the source of the event. A process that picks up an occurrence of an event is called an observer (of the event and of its source). In the MANIFOLD model of events, it is the observer that is responsible for picking up its events of interest from its environment. Event sources are oblivious to who, or if anyone at all, is picking up the events they raise. Furthermore, they cannot assume that their observers pick up and react to the events they raise in the chronological order that they were raised. Once a process has broadcast an event, it generally continues with its processing, while the event occurrence propagates through the environment independently. Communication of processes through events is thus inherently asynchronous in MANIFOLD. Communication via MANIFOLD's unit mechanism goes in a special way. If you want that data produced by a process goes to a process that consumes it, then the producer writes the data to its own output port and the consuming process reads the data from its own input port. A third party (a coordinating process, a manager) makes a connection from the output port of the producer to the input port of the consumer so that data can ow from the producer to the consumer. In the Manifold syntax we express this with p -> c where p is the producer and c is the consumer. The arrow between p and c represents a stream. Tis setting up of the communication links from the outside is very typical for MANIFOLD and has several advantages (see ??. In MANIFOLD a stream is a buer with in nite capacity that performs the transfer (so it is an active entity) of sequence of bits, grouped into (variable length) units. Once a stream is established between (a port of) a producer process and (a port of) a consumer process, units ow automatically through the MANIFOLD
27
CHAPTER 3. GETTING ACQUAINTED
28
stream. Just writing to or reading from the ports of MANIFOLD processes connected with each other by a stream is enough to let the units ow in the stream. It is as draught between two opposite open windows. No additional action is needed to initiate that ow. Received events
Input ports
Output ports
Incoming streams Process
Outgoing streams
Raised events
Figure 3.1: The MANIFOLD process Daarna naar plaatje verwijzen alla blz 9. Note that additional to the standard ports there are two user de ned input port and 1 user de ned input port. All the elements we talk about can be seen this picture unit mech. event mech. incomming an d outgoing streams, ports (defaults as well as the userde ned ones) in MANIFOLD a process can be regarded as a worker process or a manager (or coordinator) process. The responsibility of a worker process is to perform a (computational) task. A worker process is not responsible for the communication that is necessary for it to obtain the proper input it requires to perform its task, nor is it responsible for the communication that is necessary to deliver the results it produces to their proper recipients. In general, no process in MANIFOLD is responsible for its own communication with other processes. It is always the responsibility of a manager process to arrange for and to coordinate the necessary communications among a set of worker processes. An idealized Worker: is responsible for production of some results.
has no concers for where its input comes from.
has no concers for where its output goes to. An idealized Manager:
is knows only who needs what from whom and when.
is responsible for organization of a number of workers.
does not know exactly what they do.
encapsulates the cooperation protocol of its team of workers. In MANIFOLD an application is built as a (dynamic) hierarchy of worker and manager processes (see gure 3.1). Lowest in the hierarchy are pure worker processes called the atomic workers. They don't do any coordinating activities. Highest in the hierachy is a pure coordinator. A process between the lowest and highest level may consider itself as a worker doing a task for a manager higher in the hierarchy or as a manager coordinating processes lower in the hierarchy.
29
Figure 3.2: The MANIFOLD application Plaatje van geven. en daarin ook zeggen wat de manager is In next picture In next chapter we will see how we can change dynamically such networks of processes. We can implement it in the MANIFOLD langauage. Processes implemented in the MANIFOLD language are called managers, coordinations, or regular manifolds We can implement the internal operation of an atomic procxes in two dierent ways We can implement it in a traditional sequential programming language as C, C++. or Fortran.
We can implement it as as a Unix script (command). My procedure is written in the Manifold language
a)
My procedure is written in a conventional programming language b)
My procedure is written as Unix script
c)
Figure 3.3: Dierent MANIFOLD processes: a) a manager b) an internal worker c) an external worker 1) maak je in MANIFOLD Hoort een aprte terminologie bij 2) maak je in C met the API intefrace 3) is a shell script (b.v. one simple unix commando) The things you can do in the internal atomic are the same as in the regular manifolds. only creating processes and setting up streams between them are forbidden The internal atomic interface
CHAPTER 3. GETTING ACQUAINTED
30
oers almost all facilities we have in the regular manifolds (It is almost a complete mirror of of it) The external atomic interface is limited (zeggen van mapping event op signals e.d. The internal operation of the procedure in the blackbox can be written as (see 3.3) a) MANIFOLD b) conventional programming language. c) Unix script. The internal operation of some of these black boxes are indeed written in the MANIFOLD language The internal operation of some of these black boxes are indeed written in the MANIFOLD language Producer consumer pair in three ways.
Futher Reading
1 t/m 2.1, 2.3, 2.5 2.6
3.1 Producer and Consumer as Regular Manifolds 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// prod_consR.m #include "MBL.h" #include "rdid.h" /***************************************************/ manifold producer { begin: ( > -> output, guard(output, transport, end), terminated(void) ). } /***************************************************/ manifold consumer { auto process v is variable. begin: getunit(input) -> v; MES(v). } /***************************************************/ manifold Main { process p is producer. process c is consumer. begin:
(activate(p, c), p -> c).
}
sampan 262156 111 prod_consR consumer() prod_consR.m 21 -> 0 1 2 3 4 5 6 7 8 9
3.2 Producer and Consumer as Internal Workers 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// prod_consIW.m // pragma include "prod_consIW.ato.h" manifold producer atomic {internal.}. manifold consumer atomic {internal.}. /***************************************************/ manifold Main { process p is producer. process c is consumer. begin:
(activate(p, c), p -> c).
} /* prod_consIW.ato.c */ #include #include #include #include
"AP_interface.h" "adid.h" "prod_consIW.ato.h"
#define BOUND 10 /***************************************************/ void producer(void) { int ara[BOUND]; int i, err; AP_Unit u;
3.3. PRODUCER AND CONSUMER AS EXTERNAL WORKERS 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
for } u = err err
(i = 0; i < BOUND; ara[i] = i;
31
i++) {
AP_FrameIntegerArray(ara, BOUND); = AP_PortPlaceUnit(AP_OUTPUT, u, NULL); = AP_DeallocateUnit(u);
} /***************************************************/ void consumer(void) { int arb[BOUND]; AP_Unit u; int i, err, tn = 0; char buf[100]; err = AP_PortRemoveUnit(AP_INPUT, &u, NULL); err = AP_FetchIntegerArray(u, arb, BOUND); err = AP_DeallocateUnit(u); /* Echo the received data */ for (i = 0; i < BOUND; i++) { /* sprintf(buf, "%d ", arb[i]); sprintf(buf + tn * 2, "%2d", arb[i]); tn++; } }
P(u) I(err) I(err)
P(u) I(err) I(err) I(err) S(buf) */
S(buf)
1 /* prod_consIW.ato.h */ 2 3 extern void producer(void); 4 extern void consumer(void); sampan 262155 114 prod_consIW consumer() prod_consIW.ato.c 44 ->
0 1 2 3 4 5 6 7 8 9
3.3 Producer and Consumer as External Workers 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// prod_consEW.m #include #include "rdid.h" //pragma constants XDR /***************************************************/ manifold Producer atomic { external. }. /***************************************************/ manifold Consumer atomic { external. }. /***************************************************/ manifold Main { process p is Producer. process c is Consumer.
}
begin: ( activate(p, c), ).
p -> c, c -> STDOUT_LB(c)
/* producer.c */ #include #define BOUND 10 void main(void) { /* This is a heavy weight producer */ int i; for (i = 0; i < BOUND; printf("%d ", i); } printf("\n"); }
1 /* consumer.c */ 2 3 #include 4 5 #define BOUND 10 6
i++) {
CHAPTER 3. GETTING ACQUAINTED
32 7 8 9 10 11 12 13 14 15 16 17 18
/***************************************************/ void main(void) { /* This is a heavy weight consumer */ int i; for (i = 0; i < BOUND; i++) { scanf("%d", &i); printf("%d ", i); } printf("\n"); }
The producer.c and consumer.c les are respectively compiled with cc and cc consumer.c -o consumer. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
producer.c -o producer
# prod_consEW.mlink {task prod_consEW {include prod_consEW.o} } { atomic Producer {path /ufs/ever/local/mani/latex/TUT/WORK} {prog producer} } { atomic Consumer {path /ufs/ever/local/mani/latex/TUT/WORK} {prog consumer} }
sampan 262146 114 prod_consEW Consumer() prod_consEW.m 27 -> 0 1 2 3 4 5 6 7 8 9
Chapter 4
The Managers 4.1 Manifolds Events Driven Nature atomic
a)
b) Figure 4.1: State Transitions Regular manifolds (managers) always behave conform a xed set of rules. To describe their behaviour, we use short mnemonics for these rules (see ??). In the description of the examples we refer, between brackets, again and again to these rules. A survey of the rules can be found in 33
CHAPTER 4. THE MANAGERS
34 appendix ??.
4.1.1 ROMMEL HOEKJE
In general a braoadcast in the EM is Legemeen verwoorden van de event of het ook betkenet dat het meteen In EM is of niet. Voor de manifold that execute the primitive action in is inmiddeltailly acvaialbale in the EM but for other is may take a while. (cancel, raise, post, shutdown. enz) Vertellen wat activeren is ; lokale declarartie pad wordt afgehandeld Al je een manifold programm leest dan is het altijd duidelijk van welke sources je event kan krijgen (observable event sources) (se , system, parent, je lokale processesn aan boord, de globale in een le, die je asl parameter hebt binnengekrgen. Daaren tegen weet je nooit als je een rasie doet wie allemaal dit event krijgt. hallo 2.5.1 Is de body een block geschreven in manifold on in een tradionele programmeertaal blz 30 Schnabel It is necessay rts of all to preceive the pipeline concept as such, thta is independently ..... blz 28 Schnabel When we speak of rules etc..
4.2 The Pipeline Concept A pipeline is a MANIFOLD language construct that de nes:
a set of manner calls to be executed upon a transition to the state that contains it; a set of primitive actions to be performed upon in that state; a set of processes as the ones whose events can preempt the pipeline (preemptive sources); and the termination condition for the pipeline.
Pipelines are used as state bodies. Two such primitive actions are specially of interest in a pipeline: the stream constructor that exactly does what its name implies and the terminated primitive in which we can specify the termination condition for the pipeline. A pipeline is constructed upon transition to its corresponding state. The construction of a pipeline is considered to be an atomic activity. With this we mean that the construction of a pipeline is indivisible (cannot be split up). Thus there is no chance of the construction being half-completed or of another being interleaved. It is all or nothing. Also, explain that the oreder does not matter. In some exceptional cases where order can make a dierence, again, order does not matter and things will be done non-deterministically, so users should avoid such constructs (i.e., two guards with the dierent cond on the same port in the same pipeline, ETC.) A pipeline becomes preemptable once it is fully constructed. This allows event occurrences from the processes that are in the set of preemptive event sources of the pipeline to cause a state transition out of the current state (which contains the pipeline). Termination of a pipeline (see below) is a special case of preemption and thus can happen only after it becomes preemptable. Units can ow through the stream connections made in a pipeline only after it is constructed.
At what moment a pipeline is considered as fully constructed? A pipeline is considered as fully constructed if all the actions in the pipeline are performed and are considered as complete. In the reference manual we can read for each primitive action when it is done. (Look for the sentence \the job of the ... primitive action is done (or complete) as soon as ...")
4.2. THE PIPELINE CONCEPT
35
What does it mean that \a pipeline is preemptable"? A pipeline is preemptable means that event occurrences from the processes that are in the set of preemptive event sources of the pipeline that match the labels of other states, cause a state transition out of the current state (which contains the pipeline) into another state.
What is the set of preemptive event sources of the pipeline Preemptive event sources are processes what are used in the pipeline.
What does it mean \a pipeline terminates"? By de nition, a pipeline terminates when (TDP): 1
1. all streams it constructs or connects are broken (on at least, one end) (TDP1) and 2. all processes mentioned in its terminated primitives (if the pipeline has a one) are terminated. (TDP2)
What happens when we connect a stream to a dead or nonexistent process Connecting a stream to a dead or non-existent process is not an error: its eect is the same as a stream that breaks up immediately after it is connected, due to the death of its source and/or sink process.
Up to now we only spoke about termination and preeemption of pipelines. Can we use these words also in connection with states? Yes. Preemptability and termination of a state whose body is a pipeline is determined by the preemptability and termination of its pipeline. So when we say \the pipeline (of state x) is preempted" this is the same as saying \state x is preempted". The same counts for termination.
4.2.1 Some Examples of States whose Bodies are Pipelines
Let us give some examples of states whose bodies are pipelines. In the examples p, q, and r are respectively instances (made with the ``process x is y.'' construct) of the manifolds P, Q, and R and e is an event. e:
p -> q.
e:
p.output -> q.input.
The only primitive action is the stream constructor which connects the output port of the process instance p with the input port of the process instance q. system, self, p and q are preemptive event sources of the e state. This is exactly the same as e:
p -> q..
1 The bold mnemonics between brackets are used in the examples to make references to these rules. In appendix ?? we enumerate all these abbreviations.
CHAPTER 4. THE MANAGERS
36 e:
P -> Q.
e:
(p -> q, terminated(void), activate(p, q) ).
e:
(p -> q, terminated(P), activate(p, Q) ).
e:
(p -> q, terminated(void), raise(e) ).
e:
(terminated(void), preemptall).
e:
.
e:
halt.
e:
P.input, q.error, input, r.
The output port of the implicite created and activated process instances of P is connected with the input port of the implicite created and activated process instances of Q. In addition to system and self the two implicit process instances are also preemptive event sources of the e state. This pipeline contains three primitive actions, namely the stream constructor, the terminated, and activate. The preemptive event sources of the e state consist of process instances system, self, p, q and the prede ned process void. void is a frequently used process in MANIFOLD. We explore this process in an example. This pipeline contains three primitive actions, namely the stream constructor, the terminated, and activate and two implicit process instantiation; namely one in terminated and one in activate. As we know, only the the actual parameters of the primitive action terminate are all preemptive event sources of the state which contain this primitive action, thus the preemptive event sources of the e state are besides system, self, p, and q, the implicit process instance of P. The implicit process instance of Q is not a preemptive event source. In this pipeline we have three primitive actions. The preemptive event sources of the e state are system, self, p, q, and void. This pipeline does not contain streams, but only the primitive actions terminated and preemptall. The preemptive event sources are system, self, and void and is enlarged by preemptall to the whole set of obvervable event sources. This pipeline doesnot contain expressions, primitive actions, or streams and is called the empty pipeline. The preemptive event sources of the e state are system and self. This pipeline only the primitive actions halt. The preemptive event sources of the e state are system, self, and void. This pipeline only contain expressions. It are the source port of the implicit process instance of P, the error port (sink port!) of the instance q, the input port of the process instance which contains this pipeline (thus self), and nally the process name r. The preemptive event sources of the e state are system, self, the implicit process instance of P, q, and r. Strictly speaking self is added another time to the set of preemptive event sources of the e state because the input port of self is used in the pipeline.
4.2.2 Undeterministic Behaviour of a Pipeline
In every computer language it always possible to write down nonsensical constructs. For MANIFOLD no exception is made. What is the result in next state? Does the constuction of the pipeline results in a connection between input and output? begin:
(close(input), input -> output).
In this case the stream constructor sets up connections between input and output but close disconnects all streams from input and all this is done as an atomic activity. It is clear that we have in this case biting activities in the pipeline. In general we can say that when we have in
4.3. THE BLOCK CONCEPT
37
a pipeline things that bite each other, the pipeline construction is abused and its behaviour is undeterministic. Below we summarize some other nonsensical pipelines with unpredictable results. begin: (guard(input,d_connect,some_event),input->output). begin: (guard(input, .., noevent), guard(input, .., event)).
It is also possible to write nonsensical pipelines with predictable results. begin: (getunit(input) -> void, guard(input, d_connect, ..)). This result is well
de ned because we have the rule that units can not ow through streams before the pipeline is fully constructed. This means that getunit, as independent virtual process, has to wait with reading a unit from the input port until the guard is set. begin: (v=2, guard(v,input, transport, e)). This guard will never re because the \=" manner is rst performed.
begin: activate(p), deactivate(p).
where p is a process instance The end result will always be an inactive process.
4.3 The Block Concept 4.4 The Group Concept
How to study this Manifold introduction can be expresseed in next Manifold program. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// how.m Nog een beetje opleuken. Manifold Manifold Manifold Manifold Manifold
chapter1 chapter2 chapter3 chapter4 chapter5
elsewhere. elsewhere. elsewhere. elsewhere. elsewhere.
Manifold Manifold Manifold Manifold Manifold
AppendixA AppendixB AppendixC AppendixD AppendixE
elsewhere. elsewhere. elsewhere. elsewhere. elsewhere.
manifold Main { event if_necessary, look_up_in_index_ref_man. begin: chapter1; chapter2; (chapter3, chapter4). if_necessary: (chapter5, AppendixA,
AppendixB, AppendixC, AppendixD, AppendixE)
look_up_in_index_ref_man: (preemptall, IDLE). }
end:
4.5 Streams A stream is a fo buer. A stream is a communication link that transports a sequence of bits, grouped into (variable length) units. A stream represents a reliable and directed ow of information from its source to its sink. As in the IWIM model, the constructor of a stream between two processes is, in general, a third process. Once a stream is established between a producer process and a consumer process, it operates autonomously and transfers the units from its source to its sink. The sink of a stream requiring a unit is suspended only if no units are available in the stream. The suspended sink is resumed as soon as the next unit becomes available for its consumption. The source of a stream is never suspended because the in nite buer capacity of a stream is never lled.
38
CHAPTER 4. THE MANAGERS
There are four basic stream types designated as BB, BK, KB, and KK, each behaving according to a slightly dierent protocol with regards to its automatic disconnection from its source or sink. Furthermore, in MANIFOLD, the BK and KB type streams can be declared to be reconnectable. In MANIFOLD we only have asynchronous stream types. There are four dierent types of streams (BB, BK, KB, and KK2): 1. BB stream: A stream of this type is disconnected from either of its producer or consumer automatically, as soon as it is disconnected from the other. 2. BK stream: A stream of this type is disconnected from its producer automatically, as soon as it is disconnected from its consumer, but disconnection from its producer does not disconnect the stream from its consumer. 3. KB stream: A stream of this type is disconnected from its consumer automatically, as soon as it is disconnected from its producer, but disconnection from its consumer does not disconnect the stream from its producer. 4. KK stream: A stream of this type is not disconnected from either of its processes automatically, if it is disconnected from the other. A stream dies, and any units still in its FIFO queue are lost, when the following two conditions are ful lled: 1. The stream should have permission-to-die (PTD). Except reconnectable streams, all stream instances receive their permission-to-die from their creators at the time they are created. Reconnectable streams receive their permission-to-die when their creator leaves the scope (i.e., block) wherein they were created. 2. The stream should be ready-to-die which is de ned as (a) it is disconnected from both its source and its sink (RTD-DSS); or (b) it is empty and is disconnected from its source (RTD-EDS). A break-type connection between a stream and a port breaks when: 1. the stream dies (B-SD); 2. the process to which the port belongs dies (B-PD); 3. the port is closed or disconnected (B-PCD); 4. the stream is preempted by its constructor (B-SP); or 5. the stream realizes that the connection at its other end is broken (see below) (B-SRB). It is quite possible that more than one condition is true in order to break a break-type connection. E.g., in the case we have normal BK stream between p and c and the stream is empty and disconnected from its source (thus p /-> c), condition 1 (because RTD-EDS) as well as condition 5 is true. Normally, a stream realizes the breakup of a connection at either of its ends immediately after it occurs. However, a reconnectable stream cannot realize the breakup from its source unless/until its status is/becomes empty. A keep-type connection between a stream and a port breaks for the following two reasons: 1. the stream dies (K-SD); or 2. the process to which the port belongs dies (K-PD). 2 The letters \B" and \K" in the name of a stream type, respectively designate break- and keep-type connections to the ports at their corresponding ends of the asynchronous streams of that type.
4.5. STREAMS
39
Preemption of a simple state causes (is de ned as) the preemption of its pipeline, which results in (is de ned as) the preemption of its (remaining) streams. Preemption of a stream means (is de ned as) breaking its break-type connections. Normally streams are BK stream. Thus when p en q are processes and we have the stream p!q, than preemption of the stream means that p!q becomes pn ! q. So the connection between p en the source of the stream is broken and the connections between the sink of the stream and q remains undamaged. Table ?? shows the preemption behaviour of dierent stream types. When a pipeline terminates, a special event, referred to as in this document, is automatically posted to signal that the end of the state has been reached. Because has the lowest priority, it causes a transition only if no other observed event occurrence can cause a transition. The standard transition caused by the occurrence of is to an end state of the block if such a state exists; otherwise, this transition terminates the current block and returns to its parent block. Thus termination of a pipeline is a special case of preemption namely preemption by . A MANIFOLD application enters its termination phase when all of its processes except the following have terminated: 1. constants, noprocess, system, void, stdin, stdout, and stderr; and 2. all process instances (global or otherwise) created with an auto attribute. In the termination phase, all remaining global process instances created with an auto attribute are rst deactivated. The application then nally terminates (normally) when (and if), disregarding the ones listed under 1 above, the last of its processes terminates (TDA). network parallel distributed homegeneous Yes Yes heterogeneous see chapter 13 Table 4.1: Scope of the make le
4.5.1 4.5.2 4.5.3 4.5.4
BK Streams KK Streams KB Streams BB Streams
Metaphore for a BB stream: the telephone wire; A telefohone connection breaks immediately at the other side when you hang up (and visa versa). Example: Well, if you have a server and dierent clients connect to it with their own streams asking for various services, it is possible for service requests from the same client to pile up in the stream. For example, C1 can issue 20 dierent service requests while the server is busy doing other things. Now, suppose C1 dies for some reason. In some cases (e.g., if all this server did was to answer questions) it makes no sense for the server to answer the questions of a dead client. In this case a BB or a KB stream would be useful, to make sure that the server does not do unnecessary work.
4.5.5 Reconnectable BK Streams
A good metaphore for a reconnectable BK stream is the hose of a vacuum cleaner (see 4.2).
4.5.6 Reconnectable KB Streams
A good metaphore for a reconnectable KB stream is hose of a fuel tank at a gas station (or an ober serving out drinks) (see 4.3).
CHAPTER 4. THE MANAGERS
40
UNDER CONSTRUCTION
Figure 4.2: Reconnectable BK stream: the hose of a vacuum cleaner
UNDER CONSTRUCTION
Figure 4.3: Reconnectable KB stream: the hose of a fuel tank at a gas station
4.6 Behaviour of p
-> c
under Dierent Circumstances
In table 4.2 we can see how dierent streams between p and c behave under dierent circumstances. The three cases we consider are:
Process p dies (B-PD) or its the port is closed or disconnected (B-PCD).
Process c dies (B-PD) or its the port is closed or disconnected (B-PCD).
Preemption of the stream.
In this table we use "/" to indicate that a connection is broken (between a port and a stream) as a direct cause of the case under consideration. When as a result the other side also breaks (because the stream realizes that the connection at its other end is broken) we use \//" to indicate that.
4.7 Condition Constructs and Loop Constructs 4.8 The Primitive Actions Primitive actions are the basic operations in MANIFOLD and are used to construct pipelines in state bodies. We can divide the primitive actions in two categories. The rst category consist of the primitive actions that create and activate a virtual process. In this category we nd the data ow related actions; i.e., the ones that involves ports, units, and streams. The second category of primitive actions contains all the other actions. The actions in this category are mostly related to the event-driven control mechamism of MANIFOLD. In 4.8.1 and 4.8.2 we enumerate the primitive actions in both categories and a) describe them shortly, b) indicate when the primitive actions is considered as done. For a full description of the primitive actions we refer to section 18 in the reference manual.
4.8. THE PRIMITIVE ACTIONS Stream p dies, port closed c dies, port closed preemption type or disconnected or disconnected result note result note result BB p /->// c 1 p //->/ c 1 p /->/ c BK p /-> c 2 p //->/ c 1 p /-> c KB p /->// c 1 p ->/ c 3 p ->/ c BK p /-> c 2 p //->/ c 3 p /-> c RBK p /-> c 2 p //->/ c 1 p /-> c RKB p /->// c 2 p ->/ c 3 p ->/ c
41 note 1 2 3 2 -
note 1 The stream can die and any units still in it are lost (PTD+RTD-DSS). note 2 The stream can die when it is empty (PTD+RTD-EDS) note 3 The stream is used as dumping place for all units p writes on its output port. The units in this stream can't be used anymore. When p dies the stream will also die (with all those units on board). Table 4.2: Behaviour of dierent stream types between p and c under dierent circumstances
4.8.1 The Primitive Actions Creating Virtual Processes Next three primitive actions create and activate a virtual process with a certain bahaviour. Although these processes are virtual (thus not real manifold processes) we must think about them as independent running processes. So they do something in their own tempo and after some time they die (without raising a death event). 1. The reference operator &operand (a) creates and activates a virtual process that produces a reference of the operand on its output port. Special for this primitive action is that we can use & as an alias for the virtual process it creates, and therfore, it can appear as a source for a stream in a pipeline or used as parameter (see 4.8.3). After transporting the reference unit through the output port of &'s virtual process (this means that the unit is in the arrow after &), it terminates without raising a death event. (b) is done as soon as it creates and activates its virtual process. 2.
guard(PortName,
Condition, Event) (a) creates and activates a virtual process that posts the speci ed event in the event memory (source is self) of executing manifold, once the speci ed port condition becomes true for the speci ed port. After posting the speci ed event, the guard virtual process terminates, without raising a death event. (b) is done as soon as its virtual process is created and activated
3.
getunit(SourcePort)
(a) creates and activates a virtual process that tries to obtain a unit from the speci ed source port and transport this through its output port. Special for this primitive action is that we can use getunit as an alias for the virtual process it creates, and therfore, it can appear as a source for a stream in a pipeline (it can't be use as a parameter (see 4.8.3). After transporting the reference unit through the output port of getunit's virtual process (this means that the unit is in the arrow after getunit), it terminates without raising a death event. (b) is done as soon as it creates and activates its virtual process.
CHAPTER 4. THE MANAGERS
42
4.8.2 Other Primitive Actions
When a process instance broadcast an event than it produces an event occurrence that is identi ed by the broadcast event and its event source which is the process instance that raised the event. In the event memory of a manifold instance we do not store events but event occurrences. That is why we always tell in the description of the primitive actions that broadcast events, who is the source of the event. Sometimes it the manifold instance that performes the primitive actions (the exectuting manifold instance) ;we refer to this instance with the special name self. Another time the source is system which is a special ctitious process instance that represents the MANIFOLD run-time system. In MANIFOLD we notate an event occurrence, whereby e is broadcast by p, as e:p. In the primitive actions sometimes an event is broadcast for (to) all processes instances (including the exectuting manifold instance as in cancel, halt, shutdown, and return) or excluding the exectuting manifold instance as in raise). Another time it is broadcast only to exectuting manifold instance (as in post and return). In general we can say about the broadcast event that its occurrences are inmediatelly (i.e., once the primitive action is considered as done) available in the event memory of the the exectuting manifold instance. For all the other processes instances in the MANIFOLD application it my take some time before it is available in their event memories. 1. The stream constructor operator -> (a) creates, if necessary, and connects stream instances to the arrival and departure sides of ports. (b) is done as soon as it creates and connects the streams. 2. The dereference constructor $operand (a) NOT YET IMPLEMENTED (b) 3. activate(p1; p2; :::; pn ) (a) activates the speci ed processes. (b) is done as soon as the requests for the activations are issued. 4. cancel (a) broadcasts to all processes an abort event (the source is system). (b) is done as soon as it has completed its broadcast. By this time, only the process instance performing the cancel action is guaranteed to have the broadcast abort event occurrence in its event memory, and it may take some time for the others to detect it. 5. close(p1; p2 ; :::; pn ) (a) disconnects all streams from speci ed local ports. Attempts to make new connections to the speci ed ports will fail. (b) is done as soon as all streams to the speci ed ports have actually been disconnected and new connections are refused. 6. deactivate(p1; p2 ; :::; pn ) (a) deactivates the speci ed processes. (b) is done as soon as the requests for the deactivations are issued. 7. disconnect(p1; p2 ; :::; pn ) (a) disconnects all streams from speci ed local ports. Attempts to make new connections to the speci ed ports will succeed. (b) is done as soon as all streams to the speci ed ports have actually been disconnected and new connections are allowed.
4.8. THE PRIMITIVE ACTIONS 8.
halt
43
(a) terminates the executing manifold instance by storing (posting) an occurrence of a special high priority event (the source is self) (b) is done as soon as an occurrence of this special high priority event is in this event memory. 9. open(p1; p2 ; :::; pn ) (a) changes the status of the speci ed ports such that attempts to connect new streams to them will succeed. (b) is done all speci ed ports are actually opened. 10. post(e1; e2 ; :::; en ) (a) posts (stores) occurences of the speci ed events (the source (self)) in the event memory of the executing manifold instance. (b) is done as soon as the occurences of the speci ed events are in this event memory. 11. preemptall (a) states that all process instances that are observable event sources can preempt the state it appears in. (b) is done as soon as all observable event sources are marked as preemptable sources for the pipeline. 12. raise(e1; e2; :::; en ) (a) broadcasts the speci ed events (the source (self)) for all the manifold processes in the running Manifold application, except for the executing manifold instance. (b) is done as soon as it has completed its broadcast. By this time, the broadcast event occurrences are not (yet) guaranteed to be present in the event memory of any of the process instances that are potentially interested to observe them. 13. return[(Expression)] (a) i. if encountered in the body of a manifold, terminates the executing manifold instance by storing (posting) a occurrence of a special high priority event (source is self) in the event memory of the executing manifold instance . (so it behaves as halt (see above). ii. if encountered in body of a manner, rst assigns the result of the optional Expression to the optional return value of the manner . After this we return from the manner invocation by storing (posting) a occurrence of a special high priority event (source is self) in the event memory of the executing manifold instance. (b) i. if encountered in the body of a manifold, is done as soon as an occurrence of the special high priority event is in the event memory of the executing manifold instance. ii. if encountered in the body of a manner is done as soon as the result of the optional Expression is assigned to to the optional return value of the manner and an occurrence of the special high priority event is in the event memory of the executing manifold instance. 14. signin(a; p1; p2 ; :::; pn ) (a) makes the processes p1 ; p2 ; :::; pn observable event sources for the process a. Each process pi is entered as an element in the list of explicitly signed-in processes of process a. (b) is done as soon as events raised by any of the processes pi can be received by process a. 15. signout(a; p1; p2 ; :::; pn )
CHAPTER 4. THE MANAGERS
44 Action & $ getunit
Source of stream? Parameter? Yes Yes Yes Yes Yes No
Table 4.3: Primitive Actions as Parameters or Source of a Stream (a) removes the processes p1 ; p2 ; :::; pn from the list of explicitly signed-in processes of process a, When a pi doesnot exist anymore on this list then it is no more observable event source. (b) is done as soon as all processes pi have actually been removed from the list containing the explicitly signed-in processes of process a. 16.
shutdown
(a) broadcasts the prede ned terminate event (with source system) to all processes in the running MANIFOLD application, including the one that performs the shutdown action. (b) is done as soon as it has completed its broadcast. By this time, only the process instance performing the shutdown action is guaranteed to have the broadcast terminate event in its event memory, and it may take some time for the others to detect it. 17. terminated(e1; e2; :::; en ) (a) makes the termination (i.e., death) of all processes p1 through pn a precondition for the termination of the pipeline it appears in. (b) is done as soon as the termination condition of its containing pipeline is set accordingly.
4.8.3 A Primitive Action as Parameters or Source of a Stream
Some primitive actions can be used as parameter in manner calls and process instantiations or as a source of a stream. Syntactically, the primitive actions & and getunit can be used as aliases for the virtual processes they create, and therfore, can appear as sources for streams in pipelines. For the primitive action & and $ we can go one step further. These primitive actions together with their operands can also be used as a parameter in manner calls and process instantiations, so for these ones there are no syntactical limitations at all. In table 4.3 we give the syntactical properties of these primitive actions.
Chapter 5
The Internal Workers This are the Internal Atomic Processes
5.1 How to Write a Neat Atomic Process 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
// neat.m manifold Neat atomic {internal.}. manifold Main { begin: (Neat, shutdown). } /* neat.ato.c */ #include "AP_interface.h" #include "debug.h" #include typedef struct { AP_Unit u; AP_Event e; AP_EventPatternSet eps; AP_Process p; } allocated_mem; typedef struct { AP_Unit u; AP_Event e; AP_Event r; AP_EventPatternSet eps; AP_Process p; AP_Process pp; } AM_Neat; typedef struct { AP_Unit u; AP_Event e; AP_EventPatternSet eps; AP_Process p; AP_Process pp; } AM_SubA; /***************************************************/ static int ClearSubA(AP_Event ev, AP_Process proc, void *param) { AM_Neat *ptr = (AM_Neat*) param; if (AP_EqualEvent(ev, AP_abort) ) { AP_DeallocateUnit(ptr->u); AP_DeallocateUnit(ptr->e); AP_DeallocateEventPatternSet(ptr->eps); AP_DeallocateProcess(ptr->p); AP_DeallocateProcess(ptr->pp); } return AP_EXIT; } /***************************************************/ void SubA(void) { AP_Unit u; AP_Event e = AP_AllocateEvent(); AP_EventPatternSet eps = AP_AllocateEventPatternSet(); AP_Process p = AP_AllocateProcess();
45
CHAPTER 5. THE INTERNAL WORKERS
46 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
AP_Process pp = AP_AllocateProcess(); int err; AM_SubA ToBeCleaned; AP_TerminateFuncType fun; void *vp; P(e) P(eps) P(p) P(pp) ToBeCleaned.u = u; ToBeCleaned.e = e; ToBeCleaned.eps = eps; ToBeCleaned.p = p; ToBeCleaned.pp = pp; err = AP_PushTerminateFunc(ClearSubA, &ToBeCleaned);
I(err)
err = AP_PopTerminateFunc(&fun, &vp);
I(err)
ClearSubA(AP_abort, NULL, (void*) &ToBeCleaned); } /***************************************************/ static int ClearNeat(AP_Event ev, AP_Process proc, void *param) { AM_Neat *ptr = (AM_Neat*) param; if (AP_EqualEvent(ev, AP_abort) ) { AP_DeallocateUnit(ptr->u); AP_DeallocateUnit(ptr->e); AP_DeallocateUnit(ptr->r); AP_DeallocateEventPatternSet(ptr->eps); AP_DeallocateProcess(ptr->p); } return AP_EXIT; } /***************************************************/ void Neat(void) { AP_Unit u; AP_Event e = AP_AllocateEvent(); AP_Event r = AP_AllocateEvent(); AP_EventPatternSet eps = AP_AllocateEventPatternSet(); AP_Process p = AP_AllocateProcess(); int err; AM_Neat ToBeCleaned; AP_TerminateFuncType fun; void *vp; P(e) P(r) P(eps) P(p) ToBeCleaned.u = ToBeCleaned.e = ToBeCleaned.r = ToBeCleaned.eps ToBeCleaned.p =
u; e; r; = eps; p;
err = AP_PushTerminateFunc(ClearNeat, &ToBeCleaned);
I(err)
err = AP_EventPatternSetInsert(eps, AP_abort, NULL); I(err) /* It is not necessary to add AP_terminate to the eps */ SubA(); err = AP_WaitEvent(eps, r, p);
I(err)
err = AP_PopTerminateFunc(&fun, &vp);
I(err)
ClearSubA(AP_abort, NULL, (void*) &ToBeCleaned); }
Chapter 6
The External Workers This are the External Atomic Processes Dit programma werkt nog niet Kees moet er naar kijken. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
#include #include "rdid.h" //pragma constants XDR /***************************************************/ manifold Ls(port in) atomic { external. event terminate. }. /***************************************************/ manifold Sort() atomic { external. event terminate. }. /***************************************************/ manifold Main() { auto process sort is Sort(). auto process ls is Ls("-la"). begin: ( /* die 28 is eigenlijk een integer 28 */ ls -> sort -> stdout_lb(sort, "ksh4.m", "28"), ls -> stdout_lb(ls, "ksh4.m", "29"), ls.error -> stdout_lb("ls error", "ksh4.m", "30"), sort.error -> stdout_lb("sort_error", "ksh4.m", "31") ). #if 0 begin: ( ls -> sort -> STDOUT_LB(sort), ls -> STDOUT_LB(ls), ls.error -> STDOUT_LB("ls error"), sort.error -> STDOUT_LB("sort_error") ). #endif death.sort & death.ls: MES("death.sort.ls"). }
The le for MLINKis 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
{task ksh5 {include ksh5.o} } { atomic Ls {noncompliant} {path /bin} {prog ls} } { atomic Sort {noncompliant} {path /bin} {prog sort} }
The sorted output is
sampan 12345 67 taskname maniname ksh4.m 29 -> total 14658 sampan 12345 67 taskname maniname ksh4.m 28 -> 121 Jun sampan 268041 79 ksh5 Main() ksh5.m 43 -> death.sort.ls
47
3
1991 .console.meerval
48
CHAPTER 6. THE EXTERNAL WORKERS
Chapter 7
Manner Subprogrmas
7.1 Regular Manners 7.2 Atomic Manners Onderfscheid goed raise and post als je van een manner op een manifold overgaat en visa versa.
49
50
CHAPTER 7. MANNER
Chapter 8
Application Construction and Termination 8.1 Application Construction TEXT (see reference manual page 72 )
8.2 Application Termination Processes running in a MANIFOLD application can be distinguished in four dierent sets of processes.
set 1: consists of constants, and the prede ned (system) processes noprocess, system, void, stdin, stdout,
and stderr.
set 2: consists of the global processes instances created with an auto attribute set 3: consists of the other (i.e. local) processes instances created with an auto attribute. set 4: consists of all remaining process instances. When set 4 is empty the MANIFOLD application enters its termination phase. In this phase the following actions are performed in sequence. 1. The processes in set 2 are deactivated (so they recieve a terminate event). 2. We wait until all processes in set 2, 3, and 4 have been terminated. 3. The processes of set 1 are deactivated. 4. We wait until the processes in set 1 have been terminated. Under point 2 we also took up the waiting for the termination of processes in set 4. Although the termination phase is initiated by an empty set 4, this does not mean that this set stays empty during the termination phase. It is quite possible that when a process in set 2 is deactivated and as a result it handles the terminate event, it creaties new processes instances without the auto attribute (i.e., processes in set 4). In a similar way, processes in set 3 can do this too. Note that processes in set 3 get deactivated when the creator processes leave the scopes wherein they were created. Recall that deactivating of a process does not mean that they are immediatelly death after the deactivation. It just means that they receive a terminate event to which it can react (typically 51
CHAPTER 8. APPLICATION CONSTRUCTION AND TERMINATION
52
the process does some clean-up actions and then terminate) or not (default handling of terminate event is termination of the process instance). The whole start up and termination phase of an MANIFOLD application can also be expressed in next arti cial MANIFOLD program of MANIFOLD run-time system. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
manner manner manner manner manner manner manner manner manner
create_the_processes_in_set_1() elsewhere. create_all_global_processes() elsewhere. activate_the_processes_in_set_2() elsewhere. create_and_activate_main() elsewhere. wait_until_set_4_is_empty() elsewhere. deactivate_processes_in_set_2() elsewhere. wait_until_processes_in_set_2_3_4_have_been_terminated() elsewhere. deactivate_processes_in_set_1() elsewhere. wait_until_processes_in_set_1_have_been_terminated() elsewhere.
/***************************************************/ manifold run_tine_system() { event set_4_empty. begin:
create_the_processes_in_set_1(); create_all_global_processes(); // (with and without the \Ky{auto} attribute) activate_the_processes_in_set_2(); create_and_activate_main(); wait_until_set_4_is_empty(). // (this manner post the event "set_4_empty")
set_4_empty: // Now we are in the termination phase deactivate_processes_in_set_2(); wait_until_processes_in_set_2_3_4_have_been_terminated(); deactivate_processes_in_set_1(); wait_until_processes_in_set_1_have_been_terminated(). end:
// The application has been terminated, and so // the UNIX prompt appears at the screen.
}
When we have an hanging MANIFOLD application it is mostly because there are still some processes alive in set 2, 3, or 4. Another source of creating hanging applications is forgetting to activate processes as we will see. That such applications are easy to create is shown in the examples below. Next MANIFOLD example produces its output but it hangs because it will never enter its termination phase (set 4 is not empty). This is because we use an implicit instantiation for stdout nl. Because the behaviour of stdout nl is such that it only terminates when it receives an terminate event, it cause the whole application to hang. Of course we must never use manifolds with such a behaviour in an implicit instantiation. 1 2 3 4 5 6 7 8
// hang.m #include "MBL.h" manifold Main { begin: "Hello World!\n" -> stdout_nl. }
In next example the MANIFOLD application hangs because activation of a process p has been forgotten. 1 2 3 4 5 6 7 8 9 10
// hang2.m #include "MBL.h" process p is variable. manifold Main { begin: p. }
When we activate it as in the source code below, we will see that it still hangs because the application never enters the termination phase (set 4 is not empty). 1 2 3 4 5 6 7 8 9 10
// hang3.m #include "MBL.h" process p is variable. manifold Main { begin: activate(p). }
8.3. GENERAL TERMINATION PROTOCOLS OF MANIFOLD PROCESSES
53
In next example, the application enters its termination phase but it will not terminate because the process created on line 11 (which makes set 4 non-empty) will never die. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// hang4.m #include "MBL.h" /***************************************************/ manifold P { begin: terminated(void). terminate: { process q is variable. begin: activate(q). } } auto process p is P. /***************************************************/ manifold Main { begin:. }
In next example (its source code is almost the same as previous one) we see that the termination behaviour of a MANIFOLD application can also be non-deterministic. In this case it depends on the scheduling if it hangs or not. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// hang5.m #include "MBL.h" /***************************************************/ manifold P { begin:. terminate: { process q is variable. begin: activate(q). }. end:.
} auto process p is P.
/***************************************************/ manifold Main { begin:. }
When in process p the begin terminates and the terminate, event has not yet arrived in the event memory of p, the application terminates. However when the terminate event is in the event memory at the moment the begin state terminates, the application hangs.
8.3 General Termination Protocols of Manifold Processes In general you can have one of the following two termination protocol for manifold process. 1. termination is determined from inside (the manifold process determined for itself when to die 2. termination is determined from outside (another managers deactivates the manifold process) The choice you make for one of the two termination protocols also determine how you can use such manifolds E.g., it is, in general, not handy to use a manifold from the second category as implicite instantiation because the outside world has no way to deactivate it because there is no explicite process name that can be used in the deactivate primitive. Using manifolds of this type in such a way causes the whole MANIFOLD application to hang instead of terminate. Normally, it is best to use the auto attribute when creating process instances from the second category. In this case the deactivation takes place at the end of the scope that contains the declaration (thus after the g). Of course we can always use cancel or abort in order to force an application to terminate, but this is a rather violent and not so precise way of termination (a small shot).
54
CHAPTER 8. APPLICATION CONSTRUCTION AND TERMINATION
In general, for the manifold in the rst category it is a good idee to take up the ignore statement in its block, so that the internal working of the manifold is not disturbed by an eventual external terminate event. terminate
Chapter 9
Trace Messages In this chapter we explain how to use trace messages in MANIFOLD programs. Because a MANIFOLD application is in its most general form is a distributed multi-threaded application there are several problems to solve, before we are able to produce proper output on the computer screen. We mention a few. Using trace statements in the form of printf or write, in atomic workers written in C, will not work ne, because there is no guaranty that the instances of these workers (i.e., the threads), running simultaneously, synchronize their writes. In this case it is quite possible that the trace messages are gambled or out of order. Especially this can happen when printf is what is called not "thread-save". Even if printf is threads-save, we have the same problem when both process instances house in dierent task instance that both run on the same machine. Next MANIFOLD program, stored (not without reason) in le named silly.m, shows the problem. Two atomic workers write something to the screen with an unsafe printf. One worker writes the string letters "mn " and the other one writes the string "aiod". Suppose that the scheduler preempts again and again both threads at the point at which they have written just one letter to the screen. Then the nal result on the screen can be the string "manifold" and that is not what you want in this case. 1 2 3 4 5 6 7 8 9 10 11 12
// silly.m //pragma include "silly.ato.h" manifold One atomic {internal.}. manifold Two atomic {internal.}. /***************************************************/ manifold Main() { begin: (One, Two). }
The le with the internal worker(s) is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* silly.ato.c */ #include "silly.ato.h" #include "stdio.h" /***************************************************/ void One(void) { printf("mnfl\n"); } /***************************************************/ void Two(void) { printf("aiod\n"); }
In a distributed MANIFOLD application we have the following problem with using printf or write in atomic workers. Suppose that the MANIFOLD application consists of two task instances, each running on a dierent machine, and that the process instances in those task instances use printf. In that case we see the output of the process instances that house in the rst task instance, on one machine and the output of the instances that house in 55
CHAPTER 9. TRACE MESSAGES
56
the other task instance, on the other machine. (Also probably, the output is gambled or out of order (see previous item)). Of course we do not want this. All output had to go to the screen of the machine from which the MANIFOLD application has been started (the so called start-up machine). That is why we must always take care that the output generated in the dierent process instances ows via one process instance to the screen of the start-up machine. In MANIFOLD we can do this with the prede ned process instance stdout. In the internal workers we can also use this prede ned process via the MBL stdout (see in the reference manual). Implementing tracing messages can not be done in a naive way as is shown in next example. 1 2 3 4 5 6 7 8 9
// naive.m /***************************************************/ manifold Main() { begin: "begin\n" -> stdout. }
end: "end\n" -> stdout.
In this program we do not have any guaranty that we see the string "begin" before the string "end". See 9.2.3 how to solve this problem and gure 2.1 on page 18 for the reason why this happens. Even if messages do not mixed up on the screen and the output of the application is shown on that one screen of the startup machine you still can have useless output because you do not know which process instance is producing the output. What we want is that all the output, generated from a particular process instance, is preceded by a label that contains some extra information that shows us who is printed what and where. The sort of extra information we might thing of can be the machine on the process instance runs, the process identi cation of the task instances and process instances, their names, and the line numbers and le names where the trace messages are made (see 9.1). In order to solve the above problems we have implemented for the regular manifolds (i.e., the managers) four trace manners (see 9.2.1) and four trace manifolds (see 9.2.2) that we can use as draincocks. These last four manifolds are also very useful for making draincocks from external atomic workers (see 9.4). For the internal workers we have written a number of C macro for making trace messages in their source code (see 9.3). Because we also want a label, that precedes the actual trace message, that shows as information about the le and the line number where a message is produced, we have implemented the four manners and the four manifolds as preprocessor macros. Thereby we have followed the general practice to use capitals for preprocessor macros. The trace manners and the manifold are stored in the libdid.a (see 2.3) a library that is automatically linked to the MANIFOLD application when you use the make le belonging to this booklet. Once the preprocessor (gm4 in our case) has made a rst pass through each source le, the trace macros in the regular manifold le are changed in neat call to normal manners and/or manifolds and the macros in the C sources are replaced by neat ANSI C statements. The trace macros can be compiled conditionally based on the value of the RMES, and AMES ags in the gmake call (see 9.3.4).
9.1 The who-what-where label The label we want to use in a message contains the following information in sequence. the machine on which the task instance runs; the identi cation of the task instance;
9.2. TRACE MESSAGES IN MANAGERS Macro
RMES = 0
RMES = 1
p ; p2 ; :::; pn )
enabled
enabled
p ; p2 ; :::; pn )
disabled
enabled
p ; p2 ; :::; pn ) enabled
enabled
p ; p2 ; :::; pn )
MES( 1
MMES( 1 EMES( 1
disabled
MEMES( 1
enabled
57 Meaning From each parameter port one unit is read. These units are transformed into strings and concatenated to one string. This string, preceded with a who-what-where label is shown on the screen of the start-up machine (Mnemonic: MESsage). The same as MES (Mnemonic: Mandatory MESsage). The same as MES only now we also see the actual parameters followed by "=" in the message (Mnemonic: Mandatory MESsage). The same as EMES (Mnemonic: Mandatory Echo MESsage).
Table 9.1: Manner macros that can be used in regular manifolds (managers)
the identi cation of the process instance made from the manifold;
the name of the task type;
name of the manifold (i.e. process type) from which the instance is made. This name is preceded with the complete stack of module calls, separated with colons. For more information about this stack see the description of AP_SPrintMyName in the reference manual. For examples see page 67.
the name of the le in which the message is made;
the line number in the le where the message is made.
With this label we can identify in a unique way a process instance within a whole MANIFOLD application. With such a label in front of the actual message, we always know who is printing what and where. 1 .
9.2 Trace Messages in Managers 9.2.1 Trace Manners Their Implementation
For the regular manifolds we have four manner macros (i.e., a macro that expands to a manner). They are respectively MES, EMES,MMES, and MEMES (see table 9.1). They all have the same variablesized argument list (p1 ; p2 ; :::; pn ) where each pi represents a port in parameter. They dier in the way they show their message (with or without formal parameters taken up in the message) and the way we can disable them (with the ag RMES in the make le call). Because the four manner macros are implemented in the similar way it is sucient to describe the implementation of the MES macro and shows its expansion. Later we also give in an example the expansions of the other macros (page 58). The preprocessor gm4 expands MES(p1; p2 ; :::; pn ), depending on the RMES ag in the call to the make le, in one of two ways 1 We intend to introduce an environment variable with which we can speci ed the elds which are to be taken up in the label
CHAPTER 9. TRACE MESSAGES
58
When
RMES=1 (thus the call to the make le is gmake R=file.m RMES=1) it expands to Mes(>). Because RMES=1 is the default gmake R=file.m is already sucient. Mes is a manner and is de ned as Mes(port in t). Thus gm4 places the variable-sized argument list consisting of port in parameters, from which units are available, into a tuple, together with two preprocessor identi ers and the string ->. This tuple is the actual argument of the Mes manner.
When RMES=0 (thus the call to the make le is gmake empty string.
p ; p ; :::; p
R=file.m RMES=0)
it expands to an
The behavior of the Mes manner is as follows. The manner takes a single argument from its parameter port t, and expects it to contain a tuple. This tuple is transformed into a string. After replacing the newlines and tabs by a single space, this string is glued after the who-what-where label. This total string is consumed by the prede ned process instance stdout (with the help of MBL stdout). Once the unit is consumed by stdout, the manner returns. Next program examples shows how the dierent manner macros are expanded by the macro preprocessor gm4. The original source code is next program. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// closer.m #include "MBL.h" #include "rdid.h" /***************************************************/ manifold Main() { auto process a is variable(1). auto process b is variable(2). begin: }
MES(a, b); MMES(a, b); EMES(a, b); MEMES(a, b).
Its expansion with the make le call gmake 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
R=closer.m
is
// closer.m #include "MBL.h" #include "rdid.h" /***************************************************/ manifold Main() { auto process a is variable(1). auto process b is variable(2). begin:
Mes(>); Mes(>); Mes(>); Mes(>).
}
The output is in that case sampan sampan sampan sampan
262151 262151 262151 262151
79 79 79 79
closer closer closer closer
Main() Main() Main() Main()
closer.m closer.m closer.m closer.m
12 13 14 15
-> -> -> ->
1 2 1 2 a,b = 1 2 a,b = 1 2
From the rst line of output of the this expansion we see the following:
The machine on which the task instance runs is sampan
The identi cation of the task instance and process instance is respectively 262151 and 79.
The name of the task and manifold is respectively closer and Main(). This manifold does not has any parameters because nothing is placed between the parenthesis.
The name of the le and line number where the message is produced is respectively closer.m and 12
After this long label we see an arrow followed by the actual message 1 2.
9.2. TRACE MESSAGES IN MANAGERS
59
Disable Message Tracing
We can disable message tracing of the manner macros in the regular manifolds by recompiling the source code with the RMES=0 ag (Mnemonic: Regular MESsage) in the make le call. The expansion of closer.m is in that case (use gmake R=closer.m RMES=0): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// closer.m #include "MBL.h" #include "rdid.h" /***************************************************/ manifold Main() { auto process a is variable(1). auto process b is variable(2). begin:
; Mes(>); ; Mes(>).
}
The output in that case is: sampan 262153 79 closer Main() closer.m 13 -> 1 2 sampan 262153 79 closer Main() closer.m 15 -> a,b = 1 2
What we see from these two dierent expansions and output is the following. When we recompile the source code and we use the special command line ag RMES=0 in the make le le call, the preprocessor gm4 removes all MES and EMES calls from a MANIFOLD source. In that case the MANIFOLD compiler MC never the MES and EMES calls. MMES produces exactly the same output as MES only we can not disable its output with the RMES=0 ag. EMES also produces the same output as MES only now it also shows the actual pi parameters followed by a = before the actual values of them. MEMES produces exactly the same output as EMES only we can not disable its output. From the rst line of output of the rst expansion of closer.m we see the following: { The machine on which the task instance runs is sampan { The identi cation of the task instance and process instance is respectively 262151 and 79. { The name of the task and manifold is respectively closer and Main(). This manifold does not has any parameters because nothing is placed between the parenthesis. { The name of the le and line number where the message is produced is respectively closer.m and 12 { After this long label we see an arrow followed by the actual message 1 2.
Their Standard Usage
In the example below we have used the MES manner for tracing the state visiting in the manifold named one way. In this case we have only used one parameter pi in MES. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// way_one.m /* The standard way to print messages in regular manifolds */ #include "MBL.h" #include "rdid.h" /***************************************************/ manifold way_one() { begin: MES("begin"). end: MES("end"). } /***************************************************/ manifold Main() { begin: way_one. }
CHAPTER 9. TRACE MESSAGES
60 Macro STDOUT LB(process
RMES = 0
RMES = 1
enabled
enabled
disabled
enabled
p) enabled
enabled
disabled
p)
MSTDOUT LB(process in
p) p)
STDOUT LB NL(process in
MSTDOUT LB NL(process in
enabled
Short Meaning It reads every unit it receives through its input port, precedes it with a who-what-where label and shows it on the screen of the start-up machine. The process parameter is used for the construction of the label. Mnemonic: STDOUT LaBel. The same as STDOUT_LB. Mnemonic: Mandatory STDOUT LaBel. The same as STDOUT_LB only now we a newline is added at the end of each unit. Mnemonic: STDOUT LaBel NewLine. The same as STDOUT_LB_NL. Mnemonic: Mandatory STDOUT LaBel NewLine.
Table 9.2: Manifold macros that can be used in regular manifolds (managers) The output is sampan 262152 100 way_one way_one() way_one.m 11 -> begin sampan 262152 100 way_one way_one() way_one.m 13 -> end
In next example we have used EMES in order to produce a message that also contain the argument of EMES. 1 2 3 4 5 6 7 8 9 10 11 12 13 14
// echo_mes.m #include "MBL.h" #include "rdid.h" /***************************************************/ manifold Main() { auto process t is variable(99). begin: EMES(t). }
end: EMES("end").
The output is sampan 265167 79 echo_mes Main() echo_mes.m 11 -> t = 99 sampan 265167 79 echo_mes Main() echo_mes.m 13 -> "end" = end
9.2.2 Trace Manifolds Their Implementation
For the regular manifolds we also have four manifold macros (i.e., a macro that expands to a manifold). They are respectively STDOUT LB, MSTDOUT LB, STDOUT LB NL, and MSTDOUT LB NL (see table 9.2). They all have the same formal parameter process p. They dier in the way they show their message (with or without a nal newline) and the way we can disable them (with the ag RMES in the make le call as we will see). Because the four manifold macros are implemented in the similar way it is sucient to describe the implementation of the STDOUT LB macro and shows its expansion. Later we also give in an example the expansions of the other macros (page 61). The preprocessor gm4 expands STDOUT LB(p), depending on the RMES ag in the call to the make le, in one of two ways
9.2. TRACE MESSAGES IN MANAGERS
61
When
RMES=1 (thus the call to the make le is gmake R=file.m RMES=1) it expands to stdout_lb(p, __FILE__, __LINE__). Because RMES=1 is the default gmake R=file.m is already sucient. stdout lb is a manifold de ned as stdout lb(process p, process le, process line). Thus gm4 has added two preprocessor identi ers to the argument list and
has changed the name in small letters. When RMES=0 (thus the call to the make le is gmake R=file.m RMES=0) it expands to black hole which is a manifold without parameters. The behavior of the stdout lb(process p, process le, process line) manifold is as follows. An instance of stdout lb reads every unit it receive through its input port and transforms it into a string. From the actual parameters, the who-what-where label is constructed and this label precedes the string mentioned before. This whole is printed on the standard output of a MANIFOLD application. stdout lb terminates once it detects the a everdisconnected condition on its input port and all the strings have been transported through the output port. stdout lb is immune for (does not react on) a terminate event. Because the rst parameter in stdout lb is a process from which we construct the who-whatwhere label we have with this formal parameter a good way to give messages, that for some reason belong to each other, the same label. One of the rst candidates as actual parameter is of course the prede ned process self that represents the process instance itself. The behavior of the black hole manifold is as follows. An instance of black hole consumes every unit it receives through its input port and never produces a unit on its output or error port. Furthermore, it never raises any event and it terminates when the last stream is disconnected from the arrival side (i.e., once it detects the a connected ! a disconnected condition on its input port). Next program examples shows how the manifold macros are expanded by the macro preprocessor gm4. The original source code is next program. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// drain.m #include #include "rdid.h" /***************************************************/ manifold Main { begin: ( "hallo\n" -> STDOUT_LB("Here"), 1 -> STDOUT_LB_NL("There"), "hallo\n" -> MSTDOUT_LB("Here"), 1 -> MSTDOUT_LB_NL("There") }
).
Its expansion with the make le call gmake 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
R=drain.m
is
// drain.m #include #include "rdid.h" /***************************************************/ manifold Main { begin: ( "hallo\n" -> stdout_lb("Here", __FILE__, __LINE__), 1 -> stdout_lb_nl("There", __FILE__, __LINE__), "hallo\n" -> stdout_lb("Here", __FILE__, __LINE__), 1 -> stdout_lb_nl("There", __FILE__, __LINE__) }
).
The output is in that case sampan sampan sampan sampan
262158 262158 262158 262158
208 184 160 136
drain drain drain drain
’const’() ’const’() ’const’() ’const’()
drain.m drain.m drain.m drain.m
10 11 13 14
-> -> -> ->
hallo 1 hallo 1
Remark: In the output above we see that the process instances used for the construction of the who-what-where label are two strings constants Here and two string constants There. They all have an own identi cation (208, 184, 160, and 136). By sorting the messages on the rst two numbers in the who-what-where label we can group messages in a handy way together. See e.g., the sorting of the output of the pingpong example on page 18).
CHAPTER 9. TRACE MESSAGES
62
Disable Message Tracing
Just as with the manner macros we can also disable message tracing of the manifold macros by recompiling the source code with the RMES=0 ag in the make le call. The expansion of drain.m in that case is (use gmake R=drain.m RMES=0): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// drain.m #include #include "rdid.h" /***************************************************/ manifold Main { begin: ( "hallo\n" -> black_hole, 1 -> black_hole, "hallo\n" -> stdout_lb("Here", __FILE__, __LINE__), 1 -> stdout_lb_nl("There", __FILE__, __LINE__) }
).
The output in that case is: sampan 262157 160 drain ’const’() drain.m 13 -> hallo sampan 262157 136 drain ’const’() drain.m 14 -> 1
What we see from these two dierent expansions and output is the following. The preprocessor gm4 expands all STDOUT LB and STDOUT LB NL into black hole. In this case we do not see the trace message. Note that the black hole manifold certainly in uences the performance of a MANIFOLD application in a negative way. For performance benchmarking it is better to edit the manifold macros away. MSTDOUT LB produces exactly the same output as STDOUT LB only we can not disable its output with the RMES=0 ag. STDOUT LB NL produces the same output as STDOUT LB only now it adds a newline at the end MSTDOUT LB NL produces exactly the same output as STDOUT LB NL only we can not disable its output.
Their Standard Usage
We can use the manner manifolds of table 9.2 in two ways. as an implicit process instantiation. as an explicit process instantiation with or without the auto attribute. Above we already gave an example where a manner manifold is used as implicit process instantiation. The nice thing about this way of calling the manner manifold is that we then have an exact location (in the form of a line number and a le name) where the output is produced. Below we give an example where a manifold macro is used as an explicit process instantiation. In this source code the process instance printl is used as draincock to gather the units (the integers 1 and 2) from dierent states and send them to the screen. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// drain2.m #include #include "rdid.h" /***************************************************/ manifold Main { auto process printl is STDOUT_LB_NL(self). stream reconnect BK *-> printl. begin: 1 -> printl. end: 2 -> printl. }
9.2. TRACE MESSAGES IN MANAGERS
63
The sorted output is sampan 263538 95 drain2 Main() drain2.m 9 -> 1 sampan 263538 95 drain2 Main() drain2.m 9 -> 2
Do not forget to make the stream reconnectable BK, otherwise it is possible that the process instance printl terminates during the state transition (at that time, there is a moment that there is no stream connection to the input port of printl). Because the behaviour of STDOUT LB NL, from which printl is an instance, is such that it terminates once it detects a disconnection on it input port, it can happen that we do not see the output produced in the end state. The termination protocol of STDOUT LB NL is not the only reason why we must make the stream reconnectable BK. Suppose we have forgotten to made the stream reconnetable, and we are so lucky that we indeed see both output lines on the screen, then there is no garanty that we see these lines in the right order. Because each unit is send via a dierent stream to the process instance printl, the order in which the lines appear on the screen is non-deterministic (see for a similar situation gure 2.1 on page 18).
9.2.3 Some Alternative Ways*
In this section we give two other ways to print messages from regular manifolds. In manifold way two we do the same things as in manifold way one (see section 9.2.1), only now the messages are produced directly with the prede ned process stdout. The = manner (see reference manual) takes care that the string is consumed by stdout before the current state terminates. That is why the messages appear in the right order on the screen. The disadvantage of this way is that we do not have a who what where label in front of the actual message. Manifold way three is also doing the same things as in manifold way two (see section 9.2.1), only now the data transport is done with the stream constructor -> and not by the manner =. Because we have declared the stream to stdout to be a reconnectable (line 16), the messages appear in the right order on the screen. When this declaration has not been given, the messages could appear in any order on the screen2. Also in this case we have the disadvantage of the missing who-what-where label. Note that in this program, rdid.h (the le contains the prototypes of the manner macros and manifold macros) is not used. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// way_two_three.m #include "MBL.h" /***************************************************/ manifold way_two() { begin: stdout = "begin\n". end: stdout = "end\n". } /***************************************************/ manifold way_three() { stream reconnect BK * -> stdout. begin: "begin\n" -> stdout. end: "end\n" -> stdout. } /***************************************************/ manifold Main() { begin: way_two; way_three. }
The output is begin end begin end
To protect the user for this trap we have at this moment (september 1999) add the declaration stream reconnect to the MBL.h le. I do not know if this will still be the case in the future because with this addition we hide the actual behaviour of streams to stdout. 2
BK * -> stdout
CHAPTER 9. TRACE MESSAGES
64
9.2.4 More Advanced Message Printing in Managers*
In next program we give some other examples how we can use the manner macros of the table 9.1. The examples and the corresponding output explains them self. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// advanced.m: #include "MBL.h" #include "rdid.h" /***************************************************/ Main() { auto process a is variable(1). auto process b is variable(2). begin: MES(&self); MES(id(self)); MES(writestr("%7.2e", 12.978)); MES(writestr("circle: centre = (%d, %d), radius = %d", >)); EMES(a, b); EMES(tuplelen() ). }
The output is sampan 262170 119 advanced Main() advanced.m 12 -> Reference Process id:262170:3:119 has 3 ports in port signature: "input" type AP_IN conn AP_ANY "output" type AP_OUT conn AP_ANY "error" type AP_OUT conn AP_ANY sampan 262170 119 advanced Main() advanced.m 13 -> sampan 262170 119 advanced Main() sampan 262170 119 advanced Main() advanced.m 14 -> 1.30e+01 sampan 262170 119 advanced Main() advanced.m 15 -> circle: centre = (2, 3), radius = 5 sampan 262170 119 advanced Main() advanced.m 16 -> a,b = 1 2 sampan 262170 119 advanced Main() advanced.m 17 -> tuplelen() = 2
9.2.5 Using Preprocessor Identi ers in Messages* When we compile a MANIFOLD source le with the MANIFOLD compiler, always rst the preprocessor (normally cpp or cc -E) runs on the source le and then the MANIFOLD compiler runs on that output. That is why we can use the prede ned identi ers of the preprocessor (see cpp or cc -E) in MANIFOLD source les as is shown in the example below. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// preproc.m #include "MBL.h" #include "rdid.h" /***************************************************/ manifold preproc() { begin: stdout = .
end: stdout = "end\n". } /***************************************************/ manifold Main() { begin: preproc. }
The output is preproc.m 10 Sep 29 1999 16:58:15 begin end
The identi er __FILE__ has been de ned by the preprocessor and once the preprocessor is nished it becomes a string constant that contains the name of the source le being compiled. In the same way __LINE__ becomes an integer constant that contains the current line number of the le, and __DATE__ and __TIME__ become a string that contains respectively the time and date the le has been compiled. Note that we have use this facility in the manner and manifold macros for making the whowhat-where label.
9.3. MESSAGES IN INTERNAL WORKERS Macro
AMES = 0
disabled
AMES = 1
AMES = 2
I(i)
P(pi)
disabled
enabled
enabled
S(str)
disabled
disabled
enabled
M(str) PI(i)
enabled disabled
enabled disabled
enabled enabled
PF(f)
disabled
disabled
enabled
W
disabled
disabled
enabled
enabled
enabled
65 Meaning It checks if the integer i return a negative value. If it does, show its corresponding error message preceded by the who-what-where label on the screen (Mnemonic: Integer). It checks if the pointer pi is NULL. If it is, show an error message preceded by the who-what-where label on the screen (Mnemonic: Pointer). It shows the string str preceded by the who-whatwhere label on the screen (Mnemonic: String). The same as S(str) (Mnemonic: Mandatory). It shows the integer i on the screen as the the literal string of i followed by = and its value. This whole is preceded by the who-what-where label (Mnemonic: Print Integer). It shows the oat f on the screen as the the literal string of f followed by = and its value. This whole is preceded by the who-what-where label (Mnemonic: Print Float). It shows the who-what-where label on the screen (Mnemonic: Where).
Table 9.3: Macros that can be used in internal (atomic) workers
9.3 Messages in Internal Workers In the internal workers we have seven ANSI C macros available (see table 9.3) for making messages. The expansion of these macros can be found $(DID)/include/adid.h. Two are use for checking return values (integers or pointers) of AP calls (see 9.3.1). The other ve we can used for making our own messages (see 9.3.2). The ANSI C macros can be conditionally compelled based on the value (0, 1 or 2) of the AMES
ag in the gmake call (see 9.3.4).
9.3.1 Checking Return Values of AP Calls
Most AP_ calls have returns values. When an integer is return, the return value is used to indicate success (the return value is 0) or an error (return value is negative). With the macro I(i), where i is an integer, we check whether the return value i is negative or not. If it is, it shows the corresponding error message (the one that is generated by the AP_Error call) preceded by the who-what-where label. If i is 0 or positive, the macro does not generate output When a pointer is returned, this value is used to validate the pointer's address. A NULL pointer indicates an error (a corrupt address, an initialized pointer, or a memory allocation problem). Other pointer values indicate success. With the macro P(pointer) we check whether the return value of pointer is NULL or not. If it is, it shows the corresponding error message (again the one that is generated by the AP_Error call) preceded by the who-what-where label. For other pointer values the P(pointer) macro does not generate output. 1 2 3 4 5 6 7 8 9 10 11 12
// check_returns.m //pragma include "check_returns.ato.h" #include "MBL.h" #include "rdid.h" manifold DoIt atomic {internal.}. /***************************************************/ manifold Main {
CHAPTER 9. TRACE MESSAGES
66 13 begin: (MES("begin"), DoIt). 14 15 end: MES("end"). 16 }
The le with the internal worker is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/* check_returns.ato.c */ #include "AP_interface.h" #include "adid.h" #include "check_returns.ato.h" /***************************************************************************/ void DoIt(void) { int err, i; int *pi; AP_Unit u; err = AP_FetchInteger(u, &i); pi = AP_GetUnitData(u);
I(err) P(pi)
I(err = AP_FetchInteger(u, &i) ); P(pi = AP_GetUnitData(u) ); }
The le with the prototype is 1 /* check_returns.ato.h */ 2 3 extern void DoIt(void);
The sorted output is sampan 263593 sampan 263593 sampan 263593 put argument sampan 263593 sampan 263593 put argument sampan 263593
79 check_returns Main() check_returns.m 13 -> begin 79 check_returns Main() check_returns.m 15 -> end 186 check_returns DoIt() check_returns.ato.c 14 -> fatal: AP_BAD_ARG: bad in186 check_returns DoIt() check_returns.ato.c 15 -> FAILURE: Null pointer return: 186 check_returns DoIt() check_returns.ato.c 17 -> fatal: AP_BAD_ARG: bad in186 check_returns DoIt() check_returns.ato.c 18 -> FAILURE: Null pointer return:
On line 14 of this source code an error message is generated because the programmer of this source code forgot to allocate a proper structure to hold the MANIFOLD unit (use AP Allocate Unit() to repair this mistake). Later the programmer tries to get a pointer to the data part of this \not allocated" unit. This also gives an error message. As we see on the lines 17-18 (we do there the same as on lines 14-15) we can also use directly a call to a AP function as argument for a macro.
9.3.2 Making Your Own Messages
The other ve macros of table 9.3 can be use for making own messages as is shown in next example. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// own.m //pragma include "own.ato.h" #include "MBL.h" #include "rdid.h" manifold DoIt atomic {internal.}. /***************************************************/ manifold Main { begin: (MES("begin"), DoIt). }
end: MES("end").
The le with the internal worker is 1 2 3 4 5 6 7 8 9 10 11 12 13
/* own.ato.c */ #include "AP_interface.h" #include "adid.h" #include "own.ato.h" /***************************************************************************/ void DoIt(void) { int i = 2000; float f = 9.9; S("WELCOME")
9.3. MESSAGES IN INTERNAL WORKERS 14 15 16 17 18 }
67
M("We always see this string") PI(i) PF(f) W
The le with the prototype is 1 /* own.ato.h */ 2 3 extern void DoIt(void);
The sorted output is sampan sampan sampan sampan sampan sampan sampan
263592 263592 263592 263592 263592 263592 263592
79 own Main() own.m 13 -> begin 79 own Main() own.m 15 -> end 186 own DoIt() own.ato.c 13 -> WELCOME 186 own DoIt() own.ato.c 14 -> We always see this string 186 own DoIt() own.ato.c 15 -> i=2000 186 own DoIt() own.ato.c 16 -> f=9.900000 186 own DoIt() own.ato.c 17
This program explains itself. Note that it is not necessary to have the macros PI, PF and W. We can always ll a string in ANSI C with some information and send it to the screen with the S or M macro. We just add them to the macro set for convenience.
9.3.3 Manner Names in who-what-where label
When an atomic manner is using one of the C macros of table 9.3, the complete stack of module calls, separated by colons, is given before the name of the manifold (see also 9.1). In the example below, the atomic DoIt is called as manner by a regular manner (named RegMan), and this manner is called in the manifold Main. Thus the complete stack of module calls is Doit: RegMan (see who-what-where label in the output). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// stack.m //pragma include "stack.ato.h" #include "MBL.h" #include "rdid.h" manner DoIt atomic. /***************************************************/ manner RegMan() { begin: DoIt. } /***************************************************/ manifold Main { begin: (MES("begin"), RegMan). }
end: MES("end").
The le with the internal worker is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* stack.ato.c */ #include "AP_interface.h" #include "adid.h" #include "own.ato.h" /***************************************************************************/ void DoIt(void) { int i = 2000; float f = 9.9; S("WELCOME") M("We always see this string") PI(i) PF(f) W }
The le with the prototype is 1 /* stack.ato.h */ 2 3 extern void DoIt(void);
The sorted output is
CHAPTER 9. TRACE MESSAGES
68
Also study next example and output and check the stack of module calls. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
//pragma include "longer_stack.ato.h" #include "MBL.h" #include "rdid.h" manner Atomic_A_Manner() atomic. /****************************************************************************/ manner A_Manner { begin: MES("Welcome"). } /****************************************************************************/ manner B_Manner { begin: (A_Manner, Atomic_A_Manner). } /****************************************************************************/ manifold B_Manifold { begin: (A_Manner, Atomic_A_Manner). } /****************************************************************************/ manner C_Manner { begin: (B_Manner, B_Manifold). } /****************************************************************************/ manner D_Manner { begin: C_Manner. } /****************************************************************************/ manifold Main { begin: D_Manner. }
The le with the atomic manner is 1 2 3 4 5 6 7 8 9 10 11
/* longer_stack.ato.c */ #include "adid.h" #include "longer_stack.ato.h" /***************************************************************************/ void Atomic_A_Manner(void) { S("Welcome") }
The le with the prototype(s) is 1 /* longer_stack_ato.h */ 2 3 extern void Atomic_A_Manner(void);
The output is sampan 263581 > Welcome sampan 263581 > Welcome sampan 263581 sampan 263581
79 longer_stack A_Manner(): B_Manner(): C_Manner(): D_Manner(): Main() longer_stack.m 12 79 longer_stack Atomic_A_Manner(): B_Manner(): C_Manner(): D_Manner(): Main() longer_stack.ato. 195 longer_stack A_Manner(): B_Manifold() longer_stack.m 12 -> Welcome 195 longer_stack Atomic_A_Manner(): B_Manifold() longer_stack.ato.c 10 -> Welcome
9.3.4 Disable Message Tracing
We can disable the C macros in the atomic manners or manifolds by using the AMES ag (Mnemonic: Atomic MESsage) in the make le call. The make le call gmake R=filename.m AMES=0 r compiles and runs the MANIFOLD application stored in filename.m. Thereby are all C macro's except the M macro, disabled. gmake R=filename.m AMES=1 r is doing the same as previous make le call, only now also the return values of the AP calls are checked. By make le call gmake R=filename.m AMES=2 r all macros used, are enabled (so return values are checked and all own messages are shown on the screen).
9.4. MESSAGES FROM EXTERNAL WORKERS
69
9.4 Messages from External Workers The manifolds from table 9.3 on page 65 are also very usefull to make draincocks from external manifolds. Suppose that we want to see the output of the producer in the the producer-consumer pair from section 3.3 on page 31. In this source code below we have adapted the code of that producer-consumer pair. Now we have two draincocks (on line 28 and 29) that shows us the output of the heavy weight processes. Note the dierent actual parameters in the macros on line 28 and 29. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// drain_cocks.m #include #include "rdid.h" //pragma constants XDR /***************************************************/ manifold Producer atomic { external. }. /***************************************************/ manifold Consumer atomic { external. }. /***************************************************/ manifold Main { process p is Producer. process c is Consumer.
}
begin: ( activate(p, c), p -> c, p -> STDOUT_LB(p), c -> MSTDOUT_LB(c) ).
The producer and consumer are the same heavy weight processes as in section 3.3. The le for MLINK is changed a little bit because we have changed the le name. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# drain_cocks.mlink {task drain_cocks {include drain_cocks.o} } { atomic Producer {path /ufs/ever/local/mani/latex/TUT/WORK} {prog producer} } { atomic Consumer {path /ufs/ever/local/mani/latex/TUT/WORK} {prog consumer} }
The sorted output is now sampan 262288 105 drain_cocks Producer() drain_cocks.m 28 -> 0 1 2 3 4 5 6 7 8 9 sampan 262288 114 drain_cocks Consumer() drain_cocks.m 29 -> 0 1 2 3 4 5 6 7 8 9
9.5 Application Development and Tracing In general, when we start developing a MANIFOLD application, it is handy to use messages for tracing the state visiting in the regular manifolds, and to report the activities in atomic manners and workers. In this way we have a good control about what is going on in the application. For this reason we use in the regular man fold the macros MES, EMES, STDOUT LB, or STDOUT LB NL, and in the atomic manners and manifolds the C macros S, PI, PF, I, or P because we can disable them with the RMES and AMES ags when we are not interested anymore in their information. The real output of a MANIFOLD application is another story. Because we always want to see this output we must always use one of the mandatory macros (MMES, MEMES, MSTDOUT LB, or MSTDOUT LB NL).
70
CHAPTER 9. TRACE MESSAGES
We can able and disable message tracing in the regular manners and manifolds independently from those in the atomic manners or manifolds Thus we can use all kind of combinations of RMES and AMES values in the call to the make le. The default is RMES=1 and AMES=2. Thus gmake R=filename.m RMES=1 AMES=2 r is the same as gmake R=filename.m r For performance measurements, we advice to use RMES=0 and AMES=0 because this gives the quickest code.
Chapter 10
Debugging Manifold Applications Debugging a parallel/distributed application is known as a hard job. This also counts for a MANIFOLD applications which consists in its most general form, of several multi-threaded programs running in a network of heterogeneous computers. The main reasons are the following:
Bugs in MANIFOLD programs (especially buggy ones) often behave dierently in two successive runs given identical inputs because the ordering of events performed collectively by a program's threads (i.e., manifold instances) at run time, change again and again, by the dierences in thread scheduling. For example, unsynchronized access to shared data often works if events on that data don't collide. For instance, if process instance A performs an unsynchronized access of a resource it shares with process instance B (assume they house in the same task) before process instance B accesses the resource, there's no chance for a race condition to develop. However, if process instance B happens to access the resource while instance A is still busy with it, a race condition will result. Now, the race condition may not cause an error every time it occurs. Sometimes your process instances may, almost accidentally, come out of the race (condition) with the right answers.
To make matters worse, various things in your program's run-time environment, unrelated to the program itself, can impact the ordering of the program's events. Introducing a debugger, for instance, can cause the events to occur in an order that's dierent from the sequence they'd follow when the program is run in a production environment. Similarly, you may discover bugs as you move your program from one platform to another. The new platform's scheduling policy, performance, and system load could be dierent enough so that some task complete faster than the others, thus disrupting the usual ordering of events that had up to that point concealed the bug.
For the above it is clear that bugs in MANIFOLD programs are statistical instead of deterministic in character. That is why tracing the program activities is usually more eective in nding problems in the order of execution than is breakpoint-based debugging. The nice thing about \tracing" is that it not only works on a single platform, but that we can use this technic also for applications that run in a network of (heterogeneous) computers. In section 10.1 we show the standard way how to debug a MANIFOLD application and give an example of a debug session. Race conditions and deadlocks are well know sources for problems and bugs in parallel/distributed applications. In section 10.2 we discuss how to avoid them and give an example. We conclude this chapter with section 10.3, where we give some general guidelines, tips and illustrating examples.
10.1 The Standard Way to Debug The standard way to debug a MANIFOLD application is to monitor the program activities in the MANIFOLD application. We do this by inserting trace messages on appropriate places in the 71
CHAPTER 10. DEBUGGING MANIFOLD APPLICATIONS
72
source code. In the MANIFOLD source code we can use the trace manners and trace manifolds from respectively table 9.1 on page 57 and table 9.2 on page 60. In the atomic workers we can use the trace macros of table 9.3. Because the trace messages are always preceded by a who-whatwhere label, we always know who is printed what and where. As already explained (see page 17), the order in which we see the trace messages on the screen of the startup machine, is not totally deterministic. Only the order of the lines that are printed from within the same manifold is deterministic and appears in the right order on the screen. Thus, the best thing we can do with the output of a MANIFOLD application is, sort it in such a way that the output of each process instance is collected together. For this purpose we simple use UNIX's sort that sort the screen output lines with as sort keys the rst two numbers in the who-what-where label. We don't do this directly but with a call to our well know make le (see 2.4 on page 14). Suppose X is the name of the MANIFOLD application that we have run with gmake R=X.m r
then we can sort the output (stored in X.out) with gmake R=X.m s
This gives le X.out.spi that contains the sorted output, that we can use to reconstruct the overall logic of the MANIFOLD application and nd our bugs. We use the trace manners and manifolds as follows In order to trace the state transition in the regular manifolds and manners we use the MES manner (see example way one.m on page 59). To control the contents of the data ow we use the draincocks of table 9.1 on page 57 (see example drain cocks.m on page 69). To trace the program activities in the atomic workers we use the macros of table 9.3 on page 65. Especially the W macro is very useful to place it before and after functions that can block. Good candidates from which to make sandwiches are AP PortPlaceUnit, AP PortRemoveUnit, AP AP PortGetUnit, AP AP WaitEvent, MBL locksema and MBL unlocksema (see section 10.1.1 on page 72 and section 10.2 on page 74 for examples). To control the return values of the AP calls, we use the I and P macro of table 9.3 on page 65 (see section 9.3.1 on page 65 for an example).
10.1.1 Making Sandwiches
In this section we give an example of a debug session. In the source code below (written by a beginning MANIFOLD programmer), a producer and consumer are connected to each other in Main's begin state (line 14). In the same group, both process instances are activated, and because nothing else is to be done by main, it can die. That is why we post the event end on line 14. This cause a state transition to the end state (line 16) where the halt primitive action eectively terminates main. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// bug.m // pragma include "bug.ato.h" manifold producer atomic {internal.}. manifold consumer atomic {internal.}. /***************************************************/ manifold Main { process p is producer. process c is consumer. begin: }
(activate(p, c), p -> c, post(end)).
end: halt.
The le with the internal worker is
10.1. THE STANDARD WAY TO DEBUG 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
73
/* bug.ato.c */ #include #include #include #include
"AP_interface.h" "adid.h" "bug.ato.h"
#define BOUND 10 /***************************************************/ void producer(void) { int ara[BOUND]; int i, err; AP_Unit u; for } u = err err
(i = 0; i < BOUND; ara[i] = i;
i++) {
AP_FrameIntegerArray(ara, BOUND); = AP_PortPlaceUnit(AP_OUTPUT, u, NULL); = AP_DeallocateUnit(u);
} /***************************************************/ void consumer(void) { int arb[BOUND]; AP_Unit u; int i, err, tn = 0; char buf[100]; err = AP_PortRemoveUnit(AP_INPUT, &u, NULL); err = AP_FetchIntegerArray(u, arb, BOUND); err = AP_DeallocateUnit(u); /* Echo the received data */ for (i = 0; i < BOUND; i++) { /* sprintf(buf, "%d ", arb[i]); sprintf(buf + tn * 2, "%2d", arb[i]); tn++; } }
P(u) I(err) I(err)
P(u) I(err) I(err) I(err) S(buf) */
S(buf)
The le with the prototype is 1 /* bug.ato.h */ 2 3 extern void producer(void); 4 extern void consumer(void);
When we run the example we see nothing on the screen and the application hangs. In order to nd out what is going on, we trace the state transitions in the regulars and sandwich all the blocking routine in the atomics with the W macro. That gives next version of the program. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// sandwich.m // pragma include "sandwich.ato.h" #include "rdid.h" manifold producer atomic {internal.}. manifold consumer atomic {internal.}. /***************************************************/ manifold Main { process p is producer. process c is consumer. begin: }
(MES("begin"), activate(p, c), p -> c, post(end)).
end: (MES("end"), halt).
The le with the internal worker(s) is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* sandwich.ato.c */ #include "AP_interface.h" #include #include "adid.h" #include "sandwich.ato.h" #define BOUND 10 /***************************************************/ void producer(void) { int ara[BOUND]; int i, err; AP_Unit u;
CHAPTER 10. DEBUGGING MANIFOLD APPLICATIONS
74 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
for (i = 0; i < BOUND; i++) { ara[i] = i; } u = AP_FrameIntegerArray(ara, BOUND); err = AP_PortPlaceUnit(AP_OUTPUT, u, NULL); err = AP_DeallocateUnit(u); } /***************************************************/ void consumer(void) { int arb[BOUND]; AP_Unit u; int i, err, tn = 0; char buf[100]; err = AP_PortRemoveUnit(AP_INPUT, &u, NULL); err = AP_FetchIntegerArray(u, arb, BOUND); err = AP_DeallocateUnit(u); /* Echo the received data */ for (i = 0; i < BOUND; i++) { /* sprintf(buf, "%d ", arb[i]); sprintf(buf + tn * 2, "%2d", arb[i]); tn++; } }
P(u) W I(err) W I(err)
W P(u) I(err) W I(err) I(err) S(buf) */
S(buf)
The le with the prototype(s) is 1 /* sandwich.ato.h */ 2 3 extern void producer(void); 4 extern void consumer(void);
When we run this new application, it still hangs and we again see no output (we are lucky, we still have (probably) the same bug!) but now we see what is going on. To stop the program, give it a control C. The output we see on the screen is also stored in the le sandwich.out. Type in gmake R=sandwich.m sp and have a look to the sorted output which is. sampan sampan sampan sampan
262166 262166 262166 262166
79 sandwich Main() sandwich.m 16 -> begin 79 sandwich Main() sandwich.m 18 -> end 105 sandwich producer() sandwich.ato.c 22 114 sandwich consumer() sandwich.ato.c 36
From this output we see that Main has been terminated. Thus Main can not cause the hang. We also see that the producer can not place the unit on the port (it can not come on line 24) and that the consumer can not read the unit from the port (it can not come on line 38). Probably there is something with the connection between the producer and the consumer. That should ring a bell, and we realize that indeed the connection is broken caused by the state transition from the begin (line 16) state to the end state (line 18). When you want to switch to another state but let the connection make in previous state intact, then you must use a KK stream on line 16 (see 4.5.2 on page 39) and not the default BK stream. With this ascertain we found the bug and this ends the debug session.
10.2 Race Conditions and Deadlock
In next program a race condition exist between two process instances of the manifold RW (line 14). The processes try to access a shared resource (i.e., the integer i de ned on line 9) without the protection of a synchronization mechanism. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// race.m //pragma include "race.ato.h" #include "MBL.h" #include "rdid.h" manifold RW() atomic {internal.}. /***************************************************/ manifold Main() { begin: (RW, RW). }
10.2. RACE CONDITIONS AND DEADLOCK
75
The le with the internal worker is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* race.ato.c */ #include "AP_interface.h" #include "race.ato.h" #include "adid.h" int i; /***************************************************/ void RW(void) { while (1) { i++; PI(i) } }
The le with the prototype is 1 /* race.ato.h */ 2 3 #include "AP_interface.h" 4 5 extern void RW(void); 6
Note that this program never terminates and thus we have to stop it with a control C. Below we show a piece of the sorted output. It is only possible that both process instances produce \i=4"because they do not synchronize their read-write activities. sampan 262163 105 race sampan 262163 105 race sampan 262163 105 race . . . sampan 262163 124 race sampan 262163 124 race . . .
RW() race.ato.c 15 -> i=1 RW() race.ato.c 15 -> i=3 RW() race.ato.c 15 -> i=4
RW() race.ato.c 15 -> i=2 RW() race.ato.c 15 -> i=4
The improved version, in which we have made from the read-write activity of the atomic worker one critical section, is shown below. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// raceA.m //pragma include "raceA.ato.h" #include "MBL.h" #include "rdid.h" manifold RW(process sem) atomic {internal.}. process sem is semaphore(). /***************************************************/ manifold Main() { begin: (activate(sem), RW(sem), RW(sem)). }
The le with the internal worker is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/* raceA.ato.c */ #include "AP_interface.h" #include "MBL_interface.h" #include "raceA.ato.h" #include "adid.h" int i; /***************************************************/ void RW(AP_Process sem) { while (1) { MBL_locksema(sem, AP_noevent); i++; PI(i) MBL_unlocksema(sem, AP_noevent); } }
The le with the prototype is
76
CHAPTER 10. DEBUGGING MANIFOLD APPLICATIONS 1 /* raceA.ato.h */ 2 3 #include "AP_interface.h" 4 5 extern void RW(AP_Process sem); 6
In this version, the semaphore created in the MANIFOLD world (line 11) is passed as a parameter to the atomic world (line 17). We use MBL locksema and MBL unlocksema (lines 17, 19) to synchronize the read-write activities from and to the shared resource. The prototypes of MBL locksema and MBL unlocksema are in le MBL interface.h (see line 5). Deadlocks can easy arise in a MANIFOLD application when two process instances try to acquire rights to the same pair of global resources in alternate order. The rule is that all process instances in your MANIFOLD program must always pursue locks in the same order. If, en route to obtaining lock B, a lock must rst obtain lock A, then no process instance should try to obtain lock B rst without obtaining lock A. Another simple way to create a deadlock is forgetting to unlock a mutex. Situations like above are easy to detect by adding simple wrapper routines around the MBL locksema and MBL unlocksema calls in your program, that shows the status of the semaphore.
10.3 Some Additional Guidelines & Tips In this section we give some additional guidelines and tips that can help you by solving bugs. Use separate header les that contains the prototype declaration from the atomic workers and manners, and use them in the source le of the atomics (with an #include) as well as in the MANIFOLD source les (with //pragma include). In section 10.3.1 on page 77 we give the reason for this and discuss some examples. Most prede ned manifolds and manners in MBL.h raise respectively post events to signal errors. In some cases those error events are received as parameter and in other cases those event are global events. In the rst case, those error events signal \expected unusual" situations that are not really always (or ever) errors (e.g., the manifold checkunits and the manner (un)locksema). In the second case, the errors are more serious (e.g., the manifold alarm and readstr). In general we recommend that the calling modules react and handle those events, so that eventual problems clearly become visible. In section 10.3.2 on page 78 we give an example. Sometimes bugs are very dicult to reproduce. By introducing time delays in well chosen places in then MANIFOLD application, we can enforce a complete dierent ordering of the program's events in the application. It can happen that such adapted applications reveals the bugs more easily. When that is the case we have created an application for which it is easier to nd and solve the bug. In section 10.3.3 we give an example. Making deeply nested, recursive calls and using large automatic arrays in atomic workers can cause problems because multi threaded programs have a more limited stack size than single-threaded programs. In section 10.3.4 we explain how to solve this problem. Change your multi task application into a single task application and have a look if the problem still occurs. If this is the case (lets hope it is the same problem) you are lucky because single task application are in general more easy to debug. \Less is Better" to debug. Because it is easier to detect a mistake in something small than in something big, stripping your source les is also a good way to nd bugs. Be happy when you nd a bug in this way, but don't expect it is always the bug you started with. Maybe it is another bug that is also in the original source code or maybe (and this is less pleasant) the bug is introduced by the stripping itself. Be comforted, in either case you learn something from it.
10.3. SOME ADDITIONAL GUIDELINES & TIPS
77
A hang can arise in running MANIFOLD application when the application can not enter its termination phase. In principle this is also a deadlock situation. We refer to section 10.3.4 on page 81 for some examples and a discussion how to avoid problems like this. When you have a debugger with built-in thread support you can use this one to debug your atomic workers. Also the sources of the managers (the MANIFOLD compiler generates C source from it) are in principle to debug in this way. Because we, as users, do not have insight in this C code (we did not wrote it, so it is not our business), we never debug regular manifolds in this way. Sit down, lock the door, and read your code very conscientious. In section 10.3.5 we give an example. The best general, but silly, advice we can give is, try to avoid programming mistakes (we don't have an example).
10.3.1 Using Prototypes
The atomic manners and atomic processes de ned in a MANIFOLD source le represent calls to their corresponding C functions, as de ned by the MANIFOLD programmer, in the C code generated by the MANIFOLD compiler. The actual C code for the de nition of these C functions is, of course, contained in a separate le. This makes it easy for a MANIFOLD programmer to de ne, by mistake, a dierent parameter signature for an atomic manner or an atomic process in the MANIFOLD source le, than for its corresponding C function, in the C source le. Such an error can neither be detected by the MANIFOLD compiler, nor by the C compiler, nor does the C compiler produce enough information for the linker to detect it when the object codes of the two are put together. To avoid problems like this, we always specify the prototypes of the atomic manners and manifolds in a .ato.h le so that we can include this le in the source code of the atomics (with ANSI C #include) as well as in the MANIFOLD source les (with //pragma include). In this way we ensure that a parameter mismatch will result in a syntax error issued by the C compiler. Have a look to the MANIFOLD program below. In this program we make in Main's begin state an implicit process instantiation of an atomic worker named X (line 14). This atomic worker has as argument an event. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// parameters.m //pragma include "parameters.ato.h" manifold X(event) atomic {internal.}. event e. /***************************************************/ manifold Main() { event e. begin: X(e). }
The le with the internal worker is 1 2 3 4 5 6 7 8 9 10
/* parameters.ato.c */ #include "AP_interface.h" #include "parameters.ato.h" void X(AP_Event e1) { /* Here we do something with the event */ }
and the le with the prototype is 1 /* parameters.ato.h */ 2 3 #include "AP_interface.h" 4 5 extern void X(AP_Event e1); 6
CHAPTER 10. DEBUGGING MANIFOLD APPLICATIONS
78
Note that we indeed have taken up a //pragma on line 3 with the le parameters.ato.h that contains the prototype of the atomic worker and that this le is also included with #include in the atomic worker on line 1. Taking up the //pragma parameters.ato.h in the manifold source le results in a simple include of this le in the .c le that is generated by the MANIFOLD compiler from the manifold source le. Because this le is also included in parameters.ato.c we ensure with this way of working that a parameter mismatch always will result in a syntax error issued by the C compiler. Let us try it and add another formal parameter only in the le parameters.ato.c and forget to edit the les parameters.m and parameters.ato.h. The changed le is 1 2 3 4 5 6 7 8 9 10
/* parameters.ato.c */ #include "AP_interface.h" #include "parameters.ato.h" void X(AP_Event e1, AP_Event e2) { /* Here we do something with the event(s) */ }
When we now type in gmake R=parameters.m the C compiler generates the following error message when it compiles parameters.ato.c. parameters.ato.c", line 7: error(1143): declaration is incompatible with "void X(AP_Event)" (declared at line 5 of "parameters.ato.h") void X(AP_Event e1, AP_Event e2) ˆ
When we also change the parameters.ato.h)
parameters.ato.h
le as shown below (but still forget to adapt
1 /* parameters.ato.h */ 2 3 #include "AP_interface.h" 4 5 extern void X(AP_Event e1, AP_Event e2); 6
and we try to compile the application again, we get next error message. "171694.c", line 140: error(1162): too few arguments in function call if (_MaNiF_SetJump(_MaNiF_SetJumpBuf)==0) X(&_T__UQ_fpm_11_); ˆ
This error message is again coming from the C compiler that tries to compile the C code (stored in the le 171694.c) generated by the MANIFOLD compiler. Of course it is not the intention that a user must understand this generated C code. The only thing a user should notice is that when error messages are generated in which something is said about _MaNiF_SetJump, this must be interpreted as \probably there are some problems with parameter headings"
Further Reading
section 23.2 on page 76 in the reference manual.
10.3.2 Handle the Error Events Alarm Example
In the program below, we have written a manner wam (an abbreviation of \Wait A Minute") in which begin state an implicit process instantiation of alarm is made (line 13). alarm is a prede ned manifolds (see reference manual page 100 ) that waits for a number of milliseconds (speci ed by its rst argument) after which it raises an event (speci ed by its second argument). 1 2 3 4 5 6 7
/* alarm.m */ #include "MBL.h" #include "rdid.h" #define IDLE terminated(void)
10.3. SOME ADDITIONAL GUIDELINES & TIPS 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
79
/***************************************************/ manner wom(process p) { event e. begin: (alarm(p, end), IDLE). end:. } /***************************************************/ manifold Main { begin: wom(10). }
When we run this application it does not produce any output (which is correct) and it hangs for ever (which is must longer than the delay of 10 milliseconds that is speci ed on line 21). This latest is of course a bug. How can we nd out which one? Using the MES macro to trace the state visiting in the manifold and manner and adding an error handler for the prede ned event alarm can raise to signal errors, we come to next version of the program. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/* alarmA.m */ #include "MBL.h" #include "rdid.h" #define IDLE terminated(void) /***************************************************/ manner wom(process p) { event e. begin: (MES("begin"), alarm(p, end), IDLE). bad_arg: MES("bad_arg"). end: MES("end"). } /***************************************************/ manifold Main { begin: (MES("begin"), wom(10)). }
end: MES("end").
The sorted output of this program is From this output we see that Main has been terminated and that the manner wam handles on line 13 the bad arg event. Probably there is a problem with one of the arguments in the alarm call. This should ring a bell, and we realize that the second argument (which is the event alarm should raise when the speci ed amount of time is past) is wrong, because the end event is not raise-able (see reference manual page 95 ). In order to repair the bug simple change the event into a raise-able event (see the correct version of wam on lines 8-15 in the le switch.m on page 81).
readstr Example
In next MANIFOLD program an instance of the prede ned manifold readstr reads a unit, that represents a oat, from its input port and produces a unit through its output port according the speci ed format (%f) in its parameter port (see the reference manual on page 103 for a full description of readstr). This output is consumed by the manifold macro STDOUT LB NL of table 9.2 on page 60 as we see on line 9. 1 2 3 4 5 6 7 8 9 10
//readstr.m #include "MBL.h" #include "rdid.h" /***************************************************/ manifold Main() { begin: 1.0 -> readstr("%f") -> STDOUT_LB_NL(self). }
When we run this example, we do not see any output. Let us nd out the problem and add for each prede ned events readstr can raise, an handler. In the reference manual page 103 we can read that there are two event that signals errors, namely bad format and bad input. Adding handlers for them leads to the following source code.
CHAPTER 10. DEBUGGING MANIFOLD APPLICATIONS
80 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
//readstrA.m #include "MBL.h" #include "rdid.h" #define IDLE terminated(void) /***************************************************/ manifold Main() { begin: (MES("begin"), 1.0 -> readstr("%f") -> STDOUT_LB_NL(self), IDLE). bad_format: MES("bad_format"). bad_input: MES("bad_input"). }
end: MES("end").
Note that we now have to \babysit" the begin state (IDLE is added on line 9) so we are sure that eventual raised events are also handled. When we run this program its output is sampan 263897 87 readstrA Main() readstrA.m 11 -> begin sampan 263897 87 readstrA Main() readstrA.m 15 -> bad_input sampan 263897 87 readstrA Main() readstrA.m 17 -> end
From this output we see that readstr consumes a wrong unit. Again a bell will ring and we realize that we have fed readstr a oat instead of a string. So when we simply change 1.0 in "1.0" and everything is ne.
10.3.3 Setting Switches
Below we have a program that mostly produces the right output (i.e., two lines of output). However sometimes (lets say, once in every hundred (or thousand) executions) a certain scheduling makes it fail and the program only produces one line of output. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// wrong_drain2.m #include #include "rdid.h" /***************************************************/ manifold Main { auto process printl is STDOUT_LB_NL(self). begin: 1 -> printl. end: 2 -> printl. }
The output of a successful run is sampan 263704 95 wrong_drain2 Main() wrong_drain2.m 10 -> 1 sampan 263704 95 wrong_drain2 Main() wrong_drain2.m 10 -> 2
The output of a unsuccessful run is sampan 263704 95 wrong_drain2 Main() wrong_drain2.m 10 -> 1
Because the problem is so dicult to reproduce (the error lies around in the code like an accident waiting to happen), it is not always easy to detect the problem. Of course we can use the MES macro in order to follow the state switching, but that will not bring us any further in this case. One of the questions we might have is: \Can it be that printl sometimes terminates during the state switching?". How can we investigate this suspection? One way is to wait a little while at the end of the begin state on line 13 so that printl has more time to die if it wants to do that. Next MANIFOLD source code is an adaption of the previous source code whereby we now wait for 10 milliseconds at the end of the begin state (line 23). 1 2 3 4 5 6 7 8 9 10
// switch.m #include #include "rdid.h" /***************************************************/ manner wam(process p) { event e.
10.3. SOME ADDITIONAL GUIDELINES & TIPS 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
begin:
81
(alarm(p, e), terminated(void)).
e: halt. } /***************************************************/ manifold Main { auto process printl is STDOUT_LB_NL(self). begin: 1 -> printl; wam(10). end: 2 -> printl. }
wam (an abbreviation of \Wait A alarm (see reference manual page
Minute") is a manner in which we use the prede ned manifold 100 ) in order to wait a number of milliseconds (speci ed by the parameter of the manner). When we now run the adapted source again and again, we only see next one line of output. sampan 262183 95 switch Main() switch.m 20 -> 1
Thus with the addition of wam we have made the problem reproducible and we also have a serious indication that the problem has something to do with the termination behavior of printl. When we recall the termination behavior of printl (it terminates once it detects a disconnection on it input port!), we now can understand why we sometimes see only one line. The solution is to make the stream to printl reconnectable, so that there is only one stream to printl. Such a stream is only disconnected from its sink in this case, once the manifold left the scope on line 27 (see drain2.m on page 63 for the correct version of this program). For convenience we have add the manner wam to libdid.a (see 2.3 on page 14) so that we can use it for manipulating the working of a MANIFOLD program. Note that when we compile the source code above, we get the compilation error \redefinition of "wam(process)"" because now wam is de ned in the source code as well as in the library libdid.a. Simple rename wam in the source code an the error is gone. Do not use wam in a naive way. Be always prepared that by adding wam after a semicolon, this semicolon itself adds a preemption point to the state and this can eect the whole protocol. Note also that adding the manner wam into a group is only meaningful when we want to delay the total setup of a pipeline.
10.3.4 Problems with Stacksize
If the default stacksize (i.e. 16384 (2**14)) is not sucient for process instances of a certain manifold (lets name it X) and you think 99999 is a better value then take it up in the task map le of MLINK with next stanza (see the MLINK documentation). {task * {stack X 99999} }
10.3.5 Head breakers
Sometimes you really have a hard job to nd the bug. Take for example next program where ANSI C's free destroys the integrity of the heap because its argument is not a heap address from a previous call to an allocator. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// mistake.m //pragma include "mistake.ato.h" #include "MBL.h" manner OO(process) atomic. /***************************************************/ manifold Main() { auto process p is variable. begin: OO(p). }
CHAPTER 10. DEBUGGING MANIFOLD APPLICATIONS
82
The le with the atomic manner is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/* mistake.ato.c */ #include "AP_interface.h" #include "mistake.ato.h" #include "adid.h" /***************************************************/ void OO(AP_Process p) { int err; err = AP_DeallocateProcess(p); I(err) }
The le with the prototype(s) is 1 /* mistake.ato.h */ 2 3 #include "AP_interface.h" 4 5 extern void OO(AP_Process p);
In this program, a process created in a regular manifold (line 12) is parameter of an atomic worker in which somewhere, deep in the nested code (not in this case), by accident, AP_DeallocateProcess is called. Because in this routine ANSI C's free is called, we are in fact freeing non heap memory (heap memory is memory you allocate by yourself). Because you can only free memory that is allocated by yourself, this program can produce a core dump. In this case an ordinary debugger will not help you because the core dump can start, for instance, at the moment you want to use the freed memory. This can be far away (in the source code lines) from the line where the trouble maker has been called (in this case line 14). The best general advice in such case is probably to read your source code very conscientious with special attention to the last changes you made in it. Also special memory checking software as Purify1 can help you to detect such problems.
1
A product from Pure Software Inc.; email:
[email protected]
Chapter 11
Some Instructive Examples
83
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
84
11.1 Playing with variables We saw in x?? that constants are processes in MANIFOLD. In fact, MANIFOLD knows only processes; there are no data structures in MANIFOLD, not even the simplest kind, a variable. What corresponds to a variable in MANIFOLD is also a process. The fact that this process has a special prede ned behavior is irrelevant { MANIFOLD treats it the same way as it does any other process. In this section we de ne the behavior of variable processes and use them to illustrate some of the features of the MANIFOLD language. An instance of the manifold variable() reads the units it receives on its input port. Each time, it remembers the unit it reads, and if the departure side of its output is connected, it passes the unit on through its output port. A remembered unit is repeatedly copied to output every time the departure side of output is connected to a stream. Instances of variable never terminate, and they never raise any events. The manifold variable(port in i) provides initialized variables. An instance of this manifold behaves as an instance of variable() does, except that it rst obtains a single unit from its parameter i, and uses it as its initial value. We see here that MANIFOLD allows functors, e.g., variable, to be overloaded. A manifold or manner de nition is fully identi ed not by its functor name alone, but by a combination of the its functor name and its parameter types. Thus, variable() and variable(port in) designate dierent manifolds. An instance of the manifold variable(port in) can be created, for example, in a construct such as process q is variable(2.3). In this example, 2.3 is a constant, which is a process, and its output port is passed as the actual parameter to a newly created instance of variable(port in), named q. As far as q is concerned, its parameter is a port from which it can read: i.e., an in-port. By its de nition, q will use the unit it obtains from its parameter as its initial value. Similarly, a construct like process q is variable(proc.out1) causes q to use a unit produced by the process proc on its output port out1 as its initial value. 21 v
print
(a)
v
print
(b)
Figure 11.1: An in nite loop using a variable Now consider the MANIFOLD program below. It contains a new construct, called a group, in the state of the manifold Main. All streams de ned in a group are constructed simultaneously.
begin
11.1. PLAYING WITH VARIABLES
85
Thus, in our example, once main enters its begin state, three BK-type (by default) streams are constructed among the three process instances v, 21, and print. Figure 11.1.a shows these connections. 1 2 3 4 5 6 7 8 9 10 11 12
// printer_21.m manifold printunits import. manifold variable import. auto process v is variable. /***************************************************/ manifold Main { begin: (21 -> v, v -> v, v -> printunits). }
The constant 21 dies immediately after it delivers its value into the stream connecting its port to the input port of v. This stream therefore breaks at its source. Note that at this point, the stream connecting the output port of v to the input port of v contains no units. Thus, when v attempts to read from its input port, it is bound to read the unit produced by 21. As soon as v reads in this unit, the stream that contained it becomes empty, and because it is also disconnected from its source, it dies and disappears. Figure 11.1.b shows the remaining connections at this point. The de nition of v states that it will produce a copy of the unit it has read on its output port. As soon as this happens, the unit is duplicated and a copy of it is placed into each of the two outgoing streams connected to the output port of v. One of these will end up in the process print, which will print it. The other one nds its way back at the input port of v. Once again, v reads in this unit and copies it to its output port, and the same sequence is repeated inde nitely. This example shows the \plumbing" aspect of MANIFOLD programming: no explicit action is necessary to \move" information around in MANIFOLD { provide the pipes, and the units will
ow. The process main in this example will never terminate. A unit containing the value 21 loops inde nitely from the output port of v to its input port, and each time a copy of it is printed by print. Although there is no explicit loop construct in this program, it loops for ever and produces an output that consists of the value 21 printed out an in nite number of times. The following MANIFOLD program uses the initialized variable to yield the same behavior. output
1 2 3 4 5 6 7 8 9 10 11
// initialized.m manifold printunits import. manifold variable(port in) import. auto process v is variable(21). manifold Main { begin: (v -> v, v -> printunits). }
Futher Reading To be lled in later.
11.1.1 Fibonacci Series
The looping eect explained in the examples in x?? can be used to construct useful programs. In the example shown below, we use the initialized variable of x?? and a new atomic process Sum to compute the Fibonacci series (1, 2, 3, 5, 8, 13, etc.). Atomic manifolds are always written in C contrary with regular manifolds which are always written in the MANIFOLD language. 1 2 3 4 5 6 7 8 9
// fibo.m //pragma include "fibo.ato.h" #include "MBL.h" #include "rdid.h" manifold Sum(event)
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
86 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
port in x. port in y. atomic {internal.}. event overflow. auto process v0 is variable(0). auto process v1 is variable(1). auto process sigma is Sum(overflow). /***************************************************/ manifold Main() { begin: (v0 -> sigma.x, v1 -> sigma.y, v1 -> v0, sigma -> v1, sigma -> STDOUT_LB_NL(self)). overflow.sigma: halt. }
On the lines 5-8 we see the interface declaration of Sum. The real body of Sum is contained in another source le ( bo.ato.c). There are two new linguistic element in this declaration. The rst new element is that in addition to the default ports that all manifolds have, this manifold has two input ports named x and y (lines 6-7). The second new element is the declaration is the formal event parameter of Sum on line 5. On line 15 we see that we make a global auto process sigma with a global de ned event overflow as an actual parameter. An instance of Sum reads a pair of units, one from each of its input ports x and y, veri es that they contain numeric values, adds them together, and produces the result in a unit on its output port. It then tries to obtain a new pair of input units to produce their sum, and continues to do so inde nitely, as long as its input ports are still connected to incoming streams. If a pair of input values are so large that their addition causes an over ow Sum raises the event it receives as its actual parameter. In our case, the process sigma, an instance of Sum de ned on line 15, speci es this event as overflow. α|1 α
v0
α|1|0
x sigma
α|1 v1
α print
y
Figure 11.2: The Fibonacci series Figure 11.2 shows the connections made among various processes in the begin state of main. In order to understand how this set of connections produces the Fibonacci series, we consider the sequence of units that ow through each stream. Let be the sequence of units produced through the output port of the process sigma. Clearly, this is the sequence of units printed by print, and we want to show that it is indeed the Fibonacci series. The sequence of units that show up at the input port of v1 is, obviously, . This means that the sequence of units produced through the output port of v1 consists of 1 (the initial value of v1) followed by . This same sequence shows up at the input port of v0 and at the port y of sigma. It follows that the sequence of units produced through the output port of v0 (which shows up at the x port of sigma) consists of 0 (the initial value of v0), followed by 1, followed by . Now, observe that the rst pair of units that arrive at the ports x and y of sigma contain, respectively, 0 and 1. Thus, by the de nition of sum (of which sigma is an instance), the rst unit in contains 0 + 1, i.e., 1. Therefore, the second pair of units that arrive at the ports x and y of sigma contain, respectively, 1 and 1 (the rst unit in ). Hence, the second unit in contains 1+1, i.e., 2. The third pair of units that arrive at the ports x and y of sigma contain, respectively, 1 (the rst unit in ) and 2 (the second unit in ), which produces a 3 for the third unit in . This con guration of processes will continue to produce the Fibonacci numbers 1, 2, 3, 5, 8, 13, 21, 34, 55, etc., until sigma encounters an over ow. In reaction to the occurrence of the event
11.1. PLAYING WITH VARIABLES
87
raised by sigma, main makes a transition to its corresponding state. The transition out of the begin state preempts (i.e., breaks up) the streams connected therein. Both sigma and print terminate as soon as they detect they have no incoming streams. The transition into the new state (line 22) executes the action halt, which terminates the main process. The implementation of the atomic Sum is in the source le fibo.ato.c below. It is a straight forwards C subroutine that makes use of the interface libary fo atomic manners and compliant atomic processes. overflow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
/* fibo.ato.c */ #include "AP_interface.h" #include "fibo.ato.h" #include "adid.h" #define MAXINT 1000 void Sum(AP_Event too_big) { int err; int x = AP_LocalPortId("x"); int y = AP_LocalPortId("y"); int out = AP_LocalPortId("output"); AP_Unit u1, u2, u3; int i1, i2, i3, elno; int mtag, utag, fmt; I(x) I(y) I(out)
}
while (1) { err = AP_PortRemoveUnit(x, &u1, NULL); err = AP_PortRemoveUnit(y, &u2, NULL); err = AP_GetUnitTypeTag(u1, &mtag, &utag, &elno, &fmt); if (utag == AP_INTEGER) { err = AP_FetchInteger(u1, &i1); err = AP_DeallocateUnit(u1); } else { printf("sum received a ’no integer type’\n"); return; } err = AP_GetUnitTypeTag(u2, &mtag, &utag, &elno, &fmt); if (utag == AP_INTEGER) { err = AP_FetchInteger(u2, &i2); err = AP_DeallocateUnit(u2); } else { printf("sum received a ’no integer type’\n"); return; } i3 = i1 + i2; if (i3 > MAXINT) { AP_Raise(too_big); return; } u3 = AP_FrameInteger(i3); err = AP_PortPlaceUnit(out, u3, NULL); err = AP_DeallocateUnit(u3); }
P(u1) I(err) P(u2) I(err) I(err) I(err) I(err)
I(err) I(err) I(err)
P(u3) I(err) I(err)
Lookup the relevant AP functions in the reference manual[2]. and you will understand the implementation. The le that contains the atomic manifolds must always include the le AP interface.h le. In the le debug.h (see in the example directory) there are macros that can help you with checking the return codes of the AP functions. The make le we use to compile MANIFOLD programs is only appropriate to create a one task application (initially) (seex?? for a make le appropriate for a multiple task application). It can compile the x.m le and its corresponding x.ato.c in which we had to store the atomic manifolds. If a x.m le does not have a corresponding x.ato.c the make le doesnot have a problem with that. Thus both les (fibo.m and fibo.ato.c) are compiled with make P=fibo and executing starts by typing fibo. The output is as expected. The output is sampan sampan sampan sampan sampan sampan sampan sampan
262222 262222 262222 262222 262222 262222 262222 262222
108 108 108 108 108 108 108 108
fibo fibo fibo fibo fibo fibo fibo fibo
Main() Main() Main() Main() Main() Main() Main() Main()
fibo.m fibo.m fibo.m fibo.m fibo.m fibo.m fibo.m fibo.m
24 24 24 24 24 24 24 24
-> -> -> -> -> -> -> ->
1 2 3 5 8 13 21 34
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
88 sampan sampan sampan sampan sampan sampan sampan
262222 262222 262222 262222 262222 262222 262222
108 108 108 108 108 108 108
fibo fibo fibo fibo fibo fibo fibo
Main() Main() Main() Main() Main() Main() Main()
fibo.m fibo.m fibo.m fibo.m fibo.m fibo.m fibo.m
24 24 24 24 24 24 24
-> -> -> -> -> -> ->
55 89 144 233 377 610 987
Solved Problems 1. a) Rewrite the atomic Sum manifold into a regular manifold (thus in the MANIFOLD language). b) Compare its performance with the atomic version. c) Is it in this case necessary to work with an event parameter in Sum. Answer: a) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
// fibo_regular.m #define MAXINT 1000 manifold printunits import. manifold variable() import. manifold variable(port in) import. event overflow. auto process v0 is variable(0). auto process v1 is variable(1). auto process print is printunits. /***************************************************/ manifold Sum(event e) port in x. port in y. { event loop. auto process s is variable. begin|loop: if (s = x + y) > MAXINT then raise(e) else { begin: getunit(s) -> output; post(loop). }
} auto process sigma is Sum(overflow).
/***************************************************/ manifold Main() { begin: (v0 -> sigma.x, v1 -> sigma.y, v1 -> v0, sigma -> v1, sigma -> print). overflow.sigma: halt. }
b) It is much slower due to the number of proceeses on board on the regular Sum manifold. c) No. In the atomic version of Sum it was necessay to have the event parameter in Sum because Sum is a C code and thatswhy de ned in another le. Normally global event are only known to all manifold de ned in one source le. See for the new version of fibo regular.m the le fibo regular new.m in the example directory. 2. Write a manifold program that generates the Fibonacci serie without using MANIFOLD variables. Compare its performance with the the version in fibo.m. Answer: 1 2 3 4 5 6 7 8 9
// quick_fibo.m manifold printunits import. manifold pass import. manifold Sum(event) port in x. port in y. atomic {internal.}.
11.1. PLAYING WITH VARIABLES 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
event overflow. /***************************************************/ manifold Main { auto process p1 is pass. auto process p2 is pass. auto process sigma is Sum(overflow). stream reconnect BK *-> p1. begin : 0 -> p1; (1 -> p2 -> sigma.y, p2 -> p1 -> sigma.x, sigma -> p2, sigma -> printunits ). overflow.sigma: halt. }
89
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
90
11.2 The Sieve of Eratothenes 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
################# ################# ################# 2 ################# ################# #################
1th sieve
3 5 7 9 11 13 15 17 19
################# ################# sieve 2nd ################# 3 ################# ################# ################# ################# 5 7 11 13 17 19
################# ################# ################# 8th sieve ################# 19 ################# ################# ################# Figure 11.3: The sieve of Eratothenes Figure 11.3 shows the working of the sieve of Eratothenes. Figure 11.4 shows network connection in the sieve of Eratothenes. A intial try can be a program like this: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
//pragma include "era.ato.h" #include "MBL.h" #include "rdid.h" manifold Generate(port in) atomic {internal.}. manifold Shaker() port out prime. atomic {internal.}. manifold Sieve() { event goon. process shaker is Shaker.
11.2. THE SIEVE OF ERATOTHENES
91
Sieve
n
Shaker
n
Sieve
n-1
Shaker
n-1
Sieve
n
Sieve2
Shaker2
Sieve3
Sieve1
Generate
Shaker1
Sieve2
Figure 11.4: The network connection in the sieve of Eratothenes 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
stream KK input -> shaker. stream KK shaker.prime -> *. begin: ( MES("Sieve: begin"), activate(shaker), input -> shaker, guard(shaker.output, full, goon), shaker.prime -> stdout ). goon: (MES("Sieve: goon"), shaker -> Sieve). end: MES("Sieve: end") } /***************************************************/ manifold Main(process arg) { begin: (MES("Main: begin"), Generate(tuplepick(arg, 2)) -> Sieve). end: MES("Main: end"). }
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
92
Unfortunately a prograkm like this will hang. Give the reasons ETC. Change the termination behaviour of shaker. This give the following program: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
//pragma include "era.ato.h" #include "MBL.h" #include "rdid.h" #define IDLE terminated(void) manifold Generate(port in) atomic {internal.}. manifold Shaker() port out prime. atomic {internal.}. manifold Sieve() { event goon, park, input_disconnected. process shaker is Shaker. stream KK shaker -> *. stream KK shaker.prime -> *. stream KK input -> shaker. begin: ( MES("Sieve: begin"), activate(shaker), input -> shaker, guard(input, a_everdisconnected, input_disconnected), guard(shaker.output, full, goon), shaker.prime -> stdout ). goon: (MES("Sieve: goon"), shaker -> Sieve). input_disconnected: (MES("Sieve: input_disconnected"), close(input), terminated(shaker)). }
end: MES("Sieve: end")
/***************************************************/ manifold Main(process arg) { begin: (MES("Main: begin"), Generate(tuplepick(arg, 2)) -> Sieve). end: MES("Main: end"). }
The .. are implemented in C and are: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
/* fibo.ato.c */ #include "AP_interface.h" #include "era.ato.h" #include "adid.h" #include /*****************************************************************/ void read_str_from_non_local_port(AP_Port port, char *str) { AP_Unit u; int err; err = AP_PortGetUnit(port, &u, NULL); I(err) P(u) err = AP_FetchString(u, str, 40); I(err) err = AP_DeallocateUnit(u); I(err) } /*****************************************************************/ void write_int_to_port_index(int index, int i) { AP_Unit u; int err; u = AP_FrameInteger(i); P(u) err = AP_PortPlaceUnit(index, u, NULL); I(err) err = AP_DeallocateUnit(u); I(err) } /*********************************************************************/ int read_int_from_port_index(int index, int *i, AP_EventPatternSet eps) { int err, flag; AP_Unit u; flag = AP_PortRemoveUnit(index, &u, eps); /* flag is 0 to indicate succesfull termination, otherwise an error occurs */ if (flag == 0) { /* Something has been read */
11.2. THE SIEVE OF ERATOTHENES 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
93
err = AP_FetchInteger(u, i); I(err) err = AP_DeallocateUnit(u); I(err) } else if (flag < 0 && flag != AP_DISRUPTED) { /* Something else is wrong */ I(flag) } return flag; } /*********************************************************************/ void Generate(AP_Port port) { int err, num, i; char buf[100]; read_str_from_non_local_port(port, buf); sscanf(buf, "%d", &num); sprintf(buf, "The prime numbers below %d are as follows\n", num); S(buf) for (i = 2; i shaker. stream KK shaker.prime -> *. begin: ( MES("Sieve: begin"), activate(shaker), inp -> shaker, guard(shaker.output, full, goon), shaker.prime -> stdout ). goon: (MES("Sieve: goon"), Sieve(shaker) ).
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
94 34 35 36 37 38 39 40 41 42 43 44 45 46
end: MES("Sieve: end") } /***************************************************/ manifold Main(process arg) { begin: (MES("Main: begin"), Sieve(Generate(tuplepick(arg, 2))) ). end: MES("Main: end"). }
The version with the shortcut is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
//pragma include "era.ato.h" #include "MBL.h" #include "rdid.h" #define IDLE terminated(void) auto process n is variable. manifold Generate(port in) atomic {internal.}. manner checker(port in, port in, event, event) returns event atomic. manifold Shaker() port out prime. atomic {internal.}. manifold Sieve(port in inp) { event check, already_primes, not_yet_primes. process shaker is Shaker. auto process prime is variable. stream KK inp -> shaker. stream KK shaker.prime -> *. begin: ( MES("Sieve: begin"), activate(shaker), inp -> shaker, guard(shaker.output, full, check), shaker.prime -> (->stdout, -> prime) ). check: (MES("Sieve: check"), post(checker(n, prime, already_primes, not_yet_primes)) ). already_primes: (MES("Sieve: already_primes"), shaker -> stdout). not_yet_primes: (MES("Sieve: not_yet_primes"), Sieve(shaker)). end: MES("Sieve: end") } /***************************************************/ manifold Main(process arg) { begin: (MES("Main: begin"), n = tuplepick(arg, 2), Sieve(Generate(n)) ). end: MES("Main: end"). }
Futher Reading To be lled in later.
11.3. BUCKET SORT
95
11.3 Bucket Sort xn
an
xn-1 xn mn-1 an-1
x2 x3 m2 a2
x1 x2 m1 a1
read
print
Figure 11.5: Bucket Sort Figure 11.5 shows the bucket sort problem. The example in the previous section was simple enough to require only a static pattern of communication. In this section, we illustrate the dynamic capabilities of MANIFOLD through a program for sorting an unspeci ed number of input units. The particular algorithm used in this example is not necessarily the most eective one. However, it is simple to describe, and serves our purpose of demonstrating the dynamic aspects of the MANIFOLD language well. The sort algorithm is as follows. There is a suciently large (theoretically, in nite) number of atomic sorters available, where each atomic sorter ai is able to sort a bucket of ki > 0 units very eciently. (The number ki may vary from one atomic sorter to the next in an \unpredictable" way; e.g., each atomic sorter can internally decide for itself how many units it is willing to sort, taking into consideration such \unpredictable" and (ir)relevant factors as the load of the system, the phase of the moon, etc.)
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
96
Each atomic sorter receives its input through its input port; raises a speci c event it receives as a parameter to inform other processes that it has lled up its input bucket; sorts its units; produces the sorted sequence of the units through its output port; and terminates. The parallel bucket sort program is supposed to feed as many of its own input units to an atomic sorter as the latter can take; feed the rest of its own input as the input to another copy of itself; merge the two output sequences (of the atomic sorter and its new copy); and produce the resulting sequence through its own output port. Merging of the two sorted sequences can be done by a separate merger process, or by a subprogram (i.e., a manner) called by the sorter. We assume our application consists of several source les. The rst source le contains our Main manifold, as shown below. We assume that the merger is a separate process. The merger and the atomic sorter can be written in the MANIFOLD language, but they will be more ecient if they are written in a computation language, such as C. We do not concern ourselves here with the details of the merger and the atomic sorter, and assume that each is de ned in a separate source le. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
manifold manifold manifold manifold manifold
printunits import. ReadFile(process filename) atomic {internal.}. Sorter import. AtomicSorter(event) atomic {internal.}. AtomicIntMerger port in a, b. atomic {internal.}.
/*****************************************************************/ manifold Main { auto process read is ReadFile("unsorted"). auto process sort is Sorter. auto process print is printunits. begin:
read -> sort -> print.
}
The main manifold (instance) in this application creates read, sort, and print as instances of manifold de nitions ReadFile, Sorter, and PrintUnits, respectively. It then connects the output port of read to the input port of sort, and the output port of sort to the input port of print. The process main terminates when both of these connections are broken. The process read is expected to read the contents of the le named unsorted and produce a unit for every sort item in this le through its output port. When it is through with producing its units, read simply terminates. The process sort is an instance of the manifold de nition Sorter, which is expected to sort the units it receives through its input port. This process terminates when its input is disconnected and all of its output units are delivered through its output port. input
atomsort
input
Sorter
a
merge
(a) output
atomsort
(b)
atomsort
output
b
(c)
Figure 11.6: Bucket sort The manifold de nition Sorter, shown below, is our main interest. The keyword export on line 1 allows other separately compiled MANIFOLD source les to import and use this coordinator manifold. In its begin state, an instance of Sorter connects its own input to an instance of the AtomicSorter, it calls atomsort. It also installs a guard on of its input port using the guard primitive action. This guard posts the event finished if it has an empty stream connected to its departure side, after the arrival side of this port has no more stream connections, following a rst connection. This means that the event finished is posted in an instance of Sorter after a rst connection to the arrival side of its input is made, then all connections to the arrival side of its
11.3. BUCKET SORT
97
input are severed, and all units passed through this port are consumed. The connections in this state are shown in Figure 11.6.a. 1 export manifold Sorter() 2 { 3 event filled, flushed, finished. 4 process atomsort is AtomicSorter(filled). 5 stream reconnect KB input -> *. 6 priority filled < finished. 7 8 begin: ( 9 activate(atomsort), input -> atomsort, 10 guard(input, a_everdisconnected!empty, finished) // no more input 11 ). 12 13 finished: { 14 ignore filled. //possible event from atomsort 15 16 begin: atomsort -> output //your output is only that of atomsort 17 }. 18 19 filled: { 20 process merge is AtomicIntMerger. 21 stream KK * -> (merge.a, merge.b). 22 stream KK merge -> output. 23 24 begin: ( 25 activate(merge), 26 input -> Sorter -> merge.a, 27 atomsort -> merge.b, 28 merge -> output 29 ). 30 31 end | finished:. 32 }. 33 34 end: { 35 begin: ( 36 guard(output, a_disconnected, flushed), // ensure flushing 37 terminated(void) //wait for units to flush through output 38 ). 39 40 flushed: halt. 41 }. 42 }
Two events can preempt the begin state of an instance of Sorter: (1) if the incoming stream connected to input is disconnected (no more incoming units) and atomsort reads all units available in its incoming stream, the guard on input posts the event finished; and (2) the process atomsort can read its ll and raise the event filled. Normally, only one of these events occurs; however, when the number of input units is exactly equal to the bucket size, k, of atomsort, both finished and filled can occur simultaneously. In this case, the priority statement on line 6 makes sure that the handling of finished takes precedence over filled. Assume that the number of units in the input supplied to an instance of Sorter is indeed less than or equal to the bucket size k of an atomic sorter. In this case, the event finished will preempt the begin state and cause a transition to its corresponding state in Sorter. In this state, we ignore the occurrence of filled that may have been raised by atomsort (if the number of input units is equal to the bucket size k); and deliver the output of atomsort as the output of the Sorter. The connections in this state are shown in Figure 11.6.b. Now suppose the number of units in the input supplied to an instance of Sorter is greater than the bucket size k of an atomic sorter. In this case, the event filled will preempt the begin state and cause a transition to its corresponding state in Sorter. In this state we create an instance of the merger process, called merge. A new instance of the Sorter is created in the begin state of the
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
98
nested block. The rest of the input is passed on as the input to this new Sorter, and its output is merged with the output of the atomic sorter and the result is passed as the output of the Sorter itself. The connections in this state are shown in Figure 11.6.c. An occurrence of finished in this state preempts the connected streams and causes a transition to the local finished state in this block. This preemption is necessary to inform the new instance of Sorter (by breaking the stream that connects input to it) that it has no more input to receive, so that it can terminate. The empty body of the finished state means that it causes an exit from its containing block. In the end state, a Sorter instance installs a guard on its output port, to post the event flushed after there is no stream connected to the arrival side of this port following its rst connection. This means that the event flushed is posted in an instance of Sorter after a connection is made to its arrival side, and all units arriving at this port have passed through. The Sorter instance then waits for the termination of the special prede ned process void, which will never happen (the special process void never terminates). This eectively causes the Sorter instance to hang inde nitely. The only event that can terminate this inde nite wait is an occurrence of flushed which indicates there are no more units pending to go through the output port of the Sorter instance. read
a1
m1 m2
a2 a3
an-1
print
m3
mn-1
an
Figure 11.7: The atomic processes at work Figure 11.7 shows the connections among the various instances of AtomicSorter and AtomicIntMerger during an execution of our sort program. Each ai and mi denote the ith instance of the AtomicSorter and AtomicIntMerger, respectively. Note the dynamic way in which the reconnectable KB stream emanating from read cascades its output to the various atomic sorters, allowing each of them to ll up its bucket with as many input units as it decides for itself. This reconnection of the same stream to successive processes is depicted by the dashed arrows in Figure 11.7. What is not shown in this gure is the n instances of the Sorter manifold. For 0 < i < n, the ith instance of Sorter coordinates the ith instance of AtomicSorter, ai , the ith instance of AtomicIntMerger, mi , and the i + 1st instance of Sorter. The nal (i.e., nth ) instance of Sorter is a degenerate case and has nothing to \coordinate" but the nth instance of AtomicSorter, an . An interesting aspect of the Sorter manifold is the dynamic way in which it switches connections among the process instances it creates. Perhaps more interesting, is the fact that, in spite of its name, Sorter knows nothing about sorting! If we change its name to X , and systematically change the names of the identi ers it uses to Y1 through Yk , we realize that all it knows is to divert its own input to an instance of some process it creates; when this instance raises a certain event, it is to divert the rest of its input to a new instance of itself; and to divert the output of these two processes to a third process, whose output is to be passed out as its own output. What Sorter embodies is a protocol that describes how instances of two process de nitions (e.g., AtomicSorter and AtomicIntMerger in our case) should communicate with each other. Our Sorter manifold can just as happily orchestrate the cooperation of any pair of processes that have the same input/output and event behavior as AtomicSorter and AtomicIntMerger do, regardless of what computation they perform. The cooperation protocol de ned by Sorter simply doles out
11.3. BUCKET SORT
99
chunks of its input stream to instances of what it knows as AtomicSorter and diverts their output streams to instances of what it knows as AtomicIntMerger. What is called AtomicSorter needs not really sort its input units, the process called AtomicIntMerger needs not really merge them, and neither has to produce as many units through its output as it receives through its input port. They can do any computation they want. By parameterizing the names of the manifolds used in Sorter and changing its name to ProtocolX, we obtain a more general program: 1 export manifold ProtocolX(manifold M1(event), manifold M2) 2 { 3 event filled, flushed, finished. 4 process m1 is M1(filled). 5 stream reconnect KB input -> *. 6 priority filled < finished. 7 8 begin: ( 9 activate(m1), input -> m1, 10 guard(input, a_everdisconnected!empty, finished) // no more input 11 ). 12 13 finished: { 14 ignore filled. //possible event form m1 15 16 begin: m1 -> output //your output is only that of m1 17 }. 18 19 filled: { 20 process m2 is M2. 21 stream KK * -> (m2.a, m2.b). 22 stream KK m2 -> output. 23 24 begin: ( 25 activate(m2), 26 input -> ProtocolX(M1, M2) -> m2.a, 27 m1 -> m2.b, 28 m2 -> output 29 ). 30 31 end | finished:. 32 }. 33 34 end: { 35 begin: ( 36 guard(output, a_disconnected, flushed), // ensure flushing 37 terminated(void) //wait for units to flush through output 38 ). 39 40 flushed: halt. 41 }. 42 }
The new version of our bucket sort main program using ProtocolX is: 1 2 3 4 5 6 7 8 9 10 11 12 13
manifold manifold manifold manifold manifold
printunits import. ProtocolX(manifold M1(event), manifold M2) import. ReadFile(process filename) atomic {internal.}. AtomicSorter(event) atomic {internal.}. AtomicIntMerger port in a, b. atomic {internal.}.
/****************************************************************/ manifold Main { auto process read is ReadFile("unsorted"). auto process sort is ProtocolX(AtomicSorter, AtomicIntMerger). auto process print is printunits.
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
100 14 begin: 15 }
read -> sort -> print.
Futher Reading To be lled in later.
11.4. QUICK SORT
101
11.4 Quick Sort qsort
gqsort
merge
split
lqsort
readfile
print
Figure 11.8: Quick Sort Figure 11.8 shows the quick sort problem. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
//pragma include "qsort5.ato.h" #include "MBL.h" #include "rdid.h" #define IDLE terminated(void), preemptall /*********************************************************************/ manifold ReadFile(port in filename) atomic {internal.}. /*********************************************************************/ manifold Split(port in pivot) port in input. port out l. port out e. port out g. atomic {internal.}. /*********************************************************************/ manifold Merge port in l. port in e. port in g. port out output. { event connect_e, connect_g, flush. begin: ( MES("begin"), l -> output, guard(l, a_everdisconnected!empty, connect_e) ). connect_e: ( MES("connect_e"), e -> output, guard(e, a_everdisconnected!empty, connect_g)
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
102 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
). connect_g: ( MES("connect_g"), g -> output, guard(g, a_everdisconnected!empty, end) ). end: MES("end").
} /*********************************************************************/ manifold Qsort(manifold Split(port in), manifold Merge) port in input:keep-*. forward. /*********************************************************************/ manifold Main { auto process readfile is ReadFile("unsorted"). auto process qsort is Qsort(Split, Merge). begin: (MES("begin"), readfile -> qsort -> MSTDOUT_LB_NL(self) ). end: MES("end") } /*********************************************************************/ export manifold Qsort(manifold Split(port in), manifold Merge) port in input:keep-*. { event get1st. begin: ( MES("begin"), guard(input, a_everdisconnected, end), guard(input, full, get1st), IDLE ). get1st: { auto process pivot is variable. begin: guard(input, a_everdisconnected, noevent); pivot = input; { event no_input, sort_rest. begin: ( MES(""), guard(input, a_everdisconnected, no_input), guard(input, full, sort_rest), IDLE ). no_input: output = pivot. sort_rest: { event flush. process process process process
split is Split(pivot). gqsort is Qsort(Split, Merge). lqsort is Qsort(Split, Merge). merge is Merge.
/* We declare stream types as KK, except the stream from input to split which must be BK. Upon preemption by flush of the guard(input, a_everdisconnected, flush) input -> split breaks which cause a dismantling of the network from left to right. */ stream KK * -> Qsort. stream KK * -> (merge.l, merge.e, merge.g). stream KK merge -> output.
}.
}.
begin: ( MES(""), activate(split, lqsort, gqsort, merge), input -> split, split.g -> gqsort -> merge.g, split.e -> merge.e, split.l -> lqsort -> merge.l, merge -> output, guard(input, a_everdisconnected, flush) ). flush: (MES(""), guard(output, a_disconnected, end), IDLE). }.
11.4. QUICK SORT
103
131 end: MES("end"). 132 }
The le with the internal workers is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
#include #include #include #include #include
"AP_interface.h" "adid.h" "qsort.ato.h"
#define FALSE 0 #define TRUE 1 /*************************************************************************/ void ReadFile(AP_Port port) { /* This routine reads the contents of a file with name proc (=string constant); produce a unit for every sort item in this file through its AP_OUTPUT port; and terminates. At this moment the routine can only handle integers. However it can easily be adapted to other types. Termination: upon EOF. */ /* For other types use the secondary type tag for inquiring its type */ AP_Unit u; int value; FILE *in; char *in_name; int size; AP_Event get_connection_event = AP_AllocateEvent(); AP_EventPatternSet eps = AP_AllocateEventPatternSet(); AP_Event r = AP_AllocateEvent(); AP_Process p = AP_AllocateProcess(); int get_connection[] = {AP_d_connected}; int err;
err = AP_PrivateEventValue(get_connection_event); err = AP_EventPatternSetInsert(eps, get_connection_event, NULL);
S("ReadFile: WELCOME") P(get_connection_event) P(eps) P(p) P(r) I(err) I(err)
/* get_connection guard is set */ err = AP_LocalPortGuard(AP_OUTPUT, 1, get_connection, get_connection_event); err = AP_WaitEvent (eps, r, p); err = AP_DeleteEvent(r, p); if ((err = AP_EqualEvent(r, get_connection_event) ) == 1) { /* connection got */
I(err)
I(err) I(err) I(err) S("get_connection_event")
} else {
S("strange event") err = AP_Cancel(); I(err) } /* Read the unit with the file name from the port but first allocate suffucient memory for in_name, which had to store the filename. */ err = AP_PortGetUnit(port, &u, NULL); I(err) P(u) /* AP_PrintUnit(stdout, u); */ size = AP_GetUnitSize(u); I(size) PI(size) if (size > 0) { in_name = (char *) malloc (((size + 7) / 8) + 1 ); } if (in_name == NULL) { S("malloc failed") err = AP_Cancel(); I(err) } /* PI(sizeof(*in_name)) */ /* err = AP_FetchString(u, in_name, sizeof(*in_name)); I(err) */ /* PI(strlen(in_name)) */ err = AP_FetchString(u, in_name, ((size + 7) / 8) + 1); I(err) err = AP_DeallocateUnit(u); I(err) /* Open the file */ if ( (in = fopen(in_name, "r")) == NULL) { S("Can’t open file") err = AP_Cancel(); I(err) } /* Read the contents of the file */ while (fscanf(in, "%d", &value) != EOF) { PI(value) u = AP_FrameInteger(value); P(u) err = AP_PortPlaceUnit(AP_OUTPUT, u, NULL); I(err) err = AP_DeallocateUnit(u); I(err) } /* Close the file and deallocate in_name and port */ fclose(in);
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
104 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
free(in_name); } /*****************************************************************/ int read_int_from_non_local_port(AP_Port port) { AP_Unit u; int err, i; err = AP_PortGetUnit(port, &u, NULL); I(err) P(u) err = AP_FetchInteger(u, &i); I(err) err = AP_DeallocateUnit(u); I(err) return i; }
S("ReadFile: GOODBYE")
/*************************************************************************/ void Split(AP_Port port) { /* Split reads the first unit from the parameter port and remembers it as a pivot. It reads all other units from input and sends it through one of the three ports. l: if the input units is less than pivot. e: if the input units is equal than pivot. g: if the input units is greater than pivot. Remark: the pivot is always send out through the equal port. Termination protocol: terminate on AP_a_everdisconnected. */ int i, err, pivot; AP_Unit u; AP_Event term_event = AP_AllocateEvent(); AP_Event full_event = AP_AllocateEvent(); AP_EventPatternSet eps = AP_AllocateEventPatternSet(); AP_Event r = AP_AllocateEvent(); AP_Process p = AP_AllocateProcess(); int l = AP_LocalPortId("l"); int e = AP_LocalPortId("e"); int g = AP_LocalPortId("g"); int termination_guard[] = {AP_a_everdisconnected}; int full_guard[] = {AP_full}; S("Split: WELCOME") P(term_event) P(full_event) P(eps) P(r) P(p) S("Welcome in Split") /* read the pivot from the parameter port and place it on the "e" port */ err = AP_PortGetUnit(port, &u, NULL); I(err) P(u) err = AP_FetchInteger(u, &pivot); I(err) W err = AP_PortPlaceUnit(e, u, NULL); I(err) W err = AP_DeallocateUnit(u); I(err) /* term_event and full_event are insert into eps */ err = AP_PrivateEventValue(term_event); err = AP_PrivateEventValue(full_event); err = AP_EventPatternSetInsert(eps, term_event, NULL); err = AP_EventPatternSetInsert(eps, full_event, NULL);
I(err) I(err) I(err) I(err)
/* Termination guard is set */ err = AP_LocalPortGuard(AP_INPUT, 1, termination_guard, term_event);
I(err)
/* read an integer from the input port and place in one of the three ports */ while (TRUE) { err = AP_LocalPortGuard(AP_INPUT, 1, full_guard, full_event); I(err) S("Before AP_WaitEvent") err = AP_WaitEvent (eps, r, p); I(err) S("After AP_WaitEvent") if ((err = AP_EqualEvent(r, term_event) ) == 1) { I(err) S("term_event in EM") break; } else if ((err = AP_EqualEvent(r, full_event) ) == 1) { I(err) S("full_event in EM") err = AP_PortRemoveUnit(AP_INPUT, &u, NULL); I(err) P(u) /* AP_PrintUnit(stdout, u); */ err = AP_FetchInteger(u, &i); I(err) PI(i) if (i > pivot) { W err = AP_PortPlaceUnit(g, u, NULL); I(err) W } else if (pivot == i) { W err = AP_PortPlaceUnit(e, u, NULL); I(err) W } else { W err = AP_PortPlaceUnit(l, u, NULL); I(err) W }
11.4. QUICK SORT
105
187 err = AP_DeallocateUnit(u); /* no memory leakage */ 188 } 189 err = AP_DeleteEvent(r, p); 190 } 191 192 }
The le with the prototypes is 1 2 3 4 5 6 7
/* qsort.ato.h */ #include "AP_interface.h" extern void ReadFile(AP_Port port); extern void Split(AP_Port port);
The output is sampan sampan sampan sampan sampan sampan sampan sampan sampan
262146 262146 262146 262146 262146 262146 262146 262146 262146
79 79 79 79 79 79 79 79 79
qsort qsort qsort qsort qsort qsort qsort qsort qsort
Main() Main() Main() Main() Main() Main() Main() Main() Main()
qsort.m qsort.m qsort.m qsort.m qsort.m qsort.m qsort.m qsort.m qsort.m
55 55 55 55 55 55 55 55 55
-> -> -> -> -> -> -> -> ->
2 3 4 4 5 6 7 8 9
I(err) I(err) S("Split: GOODBYE")
106
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
11.5 Domain Decomposition
Figure 11.9: Domain Decomposition Figure 11.9.a shows the domain Decomposition problem.
Futher Reading To be lled in later.
11.6. THE DINING PHILOSOPHERS PROBLEM
107
11.6 The Dining Philosophers Problem Zie de program beschrijving in par computing journal blz 1001 There are 5 philosophers who spend their lives thinking and eating. The philosophers share a common circular table surrounded by 5 chairs, each belonging to one philosopher. In the center of the table is a bowl of rice, and the table is laid with 5 single chopsticks, one on each side of each philosopher. When a philosopher gets hungry and tries to pick up the 2 chopsticks that are closest to him (the chopsticks that are between her and her left and right neighbors). A philosopher may pick up only one chopstick at a time. Obviously, he cannot pick up a chopstick that is already in the hand of a neighbor. When a hungry philosopher has both his chopsticks at the same time, he eats without releasing his chopsticks. When he is nished eating, he puts down both her chopsticks and starts thinking again. Sooner or later, as each philosopher become hungry and tries to grab both chopsticks for food in a random fashion, a deadlock will occur such that a circular waiting sequence whereby each philosopher will be holding to one chopstick and waiting for his neighbor to release his chopsticks. Everyone's waiting and holding to one single chopstick and no one can start eating (or thinking). The situation may end up in a serious brawl whereby the philosophers start poking one another in the eye with their single chopstick, and the nearby hospital ward will be left with 5 more rice-hungry patients. :) The dining philosophers' problem has become a classic synchronization problem because it is an example for a large class of concurrency-control problems. It is a simple representation of the need to allocate several resources among several processes in a deadlock and starvation-free manner.
Figure 11.10: The Five Philosophers Figure 11.10 shows the ve Philosophers sitting at the round table. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// PhilosMain.m: #include "MBL.h" manifold Philosopher() import. manifold Chopstick() import. extern auto process tickets is variable(4). #define FIVE 5 /********************************************************/ manner dollar(port in v) returns process { process p deref v. begin: return(p).
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
108 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
} /********************************************************/ Main() { auto process phil is variable[FIVE]. auto process chop is variable[FIVE]. auto process i is variable. begin: for i = 0 while i < FIVE step i = i + 1 do { process p is Philosopher(). process c is Chopstick().
}
begin: (phil[i] = &p, chop[i] = &c). }; for i = 0 while i < FIVE step i = i + 1 do { begin: (signin(dollar(chop[i]), dollar(phil[i]), dollar(phil[mod(i + 1, FIVE)]) ), signin(dollar(phil[i]), dollar(chop[i]), dollar(chop[mod(i + FIVE - 1 , FIVE)]) ) ). }; for i = 0 while i < FIVE step i = i + 1 do { begin: activate(dollar(phil[i]), dollar(chop[i])). }.
1 // PhilosCP.m: 2 3 #include "rdid.h" 4 5 #define WAIT (preemptall, terminated(self)) 6 7 event request, done. 8 manner GetTicket() import. 9 manner ReturnTicket() import. 10 11 export Chopstick() 12 { 13 begin: while true do 14 { 15 begin: WAIT. 16 17 request.*phil & *ready.*phil: 18 { 19 save *. 20 begin: (raise(ready), WAIT). 21 done.phil:. 22 }. 23 }. 24 } 25 26 export Philosopher() 27 { 28 event ready. 29 30 begin: while true do 31 { 32 ignore *. //ready from other phils raised by a chop 33 begin: MES("Think"); 34 GetTicket(); 35 (raise(request, ready), WAIT). 36 37 // ready.*lchop & ready.*rchop: MES(). Werkt niet; referentie kan je niet printen 38 ready.*lchop & ready.*rchop: MES("Eat"). 39 40 end: raise(done); 41 ReturnTicket(). 42 }. 43 } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// PhilosTicket.m #include "MBL.h" #define WAIT terminated(self) event badsema. extern process tickets. auto process tksema is semaphore(). export manner GetTicket() { save *. event retry. retry | begin: locksema(tksema, badsema); if (tickets>0) then { begin: tickets=tickets-1; unlocksema(tksema, badsema). } else { begin: { save *. begin: guard(tickets.input, transport, retry); unlocksema(tksema, badsema).
11.6. THE DINING PHILOSOPHERS PROBLEM 27 28 29 30 31 32 33 34 35 36 37 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
}.
}; terminated(self)
} export manner ReturnTicket() { save *. begin: locksema(tksema, badsema); tickets=tickets+1; unlocksema(tksema, badsema). } #include #include #include #include #include
"AP_interface.h" "MBL_interface.h" "philo.ato.h"
int prpr (AP_Process pr, char* b, int s) { char buf [1024]; int l = AP_SPrintProcess (buf, 1024, pr); sscanf (buf, "Process id:%s", b); return strlen(b); } /***************************************************/ void Eat (AP_Process phil, AP_Process chop1, AP_Process chop2) { int l, idx = 0, size = 1024; char buf [1024]; AP_Unit u ; strcpy ( &buf[idx], "\nEat: "); size -= 8; idx += 8; l = prpr (phil, &buf[idx], size); size -= l; idx += l; strcpy ( &buf[idx], "-"); size -= 1; idx += 1; l = prpr (chop1, &buf[idx], size); size -= l; idx += l; strcpy ( &buf[idx], "-"); size -= 1; idx += 1; l = prpr (chop2, &buf[idx], size); size -= l; idx += l; buf[idx] = ’\0’; u = AP_FrameString (buf); (void) MBL_stdout (u); (void) AP_DeallocateUnit(u); } /***************************************************/ void Think (AP_Process phil) { int l, idx = 0, size = 1024; char buf [1024]; AP_Unit u ; strcpy ( &buf[idx], "\nThink: "); size -= 8; idx += 8; l = prpr (phil, &buf[idx], size); size -= l; idx += l; buf[idx] = ’\0’; u = AP_FrameString (buf); (void) MBL_stdout (u); (void) AP_DeallocateUnit(u); }
Futher Reading To be lled in later.
109
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
110
11.7 A Water Installation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
// ww2.m //pragma include "ww2.ato.h" #include "MBL.h" manifold myrand(port in, port in) atomic {internal.}. manifold alarm(port in, event) import. manifold kettle port out radiator. port out tap. forward. auto process k is kettle. /*********************************************************************/ manifold kettle port out radiator. port out tap. { event to_rad, to_tap, stop. priority to_tap > to_rad. stop | begin: ( guard(radiator, d_connected, to_rad), guard(tap, d_connected, to_tap) ); ( "kettle: doesnot send water" -> stdout, terminated(void) ). to_tap: { ignore to_rad. begin: ( guard(radiator, d_connected, noevent), guard(tap, d_disconnected, stop), "kettle: sending water to tap" -> stdout, terminated(void) ). }. to_rad: { begin: ( guard(radiator, d_disconnected, stop), "kettle: sending water to radiator" -> stdout, terminated(void) ). to_tap: ( guard(radiator, d_disconnected, noevent), post(stop) ). }.
} /*********************************************************************/ manifold radiator { event tc, we. we | begin: ( alarm(5000, tc), "radiator: off" -> stdout, terminated(void) ). tc: ( alarm(1000, we), k.radiator -> self.input, "radiator: on" -> stdout ).
} /*********************************************************************/ manifold tap { event on, off. off | begin: ( alarm(2000, on), "tap: off" -> stdout, terminated(void) ). on: ( alarm(100, off), k.tap -> self.input, "tap: on" -> stdout ).
} /*********************************************************************/ manifold Main
11.7. A WATER INSTALLATION 97 { 98 process r is radiator. 99 process t is tap. 100 101 event geb. 102 103 begin: ( 104 activate(r, t), 105 alarm(40000, geb), 106 terminated(void) 107 ). 108 109 geb: 110 deactivate(r, t). 111 }
Futher Reading To be lled in later.
111
112
CHAPTER 11. SOME INSTRUCTIVE EXAMPLES
11.8 The 8-puzzle Problem
Chapter 12
Miscellaneous and Advanced Features
113
114
CHAPTER 12. MISCELLANEOUS AND ADVANCED FEATURES
Chapter 13
Multi Task Applications Another Time Table Tennis De twee manieren uitleggen. Ik heb er iwat aantekeningen over. The start up machine is de nmachine waar je de eerste task instance opstart. Ook asl je in de task map zegt welke machien je moet hebben , de task instance die het eerst draait draait op de amchine waar je hem opstart. Er is dus iets merkwaardigst aan de hand. met dei start up machine. Eeen verise van prod consEW.tex in destribueerde verise maken. Gewoon twee taken maken.
115
116
CHAPTER 13. MULTI TASK APPLICATIONS
Appendix A
Of All Sorts
117
118
APPENDIX A. OF ALL SORTS
Appendix B
Contents of the libdid.a library The contents of libdid.a (see 2.3) is The manners macros of table 9.1 on page 57. The manifolds macros of table 9.2 on page 60. The ANSI C macros in table 9.3 on page 65. The manifolds and manners described in next sections
B.1 manner wam(process p) The manner wam (an abbreviation of \Wait A Minute") waits the speci ed number of milleseconds that it receives via its parameter p and returns
B.2 manifold black hole() An instance of black hole consumes every unit it receives through its input port and never produces a unit on its output or error port. Furthermore, it never raises any event and it terminates when the last stream is disconnected from the arrival side (i.e., once it detects the a connected ! a disconnected condition on its input port).
B.3 manner id(process p) returns process The manner id (an abbreviation of IDenti cation) returns the who-what-where label of the speci ed process as a constant (i.e., a process).
119
120
APPENDIX B. CONTENTS OF THE LIBDID.A LIBRARY
Appendix C
Manifold Basics in Questions & Answers Format What is a state? Something that looks syntactically as StateLabel : StateBody .
What is a simple state? A simple state is a state whose body is a pipeline.
What is a pipeline? A pipeline is a MANIFOLD language construct that de nes:
a set of manner calls to be executed upon a transition to the state that contains it;
a set of primitive actions to be performed upon in that state;
a set of processes as the ones whose events can preempt the pipeline (preemptive sources); and
the termination condition for the pipeline.
Pipelines are used as state bodies. Two such primitive actions are specially of interest in a pipeline: the stream constructor that exactly does what its name implies and the terminated primitive in which we can specify the termination condition for the pipeline. A pipeline is constructed upon transition to its corresponding state. The construction of a pipeline is considered to be an atomic activity. With this we mean that the construction of a pipeline is indivisible (cannot be split up). Thus there is no chance of the construction being half-completed or of another being interspersed. It is all or nothing. A pipeline becomes preemptable once it is fully constructed. This allows event occurrences from the processes that are in the set of preemptive event sources of the pipeline to cause a state transition out of the current state (which contains the pipeline). Termination of a pipeline (see below) is a special case of preemption and thus can happen only after it becomes preemptable. Units can ow through the stream connections made in a pipeline only after it is constructed. 121
122
APPENDIX C. MANIFOLD BASICS IN QUESTIONS & ANSWERS FORMAT
At what moment a pipeline is considered as fully constructed? A pipeline is considered as fully constructed if all the actions in the pipeline are performed and are considered as complete. In the reference manual we can read for each primitive action when it is done. (Look for the sentence \the job of the ... primitive action is done (or complete) as soon as ...")
What does it mean that \a pipeline is preemptable"? A pipeline is preemptable means that event occurrences from the processes that are in the set of preemptive event sources of the pipeline that match the labels of other states, cause a state transition out of the current state (which contains the pipeline) into another state.
What is the set of preemptive event sources of the pipeline Preemptive event sources are processes what are used in the pipeline.
What does it mean \a pipeline terminates"? By de nition, a pipeline terminates when (TDP): 1 1. all streams it constructs or connects are broken (on at least, one end) (TDP1) and 2. all processes mentioned in its terminated primitives (if the pipeline has a one) are terminated. (TDP2)
What happens when we connect a stream to a dead or nonexistent process Connecting a stream to a dead or non-existent process is not an error: its eect is the same as a stream that breaks up immediately after it is connected, due to the death of its source and/or sink process.
Up to now we only spoke about termination and preeemption of pipelines. Can we use these words also in connection with states? Yes. Preemptability and termination of a state whose body is a pipeline is determined by the preemptability and termination of its pipeline. So when we say \the pipeline (of state x) is preempted" this is the same as saying \state x is preempted". The same counts for termination.
What is a stream? A fo buer. 1 The bold mnemonics between brackets are used in the examples to make references to these rules. In appendix ?? we enumerate all these abbreviations.
123
When can a stream die? A stream dies, and any units still in its FIFO queue are lost, when the following two conditions are ful lled: 1. The stream should have permission-to-die (PTD). Except reconnectable streams, all stream instances receive their permission-to-die from their creators at the time they are created. Reconnectable streams receive their permission-to-die when their creator leaves the scope (i.e., block) wherein they were created. 2. The stream should be ready-to-die which is de ned as (a) it is disconnected from both its source and its sink (RTD-DSS); or (b) it is empty and is disconnected from its source (RTD-EDS).
When does a break-type connections break? A break-type connection between a stream and a port breaks when: 1. the stream dies (B-SD); 2. the process to which the port belongs dies (B-PD); 3. the port is closed or disconnected (B-PCD); 4. the stream is preempted by its constructor (B-SP); or 5. the stream realizes that the connection at its other end is broken (see below) (B-SRB). Normally, a stream realizes the breakup of a connection at either of its ends immediately after it occurs. However, a reconnectable stream cannot realize the breakup from its source unless/until its status is/becomes empty.
When does a keep-type connections break? A keep-type connection between a stream and a port breaks for the following two reasons: 1. the stream dies (K-SD); or
2. the process to which the port belongs dies (K-PD).
What is the eect of a preemption of a simple state? Preemption of a simple state causes (is de ned as) the preemption of its pipeline, which results in (is de ned as) the preemption of its (remaining) streams.
What is preemption of a stream? Preemption of a stream means (is de ned as) breaking its break-type connections. Normally streams are BK stream. Thus when p en q are processes and we have the stream p!q, than preemption of the stream means that p!q becomes pn ! q. So the connection between p en the source of the stream is broken and the connections between the sink of the stream and q remains undamaged. Table ?? shows the preemption behaviour of dierent stream types.
124
APPENDIX C. MANIFOLD BASICS IN QUESTIONS & ANSWERS FORMAT
What happens after the termination of a pipeline? When a pipeline terminates, a special event, referred to as in this document, is automatically posted to signal that the end of the state has been reached. Because has the lowest priority, it causes a transition only if no other observed event occurrence can cause a transition. The standard transition caused by the occurrence of is to an end state of the block if such a state exists; otherwise, this transition terminates the current block and returns to its parent block. Thus termination of a pipeline is a special case of preemption namely preemption by .
When does a MANIFOLD application terminate? A MANIFOLD application enters its termination phase when all of its processes except the following have terminated: 1. constants, noprocess, system, void, stdin, stdout, and stderr; and 2. all process instances (global or otherwise) created with an auto attribute. In the termination phase, all remaining global process instances created with an auto attribute are rst deactivated. The application then nally terminates (normally) when (and if), disregarding the ones listed under 1 above, the last of its processes terminates (TDA).
Abbreviations mnemonic RTD RTD-DSS RTD-EDS PTD B-SD B-PD B-PCD B-SP B-SRB K-SP K-PD TDP TDA
meaning Ready-To-Die Ready-To-Die, stream is Disconnected from both its Source and its Sink Ready-To-Die, stream is Empty and is Disconnected from its Source Permission-To- Die Break-type; the Stream Dies Break-type; the Process to which the port belongs Dies Break-type; the Port is Closed or Disconnected Break-type; the Stream is Preempted by its constructor Break-type; the Stream Realizes that the connection at its other end is Broken Keep-type; the Stream Dies Keep-type; the Process to which the port belongs Dies Termination de nition Pipeline Termination de nition Application Table C.1: Abbreviations used in the examples
Appendix D
Some Remarks about the Make le Hierin gmake R= A= H= doen en hierin alle label uitleegn ook ARGV voor b.v fac 10 of extra libraries The MANIFOLD compiler always runs rst the preprocessor on the le. The output of the preprocessor is input for the real manifold compiler. This compiler produce Ansi C source code which is further compiled with the ordinary C compiler to object code. * The make le (it is a gnu make le, so install gmake) used for compiling and running the manifold programs is named "make le". This make le include some other make les they are: checker - heart - runner printer * Cleanup the manifold application with make R=debug2.m clean * Er is nu ook een O= last mintute argument. de Inhoud daarvan word met -i ags aan mlink commando meegegeven en deze .0 les worden dus in elke taak meegeladen. Ik heb de O= ag nog niet beschreven in tutorial. Eesr meer ervaring opdoem met multi task applicaties. Daarna dit verder gaan beschrijven. voorbeeld in /ufs/ever/local/mani/latex/CFD/WORK6 de aanroep gmake R=model4.m O="fmodel4.f oneram6.f basis3.f" LD=f77
125
126
APPENDIX D. SOME REMARKS ABOUT THE MAKEFILE
Appendix E
Glossary task is a heavy weight process task type task instance manifold is a process type written in the MANIFOLD language. From this templete we can make process instances. manifold instance is a process made from a manifold type; Thus it is a coordinator. manifold process is a manifold instance or an internal or external atomic (this description is more general). process instance is manifold instance (coordinator) or an internal atomic (a worker). manifold type is a manifold. atomic process is a process which is not written in the MANIFOLD language. It can be written in C, C++, Fortran, Java, or it can be an Unix script or whatever. An atomic process can be external or internal. compliant internal process zie ergens in MI.
compliant external process non-compliant external process port process stream
127
136
APPENDIX E. GLOSSARY
Bibliography [1] F. Arbab. The IWIM model for coordination of concurrent activities. In Paolo Ciancarini and Chris Hankin, editors, Coordination Languages and Models, volume 1061 of Lecture Notes in Computer Science, pages 34{56. Springer-Verlag, April 1996. [2] F. Arbab. Manifold version 2: Language reference manual. Technical report, Centrum voor Wiskunde en Informatica, Kruislaan 413, 1098 SJ Amsterdam, The Netherlands, 1996. Available on-line http://www.cwi.nl/ftp/manifold/refman.ps.Z. [3] F. Arbab. The in uence of coordination on program structure. In Proceedings of the 30th Hawaii International Conference on System Sciences. IEEE, January 1997. [4] F. Arbab, C.L. Blom, F.J. Burger, and C.T.H. Everaars. Reusable coordinator modules for massively concurrent applications. Software: Practice and Experience, 28(7):703{735, June 1998. Extended version. [5] A. Geist, A. Beguelin, J. Dongarra, W. Jiang, R. Manchek, and V. Sunderam. PVM 3 user's guide and reference manual. Technical Report ORNL/TM-12187, Oak Ridge National Laboratory, September 1994. [6] D. Gelernter. Generative communication in Linda. ACM Transactions on Programming Languages and Systems, 7(1):80{112, 1985. [7] D. Gelernter and N. Carriero. Coordination languages and their signi cance. Communication of the ACM, 35(2):97{107, February 1992. [8] Bradford Nicols, Dick Buttlar, and Jacqueline Proulx Farrell. Pthreads Programming. O'Reilly & Associates, Inc., Cebastopol, CA, 1996.
137