Distributed computing: principles, algorithms, and systems [1 ed.] 9780521876346, 0521876346

Designing distributed computing systems is a complex process requiring a solid understanding of the design problems and

366 104 4MB

English Pages 748 Year 2008

Report DMCA / Copyright

DOWNLOAD PDF FILE

Recommend Papers

Distributed computing: principles, algorithms, and systems [1 ed.]
 9780521876346, 0521876346

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

c A. Kshemkalyani and M. Singhal, 2005

Limited circulation within a classroom, after seeking permission from authors.

1

DISTRIBUTED COMPUTING: PRINCIPLES, ALGORITHMS, and SYSTEMS Ajay D. Kshemkalyani Department of Computer Science University of Illinois at Chicago Chicago, IL 60607

Mukesh Singhal Department of Computer Science University of Kentucky Lexington, KY 40506

January 29, 2007

“To my father Shri Digambar and my mother Shrimati Vimala.” -Ajay D. Kshemkalyani “To my mother Chandra Prabha Singhal, my father Brij Mohan Singhal, and my daughters Meenakshi, Malvika, and Priyanka.“ -Mukesh Singhal

i

Preface Background The field of Distributed Computing covers “all aspects of computing and information access across multiple processing elements connected by any form of communication networks, whether local or wide-area in the coverage”. Since the advent of the Internet in the 1970s, there has been a steady growth of new applications requiring distributed processing. This was enabled by advances in networking and hardware technology, falling cost of hardware, and greater end-user awareness. These factors contributed to making distributed computing a cost-effective, high-performance, and fault-tolerant reality. Around the turn of the millenium, there has been an explosive growth in the expansion and efficiency of the Internet, and a matching outreach of access to networked resources through the World Wide Web, all across the world. Coupled with an equally dramatic growth in the wireless and mobile networking areas, and plummeting prices of bandwidth and storage devices, we are witnessing a rapid spurt in distributed applications and an accompanying interest in the field of distributed computing in universities, governments organizations, and private institutions. Advances in hardware technology have suddenly made sensor networking a reality, and embedded and sensor networks are rapidly becoming an integral part of each person’s life – from the home network with the interconnected gadgets to the automobile communicating by GPS (Global Positioning System), to the fully networked office with RFID monitoring. In the emerging global village, distributed computing will be the centerpiece of all computing and information access sub-disciplines within computer science. Clearly, this is a very important field. Moreover, this evolving field is characterized by a diverse range of challenges for which the solutions need to have foundations on solid principles. The field of distributed computing is very important, and there is a huge demand for a good comprehensive book. The book comprehensively covers all important topics in a great depth. This book provides both the depth and the breadth of coverage of topics in conjunction with the clarity of explanation and ease of understanding. The book will be particularly valuable to the academic community and the computer industry at large. Writing such a comprehensive book is an herculean task and is a huge undertaking. There is a deep sense of satisfaction in knowing that we were able complete this major task and perform this service to the community.

Description, Approach, and Features The book will focus on fundamental principles and models underlying all aspects of distributed computing. The book will address the principles underlying the theory, algorithms, and systems aspects of distributed computing. The manner of presentation of the algorithms is very lucid, explaining the main ideas and the intuition with figures and simple explanations rather than getting entangled in intimidating notations and lengthy and hard-to-follow rigorous proofs of the algorithms. The selection of chapter themes is broad and comprehensive, and the book covers all important topics in depth. The selection of algorithms within each chapter has been done carefully to elucidate new and important techniques of algorithm design. Although the book focuses on foundational aspects and algorithms for distributed computing, it thoroughly addresses all practical systems-like problems (e.g., mutual exclusion, deadlock detection, termination detection, failure

ii

recovery, authentication, global state and time, etc.) by presenting the theory behind and algorithms for such problems. The book is written keeping in mind the impact of emerging topics such as peer-to-peer computing and network security on the foundational aspects of distributed computing. Chapters of the book include figures, examples, exercise problems, a summary, and bibliography/references. An Index includes the index terms.

Readership This book is aimed as a textbook for • Graduate students and Senior level undergraduate students in Computer Science and Computer Engineering. • Graduate students in Electrical Engineering and Mathematics. As wireless networks, peerto-peer networks, and mobile computing continue to grow in importance, an increasing number of students from Electrical Engineering Departments will also find this book necessary. • Practitioners, systems designers/programmers, and consultants in industry and research labs will find the book a very useful reference because it will contain the state of the art algorithms and principles to address various design issues in distributed systems, as well as the latest references. The breadth and depth of coverage, accompanied by clarity of explanation and ease of understanding that the book offers will make it a very widely adopted textbook. Hard and soft prerequisites for the use of this book include • An undergraduate course in algorithms is required. • Undergraduate courses in operating systems and computer networks would be useful. • A reasonable familiarity with programming. We have aimed for a very comprehensive book that will be the single source that covers distributed computing models and algorithms. The book will have both depth and breadth of coverage of topics, and will be characterized by clear and easy explanations. None of the existing textbooks on distributed computing provides all of these features.

Acknowledgements This book grew from the notes used in the graduate courses on Distributed Computing at the Ohio State University, the University of Illinois at Chicago and at University of Kentucky. We would like to thank the graduate students at both these schools for their contributions to the book in many ways. The book is based on the published research results of numerous researchers in the field. We have made all efforts to present the material in our language and have given credit to original source of the information. We would like to thank all researchers whose work has been reported in this book. Finally, we would like to thank the staff of the Cambridge University Press for providing us with an excellent support for publication of the book. iii

Access to Resources The following web sites will be maintained for the book. Any errors and comments should be sent to [email protected] or [email protected]. Further information about the book can be obtained from authors’ web pages. • http://www.cs.uic.edu/∼ajayk/DCS-Book • http://www.cs.uky.edu/∼singhal/DCS-Book

Ajay D. Kshemkalyani Mukesh Singhal

iv

Contents 1 Introduction 1.1 Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Relation to Computer System Components . . . . . . . . . . . . . . . . . . . . 1.3 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4 Relation to Parallel Multiprocessor/Multicomputer Systems . . . . . . . . . . . 1.4.1 Characteristics of Parallel Systems . . . . . . . . . . . . . . . . . . . . 1.4.2 Flynn’s Taxonomy . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.3 Coupling, Parallelism, Concurrency, and Granularity . . . . . . . . . . 1.5 Message Passing Systems versus Shared Memory Systems . . . . . . . . . . . 1.5.1 Emulating message-passing on shared memory system (MP → SM). . 1.5.2 Emulating shared memory on a message-passing system (SM → MP ). 1.6 Primitives for Distributed Communication . . . . . . . . . . . . . . . . . . . . 1.6.1 Blocking/Nonblocking, Synchronous/Asynchronous Primitives . . . . 1.6.2 Processor Synchrony . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6.3 Libraries and Standards . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7 Synchronous versus Asynchronous Executions . . . . . . . . . . . . . . . . . 1.7.1 Emulating an asynchronous system by a synchronous system (A → S). 1.7.2 Emulating a synchronous system by an asynchronous system (S → A). 1.7.3 Emulations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.8 Design Issues and Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.8.1 Distributed Systems Challenges from a System Perspective . . . . . . . 1.8.2 Algorithmic Challenges in Distributed Computing . . . . . . . . . . . 1.8.3 Applications of Distributed Computing and Newer Challenges . . . . . 1.9 Selection and Coverage of Topics . . . . . . . . . . . . . . . . . . . . . . . . . 1.10 Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.11 Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.12 Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

1 1 2 3 5 5 10 10 13 13 13 14 14 17 17 18 19 19 20 21 21 22 28 30 31 31 32

2 A Model of Distributed Computations 2.1 A Distributed Program . . . . . . . 2.2 A Model of Distributed Executions . 2.3 Models of Communication Network 2.4 Global State of a Distributed System 2.4.1 Global State . . . . . . . . . 2.5 Cuts of a Distributed Computation .

. . . . . .

. . . . . .

37 37 38 40 40 41 42

. . . . . .

. . . . . . v

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

2.6 2.7

Past and Future Cones of an Event . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Models of Process Communications . . . . . . . . . . . . . . . . . . . . . . . . . 44

3 Logical Time 3.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 A Framework for a System of Logical Clocks . . . . . . . . 3.2.1 Definition . . . . . . . . . . . . . . . . . . . . . . . 3.2.2 Implementing Logical Clocks . . . . . . . . . . . . 3.3 Scalar Time . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1 Definition . . . . . . . . . . . . . . . . . . . . . . . 3.3.2 Basic Properties . . . . . . . . . . . . . . . . . . . 3.4 Vector Time . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4.1 Definition . . . . . . . . . . . . . . . . . . . . . . . 3.4.2 Basic Properties . . . . . . . . . . . . . . . . . . . 3.4.3 On the Size of Vector Clocks . . . . . . . . . . . . . 3.5 Efficient Implementations of Vector Clocks . . . . . . . . . 3.5.1 Singhal-Kshemkalyani’s Differential Technique . . . 3.5.2 Fowler-Zwaenepoel’s Direct-Dependency Technique 3.6 Jard-Jourdan’s Adaptive Technique . . . . . . . . . . . . . . 3.7 Matrix Time . . . . . . . . . . . . . . . . . . . . . . . . . . 3.7.1 Definition . . . . . . . . . . . . . . . . . . . . . . . 3.7.2 Basic Properties . . . . . . . . . . . . . . . . . . . 3.8 Virtual Time . . . . . . . . . . . . . . . . . . . . . . . . . . 3.8.1 Virtual Time Definition . . . . . . . . . . . . . . . . 3.8.2 Comparison with Lamport’s Logical Clocks . . . . . 3.8.3 Time Warp Mechanism . . . . . . . . . . . . . . . . 3.8.4 The Local Control Mechanism . . . . . . . . . . . . 3.8.5 Global Control Mechanism . . . . . . . . . . . . . . 3.8.6 An Example: Distributed Discrete Event Simulations 3.9 Physical Clock Synchronization: NTP . . . . . . . . . . . . 3.9.1 Motivation . . . . . . . . . . . . . . . . . . . . . . 3.9.2 Definitions and Terminology . . . . . . . . . . . . . 3.9.3 Clock Inaccuracies . . . . . . . . . . . . . . . . . . 3.10 Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . 3.11 Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . 3.12 Exercise Problems . . . . . . . . . . . . . . . . . . . . . . 4 Global State and Snapshot Recording Algorithms 4.1 Introduction . . . . . . . . . . . . . . . . . . . 4.2 System Model and Definitions . . . . . . . . . 4.2.1 System Model . . . . . . . . . . . . . 4.2.2 A Consistent Global State . . . . . . . 4.2.3 Interpretation in Terms of Cuts . . . . . 4.2.4 Issues in Recording a Global State . . . 4.3 Snapshot Algorithms for FIFO Channels . . . . vi

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

47 47 48 48 49 49 49 50 51 51 53 54 56 56 58 60 63 63 64 64 65 66 67 67 69 71 72 72 73 73 75 77 77

. . . . . . .

82 82 84 84 85 86 86 87

4.3.1 Chandy-Lamport Algorithm . . . . . . . . . . . . . . . . . . . . . . . . 4.3.2 Properties of the Recorded Global State . . . . . . . . . . . . . . . . . . 4.4 Variations of the Chandy-Lamport Algorithm . . . . . . . . . . . . . . . . . . . 4.4.1 Spezialetti-Kearns Algorithm . . . . . . . . . . . . . . . . . . . . . . . 4.4.2 Venkatesan’s Incremental Snapshot Algorithm . . . . . . . . . . . . . . 4.4.3 Helary’s Wave Synchronization Method . . . . . . . . . . . . . . . . . . 4.5 Snapshot Algorithms for Non-FIFO Channels . . . . . . . . . . . . . . . . . . . 4.5.1 Lai-Yang Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5.2 Li et al.’s Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5.3 Mattern’s Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6 Snapshots in a Causal Delivery System . . . . . . . . . . . . . . . . . . . . . . . 4.6.1 Process State Recording . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6.2 Channel State Recording in Acharya-Badrinath Algorithm . . . . . . . . 4.6.3 Channel State Recording in Alagar-Venkatesan Algorithm . . . . . . . . 4.7 Monitoring Global State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.8 Necessary and Sufficient Conditions for Consistent Global Snapshots . . . . . . 4.8.1 Zigzag Paths and Consistent Global Snapshots . . . . . . . . . . . . . . 4.9 Finding Consistent Global Snapshots in a Distributed Computation . . . . . . . . 4.9.1 Finding Consistent Global Snapshots . . . . . . . . . . . . . . . . . . . 4.9.2 Manivannan-Netzer-Singhal Algorithm for Enumerating Consistent Snapshots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.9.3 Finding Z-paths in a Distributed Computation . . . . . . . . . . . . . . . 4.10 Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.11 Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.12 Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Terminology and Basic Algorithms 5.1 Topology Abstraction and Overlays . . . . . . . . . . . . . . . . 5.2 Classifications and Basic Concepts . . . . . . . . . . . . . . . . . 5.2.1 Application Executions and Control Algorithm Executions 5.2.2 Centralized and Distributed Algorithms . . . . . . . . . . 5.2.3 Symmetric and Asymmetric Algorithms . . . . . . . . . . 5.2.4 Anonymous Algorithms . . . . . . . . . . . . . . . . . . 5.2.5 Uniform Algorithms . . . . . . . . . . . . . . . . . . . . 5.2.6 Adaptive Algorithms . . . . . . . . . . . . . . . . . . . . 5.2.7 Deterministic Versus Nondeterministic Executions . . . . 5.2.8 Execution Inhibition . . . . . . . . . . . . . . . . . . . . 5.2.9 Synchronous and Asynchronous Systems . . . . . . . . . 5.2.10 Online versus Offline Algorithms . . . . . . . . . . . . . 5.2.11 Failure Models . . . . . . . . . . . . . . . . . . . . . . . 5.2.12 Wait-free algorithms . . . . . . . . . . . . . . . . . . . . 5.2.13 Communication Channels . . . . . . . . . . . . . . . . . 5.3 Complexity Measures and Metrics . . . . . . . . . . . . . . . . . 5.4 Program Structure . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5 Elementary Graph Algorithms . . . . . . . . . . . . . . . . . . . vii

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

87 89 91 91 92 93 94 95 96 97 98 99 99 100 100 102 103 106 106

. . . . .

109 110 111 112 113

. . . . . . . . . . . . . . . . . .

118 118 120 120 120 121 121 122 122 122 123 124 124 125 126 126 127 128 129

5.5.1

5.6 5.7 5.8 5.9 5.10 5.11 5.12

5.13 5.14 5.15

Synchronous Single-Initiator Spanning Tree Algorithm Using Flooding: Algorithm 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 5.5.2 Asynchronous Single-Initiator Spanning Tree Algorithm Using Flooding: Algorithm I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 5.5.3 Asynchronous Concurrent-Initiator Spanning Tree Algorithm Using Flooding: Algorithm II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 5.5.4 Asynchronous Concurrent-Initiator Depth First Search Spanning Tree Algorithm: Algorithm III . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 5.5.5 Broadcast and Convergecast on a Tree . . . . . . . . . . . . . . . . . . . . 137 5.5.6 Single Source Shortest Path Algorithm: Synchronous Bellman-Ford . . . . 140 5.5.7 Distance Vector Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 5.5.8 Single Source Shortest Path Algorithm: Asynchronous Bellman-Ford . . . 141 5.5.9 All Sources Shortest Paths: Asynchronous Distributed Floyd-Warshall . . . 142 5.5.10 Asynchronous and Synchronous Constrained Flooding (w/o a Spanning Tree) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 5.5.11 Minimum Weight Spanning Tree (MST) Algorithm in a Synchronous System146 5.5.12 Minimum Weight Spanning Tree (MST) in an Asynchronous System . . . 152 Synchronizers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 Maximal Independent Set (MIS) . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 Connected Dominating Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 Compact Routing Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 Leader Election . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Challenges in Designing Distributed Graph Algorithms . . . . . . . . . . . . . . . 164 Object Replication Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 5.12.1 Problem Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 5.12.2 Algorithm Outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 5.12.3 Reads and Writes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 5.12.4 Converging to an Replication Scheme . . . . . . . . . . . . . . . . . . . . 167 Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

6 Message Ordering and Group Communication 6.1 Message Ordering Paradigms . . . . . . . . . . . . . . . . . . . . . . . 6.1.1 Asynchronous Executions . . . . . . . . . . . . . . . . . . . . 6.1.2 FIFO Executions . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.3 Causally Ordered (CO) Executions . . . . . . . . . . . . . . . 6.1.4 Synchronous Execution (SYNC) . . . . . . . . . . . . . . . . . 6.2 Asynchronous Execution with Synchronous Communication . . . . . . 6.2.1 Executions Realizable with Synchronous Communication (RSC) 6.2.2 Hierarchy of Ordering Paradigms . . . . . . . . . . . . . . . . 6.2.3 Simulations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3 Synchronous Program Order on an Asynchronous System . . . . . . . . 6.3.1 Rendezvous . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3.2 Algorithm for Binary Rendezvous . . . . . . . . . . . . . . . . viii

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

179 180 180 180 181 184 184 185 188 189 190 191 192

6.4 6.5

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

195 196 197 198 205 205 206 209 210 214 216 218 219 220 221 221 222 223 223 224

7 Termination Detection 7.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 System Model of a Distributed Computation . . . . . . . . . . . . . . . 7.3 Termination Detection Using Distributed Snapshots . . . . . . . . . . . 7.3.1 Informal Description . . . . . . . . . . . . . . . . . . . . . . . 7.3.2 Formal Description . . . . . . . . . . . . . . . . . . . . . . . . 7.3.3 Discussion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4 Termination Detection by Weight Throwing . . . . . . . . . . . . . . . 7.4.1 Formal Description . . . . . . . . . . . . . . . . . . . . . . . . 7.4.2 Correctness of the Algorithm . . . . . . . . . . . . . . . . . . . 7.5 A Spanning-Tree-Based Termination Detection Algorithm . . . . . . . 7.5.1 Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.5.2 A Simple Algorithm . . . . . . . . . . . . . . . . . . . . . . . 7.5.3 The Correct Algorithm . . . . . . . . . . . . . . . . . . . . . . 7.5.4 An Example . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.5.5 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.6 Message-Optimal Termination Detection . . . . . . . . . . . . . . . . . 7.6.1 The Main Idea . . . . . . . . . . . . . . . . . . . . . . . . . . 7.6.2 Formal Description of the Algorithm . . . . . . . . . . . . . . 7.6.3 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.7 Termination Detection in a Very General Distributed Computing Model 7.7.1 Model Definition and Assumptions . . . . . . . . . . . . . . . 7.7.2 Notations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.7.3 Termination Definitions . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

230 230 231 232 232 232 233 234 234 235 236 236 236 237 239 242 242 242 243 245 245 246 246 247

6.6

6.7 6.8 6.9 6.10 6.11

6.12 6.13 6.14

Group Communication . . . . . . . . . . . . . . . . . . . . . . . Causal Order (CO) . . . . . . . . . . . . . . . . . . . . . . . . . 6.5.1 The Raynal-Schiper-Toueg Algorithm . . . . . . . . . . . 6.5.2 The Kshemkalyani-Singhal Optimal Algorithm . . . . . . Total Order . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.6.1 Centralized Algorithm for Total Order . . . . . . . . . . . 6.6.2 Three-Phase Distributed Algorithm . . . . . . . . . . . . A Nomenclature For Multicast . . . . . . . . . . . . . . . . . . . Propagation Trees For Multicast . . . . . . . . . . . . . . . . . . Classification of Application-Level Multicast Algorithms . . . . . Semantics of Fault-Tolerant Group Communication . . . . . . . . Distributed Multicast Algorithms At The Network Layer . . . . . 6.11.1 Reverse Path Forwarding (RPF) For Constrained Flooding 6.11.2 Steiner Trees . . . . . . . . . . . . . . . . . . . . . . . . 6.11.3 Multicast Cost Functions . . . . . . . . . . . . . . . . . . 6.11.4 Delay-Bounded Steiner Trees . . . . . . . . . . . . . . . 6.11.5 Core-Based Trees . . . . . . . . . . . . . . . . . . . . . . Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . . . Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . .

ix

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

7.7.4 A Static Termination Detection Algorithm . . . . 7.7.5 A Dynamic Termination Detection Algorithm . . 7.8 Termination Detection in the Atomic Computation Model 7.8.1 The Atomic Model of Execution . . . . . . . . . 7.8.2 A Naive Counting Method . . . . . . . . . . . . 7.8.3 The Four Counter Method . . . . . . . . . . . . 7.8.4 The Sceptic Algorithm . . . . . . . . . . . . . . 7.8.5 The Time Algorithm . . . . . . . . . . . . . . . 7.8.6 Vector Counters Method . . . . . . . . . . . . . 7.8.7 A Channel Counting Method . . . . . . . . . . . 7.9 Termination Detection in a Faulty Distributed System . . 7.9.1 Flow Detecting Scheme . . . . . . . . . . . . . 7.9.2 Taking Snapshots . . . . . . . . . . . . . . . . . 7.9.3 Description of the Algorithm . . . . . . . . . . . 7.9.4 Performance Analysis . . . . . . . . . . . . . . 7.10 Bibliographic Notes . . . . . . . . . . . . . . . . . . . . 7.11 Exercise Problems . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

247 249 251 252 252 253 254 255 256 258 260 261 262 263 266 267 267

8 Reasoning with Knowledge 8.1 The Muddy Children Puzzle . . . . . . . . . . . . . . . 8.2 Logic of Knowledge . . . . . . . . . . . . . . . . . . . 8.2.1 Knowledge Operators . . . . . . . . . . . . . . 8.2.2 The Muddy Children Puzzle Again . . . . . . . 8.2.3 Kripke Structures . . . . . . . . . . . . . . . . . 8.2.4 Muddy Children Puzzle using Kripke Structures 8.2.5 Properties of Knowledge . . . . . . . . . . . . . 8.3 Knowledge in Synchronous Systems . . . . . . . . . . . 8.4 Knowledge in Asynchronous Systems . . . . . . . . . . 8.4.1 Logic and Definitions . . . . . . . . . . . . . . . 8.4.2 Agreement in Asynchronous Systems . . . . . . 8.4.3 Variants of Common Knowledge . . . . . . . . . 8.4.4 Concurrent Common Knowledge . . . . . . . . 8.5 Knowledge Transfer . . . . . . . . . . . . . . . . . . . . 8.6 Knowledge and Clocks . . . . . . . . . . . . . . . . . . 8.7 Chapter Summary . . . . . . . . . . . . . . . . . . . . . 8.8 Bibliographic Notes . . . . . . . . . . . . . . . . . . . . 8.9 Exercise Problems . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

271 271 272 272 273 274 275 277 277 278 278 279 280 281 283 287 288 289 289

9 Distributed Mutual Exclusion Algorithms 9.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . 9.2 Preliminaries . . . . . . . . . . . . . . . . . . . . . . 9.2.1 System Model . . . . . . . . . . . . . . . . . 9.2.2 Requirements of Mutual Exclusion Algorithms 9.2.3 Performance Metrics . . . . . . . . . . . . . . 9.3 Lamport’s Algorithm . . . . . . . . . . . . . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

293 293 294 294 294 295 297

x

. . . . . .

9.4 9.5

9.6

9.7 9.8

9.9

9.10 9.11 9.12

9.13 9.14

Ricart-Agrawala Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . 9.4.1 Description of the Algorithm . . . . . . . . . . . . . . . . . . . . Singhal’s Dynamic Information-Structure Algorithm . . . . . . . . . . . 9.5.1 The Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.2 Correctness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.3 Performance Analysis . . . . . . . . . . . . . . . . . . . . . . . 9.5.4 Adaptivity in Heterogeneous Traffic Patterns . . . . . . . . . . . Lodha and Kshemkalyani’s Fair Mutual Exclusion Algorithm . . . . . . . 9.6.1 System Model . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.6.2 The Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.6.3 Safety, Fairness and Liveness . . . . . . . . . . . . . . . . . . . 9.6.4 An Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.6.5 Message Complexity . . . . . . . . . . . . . . . . . . . . . . . . Quorum-Based Mutual Exclusion Algorithms . . . . . . . . . . . . . . . Maekawa’s Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.8.1 The Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.8.2 Problem of Deadlocks . . . . . . . . . . . . . . . . . . . . . . . Agarwal-El Abbadi Quorum-Based Algorithm . . . . . . . . . . . . . . . 9.9.1 Constructing a tree-structured quorum . . . . . . . . . . . . . . . 9.9.2 Analysis of the algorithm for constructing tree-structured quorums 9.9.3 Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.9.4 Examples of Tree-Structured Quorums . . . . . . . . . . . . . . 9.9.5 The Algorithm for Distributed Mutual Exclusion . . . . . . . . . 9.9.6 Correctness proof . . . . . . . . . . . . . . . . . . . . . . . . . . 9.9.7 An Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . Token-Based Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . Suzuki-Kasami’s Broadcast Algorithm . . . . . . . . . . . . . . . . . . . Raymond’s Tree-Based Algorithm . . . . . . . . . . . . . . . . . . . . . 9.12.1 The HOLDER Variables . . . . . . . . . . . . . . . . . . . . . . 9.12.2 The Operation of the Algorithm . . . . . . . . . . . . . . . . . . 9.12.3 Correctness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.12.4 Cost and Performance Analysis . . . . . . . . . . . . . . . . . . 9.12.5 Algorithm Initialization . . . . . . . . . . . . . . . . . . . . . . 9.12.6 Node Failures and Recovery . . . . . . . . . . . . . . . . . . . . Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10 Deadlock Detection in Distributed Systems 10.1 Introduction . . . . . . . . . . . . . . . 10.2 System Model . . . . . . . . . . . . . . 10.2.1 Wait-For-Graph (WFG) . . . . 10.3 Preliminaries . . . . . . . . . . . . . . 10.3.1 Deadlock Handling Strategies . 10.3.2 Issues in Deadlock Detection . . 10.4 Models of Deadlocks . . . . . . . . . . xi

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

300 300 303 305 307 308 309 309 309 310 313 313 313 316 317 317 318 320 320 321 321 322 323 324 324 324 325 327 328 329 333 335 335 336 336 337

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

342 342 342 343 343 343 344 345

10.4.1 The Single Resource Model . . . . . . . . . . . . . . . . . . 10.4.2 The AND Model . . . . . . . . . . . . . . . . . . . . . . . . 10.4.3 The OR Model . . . . . . . . . . . . . . . . . . . . . . . . . 10.4.4 The AND-OR Model . . . . . . . . . . . . . . . . . . . . . .  p 10.4.5 The q Model . . . . . . . . . . . . . . . . . . . . . . . . . 10.4.6 Unrestricted Model . . . . . . . . . . . . . . . . . . . . . . . 10.5 Knapp’s Classification of Distributed Deadlock Detection Algorithms 10.5.1 Path-Pushing Algorithms . . . . . . . . . . . . . . . . . . . . 10.5.2 Edge-Chasing Algorithms . . . . . . . . . . . . . . . . . . . 10.5.3 Diffusing Computations Based Algorithms . . . . . . . . . . 10.5.4 Global State Detection Based Algorithms . . . . . . . . . . . 10.6 Mitchell and Merritt’s Algorithm for the Single-Resource Model . . . 10.7 Chandy-Misra-Haas Algorithm for the AND Model . . . . . . . . . . 10.8 Chandy-Misra-Haas Algorithm for the OR Model . . . . . . . . . . . 10.9 Kshemkalyani-Singhal Algorithm for P-out-of-Q Model . . . . . . . 10.9.1 Informal Description of the Algorithm . . . . . . . . . . . . . 10.9.2 The Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . 10.9.3 An Example . . . . . . . . . . . . . . . . . . . . . . . . . . 10.10Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.11Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.12Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

345 346 346 347 347 347 348 348 348 348 349 349 352 353 355 357 358 361 364 365 365

11 Global Predicate Detection 11.1 Stable and Unstable Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1.1 Stable Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1.2 Unstable Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 Modalities on Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2.1 Complexity of Predicate Detection . . . . . . . . . . . . . . . . . . . . . . 11.3 Centralized Algorithm for Relational Predicates . . . . . . . . . . . . . . . . . . . 11.4 Conjunctive Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4.1 Interval-based Centralized Algorithm for Conjunctive Predicates . . . . . . 11.4.2 Global State based Centralized Algorithm for P ossibly(φ), where φ is conjunctive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.5 Distributed Algorithms for Conjunctive Predicates . . . . . . . . . . . . . . . . . . 11.5.1 Distributed State-based Token Algorithm for P ossibly(φ), where φ is Conjunctive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.5.2 Distributed Interval-based Token Algorithm for Def initely(φ), where φ is Conjunctive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.5.3 Distributed Interval-based Piggybacking Algorithm for P ossibly(φ), where φ is Conjunctive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.6 Further Classification of Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . 11.7 Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.8 Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.9 Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

370 370 371 372 373 374 374 378 379

xii

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

381 384 384 386 390 393 394 394 395

12 Distributed Shared Memory 12.1 Abstraction and Advantages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2 Memory Consistency Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2.1 Strict consistency/Atomic consistency/Linearizability . . . . . . . . . . . . 12.2.2 Sequential Consistency . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2.3 Causal Consistency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2.4 PRAM (Pipelined RAM) or Processor Consistency . . . . . . . . . . . . . 12.2.5 Slow Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2.6 Hierarchy of Consistency Models . . . . . . . . . . . . . . . . . . . . . . 12.2.7 Other Models based on Synchronization Instructions . . . . . . . . . . . . 12.3 Shared Memory Mutual Exclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 12.3.1 Lamport’s Bakery Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . 12.3.2 Lamport’s WRWR Mechanism and Fast Mutual Exclusion . . . . . . . . . 12.3.3 Hardware support for mutual exclusion . . . . . . . . . . . . . . . . . . . 12.4 Wait-freedom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.5 Register Hierarchy and Wait-free Simulations . . . . . . . . . . . . . . . . . . . . 12.5.1 Construction 1: SRSW Safe to MRSW Safe . . . . . . . . . . . . . . . . . 12.5.2 Construction 2: SRSW Regular to MRSW Regular . . . . . . . . . . . . . 12.5.3 Construction 3: Boolean MRSW Safe to integer-valued MRSW Safe . . . . 12.5.4 Construction 4: Boolean MRSW Safe to boolean MRSW Regular . . . . . 12.5.5 Construction 5: Boolean MRSW Regular to integer-valued MRSW Regular 12.5.6 Construction 6: Boolean MRSW Regular to integer-valued MRSW Atomic 12.5.7 Construction 7: Integer MRSW Atomic to integer MRMW Atomic . . . . 12.5.8 Construction 8: Integer SRSW Atomic to integer MRSW Atomic . . . . . 12.6 Wait-free Atomic Snapshots of Shared Objects . . . . . . . . . . . . . . . . . . . 12.7 Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.8 Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.9 Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

399 399 402 403 406 409 411 412 413 414 416 416 418 421 422 423 426 427 427 427 428 429 432 433 435 438 440 440

13 Checkpointing and Rollback Recovery 13.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2 Background and Definitions . . . . . . . . . . . . . . . . . . . . 13.2.1 System Model . . . . . . . . . . . . . . . . . . . . . . . 13.2.2 A Local Checkpoint . . . . . . . . . . . . . . . . . . . . 13.2.3 Consistent System States . . . . . . . . . . . . . . . . . . 13.2.4 Interactions with the Outside World . . . . . . . . . . . . 13.2.5 Different Types of Messages . . . . . . . . . . . . . . . . 13.3 Issues in Failure Recovery . . . . . . . . . . . . . . . . . . . . . 13.4 Checkpoint Based Recovery . . . . . . . . . . . . . . . . . . . . 13.4.1 Uncoordinated Checkpointing . . . . . . . . . . . . . . . 13.4.2 Coordinated Checkpointing . . . . . . . . . . . . . . . . 13.4.3 Impossibility of Min Process Non-blocking Checkpointing 13.4.4 Communication-Induced Checkpointing . . . . . . . . . . 13.5 Log-based Rollback Recovery . . . . . . . . . . . . . . . . . . . 13.5.1 Deterministic and Nondeterministic Events . . . . . . . .

445 445 446 446 447 447 448 449 450 452 452 454 456 456 458 458

xiii

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

13.5.2 Pessimistic Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.5.3 Optimistic Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.5.4 Causal Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.6 Koo-Toueg Coordinated Checkpointing Algorithm . . . . . . . . . . . . . . . . 13.6.1 The Checkpointing Algorithm . . . . . . . . . . . . . . . . . . . . . . . 13.6.2 The Rollback Recovery Algorithm . . . . . . . . . . . . . . . . . . . . . 13.7 Juang and Venkatesan Algorithm for Asynchronous Checkpointing and Recovery 13.7.1 System Model and Assumptions . . . . . . . . . . . . . . . . . . . . . . 13.7.2 Asynchronous Checkpointing . . . . . . . . . . . . . . . . . . . . . . . 13.7.3 The Recovery Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . 13.7.4 An Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.8 Manivannan-Singhal Quasi-Synchronous Checkpointing Algorithm . . . . . . . 13.8.1 Checkpointing Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . 13.8.2 Recovery Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.8.3 Comprehensive Message Handling . . . . . . . . . . . . . . . . . . . . . 13.9 Peterson-Kearns Algorithm Based on Vector Time . . . . . . . . . . . . . . . . . 13.9.1 System Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.9.2 Informal Description of the Algorithm . . . . . . . . . . . . . . . . . . . 13.9.3 Formal Description of the RollBack Protocol . . . . . . . . . . . . . . . 13.9.4 An Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.9.5 Correctness Proof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.10Helary-Mostefaoui-Netzer-Raynal Communication-induced Protocol . . . . . . . 13.10.1 Design Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.10.2 The Checkpointing Protocol . . . . . . . . . . . . . . . . . . . . . . . . 13.11Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.12Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

459 461 462 463 463 465 466 466 467 467 469 470 471 473 476 479 479 480 482 483 484 486 487 491 494 495

14 Consensus and Agreement Algorithms 14.1 Problem Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.1.1 The Byzantine Agreement and Other Problems . . . . . . . . . . . . . . . 14.1.2 Equivalence of the Problems and Notations . . . . . . . . . . . . . . . . . 14.2 Overview of Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.3 Agreement in a Failure-Free System (Synchronous or Asynchronous) . . . . . . . 14.4 Agreement in (Message-Passing) Synchronous Systems with Failures . . . . . . . 14.4.1 Consensus Algorithm for Crash Failures (Synchronous System) . . . . . . 14.4.2 Consensus Algorithms for Byzantine Failures (Synchronous System) . . . 14.4.3 Upper Bound on Byzantine Processes . . . . . . . . . . . . . . . . . . . . 14.4.4 Byzantine Agreement Tree Algorithm: Exponential (Synchronous System) 14.5 Agreement in Asynchronous Message-Passing Systems with Failures . . . . . . . . 14.5.1 Impossibility Result for the Consensus Problem . . . . . . . . . . . . . . . 14.5.2 Terminating Reliable Broadcast . . . . . . . . . . . . . . . . . . . . . . . 14.5.3 Distributed Transaction Commit . . . . . . . . . . . . . . . . . . . . . . . 14.5.4 k-set consensus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.5.5 Approximate Agreement . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.5.6 Renaming Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

500 500 502 503 504 505 506 506 507 507 509 518 518 520 521 521 522 527

xiv

14.5.7 Reliable Broadcast . . . . . . . . . . . . . . . . . . . . 14.6 Wait-free Shared Memory Consensus in Asynchronous Systems 14.6.1 Impossibility Result . . . . . . . . . . . . . . . . . . . 14.6.2 Consensus Numbers and Consensus Hierarchy . . . . . 14.6.3 Universality of Consensus Objects . . . . . . . . . . . . 14.6.4 Shared Memory k-set Consensus . . . . . . . . . . . . . 14.6.5 Shared Memory Renaming . . . . . . . . . . . . . . . . 14.6.6 Shared Memory Renaming using Splitters . . . . . . . . 14.7 Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . 14.8 Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . 14.9 Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . .

532 533 533 536 540 545 545 547 549 550 552

15 Failure Detectors 15.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2 Unreliable Failure Detectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2.1 The System Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2.2 Failure Detectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2.3 Completeness and Accuracy Properties . . . . . . . . . . . . . . . . . . . 15.2.4 Types of Failure Detectors . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2.5 Reducibility of Failure Detectors . . . . . . . . . . . . . . . . . . . . . . . 15.2.6 Reducing Weak Failure Detector W to a Strong Failure Detector S . . . . . 15.2.7 Reducing an Eventually Weak Failure Detector ♦W to an Eventually Strong Failure Detector ♦S . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.3 The Consensus Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.3.1 Solutions to the Consensus Problem . . . . . . . . . . . . . . . . . . . . . 15.3.2 A Solution Using Strong Failure Detector S . . . . . . . . . . . . . . . . . 15.3.3 A Solution Using Eventually Strong Failure Detector ♦S . . . . . . . . . . 15.4 Atomic Broadcast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.5 A Solution to Atomic Broadcast . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.6 The Weakest Failure Detectors to Solve Fundamental Agreement Problems . . . . 15.6.1 Realistic Failure Detectors . . . . . . . . . . . . . . . . . . . . . . . . . . 15.6.2 The weakest failure detector for consensus . . . . . . . . . . . . . . . . . 15.6.3 The Weakest Failure Detector for Terminating Reliable Broadcast . . . . . 15.7 An Implementation of a Failure Detector . . . . . . . . . . . . . . . . . . . . . . . 15.8 An Adaptive Failure Detection Protocol . . . . . . . . . . . . . . . . . . . . . . . 15.8.1 Lazy Failure Detection Protocol (F DL ) . . . . . . . . . . . . . . . . . . . 15.9 Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.10Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

555 555 556 556 557 557 560 560 561

16 Authentication in Distributed System 16.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2 Background and Definitions . . . . . . . . . . . . . . . . . 16.2.1 Basis of Authentication . . . . . . . . . . . . . . . . 16.2.2 Types of Principals . . . . . . . . . . . . . . . . . . 16.2.3 A Simple Classification of Authentication Protocols

586 586 586 587 587 588

xv

. . . . .

. . . . .

. . . . . . . . . . .

. . . . .

. . . . . . . . . . .

. . . . .

. . . . . . . . . . .

. . . . .

. . . . . . . . . . .

. . . . .

. . . . . . . . . . .

. . . . .

. . . . . . . . . . .

. . . . .

. . . . . . . . . . .

. . . . .

. . . . . . . . . . .

. . . . .

. . . . . . . . . . .

. . . . .

. . . . .

563 565 566 566 568 570 571 573 574 575 576 576 578 579 582 582

16.3

16.4

16.5

16.6 16.7 16.8

16.2.4 Notations . . . . . . . . . . . . . . . . . . . . . . 16.2.5 Design Principles for Cryptographic Protocols . . Protocols Based on Symmetric Cryptosystems . . . . . . . 16.3.1 Basic Protocol . . . . . . . . . . . . . . . . . . . 16.3.2 Modified Protocol with Nonce . . . . . . . . . . . 16.3.3 Wide-Mouth Frog Protocol . . . . . . . . . . . . . 16.3.4 A Protocol Based On an Authentication Server . . 16.3.5 One-Time Password Scheme . . . . . . . . . . . . 16.3.6 Otway-Rees Protocol . . . . . . . . . . . . . . . . 16.3.7 Kerberos Authentication Service . . . . . . . . . . Protocols Based on Asymmetric Cryptosystems . . . . . . 16.4.1 The Basic Protocol . . . . . . . . . . . . . . . . . 16.4.2 A Modified Protocol with a Certification Authority 16.4.3 Needham and Schroeder Protocol . . . . . . . . . 16.4.4 SSL Protocol . . . . . . . . . . . . . . . . . . . . Password-based Authentication . . . . . . . . . . . . . . . 16.5.1 Encrypted Key Exchange (EKE) Protocol . . . . . 16.5.2 Secure Remote Password (SRP) Protocol . . . . . Authentication Protocol Failures . . . . . . . . . . . . . . Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . Exercise Problems . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

17 Self-Stabilization 17.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.2 System Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.3 Definition of Self-Stabilization . . . . . . . . . . . . . . . . . . . . . . . . . . 17.4 Issues in the design of self-stabilization algorithms . . . . . . . . . . . . . . . 17.4.1 The Number of States in Each of the Individual Units . . . . . . . . . . 17.4.2 Uniform Vs. Non-uniform Networks . . . . . . . . . . . . . . . . . . 17.4.3 Central and Distributed Demons . . . . . . . . . . . . . . . . . . . . . 17.4.4 Reducing the number of states in a token ring . . . . . . . . . . . . . . 17.4.5 Shared memory Models . . . . . . . . . . . . . . . . . . . . . . . . . 17.4.6 Mutual Exclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.4.7 Costs of self-stabilization . . . . . . . . . . . . . . . . . . . . . . . . . 17.5 Methodologies for designing self-stabilizing systems . . . . . . . . . . . . . . 17.6 Communication Protocols . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.7 Self-Stabilizing Distributed Spanning Trees . . . . . . . . . . . . . . . . . . . 17.8 Self-Stabilizing Algorithms for Spanning-tree Construction . . . . . . . . . . . 17.8.1 Dolev, Israeli, and Moran Algorithm . . . . . . . . . . . . . . . . . . . 17.8.2 Afek, Kutten, and Yung Algorithm for Spanning-tree Construction . . . 17.8.3 Arora and Gouda Algorithm for Spanning-tree Construction . . . . . . 17.8.4 Huang et al. Algorithms for Spanning-tree Construction . . . . . . . . 17.8.5 Afek and Bremler Algorithm for Spanning-tree Construction . . . . . . 17.9 An anonymous self-stabilizing algorithm for 1-maximal independent set in trees 17.10A Probabilistic Self-Stabilizing Leader Election Algorithm . . . . . . . . . . . xvi

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

588 589 590 590 591 592 593 594 596 597 602 602 602 603 605 609 609 610 611 613 613

. . . . . . . . . . . . . . . . . . . . . .

619 619 620 622 624 625 631 632 633 633 634 634 635 637 638 640 640 642 643 643 644 645 646

17.11The role of compilers in self-stabilization . . . . . . . . . . . 17.11.1 Compilers for sequential programs . . . . . . . . . . . 17.11.2 Compilers for asynchronous message passing systems 17.11.3 Compilers for asynchronous shared memory systems . 17.12Self stabilization as a Solution to Fault Tolerance . . . . . . . 17.13Factors Preventing Self-Stabilization . . . . . . . . . . . . . . 17.14Limitations of Self-Stabilization . . . . . . . . . . . . . . . . 17.15Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . 17.16Bibliographic Notes . . . . . . . . . . . . . . . . . . . . . . . 17.17Exercise Problems . . . . . . . . . . . . . . . . . . . . . . . 18 Peer-to-Peer Computing and Overlay Graphs 18.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . 18.1.1 Napster . . . . . . . . . . . . . . . . . . . . . 18.1.2 Application Layer Overlays . . . . . . . . . . 18.2 Data Indexing and Overlays . . . . . . . . . . . . . . 18.2.1 Distributed Indexing . . . . . . . . . . . . . . 18.3 Unstructured Overlays . . . . . . . . . . . . . . . . . 18.3.1 Unstructured Overlays: Properties . . . . . . . 18.3.2 Gnutella . . . . . . . . . . . . . . . . . . . . . 18.3.3 Search in Gnutella and Unstructured Overlays . 18.3.4 Replication Strategies . . . . . . . . . . . . . 18.3.5 Implementing Replication Strategies. . . . . . 18.4 Chord Distributed Hash Table . . . . . . . . . . . . . 18.4.1 Overview . . . . . . . . . . . . . . . . . . . . 18.4.2 Simple lookup . . . . . . . . . . . . . . . . . 18.4.3 Scalable Lookup . . . . . . . . . . . . . . . . 18.4.4 Managing Churn . . . . . . . . . . . . . . . . 18.4.5 Complexity . . . . . . . . . . . . . . . . . . . 18.5 Content Addressible Networks: CAN . . . . . . . . . 18.5.1 Overview . . . . . . . . . . . . . . . . . . . . 18.5.2 CAN Initialization . . . . . . . . . . . . . . . 18.5.3 CAN Routing . . . . . . . . . . . . . . . . . . 18.5.4 CAN Maintainence . . . . . . . . . . . . . . . 18.5.5 CAN Optimizations . . . . . . . . . . . . . . 18.5.6 CAN Complexity . . . . . . . . . . . . . . . . 18.6 Tapestry . . . . . . . . . . . . . . . . . . . . . . . . . 18.6.1 Overview . . . . . . . . . . . . . . . . . . . . 18.6.2 Overlay and Routing . . . . . . . . . . . . . . 18.6.3 Object Publication and Object Search . . . . . 18.6.4 Node Insertion . . . . . . . . . . . . . . . . . 18.6.5 Node Deletion . . . . . . . . . . . . . . . . . 18.7 Some Other Challenges in P2P System Design . . . . . 18.7.1 Fairness: A Game Theory Application . . . . . 18.7.2 Trust or Reputation Management . . . . . . . xvii

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

649 649 650 651 652 654 655 657 657 658

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

666 666 667 667 668 669 670 670 670 671 673 675 676 676 677 678 678 682 683 683 684 685 686 688 689 689 689 689 692 693 694 695 695 696

18.8 Tradeoffs between Table Storage and Route Lengths . . . 18.8.1 Unifying DHT Protocols . . . . . . . . . . . . . . 18.8.2 Bounds on DHT Storage and Routing Distance . . 18.9 Graph Structures of Complex Networks . . . . . . . . . . 18.10Internet graphs . . . . . . . . . . . . . . . . . . . . . . . 18.10.1 Basic Laws and their Definitions . . . . . . . . . . 18.10.2 Properties of the Internet . . . . . . . . . . . . . . 18.10.3 Error and Attack Tolerance of Complex Networks 18.11Random Graphs . . . . . . . . . . . . . . . . . . . . . . . 18.11.1 Graph Model . . . . . . . . . . . . . . . . . . . . 18.11.2 Graph Degree Distribution . . . . . . . . . . . . . 18.11.3 Graph Diameter . . . . . . . . . . . . . . . . . . . 18.11.4 Graph Clustering Coefficient . . . . . . . . . . . . 18.11.5 Generalized Random Graph Networks . . . . . . . 18.12Small-world Networks . . . . . . . . . . . . . . . . . . . 18.13Scale-free Networks . . . . . . . . . . . . . . . . . . . . . 18.13.1 Master-equation approach . . . . . . . . . . . . . 18.13.2 Rate-equation approach . . . . . . . . . . . . . . 18.14Evolving Networks . . . . . . . . . . . . . . . . . . . . . 18.14.1 Extended Barabasi-Albert Model . . . . . . . . . 18.15Chapter Summary . . . . . . . . . . . . . . . . . . . . . . 18.16Exercise Problems . . . . . . . . . . . . . . . . . . . . . 18.17Bibliographic Notes . . . . . . . . . . . . . . . . . . . . .

xviii

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

696 696 697 699 701 701 702 704 707 707 708 708 708 709 709 710 710 711 712 713 714 716 716

Chapter 1 Introduction 1.1 Definition A distributed system is a collection of independent entities that cooperate to solve a problem that cannot be individually solved. Distributed systems have been in existence since the start of the universe. From a school of fish to a flock of birds and entire ecosystems of microorganisms, there is communication among mobile intelligent agents in nature. With the widespread proliferation of the internet and the emerging global village, the notion of distributed computing systems as a useful and widely deployed tool is becoming a reality. For computing systems, a distributed system has been characterized in one of several ways. • You know you are using one when the crash of a computer you have never heard of prevents you from doing work. (Lamport) • A collection of computers that do not share common memory or a common physical clock, and that communicate by message passing over a communication network; and each computer has its own memory and runs its own operating system. Typically the computers are semi-autonomous and are loosely coupled while they cooperate to address a problem collectively. (Singhal-Shivaratri [10]) • A collection of independent computers that appears to the users of the system as a single coherent computer. (Tanenbaum [11]) • A term that describes a wide range of computers, from weakly coupled systems such as wide-area networks to strongly coupled systems such as local area networks to very stronglycoupled systems such as multiprocessor systems. (Goscinski [6]) A distributed system can be characterized as a collection of mostly autonomous processors communicating over a communication network and having the following features. • No common physical clock. This is an important assumption because it introduces the element of “distribution” in the system and gives rise to the inherent asynchrony amongst the processors.

1

P M

P M

P M P processor(s) M memory bank(s)

Communication network (WAN/ LAN) P M

P M P M

P M

Figure 1.1: A distributed system that connects processors by a communication network. • No shared memory. This is a key feature that requires message-passing required for communication. This feature implies the absence of the common physical clock. It may be noted that a distributed system may still provide the abstraction of a common address space via the distributed shared memory abstraction. Several aspects of shared memory multiprocessor systems have also been studied in the distributed computing literature. • Geographical separation. The geographically wider apart that the processors are, the more representative is the system of a distributed system. However, it is not necessary for the processors to be on a wide-area network (WAN). Recently, the Network/Cluster of Workstations (NOW/COW) configuration connecting processors on a LAN is also being increasingly regarded as a small distributed system. This NOW configuration is becoming popular because of the low-cost high-speed off-the-shelf processors now available. The Google search engine is based on the NOW architecture. • Autonomy and heterogeneity. The processors are “loosely coupled” in that they have different speeds and each can be running a different operating system. They are usually not part of a dedicated system, but cooperate with one another by offering services or solving a problem jointly.

1.2 Relation to Computer System Components A typical distributed system is shown in Figure 1.1. Each computer has a memory-processing unit and the computers are connected by a communication network. Figure 1.2 shows the relationships of the software components that run on each of the computers and use the local operating system and network protocol stack for its functioning. The distributed software is also termed as middleware. A distributed execution is the execution of processes across the distributed system to collaboratively achieve a common goal. An execution is also sometimes termed a computation or a run. The distributed system uses a layered architecture to break down the complexity of system design. The middleware is the distributed software that drives the distributed system, while pro2

Distributed application

Extent of distributed

Application layer

Operating system

Transport layer Network layer Data link layer

Network protocol stack

protocols

Distributed software (middleware libraries)

Figure 1.2: Interaction of the software components at each processor. viding transparency of heterogeneity at the platform level. Figure 1.2 schematically shows the interaction of this software with these system components at each processor. Here we assume that the middleware layer does not contain the traditional application layer functions of the network protocol stack, such as http, mail, ftp, and telnet. Various primitives and calls to functions defined in various libraries of the middleware layer are embedded in the user program code. There exist several libraries to choose from to invoke primitives for the more common functions – such as reliable and ordered multicasting, of the middleware layer. There are several standards such as Object Management Group’s (OMG) Common Object Request Broker Architecture (CORBA), and the Remote Procedure Call (RPC) mechanism. The RPC mechanism conceptually works like a local procedure call, with the difference that the procedure code may reside on a remote machine, and the RPC software sends a message across the network to invoke the remote procedure. It then awaits a reply, after which the procedure call completes from the perspective of the program that invoked it. Currently deployed commercial versions of middleware often use CORBA, DCOM (Distributed Component Object Model), Java, and RMI (Remote Method Invocation) technologies. The Message-Passing Interface (MPI) developed in the research community is an example of an interface for various communication functions.

1.3 Motivation The motivation for using a distributed system is some or all of the following requirements. 1. Inherently distributed computations. In many applications such as money transfer in banking, or reaching consensus among parties that are geographically distant, the computation is inherently distributed. 2. Resource sharing. Resources such as peripherals, complete data sets in databases, special libraries, as well as data (variable/files) cannot be fully replicated at all the sites because it is often neither practical nor cost-effective. Further, they cannot be placed at a single site because access to that site might prove to be a bottleneck. Therefore, such resources 3

are typically distributed across the system. For example, distributed databases such as DB2 partition the data sets across several servers, in addition to replicating them at a few sites for rapid access as well as reliability. 3. Access to geographically remote data and resources. In many scenarios, the data cannot be replicated at every site participating in the distributed execution because it may be too large or too sensitive to be replicated. For example, payroll data within a multinational corporation is both too large and too sensitive to be replicated at every branch office/site. It is therefore stored at a central server which can be queried by branch offices. Similarly, special resources such as supercomputers exist only in certain locations, and to access such supercomputers, users need to log in remotely. Advances in the design of resource-constrained mobile devices as well as in wireless technology using which these devices communicate have given further impetus to the importance of distributed protocols and middleware. 4. Enhanced reliability. A distributed system has the inherent potential to provide increased reliability because of the possibility of replicating resources and executions, as well as the reality that geographically distributed resources are not likely to crash/malfunction at the same time under normal circumstances. Reliability entails several aspects. • Availability, i.e., the resource should be accessible at all times.

• Integrity, i.e., the value/state of the resource should be correct, in the face of concurrent access from multiple processors, as per the semantics expected by the application.

• Fault-tolerance, i.e., the ability to recover from system failures, where such failures may be defined to occur in one of many failure models, which we will study in Chapter 14. 5. Increased Performance/Cost ratio. By resource sharing and accessing geographically remote data and resources, the Performance/Cost ratio is increased. Although higher throughput has not necessarily been the main objective behind using a distributed system, nevertheless, a task can be partitioned across the various computers in the distributed system. Such a configuration provides a better Performance/Cost ratio than using special parallel machines. This is particularly true of the NOW configuration. In addition to meeting the above requirements, a distributed system also offers the following advantages. 6. Scalability. As the processors are usually connected by a wide-area network, adding more processors does not pose a direct bottleneck for the communication network. 7. Modularity and incremental expandability. Heterogeneous processors may be easily added into the system without affecting the performance, as long as those processors are running the same middleware algorithms. Similarly, existing processors may be easily replaced by other processors.

4

P

P

P

PM

P

PM

PM

Interconnection network

Interconnection network

M

PM

M

M

M

(a)

PM

PM

(b) M memory

P processor

Figure 1.3: Two standard architectures for parallel systems. (a) Uniform memory access (UMA) multiprocessor system. (b) Non-uniform memory access (NUMA) multiprocessor. In both architectures, the processors may locally cache data from memory.

1.4 Relation to Parallel Multiprocessor/Multicomputer Systems The characteristics of a distributed system were identified above. A typical distributed system would look as shown in Figure 1.1. However, how does one classify a system that meets some but not all of the characteristics? Is the system still a distributed system, or does it become a parallel multiprocessor system? To better answer these questions, we first examine the architecture of parallel systems, and then examine some well-known taxonomies for multiprocessor/multicomputer systems.

1.4.1 Characteristics of Parallel Systems A parallel system may be broadly classified as belonging to one of three types. 1. A multiprocessor system is a parallel system in which the multiple processors have direct access to shared memory which forms a common address space. The architecture is shown in Figure 1.3(a). Such processors usually do not have a common clock. A multiprocessor system usually corresponds to a uniform memory access (UMA) architecture in which the access latency, i.e., waiting time, to complete an access to any memory location from any processor is the same. The processors are in very close physical proximity and are usually very tightly coupled (homogenous hardware and software), are connected by an interconnection network. Interprocess communication across processors is traditionally through read and write operations on the shared memory, although the use of messagepassing primitives such as those provided by the MPI, is also possible (using emulation on the shared memory). All the processors usually run the same operating system, and both the hardware and software are very tightly coupled. The processors are usually of the same type, and are housed within the same box/container with a shared memory among them. The interconnection network to access the memory may

5

be a bus, although for greater efficiency, it is usually a multistage switch with a symmetric and regular design. Figure 1.4 shows two popular interconnection networks – the Omega network and the Butterfly network, each of which is a multi-stage network formed of 2x2 switching elements. Each 2x2 switch allows data on either of the two input wires to be switched to the upper or the lower output wire. However, in a single step, only one data unit can be sent on an output wire. So if the data from both the input wires is to be routed to an output wire in a single step, there is a collsion. Various techniques such as buffering or more elaborate interconnection designs can address collisions. Each 2x2 switch is represented as a rectangle in the figure. Furthermore, a n-input and noutput network uses log n stages and log n bits for addressing. Routing in the 2x2 switch at stage k uses only the kth bit, and hence can be done at clock speed in hardware. The multistage networks can be constructed recursively, and the interconnection pattern between any two stages can be expressed using an iterative or a recursive generating function. Besides the Omega and Butterfly (banyan) networks, other examples of multistage interconnection networks are the Benes and the shuffle-exchange networks. Each of these has very interesting mathematical properties that allow rich connectivity between the processor bank and memory bank. Omega interconnection function. The Omega network which connects n processors to n memory units has n2 · log2 n switching elements of size 2x2 arranged in log2 n stages. Between each pair of adjacent stages of the Omega network, a link exists between output i of a stage and the input j to the next stage according to the following perfect shuffle pattern which is a left-rotation operation on the binary representation of i to get j. The iterative generation function is as follows. j=



2i 2i + 1 − n

for 0 ≤ i ≤ n/2 − 1 for n/2 ≤ i ≤ n − 1

(1.1)

Consider any stage of switches. Informally, the upper (lower) input lines for each switch come in sequential order from the upper (lower) half of the switches in the earlier stage. With respect to the Omega network in Figure 1.4(a), n = 8. Hence, for any stage, for the outputs i, where 0 ≤ i ≤ 3, the output i is connected to input 2i of the next stage. For 4 ≤ i ≤ 7, the output i of any stage is connected to input 2i + 1 − n of the next stage.

Omega routing function. The routing function from input line i to output line j considers only j and the stage number s, where s ∈ [0, log2 n − 1]. In a stage s switch, if the s + 1th MSB of j is 0, the data is routed to the upper output wire, otherwise it is routed to the lower output wire.

Butterfly interconnection function. Unlike the Omega network, the generation of the interconnection pattern between a pair of adjacent stages depends not only on n but also on the stage number s. The recursive expression is as follows. Let there be M = n/2 switches per stage, and let a switch be denoted by the tuple hx, si, where x ∈ [0, M − 1] and stage s ∈ [0, log2n − 1]. 6

The two outgoing edges from any switch hx, si are as follows. There is an edge from switch hx, si to switch hy, s + 1i if (i) x = y or (ii) x XOR y has exactly one 1 bit, which is in the (s + 1)th MSB. For stage s, apply the rule above for M/2s switches. Whether the two incoming connections go to the upper or the lower input port is not important because of the routing function, given below. Example. Consider the Butterfly network in Figure 1.4(b), n = 8 and M = 4. There are three stages, s = 0, 1, 2, and the interconnection pattern is defined between s = 0 and s = 1 and between s = 1 and s = 2. The switch number x varies from 0 to 3 in each stage, i.e., x is a 2-bit string. (Note that unlike the Omega network formulation using input and output lines given above, this formulation uses switch numbers. Exercise 5 asks you to prove a formulation of the Omega interconnection pattern using switch numbers instead of input and output port numbers.) Consider the first stage interconnection (s = 0) of a butterfly of size M, and hence having log2 2M stages. For stage s = 0, as per rule (i), the first output line from switch 00 goes to input line of switch 00 of stage s = 1. As per rule (ii), the second output line of switch 00 goes to input line of switch 10 of stage s = 1. Similarly, hx, si = (01) has one output line go to an input line of switch (11) in stage s = 1. The other connections in this stage can be determined similarly. For stage s = 1 connecting to stage s = 2, we apply the rules considering only M/21 = M/2 switches, i.e., builds 2 butterflies of size M/2 - the ”upper half" and the ”lower half" switches. The recursion terminates for M/2s = 1, when there is a single switch. Butterfly routing function. In a stage s switch, if the s + 1th MSB of j is 0, the data is routed to the upper output wire, otherwise it is routed to the lower output wire. Observe that for the Butterfly and the Omega networks, the paths from the different inputs to any one output form a spanning tree. This implies that collisions will occur when data is destined to the same output line. However, the advantage is that data can be combined at the switches if the application semantics (e.g., summation of numbers) are known. 2. A multicomputer parallel system is a parallel system in which the multiple processors do not have direct access to shared memory. The memory of the multiple processors may or may not form a common address space. Such computers usually do not have a common clock. The architecture is shown in Figure 1.3(b). The processors are in close physical proximity and are usually very tightly coupled (homogenous hardware and software), and connected by an interconnection network. The processors communicate either via a common address space or via message-passing. A multicomputer system that has a common address space usually corresponds to a non-uniform memory access (NUMA) architecture in which the latency to access various shared memory locations from the different processors varies. Examples of parallel multicomputers are: the NYU Ultracomputer and the Sequent shared memory machines, the CM* Connection machine and processors configured in regular and symmetrical topologies such as an array or mesh, ring, torus, cube, and hypercube (messagepassing machines). The regular and symmetrical topologies have interesting mathematical

7

P0 000 P1 001

000 M0 P0 000 001 M1 P1 001

000 M0 001 M1

P2 010 P3 011

010 M2 P2 010 011 M3 P3 011

010 M2 011 M3

P4 100 P5 101

100 M4 P4 100 101 M5 P5 101

100 M4 101 M5

P6 110 P7 111

110 M6 P6 110 111 M7 P7 111

110 M6 111 M7

(a) 3−stage Omega network (n=8, M=4)

(b) 3−stage Butterfly network (n=8, M=4)

Figure 1.4: Interconnection networks for shared memory multiprocessor systems. (a) Omega network for n = 8 processors P 0 − P 7 and memory banks M0 − M7. (b) Butterfly network for n = 8 processors P 0 − P 7 and memory banks M0 − M7. properties that enable very easy routing and provide many rich features such as alternate routing. Figure 1.5(a) shows a wrap-around 4x4 mesh. For a k × k mesh which will contain k 2 processors, the maximum path length between any two processors is 2(k/2 − 1). Routing can be done along the Manhattan grid. Figure 1.5(b) shows a 4-dimensional hypercube. A k-dimensional hypercube has 2k processor-and-memory units. Each such unit is a node in the hypercube, and has a unique k-bit label. Each of the k dimensions is associated with a bit position in the label. The labels of any two adjacent nodes are identical except for the bit position corresponding to the dimension in which the two nodes differ. Thus, the processors are labelled such that the shortest path between any two processors is the Hamming distance (defined as the number of bit positions in which the two equal sized bit strings differ) between the processor labels. This is clearly bounded by k. Example. Nodes 0101 and 1100 have a Hamming distance of 2. THe shortest path between them has length 2. Routing in the hypercube is done hop-by-hop. At any hop, the message can be sent along any dimension corresponding to the bit position in which the current node’s address and the destination address differ. The 4-D hypercube shown in the figure is formed by connecting the corresponding edges of two 3-D hypercubes (corresponding to the left and the right “cubes” in the figure) along the fourth dimension; the labels of the 4-D hypercube are formed by prepending a ‘0’ to the labels of the left 3-D hypercube and prepending a ‘1’ to the labels of the right 3-D hypercube. This can be extended to construct hypercubes of higher dimensions. Observe that there are multiple routes between any pair of nodes, which provides fault-tolerance as well as a congestion control mechanism. The hypercube and its variant topologies have very interesting mathematical properties with implications for routing and fault-tolerance. 3. Array processors belong to a class of parallel computers that are physically co-located, are 8

0100 0000 0101 0001

0110 0010

1100 1000

0111 0011

1101 1001

1110 1010 1111 1011

processor + memory (a)

(b)

Figure 1.5: Some popular topologies for multicomputer shared-memory machines. (a) Wraparound 2D-mesh, also known as torus. (b) Hypercube of dimension 4. very tightly coupled, and have a common system clock (but may not share memory and communicate by passing data using messages). Array processors and systolic arrays that perform tightly synchronized processing and data exchange in lock-step for applications such as DSP and image processing belong to this category. These applications usually involve a large number of iterations on the data. This class of parallel systems has a very niche market. The distinction between UMA multiprocessors on the one hand, and NUMA and messagepassing multicomputers on the other hand, is important because the algorithm design and data and task partitioning among the processors must account for the variable and unpredictable latencies in accessing memory/communication. As compared to UMA systems and array processors, NUMA and message-passing multicomputer systems are less suitable when the degree of granularity of accessing shared data and communication is very fine. The primary and most efficacious use of parallel systems is for obtaining a higher throughput by dividing the computational workload among the processors. The tasks that are most amenable to higher speedups on parallel systems are those that can be partitioned into subtasks very nicely, involving much number-crunching and relatively little communication for synchronization. Once the task has been decomposed, the processors perform large vector, array and matrix computations that are common in scientific applications. Searching through large state spaces can be performed with significant speedup on parallel machines. While such parallel machines were an object of much theoretical and systems research in the 1980s and early 1990s, they have not proved to be economically viable for two related reasons. First, the overall market for the applications that can potentially attain high speedups is relatively small. Second, due to economy of scale and the high processing power offered by relatively inexpensive off-the-shelf networked PCs, specialized parallel machines are not cost-effective to manufacture. They additionally require special compiler and other system support for maximum throughput.

9

1.4.2 Flynn’s Taxonomy Flynn identified four processing modes, based on whether the processors execute the same or different instruction streams at the same time, and whether or not the processors processed the same (identical) data at the same time. It is instructive to examine this classification to understand the range of options used for configuring systems. Single Instruction stream, Single Data stream (SISD). This mode corresponds to the conventional processing in the von Neumann paradigm with a single CPU, and a single memory unit connected by a system bus. Single Instruction stream, Multiple Data stream (SIMD). This mode corresponds to the processing by multiple homogenous processors which execute in lock-step on different data items. Applications that involve operations on large arrays and matrices, such as scientific applications, can best exploit systems that provide the SIMD mode of operation because the data sets can be partitioned easily. Several of the earliest parallel computers, such as Illiac-IV, MPP, CM2, and MasPar MP-1 were SIMD machines. Vector processors, array processors and systolic arrays also belong to the SIMD class of processing. Recent SIMD architectures include co-processing units such as the MMX units in Intel processors (e.g., Pentium with the Streaming SIMD Extensions (SSE) options) and DSP chips such as the Sharc. Multiple Instruction stream, Single Data stream (MISD). This mode corresponds to the execution of different operations in parallel on the same data. This is a specialized mode of operation with limited but niche applications, e.g., visualization. Multiple Instruction stream, Multiple Data stream (MIMD). In this mode, the various processors execute different code on different data. This is the mode of operation in distributed systems as well as in the vast majority of parallel systems. There is no common clock among the system processors. Sun Ultra servers, multicomputer PCs, and IBM SP machines are example machines that execute in MIMD mode. SIMD, MISD, MIMD architectures are illustrated in Figure 1.6. MIMD architectures are most general and allow much flexibility in partitioning code and data to be processed, among the processors. MIMD architectures also include the classically understood mode of execution in distributed systems.

1.4.3 Coupling, Parallelism, Concurrency, and Granularity 1.4.3.1 Coupling. The degree of coupling among a set of modules, whether hardware or software, is measured in terms of the interdependency and binding and/or homogeneity among the modules. When the degree of coupling is high (low), the modules are said to be tightly (loosely) coupled. SIMD and MISD architectures generally tend to be tightly coupled because of the common clocking of the shared instruction stream or the shared data stream. Here we briefly examine various MIMD architectures in terms of coupling. 10

I

I C

I C

I P

D (a) SIMD

P

D

I C

I P

D (b) MIMD

I C

I P

D

C

I P

C

Control Unit

P Processing Unit P

D

I instruction stream D data stream

(c) MISD

Figure 1.6: Flynn’s taxonomy of SIMD, MIMD, and MISD architectures for multiprocessor/multicomputer systems. 1. Tightly-coupled multiprocessors (with UMA shared memory). These may be either switchbased (e.g., NYU Ultracomputer, and RP3) or bus-based (e.g., Sequent, Encore). 2. Tightly-coupled multiprocessors (with NUMA shared memory or that communicate by message passing). Examples are the SGI Origin 2000 and the Sun Ultra HPC servers (that communicate via NUMA shared memory), and the hypercube and the torus (that communicate by message passing). 3. Loosely-coupled multicomputers (without shared memory) physically co-located. These may be bus-based (e.g., NOW connected by a LAN or Myrinet card) or using a more general communication network, and the processors may be heterogenous. In such systems, processors neither share memory nor have a common clock, and hence may be classified as distributed systems – however, the processors are very close to one another, which is characteristic of a parallel system. As the communication latency may be significantly lower than in wide-area distributed systems, the solution approaches to various problems may be different for such systems than for wide-area distributed systems. 4. Loosely-coupled multicomputers (without shared memory and without common clock) that are physically remote. These correspond to the conventional notion of distributed systems. 1.4.3.2 Parallelism or speedup of a program on a specific system. This is a measure of the relative speedup of a specific program, on a given machine. The speedup depends on the number of processors and the mapping of the code to the processors. It is expressed as the ratio of the time T (n) with n processors, to the time T (1) with a single processor. 1.4.3.3 Parallelism within a parallel/distributed program. This is an aggregate measure of the percentage of time that all the processors are executing CPU instructions productively, as opposed to waiting for communication (either via shared memory or message-passing) operations to complete. The term is traditionally used to characterize parallel 11

programs. If the aggregate measure is a function of only the code, then the parallelism is independent of the architecture. Otherwise, this definition degenerates to the definition of parallelism in Section 1.4.3.2. 1.4.3.4 Concurrency of a program. This is a broader term that means roughly the same as parallelism of a program, but is used in the context of distributed programs. The parallelism/concurrency in a parallel/distributed program can be measured by the ratio of the number of local (non-communication and non-shared memory access) operations to the total number of operations, including the communication or shared memory access operations. 1.4.3.5 Granularity of a program. The relative measure of the amount of computation to the amount of communication within the parallel/distributed program is termed as granularity. If the degree of parallelism is coarse-grained (fine-grained), there are relatively many more (fewer) productive CPU instruction executions, compared to the number of times the processors communicate (either via shared memory or messagepassing) and wait to get synchronized with the other processors. Programs with fine-grained parallelism are best suited for tightly coupled systems. These typically include SIMD and MISD architectures, tightly coupled MIMD multiprocessors (that have shared memory), and looselycoupled multicomputers (without shared memory) that are physically colocated. If programs with fine-grained parallelism were run over loosely-coupled multiprocessors that are physically remote, the latency delays for the frequent communication over the WAN would significantly degrade the overall throughput. As a corollary, it follows that on such loosely-coupled multicomputers, programs with a coarse-grained communication/message-passing granularity will incur substantially less overhead. Figure 1.2 showed the relationships between the local operating system, the middleware implementing the distributed software, and the network protocol stack. Before moving on, we identify various classes of multiprocessor/multicomputer operating systems. • The operating system running on loosely coupled processors (i.e., heterogenous and/or geographically distant processors), which are themselves running loosely coupled software (i.e., software that is heterogenous) is classified as a Network Operating System. In this case, the application cannot run any significant distributed function that is not provided by the Application Layer of the network protocol stacks on the various processors. • The operating system running on loosely coupled processors, which are running tightly coupled software (i.e., the middleware software on the processors is homogenous) is classified as a Distributed Operating System. • The operating system running on tightly coupled processors, which are themselves running tightly coupled software is classified as a Multiprocessor Operating System. Such a parallel system can run sophisticated algorithms contained in the tightly coupled software.

12

1.5 Message Passing Systems versus Shared Memory Systems Shared memory systems are those in which there is a (common) shared address space throughout the system. Communication among processors takes place via shared data variables, and control variables for synchronization among the processors. Semaphores and monitors that were originally designed for shared memory uniprocessors and multiprocessors are examples of how synchronization can be achieved in shared memory systems. All multicomputer (NUMA as well as message-passing) systems that do not have a shared address space provided by the underlying architecture and hardware necessarily communicate by message passing. Conceptually, programmers find it easier to program using shared memory than by message passing. For this and several other reasons that we examine later, the abstraction called shared memory is sometimes provided to simulate a shared address space. For a distributed system, this abstraction is called distributed shared memory. Implementing this abstraction has a certain cost but it simplifies the task of the application programmer. There also exists a well-known folklore result that communication via message-passing can be simulated by communication via shared memory and vice-versa. Therefore, the two paradigms are equivalent.

1.5.1 Emulating message-passing on shared memory system (MP → SM). The shared address space can be partitioned into disjoint parts, one part being assigned to each processor. “Send” and “Receive” operations can be implemented by writing to and reading from the destination/sender processor’s address space, respectively. Specifically, a separate location can be reserved as the mailbox for each ordered pair of processes. A Pi –Pj message-passing can be emulated by a Write by Pi to the mailbox and then a Read by Pj from the mailbox. In the simplest case, these mailboxes can be assumed to have unbounded size. The write and read operations need to be controlled using synchronization primitives to inform the receiver/sender after the data has been sent/received.

1.5.2 Emulating shared memory on a message-passing system (SM → MP ). This involves the use of “Send” and “Receive” operations for “Write” and “Read” operations. Each shared location can be modeled as a separate process; “Write” to a shared location is emulated by sending an update message to the corresponding owner process; a “Read” to a shared location is emulated by sending a query message to the owner process. As accessing another processor’s memory requires Send and Receive operations, this emulation is expensive. Although emulating shared memory might seem to be more attractive from a programmer’s perspective, it must be remembered that in a distributed system, it is only an abstraction. Thus, the latencies involved in read and write operations may be high even when using shared memory emulation because the read and write operations are implemented by using network-wide communication under the covers. An application can of course use a combination of shared memory and message-passing. In a MIMD message-passing multicomputer system, each “processor” may be a tightly-coupled multiprocessor system with shared memory. Within the multiprocessor system, the processors communicate via shared memory. Between two computers, the communication is by message passing. As message-passing systems are more common and more suited for wide-area distributed systems,

13

we will consider message-passing systems more extensively than we consider shared memory systems.

1.6 Primitives for Distributed Communication 1.6.1 Blocking/Nonblocking, Synchronous/Asynchronous Primitives Message send and message receive communication primitives are denoted Send() and Receive(), respectively. A Send primitive has at least two parameters - the destination, and the buffer in the user space, containing the data to be sent. Similarly, a Receive primitive has at least two parameters - the source from which the data is to be received (this could be a wildcard), and the user buffer into which the data is to be received. There are two ways of sending data when the Send primitive is invoked - the buffered option and the unbuffered option. The buffered option which is the standard option copies the data from the user buffer to the kernel buffer. The data later gets copied from the kernel buffer onto the network. In the unbuffered option, the data gets copied directly from the user buffer onto the network. For the Receive primitive, the buffered option is usually required because the data may already have arrived when the primitive is invoked, and needs a storage place in the kernel. The following are some definitions of blocking/nonblocking and synchronous/asynchronous primitives. Synchronous primitives. A Send or a Receive primitive is synchronous if both the Send() and Receive() handshake with each other. The processing for the Send primitive completes only after the invoking processor learns that the other corresponding Receive primitive has also been invoked and that the receive operation has been completed. The processing for the Receive primitive completes when the data to be received is copied into the receiver’s user buffer. Asynchronous primitives. A Send primitive is said to be asynchronous if control returns back to the invoking process after the data item to be sent has been copied out of the user-specified buffer. It does not make sense to define asynchronous Receive primitives. Blocking primitives. A primitive is blocking if control returns to the invoking process after the processing for the primitive (whether in synchronous or asynchronous mode) completes. Nonblocking primitives. A primitive is nonblocking if control returns back to the invoking process immediately after invocation, even though the operation has not completed. For a nonblocking Send, control returns to the process even before the data is copied out of the user buffer. For a nonblocking Receive, control returns to the process even before the data may have arrived from the sender. For nonblocking primitives, a return parameter on the primitive call returns a system-generated handle which can be later used to check the status of completion of the call. The process can check for the completion of the call in two ways. First, it can keep checking (in a loop or periodically) if the handle has been flagged or posted. Second, it can issue a Wait with a list 14

Send(X, destination, handlek ) ... ... W ait(handle1 , handle2 , . . . , handlek , . . . , handlem )

//handlek is a return parameter

//Wait always blocks

Figure 1.7: A nonblocking send primitive. When the Wait call returns, at least one of its parameters is posted. of handles as parameters. The Wait call usually blocks until one of the parameter handles is posted. Presumably after issuing the primitive in nonblocking mode, the process has done whatever actions it could and now needs to know the status of completion of the call, therefore using a blocking Wait() call is usual programming practice. The code for a nonblocking Send would look as shown in Figure 1.7. If at the time that Wait() is issued, the processing for the primitive (whether synchronous or asynchronous) has completed, the Wait returns immediately. The completion of the processing of the primitive is detectable by checking the value of handlek . If the processing of the primitive has not completed, the Wait blocks and waits for a signal to wake it up. When the processing for the primitive completes, the communication subsystem software sets the value of handlek and wakes up (signals) any process with a Wait call blocked on this handlek . This is called posting the completion of the operation. There are therefore four versions of the Send primitive – synchronous blocking, synchronous nonblocking, asynchronous blocking, and asynchronous nonblocking. For the Receive primitive, there are the blocking synchronous and nonblocking synchronous versions. These versions of the primitives are illustrated in Figure 1.8 using a timing diagram. Here, three time lines are shown for each process: (1) for the process execution, (2) for the user buffer from/to which data is sent/received, and (3) for the kernel/communication subsystem. Blocking synchronous Send (Figure 1.8(a)): The data gets copied from the user buffer to the kernel buffer and is then sent over the network. After the data is copied to the receiver’s system buffer, an acknowledgement back to the sender causes control to return to the process that invoked the Send operation and completes the Send. Nonblocking synchronous Send (Figure 1.8(b)): Control returns back to the invoking process as soon as the copy of data from the user buffer to the kernel buffer is initiated. A parameter in the nonblocking call also gets set with the handle of a location that the user process can later check for the completion of the synchronous send operation. The location gets posted after an acknowledgement returns from the receiver, as per the semantics described for (a). The user process can keep checking for the completion of the nonblocking synchronous Send by testing the returned handle, or it can invoke the blocking Wait operation on the returned handle. Blocking asynchronous Send (Figure 1.8(c)): The user process that invokes the Send is blocked until the data is copied from the user’s buffer to the kernel buffer. (For the unbuffered option, the user process that invokes the Send is blocked until the data is copied from the user’s buffer to the network.) 15

process i

S

S_C

S

W

buffer_i

W P, S_C

kernel_i

kernel_j buffer_j process j

R_C

R

(a) blocking sync. Send, blocking Receive

process i

S

P, R_C W W

R

(b) nonblocking sync. Send, nonblocking Receive

S

S_C

buffer_i

W

P, S_C

W

kernel_i

(c) blocking async. Send

S R P W

(d) nonblocking async. Send

duration to copy data from or to user buffer duration in which the process issuing send or receive primitive is blocked S_C processing for Send completes Send primitive issued Receive primitive issued R_C processing for Receive completes The completion of the previously initiated nonblocking operation Process may issue Wait to check completion of nonblocking operation

Figure 1.8: Blocking/nonblocking and synchronous/asynchronous primitives. Process Pi is sending and process Pj is receiving. (a) Blocking synchronous Send and blocking (synchronous) Receive. (b) Nonblocking synchronous Send and nonblocking (synchronous) Receive. (c) Blocking asynchronous Send. (d) Nonblocking asynchronous Send. Nonblocking asynchronous Send (Figure 1.8(d)): The user process that invokes the Send is blocked until the transfer of the data from the user’s buffer to the kernel buffer is initiated. (For the unbuffered option, the user process that invokes the Send is blocked until the transfer of the data from the user’s buffer to the network is initiated.) Control returns to the user process as soon as this transfer is initiated, and a parameter in the nonblocking call also gets set with the handle of a location that the user process can check later using the Wait operation for the completion of the asynchronous Send operation. The asynchronous Send completes when the data has been copied out of the user’s buffer. The checking for the completion may be necessary if the user wants to reuse the buffer from which the data was sent. Blocking Receive (Figure 1.8(a)): The Receive call blocks until the data expected arrives and is written in the specified user buffer. Then control is returned to the user process. 16

Nonblocking Receive (Figure 1.8(b)): The Receive call will cause the kernel to register the call and return the handle of a location that the user process can later check for the completion of the nonblocking Receive operation. This location gets posted by the kernel after the expected data arrives and is copied to the user-specified buffer. The user process can keep checking for the completion of the nonblocking Receive by invoking the Wait operation on the returned handle. (If the data has already arrived when the call is made, it would be pending in some kernel buffer, and still needs to be copied to the user buffer.) A synchronous Send is easier to use from a programmer’s perspective because the handshake between the Send and the Receive makes the communication appear instantaneous, thereby simplifying the program logic. The “instantaneity” is, of course, only an illusion, as can be seen from Figure 1.8(a) and (b). In fact, the Receive may not get issued until much after the data arrives at Pj , in which case the data arrived would have to be buffered in the system buffer at Pj and not in the user buffer. All this while, the sender would remain blocked. Thus, a synchronous Send lowers the efficiency within process Pi . The nonblocking asynchronous Send (see Figure 1.8(d)) is useful when a large data item is being sent because it allows the process to perform other instructions in parallel with the completion of the Send. The nonblocking synchronous Send (see Figure 1.8(b)) also avoids the potentially large delays for handshaking, particularly when the receiver has not yet issued the Receive call. The nonblocking Receive (see Figure 1.8(b)) is useful when a large data item is being received and/or when the sender has not yet issued the Send call, because it allows the process to perform other instructions in parallel with the completion of the Receive. Note that if the data has already arrived, it is stored in the kernel buffer, and it may take a while to copy it to the user buffer specified in the Receive call. For nonblocking calls, however, the burden on the programmer increases because he has to keep track of the completion of such operations in order to meaningfully reuse (write to or read from) the user buffers again. Thus, conceptually, blocking primitives are easier to use.

1.6.2 Processor Synchrony As opposed to the classification of synchronous and asynchronous communication primitives, there is also the classification of synchronous versus asynchronous processors. Processor synchrony indicates that all the processors execute in lock-step with their clocks synchronized. As this synchrony is not attainable in a distributed system, what is more generally indicated is that for a large granularity of code, usually termed as a step, the processors are synchronized. This abstraction is implemented using some form of barrier synchronization to ensure that no processor begins executing the next step of code until all the processors have completed executing the previous steps of code assigned to each of the processors.

1.6.3 Libraries and Standards The previous subsections identified the main principles underlying all communication primitives. In this subsection, we briefly mention some publicly available interfaces that embody some of the above concepts.

17

There exists a wide range of primitives for message-passing. Many commercial software products (banking, payroll, etc. applications) use proprietary primitive libraries supplied with the software marketed by the vendors (e.g., the IBM CICS software which has a very widely installed customer base worldwide uses its own primitives). The Message-Passing Interface (MPI) library and the PVM (Parallel Virtual Machine) library are used largely by the scientific community, but other alternative libraries exist. Commercial software is often written using the Remote Procedure Calls (RPC) mechanism in which procedures that potentially reside across the network are invoked transparently to the user, in the same manner that a local procedure is invoked. Under the covers, socket primitives or socket-like transport layer primitives are invoked to call the procedure remotely. There exist many implementations of RPC - for example, Sun RPC, and Distributed Computing Environment (DCE) RPC. “Messaging” and “streaming” are two other mechanisms for communication. With the growth of object based software, libraries for Remote Method Invocation (RMI) and Remote Object Invocation (ROI) with their own set of primitives are being proposed and standardized by different agencies. CORBA (Common Object Request Broker Architecture) and DCOM (Distributed Component Object Model) are two other standardized architectures with their own set of primitives. Additionally, several projects in the research stage are designing their own flavour of communication primitives.

1.7 Synchronous versus Asynchronous Executions

P0 m1

P1

m2

m7 m6

m4

P2 m3

m5

P3 internal event

send event

receive event

Figure 1.9: An example of an asynchronous execution in a message-passing system. A timing diagram is used to illustrate the execution. In addition to the two classifications of processor synchrony/ asynchrony and of synchronous/ asynchronous communication primitives, there is another classification, namely that of synchronous/ asynchronous executions. • An asynchronous execution is an execution in which (1) there is no processor synchrony and there is no bound on the drift rate of processor clocks, (2) message delays (transmission + propagation times) are finite but unbounded, and (3) there is no upper bound on the time taken by a process to execute a step. An example asynchronous execution with four processes P0 to P3 is shown in Figure 1.9. The arrows denote the messages; the head and tail of an arrow mark the send and receive event for that message, denoted by a circle and vertical 18

line, respectively. Non-communication events, also termed as internal events, are shown by shaded circles. • A synchronous execution is an execution in which (1) processors are synchronized and the clock drift rates between any two processors is bounded, (2) message delivery (transmission + delivery) times are such that they occur in one logical step or round, and (3) there is a known upper bound on the time taken by a process to execute a step. An example of a synchronous execution with four processes P0 to P3 is shown in Figure 1.10. The arrows denote the messages. It is easier to design and verify algorithms assuming synchronous executions because of the coordinated nature of the executions at all the processes. However, there is a hurdle to having a truly synchronous execution. It is practically difficult to build a completely synchronous system, and have the messages delivered within a bounded time. Therefore, this synchrony has to be simulated under the covers, and will inevitably involve delaying or blocking some processes for some time durations. Thus, synchronous execution is an abstraction that needs to be provided to the programs. When implementing this abstraction, observe that the fewer the steps or “synchronizations” of the processors, the lower the delays and costs. If processors are allowed to have an asynchronous execution for a period of time and then they synchronize, then the granularity of the synchrony is coarse. This is really a virtually synchronous execution, and the abstraction is sometimes termed as virtual synchrony. Ideally, many programs want the processes to execute a series of instructions in rounds (also termed as steps or phases) asynchronously, with the requirement that after each round/step/phase, all the processes should be synchronized and all messages sent should be delivered. This is the commonly understood notion of a synchronous execution. Within each round/phase/step, there may be a finite and bounded number of sequential sub-rounds (or sub-phases or sub-steps) that processes execute. Each sub-round is assumed to send at most one message per process; hence the message(s) sent will reach in a single message hop. The timing diagram of an example synchronous execution is shown in Figure 1.10. In this system, there are four nodes P0 to P3 . In each round, process Pi sends a message to P(i+1) mod 4 and P(i−1) mod 4 and calculates some application-specific function on the received values. The messages are sent as per the code given in function Sync_Execution. There are k rounds in the execution.

1.7.1 Emulating an asynchronous system by a synchronous system (A → S). An asynchronous program (written for an asynchronous system) can be emulated on a synchronous system fairly trivially as the synchronous system is a special case of an asynchronous system – all communication finishes within the same round in which it is initiated.

1.7.2 Emulating a synchronous system by an asynchronous system (S → A). A synchronous program (written for a synchronous system) can be emulated on an asynchronous system using a tool called synchronizer, to be studied in Chapter 2.

19

P 0 P 1 P 2 P 3

phase 1

phase 2

phase 3

(1) Sync_Execution(int k, n) //There are k rounds. (2) for r = 1 to k do (3) process i sends a message to (i + 1) mod n and (i − 1) mod n; (4) each process i receives a message from (i + 1) mod n and (i − 1) mod n; (5) compute some application-specific function on the received values.

Figure 1.10: An example of a synchronous execution in a message-passing system. All the messages sent in a round are received within that same round.

1.7.3 Emulations. Section 1.5 showed how a shared memory system could be emulated by a message-passing system, and vice-versa. We now have 4 broad classes of programs, as shown in Figure 1.11. Using the emulations shown, any class can be emulated by any other. If system A can be emulated by system A , and if a problem is not solvable in B, then it is also not solvable in A. Likewise, if a B, denoted B problem is solvable in A, it is also solvable in B. Hence, in a sense, all four classes are equivalent in terms of “computability” – what can and cannot be computed – in failure-free systems. However, in fault-prone systems, this is not the case as we will see in Chapter 14; a synchronous system offers more computability than an asynchronous system.

Asynchronous message−passing (AMP) MP−>SM

A−>S S−>A

SM−>MP

Asynchronous shared memory (ASM)

Synchronous message−passing (SMP) MP−>SM

A−>S S−>A

SM−>MP

Synchronous shared memory (SSM)

Figure 1.11: Emulations among the principal system classes in a failure-free system. 20

1.8 Design Issues and Challenges Distributed computing systems have been in widespread existence since the 1970s when the internet and ARPANET came into being. At the time, the primary issues in the design of the distributed systems included providing access to remote data in the face of failures, file system design and directory structure design. While these continue to be important issues, many newer issues have surfaced as the widespread proliferation of the high-speed high-bandwidth internet and distributed applications continues rapidly. Below we describe the important design issues and challenges after categorizing them as (i) having a greater component related to systems design and operating systems design, or (ii) having a greater component related to algorithm design, or (iii) emerging from recent technology advances and/or driven by new applications. There is some overlap between these categories. However, it is useful to identify these categories because of the chasm among the (i) the systems community, (ii) the theoretical algorithms community within distributed computing, and (iii) the forces driving the emerging applications and technology. For example, the current practice of distributed computing follows the client-server architecture to a large degree, whereas that receives scant attention in the theoretical distributed algorithms community. Two reasons for this chasm are as follows. First, an overwhelming number of applications outside the scientific computing community of users of distributed systems are business applications for which simple models are adequate. For example, the client server model has been firmly entrenched with the legacy applications first developed by the Blue Chip companies (e.g., HP, IBM, Wang, DEC which is now Compaq, Microsoft) since the 1970s and 1980s. This model is largely adequate for traditional business applications. Second, the state of the practice is largely controlled by industry standards, which do not necessarily choose the “technically best” solution.

1.8.1 Distributed Systems Challenges from a System Perspective The following functions must be addressed when designing and building a distributed system. • Communication. This task involves designing appropriate mechanisms for communication among the processes in the network. Some example mechanisms are: Remote Procedure Call (RPC), Remote Object Invocation (ROI), message-oriented communication versus streamoriented communication. • Processes. Some of the issues involved are: management of processes and threads at clients/servers; code migration; and the design of software and mobile agents. • Naming. Devising easy to use and robust schemes for names, identifiers, and addresses is essential for locating resources and processes in a transparent and scalable manner. Naming in mobile systems provides additional challenges because naming cannot easily be tied to any static geographical topology. • Synchronization. Mechanisms for synchronization or coordination among the processes are essential. Mutual exclusion is the classical example of synchronization, but many other forms of synchronization, such as leader election are also needed. In addition, synchronizing physical clocks, and devising logical clocks that capture the essence of the passage of time, as well as global state recording algorithms all require different forms of synchronization. 21

• Data storage and access. Schemes for data storage, and implicitly for accessing the data in a fast and scalable manner across the network are important for efficiency. Traditional issues such as file system design have to be reconsidered in the setting of a distributed system. • Consistency and replication. To avoid bottlenecks, to provide fast access to data, and to provide scalability, replication of data objects is highly desirable. This leads to issues of managing the replicas, and dealing with consistency among the replicas/caches in a distributed setting. A simple exxample issue is deciding the level of granularity (i.e., size) of data access. • Fault tolerance. Fault tolerance requires maintaining correct and efficient operation in spite of any failures of links, nodes, and processes. Process resilience, reliable communication, distributed commit, checkpointing and recovery, agreement and consensus, failure detection, and self-stabilization are some of the mechanisms to provide fault-tolerance. • Security. Distributed systems security involves various aspects of cryptography, secure channels, access control, key management – generation and distribution, authorization, and secure group management. • Applications Programming Interface (API) and transparency. The API for communication and other specialized services is important for the ease of use and wider adoption of the distributed systems services by non-technical users. Transparency deals with hiding the implementation policies from the user, and can be classified as follows. Access transparency hides differences in data representation on different systems and providing uniform operations to access system resources. Location transparency makes the locations of resources transparent to the users. Migration transparency allows relocating resources without the users noticing it. The ability to relocate the resources as they are being accessed is relocation transparency. Replication transparency does not let the user become aware of any replication. Concurrency transparency deals with masking the concurrent use of shared resources for the user. Failure transparency refers to the system being reliable and fault-tolerant. • Scalability and modularity. The algorithms, data (objects), and services must be as distributed as possible. Various techniques such as replication, caching and cache management, and asynchronous processing help to achieve scalability. Some of the recent experiments in designing large-scale distributed systems include the Globe project at Vrije University [32], and the Globus project [33]. The Grid infrastructure for largescale distributed computing is a very ambitious project that has gained significant attention to date [35, 34]. All these projects attempt to provide the above listed functions as efficiently as possible.

1.8.2 Algorithmic Challenges in Distributed Computing The previous section addresses the challenges in designing distributed systems from a system building perspective. In this section, we briefly summarize the key algorithmic challenges in distributed computing.

22

Designing useful execution models and frameworks. The interleaving model and partial order model are two widely adopted models of distributed system executions. They have proved to be particularly useful for operational reasoning and the design of distributed algorithms. The Input/Output automata model and the TLA (Temporal Logic of Actions) are two other examples of models that provide different degrees of infrastructure for reasoning more formally with and proving the correctness of distributed programs. Dynamic distributed graph algorithms and distributed routing algorithms. The distributed system is modeled as a distributed graph, and the graph algorithms form the building blocks for a large number of higher level communication, data dissemination, object location, and object search functions. The algorithms need to deal with dynamically changing graph characteristics, such as to model varying link loads in a routing algorithm. The efficiency of these algorithms impacts not only the user-perceived latency but also the traffic and hence the load or congestion in the network. Hence, the design of efficient distributed graph algorithms is of paramount importance. Time and global state in a distributed system. The processes in the system are spread across three-dimensional physical space. Another dimension, time, has to be superimposed uniformly across space. The challenges pertain to providing accurate physical time, and to providing a variant of time, called logical time. Logical time is relative time, and eliminates the overheads of providing physical time for applications where physical time is not required. More importantly, logical time can (i) capture the logic and inter-process dependencies within the distributed program, and also (ii) track the relative progress at each process. Observing the global state of the system (across space) also involves the time dimension for consistent observation. Due to the inherent distributed nature of the system, it is not possible for any one process to directly observe a meaningful global state across all the processes, without using extra state-gathering effort which needs to be done in a coordinated manner. Deriving appropriate measures of concurrency also involves the time dimension, as judging the independence of different threads of execution depends not only on the program logic but also on execution speeds within the logical threads, and communication speeds among threads. Synchronization/ coordination mechanisms. The processes must be allowed to execute concurrently, except when they need to synchronize to exchange information, i.e., communicate about shared data. Synchronization is essential for the distributed processes to overcome the limited observation of the system state from the viewpoint of any one process. Overcoming this limited observation is necessary for taking any actions that would impact other processes. The synchronization mechanisms can also be viewed as resource management and concurrency management mechanisms to streamline the behavior of the processes that would otherwise act independently. Here are some examples of problems requiring synchronization. • Physical clock synchronization. Physical clocks ususally diverge in their values due to hardware limitations. Keeping them synchronized is a fundamental challenge to maintain common time. 23

• Leader election. All the processes need to agree on which process will play the role of a distinguished process – called a leader process. A leader is necessary even for many distributed algorithms because there is often some asymmetry – as in initiating some action like a broadcast or collecting the state of the system, or in “regenerating” a token that gets “lost” in the system. • Mutual exclusion. This is clearly a synchronization problem because access to the critical resource(s) has to be coordinated. • Deadlock detection and resolution. Deadlock detection should be coordinated to avoid duplicate work, and deadlock resolution should be coordinated to avoid unnecessary aborts of processes. • Termination detection. This requires cooperation among the processes to detect the specific global state of quiescence. • Garbage collection. Garbage refers to objects that are no longer in use and that are not pointed to by any other process. Detecting garbage requires coordination among the processes. Group communication, multicast, and ordered message delivery. A group is a collection of processes that share a common context and collaborate on a common task within an application domain. Specific algorithms need to be designed to enable efficient group communication and group management wherein processes can join and leave groups dynamically, or even fail. When multiple processes send messages concurrently, different recipients may receive the messages in different orders, possibly violating the semantics of the distributed program. Hence, formal specifications of the semantics of ordered delivery need to be formulated, and then implemented. Monitoring distributed events and predicates. Predicates defined on program variables that are local to different processes are useful for specifying conditions on the global system state, and are useful for applications such as debugging, sensing the environment, and in industrial process control. On-line algorithms for monitoring such predicates are hence important. An important paradigm for monitoring distributed events is that of event streaming, wherein streams of relevant events reported from different processes are examined collectively to detect predicates. Typically, the specification of such predicates uses physical or logical time relationships. Distributed program design and verification tools. Methodically designed and verifiably correct programs can greatly reduce the overhead of software design, debugging, and engineering. Designing mechanisms to achieve these design and verification goals is a challenge. Debugging distributed programs. Debugging sequential programs is hard; debugging distributed programs is that much harder because of the concurrency in actions and the ensuing uncertainty due to the large number of possible executions defined by the interleaved concurrent actions. Adequate debugging mechanisms and tools need to be designed to meet this challenge. Data replication, consistency models, and caching. Fast access to data and other resources requires them to be replicated in the distributed system. Managing such replicas in the face 24

of updates introduces the problems of ensuring consistency among the replicas and cached copies. Additionally, placement of the replicas in the systems is also a challenge because resources usually cannot be freely replicated. World Wide Web design – caching, searching, scheduling. The Web is an example of a widespread distributed system with a direct interface to the end user, wherein the operations are predominantly read-intensive on most objects. The issues of object replication and caching discussed above have to be tailored to the web. Further, prefetching of objects when access patterns and other characteristics of the objects are known, can also be performed. An example of where prefetching can be used is the case of subscribing to Content Distribution Servers. Minimizing response time to minimize user-perceived latencies is an important challenge. Object search and navigation on the web are important functions in the operation of the web, and are very resource-intensive. Designing mechanisms to do this efficiently and accurately are great challenges. Distributed shared memory abstraction. A shared memory abstraction simplifies the task of the programmer because he has to deal only with Read and Write operations, and no message communication primitives. However, under the covers in the middleware layer, the abstraction of a shared address space has to be implemented by using message-passing. Hence, in terms of overheads, the shared memory abstraction is not less expensive. • Wait-free algorithms. Wait-freedom, which can be informally defined as the ability of a process to complete its execution irrespective of the actions of other processes, gained prominence in the design of algorithms to control acccess to shared resources in the shared memory abstraction. It corresponds to n − 1-fault resilience in a n process system and is an important principle in fault-tolerant system design. While wait-free algorithms are highly desirable, they are also expensive, and designing low overhead wait-free algorithms is a challenge. • Mutual exclusion. A first course in Operating Systems covers the basic algorithms (such as the Bakery algorithm and using semaphores) for mutual exclusion in a multiprocessing (uniprocessor or multiprocessor) shared memory setting. More sophisticated algorithms – such as those based on hardware primitives, fast mutual exclusion, and wait-free algorithms – will be covered in this book. • Register constructions. In light of promising and emerging technologies of tomorrow – such as biocomputing and quantum computing – that can alter the present foundations of computer “hardware” design, we need to revisit the assumptions of memory access of current systems that are exclusively based on the semiconductor technology and the von Neumann architecture. Specifically, the assumption of single/multiport memory with serial access via the bus in tight synchronization with the system hardware clock may not be a valid assumption in the possibility of “unrestricted” and “overlapping” concurrent access to the same memory location. The study of register constructions deals with the design of registers from scratch, with very weak assumptions on the accesses allowed to a register. This field forms a foundation for future architectures that allow concurrent access even to primitive units of memory (independent of technology) without any restrictions on the concurrency permitted. 25

• Consistency models. For multiple copies of a variable/object, varying degrees of consistency among the replicas can be allowed. These represent a trade-off of coherence versus cost of implementation. Clearly, a strict definition of consistency (such as in a uniprocessor system) would be expensive to implement in terms of high latency, high message overhead, and low concurrency. Hence, relaxed but still meaningful models of consistency are desirable. Reliable and fault-tolerant distributed systems. A reliable and fault-tolerant environment has multiple requirements and aspects, and these can be addressed using various strategies. • Consensus algorithms. All algorithms ultimately rely on message-passing, and the recipients take actions based on the contents of the received messages. Consensus algorithms allow correctly functioning processes to reach agreement among themselves in spite of the existence of some malicious (adversarial) processes whose identities are not known to the correctly functioning processes. The goal of the malicious processes is to prevent the correctly functioning processes from reaching agreement. The malicious processes operate by sending messages with misleading information, to confuse the correctly functioning processes. • Replication and replica management. Replication (as in having backup servers) is a classical method of providing fault-tolerance. The Triple Modular Redundancy (TMR) technique has long been used in software as well as hardware installations. More sophisticated and efficient mechanisms for replication are the subject of study here. • Voting and quorum systems. Providing redundancy in the active (e.g., processes) or passive (e.g., hardware resources) components in the system and then performing voting based on some quorum criterion is a classical way of dealing with fault-tolerance. Designing efficient algorithms for this purpose is the challenge. • Distributed databases and distributed commit. For distributed databases, the traditional properties of the transaction (A.C.I.D. – Atomicity, Consistency, Isolation, Durability) need to be preserved in the distributed setting. The field of traditional “transaction commit” protocols is a fairly matura area. Transactional properties can also be viewed as having a counterpart for guarantees on message delivery in group communication in the presence of failures. Results developed in one field can be adapted to the other. • Self-stabilizing systems. All system executions have associated good (or legal) states and bad (or illegal) states; during correct functioning, the system makes transitions among the good states. Faults, internal or external to the program and system, may cause a bad state to arise in the execution. A self-stabilizing algorithm is any algorithm that is guaranteed to eventually take the system to a good state even if a bad state were to arise due to some error. Self-stabilizing algorithms require some in-built redundancy to track additional variables of the state or by doing extra work. Designing efficient self-stabilizing algorithms is a challenge. • Checkpointing and recovery algorithms. Checkpointing involves periodically recording state on secondary storage so that in case of a failure, the entire computation is not lost but can be recovered from one of the recently taken checkpoints. Checkpointing

26

in a distributed environment is difficult because if the checkpoints at the different processes are not coordinated, the local checkpoints may become useless because they are inconsistent with the checkpoints at other processes. • Failure detectors. A fundamental limitation of asynchronous distributed systems is that there is no theoretical bound on the message transmission times. Hence, it is impossible to distinguish a sent-but-not-yet-arrived message from a message that was never sent. This implies that it is impossible using message transmission to determine whether some other process across the network is alive or has failed. Failure detectors represent a class of algorithms that probabilistically suspect another process as having failed (such as after timing out after non-receipt of a message for some time), and then converge on a determination of the up/down status of the suspected process. Load balancing. The goal of load balancing is to gain higher throughput, and reduce the userperceived latency. Load balancing may be necessary because of a variety of factors such as: high network traffic or high request rate causing the network connection to be a bottleneck, or high computational load. A common situation where load balancing is used is in server farms, where the objective is to service incoming client requests with the least turnaround time. Several results from traditional operating systems can be used here, although they need to be adapted to the specifics of the distributed environment. The following are some forms of load balancing. • Data migration. The ability to move data (which may be replicated) around in the system, based on the access pattern of the users. • Computation migration. The ability to relocate processes in order to perform a redistribution of the workload. • Distributed scheduling. This achieves a better turnaround time for the users by using idle processing power in the system more efficiently. Real-time scheduling. Real-time scheduling is important for mission-critical applications, to accomplish the task execution on schedule. The problem becomes more challenging in a distributed system where a global view of the system state is absent. On-line or dynamic changes to the schedule are also harder to make without a global view of the state. Furthermore, message propagation delays which are network-dependent are hard to control or predict, which makes meeting real-time guarantees that are inherently dependent on communication among the processes harder. Although networks offering Quality-of-Service guarantees can be used, they alleviate the uncertainty in propagation delays only to a limited extent. Further, such networks may not always be available. Performance. Although high throughput is not the primary goal of using a distributed system, achieving good performance is important. In large distributed systems, network latency (propagation and transmission times) and access to shared resources can lead to large delays which must be minimized. The user-perceived turn-around time is very important. The following are some example issues arise in determining the performance.

27

• Metrics. Appropriate metrics must be defined or identified for measuring the performance of theoretical distributed algorithms, as well as for implementations of such algorithms. The former would involve various complexity measures on the metrics, whereas the latter would involve various system and statistical metrics. • Measurement methods/tools. As a real distributed system is a complex entity and has to deal with all the difficulties that arise in measuring performance over a WAN/the Internet, appropriate methodologies and tools must be developed for measuring the performance metrics.

1.8.3 Applications of Distributed Computing and Newer Challenges Mobile systems. Mobile systems typically use wireless communication which is based on electromagnetic waves and utilizes a shared broadcast medium. Hence, the characteristics of communication are different; many issues such as range of transmission and power of transmission come into play, besides various engineering issues such as battery power conservation, interfacing with the wired internet, signal processing and interference. From a computer science perspective, there is a rich set of problems such as routing, location management, channel allocation, localization and position estimation, and the overall management of mobility. There are two popular architectures for a mobile network. The first is the base-station approach, also known as the cellular approach, wherein a cell which is the geographical region within range of a static but powerful base transmission station is associated with that base station. All mobile processes in that cell communicate with the rest of the system via the base station. The second approach is the ad-hoc network approach where there is no base station (which essentially acted as a centralized node for its cell). All responsibility for communication is distributed among the mobile nodes, wherein mobile nodes have to participate in routing by forwarding packets of other pairs of communicating nodes. Clearly, this is a complex model. It poses many graph-theoretical challenges from a computer science perspective, in addition to various engineering challenges. Sensor networks. A sensor is a processor with an electro-mechanical interface that is capable of sensing physical parameters, such as temperature, velocity, pressure, humidity, and chemicals. Recent developments in cost-effective hardware technology have made it possible to deploy very large (of the order of 106 or higher) low-cost sensors. An important paradigm for monitoring distributed events is that of event streaming, defined earlier. The streaming data reported from a sensor network differs from the streaming data reported by “computer processes” in that the events reported by a sensor network are in the environment, external to the computer network and processes. This limits the nature of information about the reported event in a sensor network. Sensor networks have a wide range of applications. Sensors may be mobile or static; sensors may communicate wirelessly, although they may also communicate across a wire when they are statically installed. Sensors may have to self-configure to form an ad-hoc network, which introduces a whole new set of challenges, such as position estimation and time estimation.

28

Ubiquitous or pervasive computing. Ubiquitous systems represent a class of computing where the processors embedded in and seamlessly pervading through the environment perform application functions in the background, much like in the sci-fi movies. The intelligent home, and the smart workplace are some example of ubiquitous environments currently under intense research and development. Ubiquitous systems are essentially distributed systems; recent advances in technology allow them to leverage wireless communication and sensor and actuator mechanisms. They can be self-organizing and network-centric, while also being resource constrained. Such systems are typically characterized as having many small processors operating collectively in a dynamic ambient network. The processors may be connected to more powerful networks and processing resources in the background for processing and collating data. Peer-to-peer computing. Peer-to-peer (P2P) computing represents computing over an application layer network wherein all interactions among the processors are at a “peer” level, without any hierarchy among the processors. Thus, all processors are equal and play a symmetric role in the computation. P2P computing arose as a paradigm shift from client-server computing where the roles among the processors are essentially asymmetrical. P2P networks are typically self-organizing, and may or may not have a regular structure to the network. No central directories (such as those used in Domain Name Servers) for name resolution and object lookup are allowed. Some of the key challenges include: object storage mechanisms, efficient object lookup and retreival in a scalable manner; dynamic reconfiguration with nodes as well as objects joining and leaving the network randomly; replication strategies to expedite object search; tradeoffs between object size latency and table sizes; anonymity, privacy, and security. Publish-subscribe, content distribution, and multimedia. With the explosion in the amount of information, there is a greater need to receive and access only information of interest. Such information can be specified using filters. In a dynamic environment where the information constantly fluctuates (varying stock prices is a typical example), there needs to be: (i) an efficient mechanism for distributing this information (publish), (ii) an efficient mechanism to allow end users to indicate interest in receiving specific kinds of information (subscribe), and (iii) an efficient mechanism for aggregating large volumes of published information and filtering it as per the user’s subscription filter. Content distribution refers to a class of mechanisms, primarily in the web and P2P computing context, whereby specific information which can be broadly characterized by a set of parameters is to be distributed to interested processes. Clearly, there is overlap between content distribution mechanisms and publish-subscribe mechanisms. When the content involves multimedia data, special requirement such as the following arise: multimedia data is usually very large and information-intensive, requires compression, and often requires special synchronization during storage and playback. Distributed agents. Agents are software processes or robots that can move around the system to do specific tasks for which they are specially programmed. The name “agent” derives from the fact that the agents do work on behalf of some broader objective. Agents collect and process information, and can exchange such information with other agents. Often, the agents 29

cooperate as in an ant colony, but they can also have friendly competition, as in a free market economy. Challenges in distributed agent systems include coordination mechanisms among the agents, controlling the mobility of the agents, and their software design and interfaces. Research in agents is inter-disciplinary: spanning artificial intelligence, mobile computing, economic market models, software engineering, and distributed computing. Distributed data mining. Data mining algorithms examine large amounts of data to detect patterns and trends in the data, to mine or extract useful information. A traditional example is: examining the purchasing patterns of customers in order to profile the customers and enhance the efficacy of directed marketing schemes. The mining can be done by applying database and artificial intelligence techniques to a data repository. In many situations, the data is necessarily distributed and cannot be collected in a single repository, as in banking applications where the data is private and sensitive, or in atmospheric weather prediction where the data sets are far too massive to collect and process at a single repository in real-time. In such cases, efficient distributed data mining algorithms are required. Grid computing. Analogous to the electrical power distribution grid, it is envisaged that the information and computing grid will become a reality some day. Very simply stated, idle CPU cycles of machines connected to the network will be available to others. Many challenges in making grid computing a reality include: scheduling jobs in such a distributed environment, a framework for implementing Quality of Service and real-time guarantees, and of course, security of individual machines as well as of jobs being executed in this setting. Security in distributed systems. The traditional challenges of security in a distributed setting include: confidentiality (ensuring that only authorized processes can access certain information), authentication (ensuring the source of received information and the identity of the sending process), and availability (maintaining allowed access to services despite malicious actions). The goal is to meet these challenges with efficient and scalable solutions. These basic challenges have been addressed in traditional distributed settings. For the newer distributed architectures, such as wireless, peer-to-peer, grid, and pervasive computing discussed in this subsection), these challenges become more interesting due to factors such as a resource-constrained environment, a broadcast medium, the lack of structure, and the lack of trust in the network.

1.9 Selection and Coverage of Topics This is a long list of topics and difficult to cover in a single textbook. This book covers a broad selection of topics from the above list, in order to present the fundamental principles underlying the various topics. The goal has been to select topics that will give a good understanding of the field, and of the techniques used to design solutions. Some topics that have been omitted are interdisciplinary, across fields within computer science. An example is load balancing, which is traditionally covered in detail in a course on Parallel Processing. As the focus of distributed systems has shifted away from gaining higher efficiency to providing better services and fault-tolerance, the importance of load balancing in distributed computing has diminished. Another example is mobile systems. A mobile system is a distributed 30

system having certain unique characteristics, and there are courses devoted specifically to mobile systems.

1.10 Chapter Summary This chapter first characterized distributed systems by looking at various informal definitions based on functional aspects. It then looked at various architectures of multiple processor systems, and the requirements that have traditionally driven distributed systems. The relationship of a distributed system to “middleware”, the operating system, and the network protocol stack provided a different perspective on a distributed system. The relationship between parallel systems and distributed systems, covering aspects such degrees of software and hardware coupling, and the relative placement of the processors, memory units, and interconnection networks, was examined in detail. There is some overlap between the fields of parallel computing and distributed computing, and hence it is important to understand their relationhip clearly. For example, various interconnection networks such as the Omega network, the Butterfly network, and the hypercube network, were designed for parallel computing but they are recently finding surprising applications in the design of application-level overlay networks for distributed computing. The traditional taxonomy of multiple processor systems by Flynn was also studied. Important concepts such as the degree of parallelism and of concurrency, and the degree of coupling were also introduced informally. The chapter then introduced three fundamental concepts in distributed computing. The first concept is the paradigm of shared memory communication versus message-passing communication. The second concept is the paradigm of synchronous executions and asynchronous executions. For both these concepts, emulation of one paradigm by another was studied for error-free systems. The third concept was that of synchronous and asynchronous send communication primitives, of synchronous receive communicaiton primitives, and of blocking and nonblocking send and receive communication primitives. The chapter then presented design issues and challenges in the field of distributed computing. The challenges were classified as (i) being important from a systems design perspective, or (ii) being important from an algorithmic perspective, or (iii) those that are driven by new applications and emerging technologies. This classification is not orthogonal and is somewhat subjective. The various topics that will be covered in the rest of the book are portrayed on a miniature canvas in the section on the design issues and challenges.

1.11 Bibliographic Notes The selection of topics and material for this book has been shaped by the authors’ perception of the importance of various subjects, as well as the coverage by the existing textbooks. There are many books on distributed computing and distributed systems. Attiya and Welch [1] and Lynch [7] provide a formal theoretical treatment of the field. The books by Barbosa [2] and Tel [12] focus on algorithms. The books by Chow and Johnson [3], Coulouris, Dollimore, and Kindberg [4], Garg [5], Goscinski [6], Mullender [8], Raynal [22], Singhal and Shivaratri [10], and Tanenbaum and van Steen [11] provide a blend of theoretical and systems issues. 31

Much of the material in this introductory chapter is based on well understood concepts and paradigms in the distributed systems community, and is difficult to attribute to any particular source. A recent overview of the challenges in middleware design from systems’ perspective is given in the special issue by Lea, Vinoski, and Vogels [30]. An overview of the Common Object Request Broker Model (CORBA) of the Object Management Group (OMG) is given by Vinoski [22]. The Distributed Component Object Model (DCOM) from Microsoft, Sun’s Java Remote Method Invocation (RMI), and CORBA are analyzed in perspective by Campbell, Coulson, and Kounavis [26]. A detailed treatment of CORBA, RMI, and RPC is given by Coulouris, Dollimore, and Kindberg [4]. The Open Foundations’s Distributed Computing Environment (DCE) is described in [31]; DCE is not likely to be enjoy a continuing support base. Descriptions of the Message Passing Interface can be found in Snir et al. [23] and Gropp et al. [24]. The Parallel Virtual Machine (PVM) framework for parallel distributed programming is described by Sunderam [29]. The discussion of parallel processing, and of the UMA and NUMA parallel architectures, is based on Kumar, Grama, Gupta, and Karypis [18]. The properties of the hypercube architecture are surveyed by Feng [27] and Harary, Hayes, and Wu [28]. The multi-stage interconnection architectures – the Omega (Benes) [14], the Butterfly [16], and Clos [15] were proposed in the papers indicated. A good overview of multistage interconnection networks is given by Wu and Feng [25]. Flynn’s taxomomy of multiprocessors is based on [21]. The discussion on blocking/nonblocking primitives as well as synchronous and asynchropnous primitives is extended from Cypher and Leu [20]. The section on design issues and challenges is based on the vast research literature in the area. The Globe architecture is described by van Steen, Homburg, and Tanenbaum [32]. The Globus architecture is described by Foster and Kesselman [33]. The grid infrastructure and the distributed computng vision for the 21st century is described by Foster and Kesselman [35] and by Foster [34]. The World-Wide Web is an excellent example of a distributed system that has largely evolved of its own; Tim Berners-Lee is credited with seeding the WWW project; its early description is given by Berners-Lee, Cailliau, Luotonen, Nielsen, and Secret [36].

1.12 Exercise Problems 1. What are the main differences between a parallel system and a distributed system? 2. Identify some distributed applications in the scientific and commercial application areas. For each application, determine which of the motivating factors listed in Section 1.3 are important for building the application over a distributed system. 3. Draw the Omega and Butterfly networks for n = 16 inputs and outputs. 4. For the Omega and Butterfly networks shown in Figure 1.4, trace the paths from P5 to M2 , and from P6 to M1 . 5. Formulate the interconnection function for the Omega network having n inputs and outputs, only in terms of the M = n/2 switch numbers in each stage. (Hint: Follow an approach similar to the Butterfly network formulation.) 32

6. In Figure 1.4, observe that the paths from input 000 to output 111 and from input 101 to output 110 have a common edge. Therefore, simultaneous transmission over these paths is not possible; one path blocks another. Hence, the Omega and Butterfly networks are classified as blocking interconnection networks. Let Π(n) be any permutation on {0 . . . n−1}, mapping the input domain to the output range. A nonblocking interconnection network allows simultaneous transmission from the inputs to the outputs for any permutation. Consider the network built as follows. Take the image of a butterfly in a vertical mirror, and append this mirror image to the output of a butterfly. Hence, for n inputs and outputs, there will be 2log2 n stages. Prove that this network is nonblocking. 7. The Baseline Clos network has a interconnection generation function as follows. Let there be M = n/2 switches per stage, and let a switch be denoted by the tuple hx, si, where x ∈ [0, M − 1] and stage s ∈ [0, log2 n − 1]. There is an edge from switch hx, si to switch hy, s + 1i if (i) y is the cyclic right-shift of the (log2 n − s) least significant bits of x, (ii) y is the cyclic right-shift of the (log2 n − s) least significant bits of x′ , where x′ is obtained by complementing the LSB of x.

Draw the interconnection diagram for the Clos network having n = 16 inputs and outputs, i.e., having 8 switches in each of the 4 stages. 8. Two interconnection networks are isomorphic if there is a 1:1 mapping f between the switches such that for any switches x and y that are connected to each other in adjacent stages in one network, f (x) and f (y) are also connected in the other network. Show that the Omega, Butterfly, and Clos (Baseline) networks are isomorphic to each other. 9. Explain why a Receive call cannot be asynchronous. 10. What are the three aspects of reliability? Is it possible to order them in different ways in terms of importance, based on different applications’ requirements? Justify your answer by giving examples of different applications. 11. Figure 1.11 shows the emulations among the principal system classes in a failure-free system. (a) Which of these emulations are possible in a failure-prone system? Explain. (b) Which of these emulations are not possible in a failure-prone system? Explain. 12. Examine the impact of unreliable links and node failures on each of the challenges listed in Section 1.8.2.

33

Bibliography [1] H. Attiya, J. Welch, Distributed Computing Fundamentals, Simulations, and Advanced Topics, Wiley Inter-Science, 2nd edition, 2004. [2] V. Barbosa, An Introduction to Distributed Algorithms, MIT Press, 1996. [3] R. Chow and D. Johnson, Distributed Operating Systems and Algorithms, Addison-Wesley, 1997. [4] G. Coulouris, J. Dollimore, and T. Kindberg, Distributed Systems Concepts and Design, Addison-Wesley, 3rd edition, 2001. [5] V. Garg, Elements of Distributed Computing, John Wiley, 2003. [6] A. Goscinski, Distributed Operating Systems: The Logical Design, Addison-Wesley, 1991. [7] N. Lynch, Distributed Algorithms, Morgan Kaufmann, 1996. [8] S. Mullender, Distributed Systems, Addison-Wesley, 2nd edition, 1993. [9] M. Raynal, Distributed Algorithms and Protocols, John Wiley, 1988. [10] M. Singhal and N. Shivaratri, Advanced Concepts in Operating Systems, McGraw Hill, 1994. [11] A. Tanenbaum, M. Van Steen, Distributed Systems: Principles and Paradigms, Prentice-Hall, 2003. [12] G. Tel, Introduction to Distributed Algorithms, Cambridge University Press, 1994. [13] A. Ananda, B. Tay, E, Koh, A survey of asynchronous remore procedure calls, ACM SIGOPS Operating Systems Review, 26(2): 92-109, 1992. [14] V. E. Benes, Mathematical Theory of Connecting Networks and Telephone Traffic, Academic Press, New York, 1965. [15] C. Clos, A study of non-blocking switching networks, Bell Systems Technical Journal, 32: 406-424, 1953. [16] J. M. Cooley, J. W. Tukey, An algorithm for the machine calculation of comple Fourier series, Mathematical Comp., 19, 297-301, 1965.

34

[17] A. Birrell, B. Nelson, Implementing remote procedure calls, ACM Transactions on Computer Systems, 2(1): 39-59, 1984. [18] V. Kumar, A. Grama, A. Gupta, G. Karypis, Introduction to Parallel Computing, BenjaminCummins, 2nd edition, 2003. [19] A. Tanenbaum, Computer Networks, 3rd edition, Prentice-Hall PTR, NJ, 1996. 1994 [20] R. Cypher, E. Leu, The semantics of blocking and nonblocking send and receive primitives, 8th International Symposium on Parallel Processing, 729-735, 1994. [21] M. Flynn, Some computer organizations and their effectiveness, IEEE Trans. Comput., Vol. C-21, pp. 94, 1972. [22] S. Vinoski, CORBA: Integrating diverse applications within heterogeneous distributed environments, IEEE Communications Magazine, 35(2): 1997. [23] M. Snir, S. Otto, S. Huss-Lederman, D. Walker, J. Dongarra, MPI: The Complete Reference, MIT Press, 1996. [24] W. Gropp, E. Lusk, A. Skjellum, Using MPI: portable parallel programming with the message-passing interface, MIT Press, 1994. [25] C.L. Wu, T.-Y. Feng, On a class of multistage interconnection networks, IEEE Transactions on Computers. Vol. C-29, pp. 694-702. Aug. 1980 [26] A. Campbell, G. Coulson, M. Counavis, Managing complexity: Middleware explained, IT Professional Magazine, October 1999. [27] T. Y. Feng, A survey of interconnection networks, IEEE Comput., 14, pp. 12-27, Dec. 1981. [28] F. Harary, J.P. Hayes, H. Wu, A survey of the theory of hypercube graphs, Computational Mathematical Applications 15(4): 277-289, 1988. [29] V. Sunderam, PVM: A framework for parallel distributed computing, Concurrency - Practice and Experience, 2(4): 315-339, 1990. [30] D. Lea, S. Vinoski, W. Vogels, Guest editors’ introduction: Asynchronous middleware and services, IEEE Internet Computing, 10(1): 14-17, 2006. [31] J. Shirley, W. Hu, D. Magid, Guide to Writing DCE Applications, O’Reilly and Associates, Inc., ISBN 1-56592-045-7, USA. [32] M. van Steen, P. Homburg, A. Tanenbaum, Globe: A wide-area distributed system, IEEE Concurrency, 70-78, 1999. [33] I. Foster, C. Kesselman, Globus: A metacomputing infrastructure toolkit, International Journal of Supercomputer Applications, 11(2): 115-128, 1997. [34] I. Foster, The Grid: A new infrastructure for 21st century science, Physics Today, 55(2):4247, 2002. 35

[35] I. Foster, C. Kesselman, The Grid: Blueprint for a New Computing Infrastructure, Morgan Kaufmann, 1998. [36] T. Berners-Lee, R. Cailliau, A. Luotonen, H. Nielsen, A. Secret, The World-Wide Web, Communications of the ACM, 37(8): 76-82, 1994.

36

Chapter 2 A Model of Distributed Computations A distributed system consists of a set of processors that are connected by a communication network. The communication network provides the facility of information exchange among processors. The communication delay is finite but unpredictable. The processors do not share a common global memory and communicate solely by passing messages over the communication network. There is no physical global clock in the system to which processes have instantaneous access. The communication medium may deliver messages out of order, messages may be lost, garbled, or duplicated due to timeout and retransmission, processors may fail, and communication links may go down. The system can be modeled as a directed graph in which vertices represent the processes and edges represent unidirectional communication channels. A distributed application runs as a collection of processes on a distributed system. This chapter presents a model of a distributed computation and introduces several terms, concepts, and notations that will be used in the subsequent chapters.

2.1 A Distributed Program A distributed program is composed of a set of n asynchronous processes p1 , p2 , ..., pi , ..., pn that communicate by message passing over the communication network. Without loss of generality, we assume that each process is running on a different processor. The processes do not share a global memory and communicate solely by passing messages. Let Cij denote the channel from process pi to process pj and let mij denote a message sent by pi to pj . The communication delay is finite and unpredictable. Also, these processes do not share a global clock that is instantaneously accessible to these processes. Process execution and message transfer are asynchronous – a process may execute an action spontaneously and a process sending a message does not wait for the delivery of the message to be complete. The global state of a distributed computation is composed of the states of the processes and the communication channels [2]. The state of a process is characterized by the state of its local memory and depends upon the context. The state of a channel is characterized by the set of messages in transit in the channel.

37

2.2 A Model of Distributed Executions The execution of a process consists of a sequential execution of its actions. The actions are atomic and the actions of a process are modeled as three types of events, namely, internal events, message send events, and message receive events. Let exi denote the xth event at process pi . Subscripts and/or superscripts will be dropped when they are irrelevant or are clear from the context. For a message m, let send(m) and rec(m) denote its send and receive events, respectively. The occurrence of events changes the states of respective processes and channels, thus causing transitions in the global system state. An internal event changes the state of the process at which it occurs. A send event (or a receive event) changes the state of the process that sends (or receives) the message and the state of the channel on which the message is sent (or received). An internal event only affects the process at which it occurs. The events at a process are linearly ordered by their order of occurrence. The execution of process pi produces a sequence of events e1i , e2i , ..., exi , ex+1 , ... and is denoted by Hi where i Hi = (hi , →i ) hi is the set of events produced by pi and binary relation →i defines a linear order on these events. Relation →i expresses causal dependencies among the events of pi . The send and the receive events signify the flow of information between processes and establish causal dependency from the sender process to the receiver process. A relation →msg that captures the causal dependency due to message exchange, is defined as follows. For every message m that is exchanged between two processes, we have send(m) →msg rec(m). Relation →msg defines causal dependencies between the pairs of corresponding send and receive events.

e1 1

p

e14

e13

e12

e15

1

e22

e21

p

e23

e62

e42

2

p

e31 3

e52

e33 e32

e34

time

Figure 2.1: The space-time diagram of a distributed execution. The evolution of a distributed execution is depicted by a space-time diagram. Figure 2.1 shows the time-space diagram of a distributed execution involving three processes. A horizontal line represents the progress of the process; a dot indicates an event; a slant arrow indicates a message transfer. Generally, the execution of an event takes a finite amount of time; however, since we 38

assume that an event execution is atomic (hence, indivisible and instantaneous), it is justified to denote it as a dot on a process line. In this figure, for process p1 , the second event is a message send event, the third event is an internal event, and the fourth event is a message receive event.

Causal Precedence Relation The execution of a distributed application results in a set of distributed events produced by the processes. Let H=∪i hi denote the set of events executed in a distributed computation. Next, we define a binary relation on the set H, denoted as →, that expresses causal dependencies between events in the distributed execution.  exi →i eyj i.e., (i = j) ∧ (x ≤ y)      or y y x x exi →msg eyj ∀ei , ∀ej ∈ H, ei → ej ⇔   or    ∃ezk ∈ H : exi → ezk ∧ ezk → eyj

The causal precedence relation induces an irreflexive partial order on the events of a distributed computation [6] that is denoted as H=(H, →). Note that the relation → is Lamport’s “happens before" relation1 [4]. For any two events ei and ej , if ei → ej , then event ej is directly or transitively dependent on event ei ; graphically, it means that there exists a path consisting of message arrows and process-line segments (along increasing time) in the space-time diagram that starts at ei and ends at ej . For example, in Figure 2.1, e11 → e33 and e33 → e62 . Note that relation → denotes flow of information in a distributed computation and ei → ej dictates that all the information available at ei is potentially accessible at ej . For example, in Figure 2.1, event e62 has the knowledge of all other events shown in the figure. For any two events ei and ej , ei 6→ ej denotes the fact that event ej does not directly or transitively dependent on event ei . That is, event ei does not causally affect event ej . Event ej is not aware of the execution of ei or any event executed after ei on the same process. For example, in Figure 2.1, e31 6→ e33 and e42 6→ e13 . Note the following two rules: • For any two events ei and ej , ei 6→ ej 6 : ej 6→ ei . • For any two events ei and ej , ei → ej : ej 6→ ei . For any two events ei and ej , if ei 6→ ej and ej 6→ ei , then events ei and ej are said to be concurrent and the relation is denoted as ei k ej . In the execution of Figure 2.1, e31 k e33 and e42 k e13 . Note that relation k is not transitive; that is, (ei k ej ) ∧ (ej k ek ) 6 : ei k ek . For example, in Figure 2.1, e33 k e42 and e42 k e51 , however, e33 6k e51 . Note that for any two events ei and ej in a distributed execution, ei → ej or ej → ei , or ei k ej . 1

In Lamport’s “happens before” relation, an event e1 happens before an event e2 , denoted by ei → ej , if (a) e1 occurs before e2 on the same process, or (b) e1 is the send event of a message and e2 is the receive event of that message, or (c) ∃e′ | e1 happens before e′ and e′ happens before e2 .

39

Logical vs. Physical Concurrency In a distributed computation, two events are logically concurrent if and only if they do not causally affect each other. Physical concurrency, on the other hand, has a connotation that the events occur at the same instant in physical time. Note that two or more events may be logically concurrent even though they do not occur at the same instant in physical time. For example, in Figure 2.1, events in the set {e31 , e42 , e33 } are logically concurrent, but they occurred at different instants in physical time. However, note that if processor speed and message delays would have been different, the execution of these events could have very well coincided in physical time. Whether a set of logically concurrent events coincide in the physical time or in what order in the physical time they occur does not change the outcome of the computation. Therefore, even though a set of logically concurrent events may not have occurred at the same instant in physical time, for all practical and theoretical purposes, we can assume that these events occured at the same instant in physical time.

2.3 Models of Communication Network There are several models of the service provided by communication networks, namely, FIFO, Non-FIFO, and causal ordering. In the FIFO model, each channel acts as a first-in first-out message queue and thus, message ordering is preserved by a channel. In the non-FIFO model, a channel acts like a set in which the sender process adds messages and the receiver process removes messages from it in a random order. The “causal ordering” model [1] is based on Lamport’s “happens before” relation. A system that supports the causal ordering model satisfies the following property: CO: For any two messages mij and mkj , if send(mij ) −→ send(mkj ), then rec(mij ) −→ rec(mkj ). That is, this property ensures that causally related messages destined to the same destination are delivered in an order that is consistent with their causality relation. Causally ordered delivery of messages implies FIFO message delivery. Furthermore, note that CO ⊂ FIFO ⊂ Non-FIFO. Causal ordering model is useful in developing distributed algorithms. Generally, it considerably simplifies the design of distributed algorithms because it provides a built-in synchronization. For example, in replicated database systems, it is important that every process responsible for updating a replica receives the updates in the same order to maintain database consistency. Without causal ordering, each update must be checked to ensure that database consistency is not being violated. Causal ordering eliminates the need for such checks.

2.4 Global State of a Distributed System The global state of a distributed system is a collection of the local states of its components, namely, the processes and the communication channels [2], [3]. The state of a process at any time is defined by the contents of processor registers, stacks, local memory, etc. and depends on the local context of the distributed application. The state of channel is given by the set of messages in transit in the channel. The occurrence of events changes the states of respective processes and channels, thus causing transitions in global system state. For example, an internal event changes the state of the process 40

at which it occurs. A send event (or a receive event) changes the state of the process that sends (or receives) the message and the state of the channel on which the message is sent (or received). Let LSix denote the state of process pi after the occurrence of event exi and before the event x+1 ei . LSi0 denotes the initial state of process pi . LSix is a result of the execution of all the events executed by process pi till exi . Let send(m)≤LSix denote the fact that ∃y:1≤y≤x :: eyi =send(m). Likewise, let rec(m)6≤LSix denote the fact that ∀y:1≤y≤x :: eyi 6=rec(m). The state of a channel is difficult to state formally because a channel is a distributed entity and its state depends upon the states of the processes it connects. Let SCijx,y denote the state of a channel Cij defined as follows: V SCijx,y ={mij | send(mij ) ≤ exi rec(mij ) 6≤ eyj } Thus, channel state SCijx,y denotes all messages that pi sent upto event exi and which process pj had not received until event eyj .

2.4.1 Global State The global state of a distributed system is a collection of the local states of the processes and the channels. Notationally, global state GS is defined as, S S y ,z GS = { i LSixi , j,k SCjkj k }

For a global snapshot to be meaningful, the states of all the components of the distributed system must be recorded at the same instant. This will be possible if the local clocks at processes were perfectly synchronized or if there were a global system clock that can be instantaneously read by the processes. However, both are impossible. However, it turns out that even if the state of all the components in a distributed system has not been recorded at the same instant, such a state will be meaningful provided every message that is recorded as received is also recorded as sent. Basic idea is that an effect should not be present without its cause. A message cannot be received if it was not sent; that is, the state should not violate causality. Such states are called consistent global states and are meaningful global states. Inconsistent global states are not meaningful in the sense that a distributed system can never be in an inconsistent state. S S y ,z A global state GS = { i LSixi , j,k SCjkj k } is a consistent global state iff it satisfies the following condition: yi ,zk ∀LSixi ∀SCik :: yi = xi yi ,zk That is, channel state SCik and process state LSkzk must not include any message that process pi sent after executing event exi i and must include all messages that process pi sent upto the execution of event exi i . In the distributed execution of Figure 2.2, a global state GS1 consisting of local states {LS11 , 3 LS2 , LS33 , LS42 } is inconsistent because the state of p2 has recorded the receipt of message m12 , however, the state of p1 has not recorded its send. On the contrary, a global state GS2 consisting of local states {LS12 , LS24 , LS34 , LS42 } is consistent; all the channels are empty except C21 that contains message m21 .

41

p

e11

e21

e13

e 14

1

p2 1

p3 p4

m

e12

12

e 23

e3

e22

4

e2

e23

m 21

e35

e34

e 33 e42

e41

time Figure 2.2: The space-time diagram of a distributed execution. A global state GS = is transitless iff y ,zj

∀i, ∀j : 1 ≤ i, j ≤ n :: SCiji

= φ.

Thus, all channels are recorded as empty in a transitless global state. Note that a transitless global state is always a consistent global state. A global state is strongly consistent iff it is transitless as well as consistent. Note that in Figure 2.2, the global state consisting of local states {LS12 , LS23 , LS34 , LS42 } is strongly consistent. Recording the global state of a distributed system is an important paradigm when one is interested in analyzing, monitoring, testing, or verifying properties of distributed applications, systems, and algorithms. Design of efficient methods for recording the global state of a distributed system is an important problem.

2.5 Cuts of a Distributed Computation In the space-time diagram of a distributed computation, a zigzag line joining one arbitrary point on each process line is termed a cut in the computation. Such a line slices the space-time diagram, and thus the set of events in the distributed computation, into a PAST and a FUTURE. The PAST contains all the events to the left of the cut and the FUTURE contains all the events to the right of the cut. For a cut C, let PAST(C) and FUTURE(C) denote the set of events in the PAST and FUTURE of C, respectively. Every cut corresponds to a global state and every global state can be graphically represented as a cut in the computation’s space-time diagram [6]. M ax_P AST (C)

i Definition: If ei denotes the latest event at process pi that is in the PAST of a cut C, S y ,z y ,z M ax_P ASTi (C) S then the global state represented by the cut is { i LSi , j,k SCjkj k } where SCjkj k ={m | send(m)∈PAST(C) ∧ rec(m)∈FUTURE(C)}.

A consistent global state corresponds to a cut in which every message received in the PAST of the cut was sent in the PAST of that cut. Such a cut is known as a consistent cut. All messages that cross the cut from the PAST to the FUTURE are in transit in the corresponding consistent 42

p

C1

e11

C2

e21

e3 1

e 14

1

p2 p3 p4

e22

e12 e

1 3

e 23 e41

4

e2

e23

e34

e 33

e35

e42 time Figure 2.3: Illustration of cuts in a distributed execution.

global state. A cut is inconsistent if a message crosses the cut from the FUTURE to the PAST. For example, the space-time diagram of Figure 2.3 shows two cuts, C1 and C2 . C1 is an inconsistent cut, whereas C2 is a consistent cut. Note that these two cuts respectively correspond to the two global states GS1 and GS2 , identified in the previous subsection. Cuts in a space-time diagram provide a powerful graphical aid in representing and reasoning about global states of a computation.

2.6 Past and Future Cones of an Event In a distributed computation, an event ej could have been affected only by all events ei such that ei → ej and all the information available at ei could be made accessible at ej . All such events ei belong to the past of ej [6]. Let P ast(ej ) denote all events in the past of ej in a computation (H, →). Then, P ast(ej ) = {ei |∀ei ∈ H, ei → ej }. Figure 2.4 shows the past of an event ej . Let P asti (ej ) be the set of all those events of P ast(ej ) that are on process pi . Clearly, P asti (ej ) is a totally ordered set, ordered by the relation →i , whose maximal element is denoted by max(P asti (ej )). Obviously, max(P asti (ej )) is the latest event at process pi that affected event ej (see Figure ??). Note that max(P asti (ej )) is always a message send event. S Let Max_P ast(ej ) = (∀i) {max(P asti (ej ))}. Max_P ast(ej ) consists of the latest event at every process that affected event ej and is referred to as the surface of the past cone of ej [6]. Note that Max_P ast(ej ) is a consistent cut [7]. P ast(ej ) represents all events on the past light cone that affect ej . Similar to the past is defined the future of an event. The future of an event ej , denoted by F uture(ej ), contains all events ei that are causally affected by ej (see Figure 2.4). In a computation (H, →), F uture(ej ) is defined as: F uture(ej ) = {ei |∀ei ∈ H, ej → ei }. 43

max(Past (e )) i j

min(Future (ej )) i

FUTURE( e j )

PAST( ej )

e j

Figure 2.4: Illustration of past and future cones in a distributed computation. Likewise, we can define F uturei(ej ) as the set of those events of F uture(ej ) that are on process pi and min(F uturei (ej )) as the first event on process pi that is affected by ej . Note that S min(F uturei (ej )) is always a message receive event. Likewise, Min_P ast(ej ), defined as (∀i) {min(F uturei (ej ))}, consists of the first event at every process that is causally affected by event ej and is referred to as the surface of the future cone of ej [6]. It denotes a consistent cut in the computation [7]. F uture(ej ) represents all events on the future light cone that are affected by ej . It is obvious that all events at a process pi that occurred after max(P asti (ej )) but before min(F uturei (ej )) are concurrent with ej . Therefore, all and only those events of computation H that belong to the set “H − P ast(ej ) − F uture(ej )” are concurrent with event ej .

2.7 Models of Process Communications There are two basic models of process communications [8] – synchronous and asynchronous. The synchronous communication model is a blocking type where on a message send, the sender process blocks until the message has been received by the receiver process. The sender process resumes execution only after it learns that the receiver process has accepted the message. Thus, the sender and the receiver processes must synchronize to exchange a message. On the other hand, asynchronous communication model is a non-blocking type where the sender and the receiver do not synchronize to exchange a message. After having sent a message, the sender process does not wait for the message to be delivered to the receiver process. The message is bufferred by the system and is delivered to the receiver process when it is ready to accept the message. A buffer overflow may occur if a process sends a large number of messages in a burst to another process. Neither of the communication models is superior to the other. Asynchronous communication provides higher parallelism because the sender process can execute while the message is in transit 44

to the receiver. However, an implementation of asynchronous communication requires more complex buffer management. In addition, due to higher degree of parallelism and non-determinism, it is much more difficult to design, verify, and implement distributed algorithms for asynchronous communications. The state space of such algorithms are likely to be much larger. Synchronous communication is simpler to handle and implement. However, due to frequent blocking, it is likely to have poor performance and is likely to be more prone to deadlocks.

45

Bibliography [1] K. Birman, T. Joseph, Reliable Communication in Presence of Failures, ACM Transactions on Computer Systems, 47-76, 3, 1987. [2] K.M. Chandy, L. Lamport, Distributed Snapshots: Determining Global States of Distributed Systems, ACM Transactions on Computer Systems, 63-75, 3(1), 1985. [3] A. Kshemkalyani, M. Raynal, and M. Singhal, “Global Snapshots of a Distributed System”, Distributed Systems Engineering Journal, Vol 2, No 4, December 1995, pp. 224-233. [4] L. Lamport, Time, clocks and the ordering of events in a distributed system. Comm. ACM, vol.21, (July 1978), pp. 558-564. [5] A. Lynch, Distributed Processing Solves Main-frame Problems. Data Communications, pp. 17-22, December 1976. [6] F. Mattern, Virtual time and global states of distributed systems. Proc. "Parallel and distributed algorithms" Conf., (Cosnard, Quinton, Raynal, Robert Eds), North-Holland, (1988), pp. 215-226. [7] P. Panengaden and K. Taylor, Concurrent Common Knowledge: A New Definition of Agreement for Asynchronous Events. Proc. of the 5th Symp. on Principles of Distributed Computing, pp. 197-209, 1988. [8] Sol M. Shatz, Communication Mechanisms for Programming Distributed Systems, IEEE Computer, June 1984, pp. 21-28.

46

Chapter 3 Logical Time 3.1 Introduction The concept of causality between events is fundamental to the design and analysis of parallel and distributed computing and operating systems. Usually causality is tracked using physical time. However, in distributed systems, it is not possible to have global physical time; it is possible to realize only an approximation of it. As asynchronous distributed computations make progress in spurts, it turns out that the logical time, which advances in jumps, is sufficient to capture the fundamental monotonicity property associated with causality in distributed systems. This chapter discusses three ways to implement logical time (e.g., scalar time, vector time, and matrix time) that have been proposed to capture causality between events of a distributed computation. Causality (or the causal precedence relation) among events in a distributed system is a powerful concept in reasoning, analyzing, and drawing inferences about a computation. The knowledge of the causal precedence relation among the events of processes helps solve a variety of problems in distributed systems. Examples of some of these problems is as follows: • Distributed algorithms design: The knowledge of the causal precedence relation among events helps ensure liveness and fairness in mutual exclusion algorithms, helps maintain consistency in replicated databases, and helps design correct deadlock detection algorithms to avoid phantom and undetected deadlocks. • Tracking of dependent events: In distributed debugging, the knowledge of the causal dependency among events helps construct a consistent state for resuming reexecution; in failure recovery, it helps build a checkpoint; in replicated databases, it aids in the detection of file inconsistencies in case of a network partitioning. • Knowledge about the progress: The knowledge of the causal dependency among events helps measure the progress of processes in the distributed computation. This is useful in discarding obsolete information, garbage collection, and termination detection. • Concurrency measure: The knowledge of how many events are causally dependent is useful in measuring the amount of concurrency in a computation. All events that are not causally related can be executed concurrently. Thus, an analysis of the causality in a computation gives an idea of the concurrency in the program. 47

The concept of causality is widely used by human beings, often unconsciously, in planning, scheduling, and execution of a chore or an enterprise, in determining infeasibility of a plan or the innocence of an accused. In day-to-day life, the global time to deduce causality relation is obtained from loosely synchronized clocks (i.e., wrist watches, wall clocks). However, in distributed computing systems, the rate of occurrence of events is several magnitudes higher and the event execution time is several magnitudes smaller. Consequently, if the physical clocks are not precisely synchronized, the causality relation between events may not be accurately captured. Network Time Protocols [18], which can maintain time accurate to a few tens of milliseconds on the Internet, are not adequate to capture the causality relation in distributed systems. However, in a distributed computation, generally the progress is made in spurts and the interaction between processes occurs in spurts. Consequently, it turns out that in a distributed computation, the causality relation between events produced by a program execution and its fundamental monotonicity property can be accurately captured by logical clocks. In a system of logical clocks, every process has a logical clock that is advanced using a set of rules. Every event is assigned a timestamp and the causality relation between events can be generally inferred from their timestamps. The timestamps assigned to events obey the fundamental monotonicity property; that is, if an event a causally affects an event b, then the timestamp of a is smaller than the timestamp of b. This chapter first presents a general framework of a system of logical clocks in distributed systems and then discusses three ways to implement logical time in a distributed system. In the first method, Lamport’s scalar clocks, the time is represented by non-negative integers; in the second method, the time is represented by a vector of non-negative integers; in the third method, the time is represented as a matrix of non-negative integers. We also discuss methods for efficient implementation of the systems of vector clocks. The chapter ends with a discussion of virtual time, its implementation using the time-warp mechanism and a brief discussion of physical clock synchronization and the Network Time Protocol.

3.2 A Framework for a System of Logical Clocks 3.2.1 Definition A system of logical clocks consists of a time domain T and a logical clock C [23]. Elements of T form a partially ordered set over a relation 0)

In general, every time R1 is executed, d can have a different value, and this value may be application-dependent. However, typically d is kept at 1 because this is able to identify the time of each event uniquely at a process, while keeping the rate of increase of d to its lowest level. • R2: Each message piggybacks the clock value of its sender at sending time. When a process pi receives a message with timestamp Cmsg , it executes the following actions: – Ci := max(Ci , Cmsg ) – Execute R1. – Deliver the message. Figure 3.1 shows the evolution of scalar time with d=1. 1

2

3

8

9

p 1 9

2 p 2

1

4

7

5

3

11 10

4 p 3

1

b 5

6

7

Figure 3.1: Evolution of scalar time.

3.3.2 Basic Properties Consistency Property Clearly, scalar clocks satisfy the monotonicity and hence the consistency property: for two events ei and ej , ei → ej = : C(ei ) < C(ej ). Total Ordering Scalar clocks can be used to totally order events in a distributed system [11]. The main problem in totally ordering events is that two or more events at different processes may have identical timestamp. (Note that for two events e1 and e2 , C(e1 ) = C(e2 ) = : e1 k e2 .) For example in Figure 3.1, the third event of process P1 and the second event of process P2 have identical scalar timestamp. Thus, a tie-breaking mechanism is needed to order such events. Typically, a tie is broken as follows: process identifiers are linearly ordered and a tie among events with identical scalar timestamp is broken on the basis of their process identifiers. The lower the process identifier in the ranking, the higher the priority. The timestamp of an event is denoted by a tuple (t, i) where 50

t is its time of occurrence and i is the identity of the process where it occurred. The total order relation ≺ on two events x and y with timestamps (h,i) and (k,j), respectively, is defined as follows: x ≺ y ⇔ (h < k or (h = k and i < j)) Since events that occur at the same logical scalar time are independent (i.e., they are not causally related), they can be ordered using any arbitrary criterion without violating the causality relation →. Therefore, a total order is consistent with the causality relation “→". Note that x ≺ y = :x → y ∨ x k y. A total order is generally used to ensure liveness properties in distributed algorithms. Requests are timestamped and served according to the total order based on these timestamps [11]. Event Counting If the increment value d is always 1, the scalar time has the following interesting property: if event e has a timestamp h, then h-1 represents the minimum logical duration, counted in units of events, required before producing the event e [6]; we call it the height of the event e. In other words, h-1 events have been produced sequentially before the event e regardless of the processes that produced these events. For example, in Figure 3.1, five events precede event b on the longest causal path ending at b. No Strong Consistency The system of scalar clocks is not strongly consistent; that is, for two events ei and ej , C(ei ) < C(ej ) 6= : ei → ej . For example, in Figure 3.1, the third event of process P1 has smaller scalar timestamp than the third event of process P2 . However, the former did not happen before the latter. The reason that scalar clocks are not strongly consistent is that the logical local clock and logical global clock of a process are squashed into one, resulting in the loss causal dependency information among events at different processes. For example, in Figure 3.1, when process P2 receives the first message from process P1 , it updates its clock to 3, forgetting that the timestamp of the latest event at P1 on which it depends is 2.

3.4 Vector Time 3.4.1 Definition The system of vector clocks was developed independently by Fidge [6], Mattern [14] and Schmuck [27]. In the system of vector clocks, the time domain is represented by a set of n-dimensional nonnegative integer vectors. Each process pi maintains a vector vti [1..n], where vti [i] is the local logical clock of pi and describes the logical time progress at process pi . vti [j] represents process pi ’s latest knowledge of process pj local time. If vti [j]=x, then process pi knows that local time at process pj has progressed till x. The entire vector vti constitutes pi ’s view of the global logical time and is used to timestamp events. Process pi uses the following two rules R1 and R2 to update its clock:

51

• R1: Before executing an event, process pi updates its local logical time as follows: vti [i] := vti [i] + d

(d > 0)

• R2: Each message m is piggybacked with the vector clock vt of the sender process at sending time. On the receipt of such a message (m,vt), process pi executes the following sequence of actions: – Update its global logical time as follows: 1 ≤ k ≤ n : vti [k] := max(vti [k], vt[k]) – Execute R1. – Deliver the message m. The timestamp associated with an event is the value of the vector clock of its process when the event is executed. Figure 3.2 shows an example of vector clocks progress with the increment value d=1. Initially, a vector clock is [0, 0, 0, ...., 0]. The following relations are defined to compare two vector timestamps, vh and vk: ⇔ ⇔ ⇔ ⇔

vh = vk vh ≤ vk vh < vk vh k vk 1 0 0

∀x : vh[x] = vk[x] ∀x : vh[x] ≤ vk[x] vh ≤ vk and ∃x : vh[x] < vk[x] ¬(vh < vk) ∧ ¬(vk < vh)

2 0 0

3 0 0

4 3 4

p 1

p 2

p 3

2 0 0

0 1 0

2 2 0

2 3 0

2 4 0

2 3 4

2 3 2

5 3 4 5 6 4 5 5 4

2 3 0 0 0 1

5 3 4

2 3 3

2 3 4

Figure 3.2: Evolution of vector time.

52

3.4.2 Basic Properties Isomorphism Recall that relation “→" induces a partial order on the set of events that are produced by a distributed execution. If events in a distributed system are timestamped using a system of vector clocks, we have the following property. If two events x and y have timestamps vh and vk, respectively, then x → y ⇔ vh < vk x k y ⇔ vh k vk. Thus, there is an isomorphism between the set of partially ordered events produced by a distributed computation and their vector timestamps. This is a very powerful, useful, and interesting property of vector clocks. If the process at which an event occurred is known, the test to compare two timestamps can be simplified as follows: If events x and y respectively occurred at processes pi and pj and are assigned timestamps vh and vk, respectively, then x → y ⇔ vh[i] ≤ vk[i] x k y ⇔ vh[i] > vk[i] ∧ vh[j] < vk[j] Strong Consistency The system of vector clocks is strongly consistent; thus, by examining the vector timestamp of two events, we can determine if the events are causally related. However, Charron-Bost showed that the dimension of vector clocks cannot be less than n, the total number of processes in the distributed computation, for this property to hold [4]. Event Counting If d is always 1 in the rule R1, then the ith component of vector clock at process pi , vti [i], denotes the number of events that have occurred at pi until that instant. So, if an event e has timestamp vh, P vh[j] denotes the number of events executed by process pj that causally precede e. Clearly, vh[j] − 1 represents the total number of events that causally precede e in the distributed computation. Applications Since vector time tracks causal dependencies exactly, it finds a wide variety of applications. For example, they are used in distributed debugging, implementations of causal ordering communication and causal distributed shared memory, establishment of global breakpoints, and in determining the consistency of checkpoints in optimistic recovery.

53

A Brief Historical Perspective of Vector Clocks Although the theory associated with vector clocks was first developed in 1988 independently by Fidge and Mattern, vector clocks were informally introduced and used by several researchers earlier. Parker et al. [20] used a rudimentary vector clocks system to detect inconsistencies of replicated files due to network partitioning. Liskov and Ladin [13] proposed a vector clock system to define highly available distributed services. Similar system of clocks was used by Strom and Yemini [30] to keep track of the causal dependencies between events in their optimistic recovery algorithm and by Raynal to prevent drift between logical clocks [21]. Singhal [28] used vector clocks coupled with a boolean vector to determine the currency of a critical section execution request by detecting the cusality relation between a critical section request and its execution.

3.4.3 On the Size of Vector Clocks An important question to ask is whether vector clocks of size n are necessary in a computation consisting of n processes. To answer this, we examine the usage of vector clocks. • A vector clock provides the latest known local time at each other process. If this information in the clock is to be used to explicitly track the progress at every other process, then a vector clock of size n is necessary. • A popular use of vector clocks is to determine the causality between a pair of events. Given any events e and f , the test for e ≺ f if and only if T (e) < T (f ), which requires a comparison of the vector clocks of e and f . Although it appears that the clock of size n is necessary, that is not quite accurate. It can be shown that a size equal to the dimension of the partial order (E, ≺) is necessary, where the upper bound on this dimension is n. This is explained below. To understand this result on the size of clocks for determining causality between a pair of events, we first introduce some definitions. A linear extension of a partial order (E, ≺) is a linear ordering of E that is consistent with the partial order, i.e., if two events are ordered in the partial order, they are also ordered in the linear order. A linear extension can be viewed as projecting all the events from the different processes on a single time axis. However, the linear order will necessarily introduce ordering between each pair of events, and some of these orderings are not in the partial order. Also observe that different linear extensions are possible in general. Let P denote the set of tuples in the partial order defined by the causality relation; so there is a tuple (e, f ) in P for each pair of events e and f such that e ≺ f . Let L1 , L2 . . . denote the sets of tuples in different linear extensions of this partial order. The set P is contained in the set obtained by taking the intersection of any such collection of linear extensions L1 , L2 . . .. This is because each Li must contain all the tuples, i.e., causality dependencies, that are in P. The dimension of a partial order is the minimum number of linear extensions whose intersection gives exactly the partial order. Consider a client-server interaction between a pair of processes. Queries to the server and responses to the client occur in strict alternating sequences. Although n = 2, all the events are strictly ordered, and there is only one linear order of all the events that is consistent with the “partial” order. Hence the dimension of this "partial order" is 1. A scalar clock such as one implemented by Lamport’s scalar clock rules is adequate to determine e ≺ f for any events e and f in this execution. 54

Now consider an execution on processes P1 and P2 such that each sends a message to the other before receiving the other’s message. The two send events are concurrent, as are the two receive events. To determine the causality between the send events or between the receive events, it is not sufficient to use a single integer; a vector clock of size n = 2 is necessary. This execution exhibits the graphical property called a crown, wherein there are some messages m0 , . . . mn−1 such that Send(mi ) ≺ Receive(mi+1 mod (n−1) ) for all i from 0 to n − 1. A crown of n messages has dimension n. We introduced the notion of crown and studied its properties in Chapter 3. a

h

i

a

b

g

d

h

i

j

(i) c d

c

e

g

b

f

e j

range of events "c", "e", "f" (ii) two linear extensions < c, e, f, a, b, d, g, h, i , j >

f




Figure 3.3: Example illustrating dimension of a execution (E, ≺). For n = 4 processes, the dimension is 2. For a complex execution, it is not straightforward to determine the dimension of the partial order. Figure 3.3 shows an execution involving four processes. However, the dimension of this partial order is two. To see this informally, consider the longest chain ha, b, d, g, h, i, ji. There are events outside this chain that can yield multiple linear extensions. Hence, the dimension is more than 1. The right side of Figure 3.3 shows the earliest possible and the latest possible occurrences of the events not in this chain, with respect to the events in this chain. Let L1 be hc, e, f, a, b, d, g, h, i, ji, which contains the following tuples that are not in P: (c, a), (c, b), (c, d), (c, g), (c, h), (c, i), (c, j), (e, a), (e, b), (e, d), (e, g), (e, h), (e, i), (e, j), (f, a), (f, b), (f, d), (f, g), (f, h), (f, i), (f, j). Let L2 be ha, b, c, d, g, h, i, e, j, f i, which contains the following tuples not in P: (a, c), (b, c), (c, d), (c, g), (c, h), (c, i), (c, j), (a, e), (b, e), (d, e), (g, e), (h, e), (i, e), (e, j), (a, f ), (b, f ), (d, f ), (g, f ), (h, f ), (i, f ), (j, f ). T T T Further, observe that (L1 \ P ) L2 = ∅ and (L2 \ P ) L1 = ∅. Hence, L1 L2 = P and the dimension of the execution is 2 as these two linear extensions are enough to generate P. Unfortunately, it is not computationally easy to determine the dimension of a partial order. To exacerbate the problem, the above form of analysis has to be completed a posteriori (i.e., off-line), once the entire partial order has been determined after the completion of the execution. 55

3.5 Efficient Implementations of Vector Clocks If the number of processes in a distributed computation is large, then vector clocks will require piggybacking of huge amount of information in messages for the purpose of disseminating time progress and updating clocks. The message overhead grows linearly with the number of processors in the system and when there are thousands of processors in the system, the message size becomes huge even if there are only a few events occurring in few processors. In this section, we discuss efficient ways to maintain vector clocks; similar techniques can be used to efficiently implement matrix clocks. Charron-Bost showed [4] that if vector clocks have to satisfy the strong consistency property, then in general vector timestamps must be at least of size n, the total number of processes. Therefore, in general the size of a vector timestamp is the number of processes involved in a distributed computation; however, several optimizations are possible and next, we discuss techniques to implement vector clocks efficiently [23].

3.5.1 Singhal-Kshemkalyani’s Differential Technique Singhal-Kshemkalyani’s differential technique [29] is based on the observation that between successive message sends to the same process, only a few entries of the vector clock at the sender process are likely to change. This is more likely when the number of processes is large because only a few of them will interact frequently by passing messages. In this technique, when a process pi sends a message to a process pj , it piggybacks only those entries of its vector clock that differ since the last message sent to pj . The technique works as follows: If entries i1 , i2 , . . . , in1 of the vector clock at pi have changed to v1 , v2 , . . . , vn1 , respectively, since the last message sent to pj , then process pi piggybacks a compressed timestamp of the form {(i1 , v1 ), (i2 , v2 ), . . . , (in1 , vn1 )} to the next message to pj . When pj receives this message, it updates its vector clock as follows: vti [k] = max(vti [k], vk ) for k = 1, 2, . . . , n1 . Thus this technique cuts down the message size, communication bandwidth and buffer (to store messages) requirements. In the worst of case, every element of the vector clock has been updated at pi since the last message to process pj , and the next message from pi to pj will need to carry the entire vector timestamp of size n. However, on the average the size of the timestamp on a message will be less than n. Note that implementation of this technique requires each process to remember the vector timestamp in the message last sent to every other process. Direct implementation of this will result in O(n2 ) storage overhead at each process. This technique also requires that the communication channels follow FIFO discipline for message delivery. Singhal and Kshemkalyani developed a clever technique that cuts down this storage overhead at each process to O(n). The technique works in the following manner: Process pi maintains the following two additional vectors:

56

• LSi [1..n] (‘Last Sent’): LSi [j] indicates the value of vti [i] when process pi last sent a message to process pj . • LUi [1..n] (‘Last Update’): LUi [j] indicates the value of vti [i] when process pi last updated the entry vti [j]. Clearly, LUi [i] = vti [j] at all times and LUi [j] needs to be updated only when the receipt of a message causes pi to update entry vti [j]. Also, LSi [j] needs to be updated only when pi sends a message to pj . Since the last communication from pi to pj , only those elements of vector clock vti [k] have changed for which LSi [j] < LUi [k] holds. Hence, only these elements need to be sent in a message from pi to pj . When pi sends a message to pj , it sends only a set of tuples {(x, vti [x])|LSi [j] < LUi [x]} as the vector timestamp to pj , instead of sending a vector of n entries in a message. Thus the entire vector of size n is not sent along with a message. Instead, only the elements in the vector clock that have changed since the last message send to that process are sent in the format {(p1 , latest_value), (p2 , latest_value), . . .}, where pi indicates that the pi th component of the vector clock has changed. p 1 1 0 0 0

{(1,1)}

p 2

p 3

p 4

1 2 1 0

1 1 0 0

0 0 1 0

{(3,1)}

0 0 0 1

1 3 2 0

0 0 2 0

0 {(3,2)} 0 3 1

1 4 4 1

0 0 4 1

{(3,4),(4,1)}

{(4,1)}

Figure 3.4: Vector clocks progress in Singhal-Kshemkalyani technique This method is illustrated in Figure 3.4. For instance, the second message from p3 to p2 (which contains a timestamp {(3, 2)}) informs p2 that the third component of the vector clock has been modified and the new value is 2. This is because the process p3 (indicated by the third component of the vector) has advanced its clock value from 1 to 2 since the last message sent to p2 . The cost of maintaining vector clocks in large systems can be substantially reduced by this technique, especially if the process interactions exhibit temporal or spatial localities. This technique would turn advantageous in a variety of applications including causal distributed shared memories, distributed deadlock detection, enforcement of mutual exclusion and localized communications typically observed in distributed systems. 57

3.5.2 Fowler-Zwaenepoel’s Direct-Dependency Technique Fowler-Zwaenepoel direct dependency technique [8] reduces the size of messages by transmitting only a scalar value in the messages. No vector clocks are maintained on-the-fly. Instead, a process only maintains information regarding direct dependencies on other processes. A vector time for an event, that represents transitive dependencies on other processes, is constructed off-line from a recursive search of the direct dependency information at processes. Each process pi maintains a dependency-vector Di . Initially, Di [j] = 0 for j = 1, . . . , n. Di is updated as follows: Step 1: Whenever an event occurs at pi , Di [i] := Di [i] + 1. That is, the vector component corresponding to its own local time is incremented by one. Step 2: When a process pi sends a message to process pj , it piggybacks the updated value of Di [i] in the message. Step 3: When pi receives a message from pj with piggybacked value d, pi updates its dependency vector as follows: Di [j]:= max{Di [j], d}. Thus the dependency vector Di reflects only direct dependencies. At any instant, Di [j] denotes the sequence number of the latest event on process pj that directly affects the current state. Note that this event may precede the latest event at pj that causally affects the current state.

p 1 1 0 0 0

{1}

1 1 0 0

p 2

p 3

p 4

0 0 1 0

1 2 1 0

{1}

0 0 0 1

1 4 4 0

1 3 2 0

0 0 2 0

{2}

0 0 3 1

0 0 4 1

{4}

{1}

Figure 3.5: Vector clock progress in Fowler-Zwaenepoel technique Figure 3.5 illustrates the Fowler-Zwaenepoel technique. For instance, when process p4 sends a message to process p3 , it piggybacks a scalar that indicates the direct dependency of p3 on p4 because of this message. Subsequently, process p3 sends a message to process p2 piggybacking a 58

scalar to indicate the direct dependency of p2 on p3 because of this message. Now, process p2 is in fact indirectly dependent on process p4 since process p3 is dependent on process p4 . However, process p2 is never informed about its indirect dependency on p4 . Thus although the direct dependencies are duly informed to the receiving processes, the transitive (indirect) dependencies are not maintained by this method. They can be obtained only by recursively tracing the direct-dependency vectors of the events off-line. This involves computational overhead and latencies. Thus this method is ideal only for those applications that do not require computation of transitive dependencies on the fly. The computational overheads characteristic of this method makes it best suitable for applications like causal-breakpoints and asynchronous checkpoint recovery where computation of causal dependencies is performed offline. This technique results in considerable saving in the cost; only one scalar is piggybacked on every message. However, the dependency vector does not represent transitive dependencies (i.e., a vector timestamps). The transitive dependency (or the vector timestamp) of an event is obtained by recursively tracing the direct-dependency vectors of processes. Clearly, this will have overhead and will involve latencies. Therefore, this technique is not suitable for applications that require on-thefly computation of vector timestamps. Nonetheless, this technique is ideal for applications where computation of causal dependencies is performed off-line (e.g., causal breakpoint, asynchronous checkpointing recovery). The transitive dependencies could be determined by combining an event’s direct dependency with that of its directly dependent event. In Figure 3.5, the fourth event of process p3 is dependent on the first event of process p4 and fourth event of process p2 is dependent on fourth event of process p3 . By combining these two direct dependencies, it is possible to deduce that fourth event of process p2 depends on first event of process p4 . It is important to note that if event ej at process pj occurs before event ei at process pi , then all the events from e0 to ej−1 in process pj also happen before ei . Hence, it is sufficient to record for ei the latest event of process pj that happened before ei . This way, each event would record its dependencies on the latest event on every other process it depends on and those events maintain their own dependencies. Combining all these dependencies, the entire set of events that a particular event depends on could be determined off-line. The off-line computation of transitive dependencies can be performed using a recursive algorithm proposed in [8] and is illustrated in a modified form in Algorithm 1. DTV is the dependencytracking vector of size n (where n is the number of process) which is supposed to track all the causal dependencies of a particular event ei in process pi . The algorithm then needs to be invoked as DependencyTrack(i, Die [i]). The algorithm initializes DTV to the least possible timestamp value which is 0 for all entries except i for which the value is set to Die [i]: For all k = 1, . . . , n and k 6= i, DTV[k]=0 and DTV[i]=Die [i] The algorithm then calls the VisitEvent algorithm on process pi and event ei . VisitEvent checks all the entries (1, . . . , n) of DTV and Die and if the value in Die is greater than the value in DTV for that entry, then DTV assumes the value of Die for that entry. This ensures that the latest event in process j that ei depends on is recorded in DTV. VisitEvent is recursively called on all entries that are newly included in DTV so that the latest dependency information can be accurately tracked. Let us illustrate the Recursive Dependency Trace Algorithm by by tracking the dependencies of fourth event at process p2 . The algorithm is invoked as DependencyTrack (2, 4). DTV is initially set to < 0 4 0 0 > by DependenyTrack. It then calls VisitEvent (2, 4). The values held by D24 are < 1 4 4 0 >. So, DTV is now updated to < 1 4 0 0 > and VisitEvent (1, 1) is called. The values 59

DependencyTrack (i : process, σ:event index) \∗ Casual distributed breakpoint for σi ∗\ \∗ DTV holds the result ∗\ for all k 6= i do DTV[k]=0 end for DTV[i]=σ end DependencyTrack VisitEvent (j : process, e : event index) \∗ Place dependencies of τ into DTV ∗\ for all k 6= j do α = Dje [k] if α >DTV[k] then DTV[k]=α VisitEvent ( k, α) end if end for end VisitEvent Algorithm 1: Recursive Dependency Trace Algorithm held by D11 are < 1 0 0 0 >. Since none of the entries are greater than those in DTV, the algorithm returns. Again the values held by D24 are checked and this time entry 3 is found to be greater in D24 than DTV. So, DTV is updated as < 1 4 4 0 > and VisiEvent (3, 4) is called. The values held by D34 are < 0 0 4 1 >. Since entry 4 of D34 is greater than that of DTV, it is updated as < 1 4 4 1 > and VisitEvent (4, 1) is called. Since none of the entries in D41 : < 1 0 0 0 > are greater than those of DTV, the algorithm returns to VisitEvent (2, 4). Since all the entries have been checked, VisitEvent (2, 4) is exited and so is DependencyTrack. At this point, DTV holds < 1 4 4 1 >, meaning event 4 of process p2 is dependent upon event 1 of process p1 , event 4 of process p3 and event 1 in process p4 . Also, it is dependent on events that precede event 4 of process p3 and these dependencies could be obtained by invoking the DependencyTrack algorithm on fourth event of process p3 . Thus, all the causal dependencies could be tracked off-line. This technique can result in a considerable saving of cost since only one scalar is piggybacked on every message. One of the important requirements is that a process updates and records its dependency vectors after receiving a message and before sending out any message. Also, if events occur frequently, this technique will require recording the history of a large number of events.

3.6 Jard-Jourdan’s Adaptive Technique The Fowler-Zwaenepoel’s direct-dependency technique does not allow the transitive dependencies to be captured in real time during the execution of processes. In addition, a process must observe an event (i.e., update and record its dependency vector) after receiving a message but before sending out any message. Otherwise, during the reconstruction of a vector timestamp from the 60

direct-dependency vectors, all the causal dependencies will not be captured. If events occur very frequently, this technique will require recording the history of a large number of events. In the Jard-Jourdan’s technique [10], events can be adaptively observed while maintaining the capability of retrieving all the causal dependencies of an observed event. (Observing an event means recording of the information about its dependencies.) This method uses the idea that when an observed event e records its dependencies, then events that follow can determine their transitive dependencies, that is, the set of events that they indirectly depend on, by making use of the information recorded about e. The reason is that when an event e is observed, the information about the send and receive of messages maintained by a process is recorded in that event and the information maintained by the process is then reset and updated. So, when the process propagates information after e, it propagates only history of activities that took place after e. The next observed event either in the same process or in a different one, would then have to look at the information recorded for e to know about the activities that happened before e. This method still does not allow determining all the causal dependencies in real time, but avoids the problem of recording a large amount of history which is realized when using the direct dependency technique. To implement the technique of recording the information in an observed event and resetting the information managed by a process, Jard-Jourdan defined a pseudo-direct relation ≪ on the events of a distributed computation as follows: If events ei and ej happen at process pi and pj , respectively, then ej ≪ei iff there exists a path of message transfers that starts after ej on the process pj and ends before ei on the process ei such that there is no observed event on the path. The relation is termed pseudo-direct because event ei may depend upon many unobserved events on the path, say ue1 , ue2 ,. . . , uen , etc., which are in turn dependent on each other. If ei happens after uen , then ei is still considered directly dependent upon ue1 , ue2 ,. . . , uen , since these events are unobserved, which is a falsely assumed direct dependency. If another event ek happens after ei , then the transitive dependencies of ek on ue1 , ue2 ,. . . , uen can be determined by using the information recorded at ei and ei can do the same with ej . The technique is implemented using the following mechanism: The partial vector clock p_vti at process pi is a list of tuples of the form (j, v) indicating that the current state of pi is pseudodependent on the event on process pj whose sequence number is v. Initially, at a process pi : p_vti ={(i, 0)}. Let p_vti = {(i1 , v1 ), . . . , (i, v), . . . (in , vn } denote the current partial vector clock at process pi . Let e_vti be a variable that holds the timestamp of the observed event (i) Whenever an event is observed at process pi , the contents of the partial vector clock p_vti are transferred to e_vti and p_vti is reset and updated as follows: e_vti = {(i1 , v1 ), . . . , (i, v), . . . , (in , vn )} p_vti = {(i, v + 1)} (ii) When process pj sends a message to pi , it piggybacks the current value of p_vtj in the message. (iii) When pi receives a message piggybacked with timestamp p_vt, pi updates p_vti such that it is the union of the following: (Let p_vt={(im1 , vm1 ),...,(imk , vmk )} and p_vti ={(i1 , v1 )„...,(il , vl )}.) – all (imx , vmx ) such that (imx , .) does not appear in v_pti , 61

– all (ix , vx ) such that (ix , .) does not appear in v_pt, and – all (ix , max(vx , vmx )) for all (vx , .) that appear in v_pt and v_pti .

p

v_pt 1 ={(1,1)}

v_pt ={(1,0)} 1 1 {(1,0)}

p 2

v_pt 2 ={(2,0)}

v_pt = 2 {(1,0),(2,0)} {(1,0),(2,0)}

v_pt 3 = {(1,0), (2,0),(3,1)}

v_pt p 3

v_pt 3 ={(3,0)}

= 3 {(3,1)}

e1_pt 3 = {(3,0)}

v_pt 3= {(3,2)}

v−pt = v−pt = 3 3 {(3,2),(4,1)} {(3,3)} e3_pt 3 = {(3,2),(4,1)}

e2_pt 3 = {(1,0) (2,0),(3,1)} {(4,1)}

p 4

v_pt 4 ={(4,0)}

v_pt 4 =

v_pt 4 =

{(4,0),(5,1)}

{(4,1)} e1_pt 4 = {(4,0),(5,1)} {(4,1)}

{(5,1)} v_pt 5 ={(5,2)}

={(5,0)} p v_pt 5 5 v_pt 5 ={(5,1)} e1_pt = {(5,0)} 5

v_pt = 5 {(4,1),(5,1)}

e2_pt 5 = {(4,1),(5,1)}

Figure 3.6: Vector clocks progress in Jard-Jourdan technique In Figure 3.6, eX_ptn denotes the timestamp of the X th observed event at process pn . For instance, the event 1 observed at p4 is timestamped e1_pt4 = {(4, 0), (5, 1)}; this timestamp means that the pseudo-direct predecessors of this event are located at process p4 and p5 , and are respectively the event 0 observed at p4 and event 1 observed at p5 . v_ptn denotes a list of timestamps collected by a process pn for the unobserved events and is reset and updated after an event is observed at pn . For instance, let us consider v_pt3 . Process p3 first collects the timestamp of event zero (3, 0) into v_pt3 and when the observed event 1 occurs, it transfers its content to e1_pt3 , resets its list and updates its value to (3, 1) which is the timestamp of the observed event. When it receives a message from process p2 , it includes those elements that are not already present in its list, namely, (1, 0) and (2, 0) to v_pt3 . Again, when event 2 is observed, it resets its list to {(3, 2)} and transfers its content to e2_pt3 which holds {(1, 0), (2, 0), (3, 1)}. It can be seen that event 2 at process p3 is directly dependent upon event 0 on process p2 and event 1 on process p3 . But, it 62

is pseudo-directly dependent upon event 0 at process p1 . It also depends on event 0 at process p3 but this dependency information is obtained by examining e1_pt3 recorded by the observed event. Thus, transitive dependencies of event 2 at process p3 can be computed by examining the observed events in e2_pt3 . If this is done recursively, then all the causal dependencies of an observed event can be retrieved. It is also pertinent to observe here that these transitive dependencies cannot be determined online but from a log of the events. This method can help ensure that the list piggybacked on a message is of optimal size. It is also possible to limit the size of the list by introducing a dummy observed event. If the size of the list is to be limited to k, then when timestamps of k events have been collected in the list, a dummy observed event can be introduced to receive the contents of the list. This allows a lot of flexibility in managing the size of messages.

3.7 Matrix Time 3.7.1 Definition In a system of matrix clocks, the time is represented by a set of n × n matrices of non-negative integers. A process pi maintains a matrix mti [1..n, 1..n] where, • mti [i, i] denotes the local logical clock of pi and tracks the progress of the computation at process pi . • mti [i, j] denotes the latest knowledge that process pi has about the local logical clock, mtj [j, j], of process pj . Note that row mti [i, .] is nothing but the vector clock vti [.] and exhibits all the properties of vector clocks. • mti [j, k] represents the knowledge that process pi has about the latest knowledge that pj has about the local logical clock, mtk [k, k], of pk . The entire matrix mti denotes pi ’s local view of the global logical time. The matrix timestamp of an event is the value of the matrix clock of the process when the event is executed. Process pi uses the following rules R1 and R2 to update its clock: • R1 : Before executing an event, process pi updates its local logical time as follows: mti [i, i] := mti [i, i] + d

(d > 0)

• R2: Each message m is piggybacked with matrix time mt. When pi receives such a message (m,mt) from a process pj , pi executes the following sequence of actions: – Update its global logical time as follows: (a) 1 ≤ k ≤ n : mti [i, k] := max(mti [i, k], mt[j, k]) (That is, update its row mti [i, ∗] with the pj ’s row in the received timestamp, mt.) (b) 1 ≤ k, l ≤ n : mti [k, l] := max(mti [k, l], mt[k, l]) 63

– Execute R1. – Deliver message m. mt e [i,k ]

mt e [i,k ]

e 1k

e 2k

p k

mt e [k,j ]

e 1j

p j

m1

mt e [j,j ]

m2

e 2j

m4

m3

e

p i

mte

Figure 3.7: Evolution of matrix time. Figure 3.7 gives an example to illustrate how matrix clocks progress in a distributed computation. We assume d=1. Let us consider the following events: e which is the xi -th event at process pi , e1k and e2k which are the x1k -th and x2k -th event at process pk , and e1j and e2j which are the x1j -th and x2j -th events at pj . Let mte denote the matrix timestamp associated with event e. Due to message m4 , e2k is the last event of pk that causally precedes e, therefore, we have mte [i, k]=mte [k, k]=x2k . Likewise, mte [i, j]=mte [j, j]=x2j . The last event of pk known by pj , to the knowledge of pi when it executed event e, is e1k ; therefore, mte [j, k]=x1k . Likewise, we have mte [k, j]=x1j . A system of matrix clocks was first informally proposed by Michael and Fischer [7] and has been used by Wuu and Bernstein [32] and by Lynch and Sarin [26]to discard obsolete information in replicated databases.

3.7.2 Basic Properties Clearly, vector mti [i, .] contains all the properties of vector clocks. In addition, matrix clocks have the following property: min(mti [k, l]) ≥ t: process pi knows that every other process pk knows k

that pl ’s local time has progressed till t If this is true, it is clear that process pi knows that all other processes know that pl will never send information with a local time ≤ t. In many applications, this implies that processes will no longer require from pl certain information and can use this fact to discard obsolete information. If d is always 1 in the rule R1, then mti [k, l] denotes the number of events occurred at pl and known by pk as far as pi ’s knowledge is concerned.

3.8 Virtual Time Virtual time system is a paradigm for organizing and synchronizing distributed systems using virtual time [9]. This section a provides description of virtual time and its implementation using 64

the Time Warp mechanism (a lookahead-rollback synchronization mechanism using rollback via antimessages). The implementation of virtual time using Time Warp mechanism works on the basis of an optimistic assumption. Time Warp relies on the general lookahead-rollback mechanism where each process executes without regard to other processes having synchronization conflicts. If a conflict is discovered, the offending processes are rolled back to the time just before the conflict and executed forward along the revised path. Detection of conflicts and rollbacks are transparent to users. The implementation of Virtual Time using Time Warp mechanism makes the following optimistic assumption: synchronization conflicts and thus rollbacks generally occurs rarely. In the following sections, we discuss in detail Virtual Time and how Time Warp mechanism is used to implement it.

3.8.1 Virtual Time Definition Virtual time is a global, one dimensional, temporal coordinate system on a distributed computation to measure the computational progress and to define synchronization. A virtual time system is a distributed system executing in coordination with an imaginary virtual clock that uses virtual time [9]. Virtual times are real values that are totally ordered by the less than relation, “ ‘t’ will start only after events at time ‘t’ are complete.

Characteristics of Virtual Time 1. Virtual time systems are not all isomorphic; it may be either discrete or continuous. 2. Virtual time may be only partially ordered (In this implementation, total order is assumed.) 3. Virtual time may be related to real time or may be independent of it. 4. Virtual time systems may be visible to programmers and manipulated explicitly as values, or hidden and manipulated implicitly according to some system-defined discipline 5. Virtual times associated with events may be explicitly calculated by user programs or they may be assigned by fixed rules.

3.8.2 Comparison with Lamport’s Logical Clocks Lamport showed that real-time temporal relationships “happens before” and “happens after”, operationally definable within a distributed system, form only a partial order, not a total order, and concurrent events are incomparable under that partial order. He also showed that it is always possible to extend partial order to total order by defining artificial clocks. An artificial clock is created one for each process with unique labels from a totally ordered set in a manner consistent with partial order. He also provided an algorithm on how to accomplish this task of yielding an assignment of totally ordered clock values. In virtual time, the reverse of the above is done by assuming that every event is labeled with a clock value from a totally ordered virtual time scale satisfying Lamport’s clock conditions. Thus the Time Warp mechanism is an inverse of Lamport’s scheme. In Lamport’s scheme, all clocks are conservatively maintained so that they never violate causality. A process advances its clock as soon as it learns of new causal dependency. In the virtual time, clocks are optimisticaly advanced and corrective actions are taken whenever a violation is detected. Lamport’s initial idea brought about the concept of virtual time but the model failed to preserve causal independence. It was possible to make an analysis in the real world using timestamps but the same principle could not be implemented completely in the case of asynchronous distributed systems for the lack of a common time base. The implementation of virtual time concept using Time Warp mechanism is easier to understand and reason about than real time. 66

3.8.3 Time Warp Mechanism In the implementation of virtual time using Time Warp mechanism, virtual receive time of message is considered as its timestamp. The necessary and sufficient conditions for the correct implementation of virtual time are that each process must handle incoming messages in timestamp order. This is highly undesirable and restrictive because process speeds and message delays are likely to highly variable. So it natural for some processes to get ahead in virtual time of other processes. Since we assume virtual times are real numbers, it is impossible for a process on the basis of local information alone to block and wait for the message with the next timestamp. It is always possible that a message with earlier timestamp arrives later. So, when a process executes a message, it is very difficult for it determine whether a message with an earlier timestamp will arrive later. This is the central problem in virtual time that is solved by the Time Warp mechanism. The advantage of Time warp mechanism is that it doesn’t depend on the underlying computer architecture and so portability to different systems is easily achieved. However, message communication is assumed to be reliable, but messages may not be delivered in FIFO order. Time Warp mechanism consists of two major parts: local control mechanism and global control mechanism. The local control mechanism insures that events are executed and messages are processed in the correct order. The global control mechanism takes care of global issues such as global progress, termination detection, I/O error handling, flow control, etc.

3.8.4 The Local Control Mechanism There is no global virtual clock variable in this implementation; each process has a local virtual clock variable. The local virtual clock of a process doesn’t change during an event at that process but it changes only between events. On the processing of next message from the input queue, the process increases its local clock to the timestamp of the message. At any instant, the value of virtual time may differ for each process but the value is transparent to other processes in the system. When a message is sent, the virtual send time is copied from the sender’s virtual clock while the name of the receiver and virtual receive time are assigned based on application specific context. All arriving messages at a process are stored in an input queue in the increasing order of timestamps (receive times). Ideally, no messages from the past (called late messages) should arrive at a process. However, processes will receive late messages due to factors such as different computation rates of processes and network delays. The semantics of virtual time demands that incoming messages be received by each process strictly in the timestamp order. The only way to accomplish this is as follows: on the reception of a late message, the receiver rolls back to an earlier virtual time, cancelling all intermediate side effects and then executes forward again by executing the late message in the proper sequence. If all the messages in the input queue of a process are processed, the state of the process is said to terminate and its clock is set to + inf. However, the process is not destroyed as a late message may arrive resulting it to rollback and execute again. The situation can be described by saying that each process is doing a constant “lookahead”, processing future messages from its input queue. Over a length computation, each process may roll back several times while generally progressing forward with rollback completely transparent to other processes in the system. Programmers can write correct software without paying much attention to late-arriving messages. 67

Rollback in a distributed system is complicated by the fact that the process that wants to rollback might have sent many messages to other processes, which in turn might have sent many messages to other processes, and so on, leading to deep side effects. For rollback, messages must be effectively “unsent” and their side effects should be undone. This is achieved efficiently by using antimessages.

Antimessages and the Rollback Mechanism Runtime representation of a process is composed of the following: 1. Process name: Virtual spaces coordinate which is unique in the system. 2. Local virtual clock: Virtual time coordinate 3. State: Data space of the process including execution stack, program counter and its own variables 4. State queue: Contains saved copies of process’s recent states as roll back with Time warp mechanism requires the state of the process being saved. But it is not necessary to retain states from all the way beginning of the virtual time the reason for which will be explained in Global Control mechanism. 5. Input queue: Contains all recently arrived messages in order of virtual receive time. Processed messages from the input queue are not deleted as they are saved in the output queue with a negative sign (antimessage) to facilitate future roll backs. 6. Output queue: Contains negative copies of messages the process has recently sent in virtual send time order. They are needed in case of a rollback. For every message, there exists an antimessage that is the same in content but opposite in sign. Whenever a process sends a message, a copy of the message is transmitted to receiver’s input queue and a negative copy (antimessage) is retained in the sender’s output queue for use in sender rollback. Whenever a message and its antimessage appear in the same queue no matter in which order they arrived, they immediately annihilate each other resulting in shortening of the queue by one message. Generally when a message arrives at the input queue of a process with timestamp greater than virtual clock time of its destination process, it is simply enqueued by the interrupt routine and the running process continues. But when the destination process’ virtual time is greater than the virtual time of message received, the process must do a rollback. The first step in the rollback mechanism is to search the "State queue" for the last saved state with timestamp that is less than the timestamp of the message received and restore it. We make the timestamp of the received message as the value of the local virtual clock and discard from the state queue all states saved after this time. Then the execution resumes forward from this point. Now all the messages that are sent between the current state and earlier state must be “unsent”. This is taken care of by executing a simple rule:

68

“To unsend a message, simply transmit its antimessage.” This results in antimessages following the positive ones to the destination. A negative message causes a rollback at its destination if it’s virtual receive time is less than the receiver’s virtual time (just as a positive message does). Depending on the timing, there are several possibilities at the receiver’s end: 1. If the original (positive) message has arrived but not yet been processed, its virtual receive time must be greater than the value in the receiver’s virtual clock. The negative message, having the same virtual receives time, will be enqueued and will not cause a rollback. It will, however cause annihilation with the positive message leaving the receiver with no record of that message. 2. Second possibility is that the original positive message has a virtual receive time that is now in the present or past with respect to the receiver’s virtual clock and it may have already been partially or completely processed, causing side effects on receiver’s state. In this case, the negative message will also arrive in the receiver’s past and cause the receiver to roll back to a virtual time when the positive message was received. It will also annihilate the positive message leaving the receiver with no record that the message existed. When the receiver executes again, the execution will assume that these message never existed. Note that as aresult of the rollback, the process may send antimessages to other processes. 3. A negative message can also arrive at the destination before the positive one. In this case, it is enqueued and will be annihilated when positive message arrives. If it is negative message’s turn to be executed at a processs’ input queqe, the receiver may take any action like a no-op. Any action taken will eventually be rolled back when the corresponding positive message arrives. An optimization would be to skip the antimessage from the input queue and treat it as a no-op, and when the corresponding positive message arrives, it will annihilate the negative message, and inhibit any rollback. The antimessage protocol has several advantages: It is extremely robust and works under all possible circumstances. It is free from deadlocks as there is no blocking. It is also free from domino effects. In the worst case, all processes in system roll back to same virtual time as original one did and then proceed forward again.

3.8.5 Global Control Mechanism Global control mechanism resolves the following issues: 1. System global progress amidst rollback activity? 2. Detection of global termination? 3. Errors, I/O handling on rollbacks? 4. Running out of memory while saving copies of messages? How these issues are resolved by global control mechanism will be discussed later; first we discuss the important concept of global virtual time. 69

Global Virtual Time (GVT) The concept of global virtual time is central to Global Control mechanism. Global virtual time [14] is a property of an instantaneous global snapshot of system at real time ’r’ and is defined as follows: Global virtual time GVT at real time r is the minimum of 1. All virtual times in all virtual clocks at time r, and 2. Virtual send times of all messages that have been sent but have not yet been processed at time ’r’. GVT is defined in terms of virtual send time of unprocessed messages, instead of the virtual receive time because of the flow control which will be discussed later. If every event completes normally, if messages are delivered reliably, if the scheduler does not indefinitely postpone execution of the farthest behind process and if there is sufficient memory, then GVT will eventually increase. It is easily shown by induction that the message (sends, arrivals, and receipts) never decreases GVT even though local virtual time clocks roll back frequently. These properties make it appropriate to consider GVT as virtual clock for the system as a whole and to use it as the measure of system progress. GVT can thus be viewed as a moving commitment horizon: any event with virtual time less than GVT cannot be rolled back and may be committed safely. It is generally impossible for Time warp mechanism to know at any real time ’r’, exactly what GVT is. But GVT can be characterized more operationally by its two properties discussed above. This characterization leads to a fast distributed GVT estimation algorithm that takes O(d) time where ’d’ is the delay required for one broadcast to all processors in the system. The algorithm runs concurrently with main computation and returns value that is between true GVT at the moment when the algorithm starts and the true GVT at moment of completion. Thus it gives a slightly outof-date value for GVT which is the best one can get. During execution of a virtual time system, Time warp must estimate GVT every so often. Higher frequency of GVT estimation produces faster response time and better space utilization at the expense of processor time and network bandwidth.

Applications of GVT GVT finds several applications in a virtual time system using the time warp mechanism. Memory Management and Flow Control An attractive feature in Time Warp mechanism is that it is possible to give simple algorithms for managing memory. Time Warp mechanism uses the concept of fossil detection where information older than GVT is destroyed to avoid memory overheads due to old states in state queues, messages stored in output queues, "past" messages in input queue that have already been processed and "future" messages in input queue that have not yet been received. There is another kind of memory overhead due to future messages in the input queues that have not yet been received. So, if a receiver’s memory is full of input messages, the Time Warp mechanism may be able to recover space by returning an unreceived message to the process that 70

sent it and then rollback to cancel out the sending event. Normal Termination Detection The Time Warp mechanism handles the termination detection problem through GVT. A process terminates whenever it runs out of messages and its local virtual clock is set to +inf. Whenever GVT reaches +inf, all local virtual clock variables must read +inf and no message can be in transit. No process can ever again unterminate by rolling back to a finite virtual time. The Time Warp mechanism signals termination whenever GVT calculation returns “+inf” value in the system. Error Handling All errors don’t cause termination. Most of the errors can be avoided by rolling back the local virtual clock to some finite value. The error is only “committed” if it is impossible for the process to roll back to a virtual time on or before the error. The committed error is reported to some policy software or to the user. Input and output When a process sends a command to an output device, it is important that the physical output activity not be committed immediately because the sending process may roll back and cancel the output request. An output activity can only be performed when GVT exceeds the virtual receive time of the message containing the command. Snapshots and Crash Recovery An entire snapshot of the system at virtual time ‘t’ can be constructed by a procedure in which each process “snapshots” itself as it passes virtual time t in the forward direction and “unsnapshots” itself whenever it rolls back over virtual time ‘t’. Whenever GVT exceeds ‘t’, the snapshot is complete and valid.

3.8.6 An Example: Distributed Discrete Event Simulations Distributed discrete event simulation [16, 2, 25] is the most studid example of virtual time systems, every process represents an object in the simulation and virtual time is identified with simulation time. The fundamental operation in discrete event simulation is for one process to schedule an event for execution by another process at a later simulation time. This is emulated by having the first process send a message to the second process with virtual receive time of the message equal to event’s scheduled time in the simulation. When an event message is received by a process, there are three possibilities: its timestamp is either before, after or equal to the local value of simulation time. If its timestamp is after the local time, an input event combination is formed and the appropriate action is taken. However if the timestamp of the received event message is less than or equal to the local clock value, the process has already processed an event combination with time greater than or equal to the incoming event. The process must then rollback to the time of the incoming message which is done by an elaborate checkpointing mechanism that allows earlier states to be restored. Essentially an earlier state is restored, input event combinations are rescheduled and output events are cancelled by sending antimessages. The process has buffers that save past inputs, past states and antimessages. 71

Distributed discrete event simulation is one of the most general applications of the virtual time paradigm because the virtual times of events are completely under the control of the user and because it makes use of almost all the degrees of freedom allowed in the definition of a virtual time system.

3.9 Physical Clock Synchronization: NTP 3.9.1 Motivation In centralized systems, there is no need for clocks synchronization because generally, there is only single clock. A process gets the time by simply issuing a system call to the kernel. When another process after that tries to get the time, it will get a higher time value. Thus, in such systems, there is a clear ordering of events and there is no ambiguity about the times at which these events occur. In distributed systems, there is no global clock or common memory. Each processor has its own internal clock and its own notion of time. In practice, these clocks can easily drift seconds per day, accumulating significant errors over time. Also, because different clocks tick at different rates, they may not remain always synchronized although they might be synchronized when they start. This clearly poses serious problems to applications that depend on a synchronized notion of time. For most applications and algorithms that run in a distributed system, we need to know time in one or more of the following contexts: • The time of the day at which an event happened on a specific machine in the network. • The time interval between two events that happened on different machines in the network. • The relative ordering of events that happened on different machines in the network. Unless the clocks in each machine have a common notion of time, time-based queries cannot be answered. Some practical examples that stress the need for synchronization are listed below. • In database systems, the order in which processes perform updates on a database is important to ensure a consistent, correct view of the database. To ensure the right ordering of events, a common notion of time between co-operating processes becomes imperative. • Liskov [12] states that clock synchronization improves the performance of distributed algorithms by replacing communication with local computation. When a node p needs to query node q regarding a property, it can deduce the property with some previous information it has about node p and its knowledge of the local time in node q. • It is quite common that distributed applications and network protocols use timeouts, and their performance depends on how well physically dispersed processors are time-synchronized. Design of such applications is simplified when clocks are synchronized. Clock synchronization is the process of ensuring that physically distributed processors have a common notion of time. It has a significant effect on many problems like secure systems, fault diagnosis and recovery, scheduled operations, database systems, and real-world clock values. It 72

is quite common that distributed applications and network protocols use timeouts, and their performance depends on how well physically dispersed processors are time-synchronized. Design of such applications is simplified when clocks are synchronized. Clock synchronization is the process of ensuring that physically distributed processors have a common notion of time. It has a significant effect on many areas like security systems, fault diagnosis and recovery, scheduled operations, database systems, and real-world clock values. Due to different clocks rates, the clocks at various sites may diverge with time and periodically a clock synchrinization must be performed to correct this clock skew in distributed systems. Clocks are synchronized to an accurate real-time standard like UTC (Universal Coordinated Time). Clocks that must not only be synchronized with each other but also have to adhere to physical time are termed physical clocks.

3.9.2 Definitions and Terminology We provide the following definitions [16, 17]. Ca and Cb are any two clocks. Time: The time of a clock in a machine p is given by the function Cp (t), where Cp (t) = t for a perfect clock. Frequency : Frequency is the rate at which a clock progresses. The frequency at time t of clock ′ Ca is Ca (t). Offset: Clock offset is the difference between the time reported by a clock and the real time. The offset of the clock Ca is given by Ca (t) − t. The offset of clock Ca relative to Cb at time t ≥ 0 is given by Ca (t) − Cb (t). Skew: The skew of a clock is the difference in the frequencies of the clock and the perfect clock. The skew of a clock Ca relative to clock Cb at time t is (Ca′ (t) − Cb′ (t)). If the skew is bounded by ρ, then as per Equation 3.1, clock values are allowed to diverge at a rate in the range of 1 − ρ to 1 + ρ.

Drift (rate): The drift of clock Ca is the second derivative of the clock value with respect to time, namely, Ca′′ (t). The drift of clock Ca relative to clock Cb at time t is Ca′′ (t) − Cb′′ (t).

3.9.3 Clock Inaccuracies Physical clocks are synchronized to an accurate real-time standard like UTC (Universal Coordinated Time). However, due to the clock inaccuracy discussed above, a timer (clock) is said to be working within its specification if dC ≤1+ρ (3.1) 1−ρ≤ dt where constant ρ is the maximum skew rate specified by the manufacturer. Figure 3.8 illustrates the behavior of fast, slow, and perfect clocks with respect to UTC.

73

Fast Clock dC/dt > 1 Clock time, C

Perfect Clock dC/dt = 1 Slow Clock dC/dt < 1

UTC, t

Figure 3.8: The behavior of fast, slow, and perfect clocks with respect to UTC.

Offset delay estimation method The Network Time Protocol (NTP) [18] which is widely used for clock synchronization on the Internet uses the The Offset Delay Estimation method. The design of NTP involves a hierarchical tree of time servers. The primary server at the root synchronizes with the UTC. The next level contains secondary servers, which act as a backup to the primary server. At the lowest level is the synchronization subnet which has the clients. T1

T2

B

A T4

T3

Figure 3.9: Offset and delay estimation [18].

Clock offset and delay estimation: In practice, a source node cannot accurately estimate the local time on the target node due to varying message or network delays between the nodes. This protocol employs a very common practice of performing several trials and chooses the trial with the minimum delay. Recall that Cristian’s remote clock reading method [5] also relied on the same strategy to estimate message delay. Figure 3.9 shows how NTP timestamps are numbered and exchanged between peers A and B. Let T1 , T2 , T3 , T4 be the values of the four most recent timestamps as shown. Assume that clocks 74

A and B are stable and running at the same speed. Let a = T1 − T3 and b = T2 − T4 . If the network delay difference from A to B and from B to A, called differential delay, is small, the clock offset θ and roundtrip delay δ of B relative to A at time T4 are approximately given by the following. θ=

T i-2

Server A

Server B

T i-3

a+b , 2

δ =a−b

(3.2)

T i-1

Ti

Figure 3.10: Timing diagram for the two servers [18]. Each NTP message includes the latest three timestamps T1 , T2 and T3 , while T4 is determined upon arrival. Thus, both peers A and B can independently calculate delay and offset using a single bidirectional message stream as shown in Figure 3.10. The NTP protocol is shown in Figure 3.11.

3.10 Chapter Summary The concept of causality between events is fundamental to the design and analysis of distributed programs. The notion of time is basic to capture causality between events; however, there is no built-in physical time in distributed systems and it is possible only to realize an approximation of it. Typically, a distributed computation makes progress in spurts and consequently logical time, which advances in jumps, is sufficient to capture the monotonicity property induced by causality in distributed systems. Causality among events in a distributed system is a powerful concept in reasoning, analyzing, and drawing inferences about a computation. We presented a general framework of logical clocks in distributed systems and discussed three systems of logical clocks, namely, scalar, vector, and matrix clocks, that have been proposed to capture causality between events of a distributed computation. These systems of clocks have been used to solve a variety of problems in distributed systems such as distributed algorithms design, debugging distributed programs, checkpointing and failure recovery, data consistency in replicated databases, discarding obsolete information, garbage collection, and termination detection. In scalar clocks, the clock at a process is represented by an integer. The message and the compuatation overheads are small, but the power of scalar clocks is limited – they are not strongly consistent. In vector clocks, the clock at a process is represented by a vector of integers. Thus, the message and the compuatation overheads are likely to be high; however, vector clocks possess a powerful property – there is an isomorphism between the set of partially ordered events in a distributed computation and their vector timestamps. This is a very useful and interesting property 75

• A pair of servers in symmetric mode exchange pairs of timing messages. • A store of data is then built up about the relationship between the two servers (pairs of offset and delay). Specifically, assume that each peer maintains pairs (Oi ,Di ), where Oi - measure of offset (θ) Di - transmission delay of two messages (δ). • The offset corresponding to the minimum delay is chosen.

Specifically, the delay and offset are calculated as follows. Assume that message m takes time t to transfer and m′ takes t′ to transfer. – The offset between A’s clock and B’s clock is O. If A’s local clock time is A(t) and B’s local clock time is B(t), we have A(t) = B(t) + O

(3.3)

Ti−2 = Ti−3 + t + O

(3.4)

Ti = Ti−1 − O + t′

(3.5)

Then,

Assuming t = t′ , the offset Oi can be estimated as: Oi = (Ti−2 − Ti−3 + Ti−1 − Ti )/2

(3.6)

The round-trip delay is estimated as: Di = (Ti − Ti−3 ) − (Ti−1 − Ti−2 )

(3.7)

– The eight most recent pairs of (Oi , Di ) are retained. – The value of Oi that corresponds to minimum Di is chosen to estimate O.

Figure 3.11: The Network Time Protocol synchronization protocol [18]. of vector clocks that finds applications in several problem domains. In matrix clocks, the clock at a process is represented by a matrix of integers. Thus, the message and the compuatation overheads are high; however, matrix clocks are very powerful – besides containing information about the direct dependencies, a matrix clock contains information about the latest direct dependencies of those dependencies. This information can be very useful in aplications such as distributed garbage collection. Thus, the power of systems of clocks increases in the order of scalar, vector, and matrix, but so do the complexity and the overheads. We discussed three efficient implementations of vector clocks; similar techniques can be used to efficiently implement matrix clocks. Singhal-Kshemkalyani’s differential technique exploits the fact that between successive events at a process, only few entries of its vector clock are likely to 76

change. Thus, when a process pi sends a message to a process pj , it piggybacks only those entries of its vector clock that have changed since the last message send to pj , reducing the communication and buffer (to store messages) overheads. Fowler-Zwaenepoel’s direct-dependency technique does not maintain vector clocks on-the-fly. Instead, a process only maintains information regarding direct dependencies on other processes. A vector timestamp for an event, that represents transitive dependencies on other processes, is constructed off-line from a recursive search of the direct dependency information at processes. Thus, the technique has low run-time overhead. In the Fowler-Zwaenepoel’s technique, however, a process must update and record its dependency vector after receiving a message but before sending out any message. If events occur very frequently, this technique will require recording the history of a large number of events. In the Jard-Jourdan’s technique, events can be adaptively observed while maintaining the capability of retrieving all the causal dependencies of an observed event. Virtual time system is a paradigm for organizing and synchronizing distributed systems using virtual time. We discussed virtual time and its implementation using the Time Warp mechanism.

3.11 Bibliographic Notes The idea of logical time was proposed by Lamport in 1978 [11] in an attempt to order events in distributed systems. He also suggested an implementation of logical time as a scalar time. Vector clocks were developed independently by Fidge [6], Mattern [14] and Schmuck [27]. Charron-Bost formally showed [4] that if vector clocks have to satisfy the strong consistency property, then the length of vector timestamps must be at least n. Efficient implementations of vector clocks can be found in [10, 29]. Matrix clocks was informally proposed by Michael and Fischer [7] and used by Wuu and Bernstein [32] and by Lynch and Sarin [26] to discard obsolete information. Raynal and Singhal present a survey of scalar, vector, and matrix clocks in [23]. More details on virtual time can be found in a classical paper by Jefferson [9]. A survey of physical clock synchronization in wireless sensor networks can be found in [31].

3.12 Exercise Problems 1. Why is it difficult to keep a synchronized system of physical clocks in distributed systems? 2. If events corresponding to vector timestamps V t1 , V t2 , ...., V tn are mutually concurrent, then prove that (V t1 [1], V t2 [2], ....V tn [n])= max(V t1 , V t2 , ...., V tn ). 3. If events ei and ej respectively occurred at processes pi and pj and are assigned vector timestamps V Tei and V Tej , respectively, then show that ei → ej ⇔ V Tei [i] < V Tej [i].

77

4. The size of matrix clocks is quadratic with respect to the system size. Hence the message overhead is likely to be substantial. Propose a technique for matrix clocks similar to that of Singhal-Kshemkalyani to decrease the volume of information transmitted in messages and stored at processes.

78

Bibliography [1] Awerbuch, B. Complexity of network synchronization. Journal of the ACM, vol.32,4, (1985), pp. 804-823. [2] Bruno R. Preiss, University of Waterloo, “The Yaddes Distributed Discrete Event Simulation Specification Language and Execution Environments” [3] Chandy, K.M., Misra, J. The drinking philosophers problem. ACM Toplas, vol.6,4, (1984), pp. 632-646. [4] Charron-Bost, B. Concerning the size of logical clocks in distributed systems. Inf. Proc. Letters, vol.39, (1991), pp. 11-16. [5] F. Cristian. Probabilistic Clock Synchronization. Springer-Verlag, 1989.

Distributed Computing, 3:146–158,

[6] Fidge, C. Logical time in distributed computing systems. IEEE Computer, (August 1991), pp. 28-33. [7] Fischer, M.J., Michael, A. Sacrifying serializability to attain hight availability of data in an unreliable network. Proc. of ACM Symposium on Principles of Database Systems, (1982), pp. 70-75. [8] Fowler J., Zwaenepoel W. Causal distributed breakpoints. Proc. of 10th Int’l. Conf. on Distributed Computing Systems, (1990), pp. 134-141. [9] Jefferson, D. Virtual time. ACM Toplas, vol.7,3, (1985), pp. 404-425. [10] Jard C., Jourdan G-C. Dependency tracking and filtering in distributed computations. in Brief announcements of the ACM symposium on PODC, (1994). (A full presentation appeared as IRISA Tech. Report No. 851, 1994). [11] Lamport, L. Time, clocks and the ordering of events in a distributed system. Comm. ACM, vol.21, (July 1978), pp. 558-564. [12] B. Liskov, Practical Uses of Synchronized Clocks in Distributed Systems, Proc. Tenth Annual ACM Symposium on Principles of Distributed Computing, pp. 1–9, Aug. 1991. [13] Liskov, B., Ladin, R. Highly available distributed services and fault-tolerant distributed garbage collection. Proc. 5th ACM Symposium on PODC, (1986), pp. 29-39. 79

[14] Mattern, F. Virtual time and global states of distributed systems. Proc. "Parallel and distributed algorithms" Conf., (Cosnard, Quinton, Raynal, Robert Eds), North-Holland, (1988), pp. 215-226. [15] Mills, David L. On the Accuracy and Stability of Clocks Synchronized by Network Time Protocol in the Internet System. ACM Computer Communication Review 20, 1 (January 1990), pp. 65-75. [16] D.L. Mills. Network Time Protocol (version 3): Specification, Implementation, and Analysis. Technical Report, Network Information Center, SRI International, Menlo Park, CA, Mar. 1992. [17] D.L. Mills. Modelling and Analysis of Computer Network Clocks. Technical Report, 92-5-2, Electrical Engineering Department, Univ ersity of Delaware, May 1992. [18] D.L. Mills. Internet Time Synchronization: the Network Time Protocol. IEEE Trans. Communications, Vol 39, no 10, pp. 1482–1493, Oct. 1991. [19] Misra, J. Distributed discrete event simulation. ACM Computing Surveys, vol.18,1, (1986), pp. 39-65. [20] Parker, D.S. et al. Detection of mutual inconsistency in distributed systems. IEEE Trans. on Soft. Eng., vol.SE 9,3, (May 1983), pp. 240-246. [21] Raynal, M. A distributed algorithm to prevent mutual drift between n logical clocks. Inf. Processing Letters, vol.24, (1987), pp. 199-202. [22] Raynal, M., Helary, J.M. Synchronization and control of distributed systems and programs. Wiley & sons, (1990), 124 p. [23] M. Raynal and M. Singhal, Logical Time: Capturing Causality in Distributed Systems, IEEE Computer, February 1996, Vol 30, No 2, pp. 49-56. [24] Ricart, G., Agrawala, A.K. An optimal algorithm for mutual exclusion in computer networks. Comm. ACM, vol.24,1, (Jan. 1981), pp. 9-17. [25] Righter, R., Walrand, J.C. Distributed simulation of discrete event systems. Proc. of the IEEE, (Jan. 1988), pp. 99-113. [26] Sarin, S.K., Lynch, L. Discarding obsolete information in a replicated data base system. IEEE Trans. on Soft. Eng., vol.SE 13,1, (Jan. 1987), pp. 39-46. [27] Schmuck, F. The use of efficient broadcast in asynchronous distributed systems. Ph. D. Thesis, Cornell University, TR88-928, (1988), 124 pages. [28] M. Singhal, A Heuristically-Aided Mutual Exclusion Algorithm for Distributed Systems. IEEE Trans. on Computers, Vol 38, No 5, (May 1989), pp. 651-662. [29] Singhal, M., Kshemkalyani, A. An Efficient Implementation of Vector Clocks. Information Processing Letters, 43, August 1992, pp. 47-52. 80

[30] Strom, R.E., Yemini, S. Optimistic recovery in distributed systems. ACM TOCS, vol.3,3, (August 1985), pp. 204-226. [31] B. Sundararaman, U. Buy, A.D. Kshemkalyani, Clock Synchronization in Wireless Sensor Networks: A Survey, Ad-Hoc Networks, 3(3): 281-323, May 2005. [32] Wuu, G.T.J., Bernstein, A.J. Efficient solutions to the replicated log and dictionary problems. Proc. 3rd ACM Symposium on PODC, (1984), pp. 233-242

81

Chapter 4 Global State and Snapshot Recording Algorithms Recording on-the-fly the global state of a distributed system is an important paradigm when one is interested in analyzing, testing, or verifying properties associated with distributed executions. Unfortunately, the lack of both a globally shared memory and a global clock in a distributed system, added to the fact that message transfer delays in these systems are finite but unpredictable, makes this problem non-trivial. This chapter first defines consistent global states (also called consistent snapshots) and discusses issues which have to be addressed to compute consistent distributed snapshots. Then several algorithms to determine on-the-fly such snapshots are presented for several types of networks (according to the properties of their communication channels, namely, FIFO, non-FIFO, and causal delivery).

4.1 Introduction A distributed computing system consists of spatially separated processes that do not share a common memory and communicate asynchronously with each other by message passing over communication channels. Each component of a distributed system has a local state. The state of a process is characterized by the state of its local memory and a history of its activity. The state of a channel is characterized by the set of messages sent along the channel less the messages received along the channel. The global state of a distributed system is a collection of the local states of its components. Recording the global state of a distributed system is an important paradigm and it finds applications in several aspects of distributed system design. For examples, in detection of stable properties such as deadlocks [17] and termination [22], global state of the system is examined for certain properties; for failure recovery, a global state of the distributed system (called a checkpoint) is periodically saved and recovery from a processor failure is done by restoring the system to the last saved global state [15]; for debugging distributed software, the system is restored to a consistent global state [8, 9] and the execution resumes from there in a controlled manner. A snapshot recording method has been used in the distributed debugging facility of Estelle [13, 11], a distributed programming environment. Other applications include monitoring distributed events [30] 82

such as in industrial process control, setting distributed breakpoints [24], protocol specification and verification [4, 10, 14], and discarding obsolete information [12]. Therefore, it is important that we have efficient ways of recording the global state of a distributed system [6, 16]. Unfortunately, there is no shared memory and no global clock in a distributed system and the distributed nature of the local clocks and local memory makes it difficult to record the global state of the system efficiently. If shared memory were available, an up-to-date state of the entire system would be available to the processes sharing the memory. The absence of shared memory necessitates ways of getting a coherent and complete view of the system based on the local states of individual processes. A meaningful global snapshot can be obtained if the components of the distributed system record their local states at the same time. This would be possible if the local clocks at processes were perfectly synchronized or if there were a global system clock that could be instantaneously read by the processes. However, it is technologically infeasible to have perfectly synchronized clocks at various sites – clocks are bound to drift. If processes read time from a single common clock (maintained at one process), various indeterminate transmission delays during the read operation will cause the processes to identify various physical instants as the same time. In both cases, the collection of local state observations will be made at different times and may not be meaningful, as illustrated by the following example.

$600

$550

$550 $630

$630

$120 $120 t2 t3 $50 $50 $80 $0

$170 t4 $0

S1:A $50 $80 S2:B $200 $200 t0 t1 C12 $0 $50 C21

$0

$0

$0

Figure 4.1: A Banking example to illustrate recording of consistent states.

An Example Let S1 and S2 be two distinct sites of a distributed system which maintain bank accounts A and B, respectively. A site refers to a process in this example. Let the communication channels from site S1 to site S2 and from site S2 to site S1 be denoted by C12 and C21 , respectively. Consider the following sequence of actions, which are also illustrated in the timing diagram of Figure 4.1. time t0 : Initially, Account A = $600, Account B = $200, C12 = $0, C21 = $0. 83

time t1 : Site S1 initiates a transfer of $50 from Account A to Account B. Account A is decremented by $50 to $550 and a request for $50 credit to Account B is sent on Channel C12 to site S2. Account A = $550, Account B = $200, C12 = $50, C21 = $0. time t2 : Site S2 initiates a transfer of $80 from Account B to Account A. Account B is decremented by $80 to $120 and a request for $80 credit to Account A is sent on Channel C21 to site S1. Account A = $550, Account B = $120, C12 = $50, C21 = $80. time t3 : Site S1 receives the message for a $80 credit to Account A and updates Account A. Account A = $630, Account B = $120, C12 = $50, C21 = $0. time t4 : Site S2 receives the message for a $50 credit to Account B and updates Account B. Account A = $630, Account B = $170, C12 = $0, C21 = $0. Suppose the local state of Account A is recorded at time t0 to show $600 and the local state of Account B and channels C12 and C21 are recorded at time t2 to show $120, $50, and $80, respectively. Then the recorded global state shows $850 in the system. An extra $50 appears in the system. The reason for the inconsistency is that Account A’s state was recorded before the $50 transfer to Account B using channel C12 was initiated, whereas channel C12 ’s state was recorded after the $50 transfer was initiated. This simple example shows that recording a consistent global state of a distributed system is not a trivial task. Recording activities of individual components must be coordinated appropriately. This chapter addresses the fundamental issue of recording a consistent global state in distributed computing systems. Next section presents the system model and a formal definition of the notion of consistent global state. The subsequent sections present algorithms to record such global states under various communication models such as FIFO communication channels, non-FIFO communication channels, and causal delivery of messages. These algorithms are called snapshot recording algorithms. to show we present

4.2 System Model and Definitions 4.2.1 System Model The system consists of a collection of n processes, p1 , p2 , ..., pn , that are connected by channels. There is no globally shared memory and processes communicate solely by passing messages. There is no physical global clock in the system. Message send and receive is asynchronous. Messages are delivered reliably with finite but arbitrary time delay. The system can be described as a directed graph in which vertices represent the processes and edges represent unidirectional communication channels. Let Cij denote the channel from process pi to process pj . Processes and channels have states associated with them. The state of a process at any time is defined by the contents of processor registers, stacks, local memory, etc. and may be highly dependent on the local context of the distributed application. The state of channel Cij , denoted by SCij , is given by the set of messages in transit in the channel.

84

The actions performed by a process are modeled as three types of events, namely, internal events, message send events, and message receive events. For a message mij that is sent by process pi to process pj , let send(mij ) and rec(mij ) denote its send and receive events, respectively. Occurrence of events changes the states of respective processes and channels, thus causing transitions in the global system state. For example, an internal event changes the state of the process at which it occurs. A send event (or a receive event) changes the state of the process that sends (or receives) the message and the state of the channel on which the message is sent (or received). The events at a process are linearly ordered by their order of occurrence. At any instant, the state of process pi , denoted by LSi , is a result of the sequence of all the events executed by pi till that instant. For an event e and a process state LSi , e∈LSi iff e belongs to the sequence of events that have taken process pi to state LSi . For an event e and a process state LSi , e6∈LSi iff e does not belong to the sequence of events that have taken process pi to state LSi . A channel is a distributed entity and its state depends on the local states of the processes on which it is incident. For a channel Cij , the following set of messages can be defined based on the local states of the processes pi and pj [12]. Transit: transit(LSi , LSj ) = {mij |send(mij ) ∈ LSi

V

rec(mij ) 6∈ LSj }

Thus, if a snapshot recording algorithm records the state of processes pi and pj as LSi and LSj , respectively, then it must record the state of channel Cij as transit(LSi , LSj ). There are several models of communication among processes and different snapshot algorithms have assumed different models of communication. In FIFO model, each channel acts as a firstin first-out message queue and thus, message ordering is preserved by a channel. In non-FIFO model, a channel acts like a set in which the sender process adds messages and the receiver process removes messages from it in a random order. A system that supports causal delivery of messages satisfies the following property: “For any two messages mij and mkj , if send(mij ) −→ send(mkj ), then rec(mij ) −→ rec(mkj )". Causally ordered delivery of messages implies FIFO message delivery. Causal ordering model is useful in developing distributed algorithms and may simplify the design of algorithms.

4.2.2 A Consistent Global State The global state of a distributed system is a collection of the local states of the processes and the channels. Notationally, global state GS is defined as, S S GS = { i LSi , i,j SCij } A global state GS is a consistent global state iff it satisfies the following two conditions [16]:

C1: send(mij )∈LSi : mij ∈SCij ⊕ rec(mij )∈LSj . (⊕ is Ex-OR operator.) C2: send(mij )6∈LSi : mij 6∈SCij ∧ rec(mij )6∈LSj . Condition C1 states the law of conservation of messages. Every message mij that is recorded as sent in the local state of a process pi must be captured in the state of the channel Cij or in the collected local state of the receiver process pj . Condition C2 states that in the collected global state, for every effect, its cause must be present. If a message mij is not recorded as sent in the 85

local state of process pi , then it must neither be present in the state of the channel Cij nor in the collected local state of the receiver process pj . In a consistent global state, every message that is recorded as received is also recorded as sent. Such a global state captures the notion of causality that a message cannot be received if it was not sent. Consistent global states are meaningful global states and inconsistent global states are not meaningful in the sense that a distributed system can never be in an inconsistent state.

4.2.3 Interpretation in Terms of Cuts Cuts in a space-time diagram provide a powerful graphical aid in representing and reasoning about global states of a computation. A cut is a line joining an arbitrary point on each process line that slices the space-time diagram into a PAST and a FUTURE. Recall that every cut corresponds to a global state and every global state can be graphically represented by a cut in the computation’s space-time diagram [3]. A consistent global state corresponds to a cut in which every message received in the PAST of the cut has been sent in the PAST of that cut. Such a cut is known as a consistent cut. All the messages that cross the cut from the PAST to the FUTURE are captured in the corresponding channel state. For example, consider the space-time diagram for the computation illustrated in Figure 4.2. Cut C1 is inconsistent because message m1 is flowing from the FUTURE to the PAST. Cut C2 is consistent and message m4 must be captured in the state of channel C21 . Note that in a consistent snapshot, all the recorded local states of processes are concurrent; that is, recorded local state of no process casually affects the recorded local state of any other process. (Note that the notion of causality can be extended from the set of events to the set of recorded local states.)

p

C

1

e11 1

p3 p4

e

1 3

e 23 e41

e3 1

m1

e21

p2

C2

e21

m 2

e22

e42

e23

e 14 m5

m 4

e35

e34

e 33 m 3

e2 4

time

Figure 4.2: An Interpretation in Terms of a Cut.

4.2.4 Issues in Recording a Global State If a global physical clock were available, the following simple procedure could be used to record a consistent global snapshot of a distributed system: The initiator of the snapshot collection decides a future time at which the snapshot is to be taken and broadcasts this time to every process. All 86

processes take their local snapshots at that instant in the global time. The snapshot of channel Cij includes all the messages that process pj receives after taking the snapshot and whose timestamp is smaller than the time of the snapshot. (All messages are timestamped with the sender’s clock.) Clearly, if channels are not FIFO, a termination detection scheme will be needed to determine when to stop waiting for messages on channels. However, a global physical clock is not available in a distributed system and the following two issues need to be addressed in recording of a consistent global snapshot of a distributed system [16]: I1: How to distinguish between the messages to be recorded in the snapshot (either in a channel state or a process state) from those not to be recorded. The answer to this comes from conditions C1 and C2 as follows: Any message that is sent by a process before recording its snapshot, must be recorded in the global snapshot (from C1). Any message that is sent by a process after recording its snapshot, must not be recorded in the global snapshot (from C2). I2: How to determine the instant when a process takes its snapshot. The answer to this comes from condition C2 is as follows: A process pj must record its snapshot before processing a message mij that was sent by process pi after recording its snapshot. We next discuss a set of representative snapshot algorithms for distributed systems. These algorithms assume different interprocess communication capabilities about the underlying system and illustrate how interprocess communication affects the design complexity of these algorithms. There are two types of messages: computation messages and control messages. The former are exchanged by the underlying application and the latter are exchanged by the snapshot algorithm. Execution of a snapshot algorithm is transparent to the underlying application, except for occasional delaying of some actions of the application.

4.3 Snapshot Algorithms for FIFO Channels This section presents Chandy and Lamport algorithm [6], which was the first algorithm to record the global snapshot. We also present three variations of the Chandy and Lamport algorithm.

4.3.1 Chandy-Lamport Algorithm The Chandy-Lamport algorithm uses a control message, called a marker. After a site has recorded its snapshot, it sends a marker, along all of its outgoing channels before sending out any more messages. Since channels are FIFO, a marker separates the messages in the channel into those to be included in the snapshot (i.e., channel state or process state) from those not to be recorded in the snapshot. This addresses issue I1. The role of markers in a FIFO system is to act as delimiters for the messages in the channels so that the channel state recorded by the process at the receiving end of the channel satisfies the condition C2. 87

Marker Sending Rule for process i 1. Process i records its state. 2. For each outgoing channel C on which a marker has not been sent, i sends a marker along C before i sends further messages along C. Marker Receiving Rule for process j On receiving a marker along channel C: if j has not recorded its state then Record the state of C as the empty set Follow the “Marker Sending Rule" else Record the state of C as the set of messages received along C after j’s state was recorded and before j received the marker along C Figure 4.3: The Chandy-Lamport algorithm. Since all messages that follow a marker on channel Cij have been sent by process pi after pi has taken its snapshot, process pj must record its snapshot no later than when it receives a marker on channel Cij . In general, a process must record its snapshot no later than when it receives a marker on any of its incoming channels. This addresses issue I2.

The Algorithm The Chandy-Lamport snapshot recording algorithm is given in Figure 4.3. A process initiates snapshot collection by executing the “Marker Sending Rule" by which it records its local state and sends a marker on each outgoing channel. A process executes the “Marker Receiving Rule" on receiving a marker. If the process has not yet recorded its local state, it records the state of the channel on which the marker is received as empty and executes the “Marker Sending Rule" to record its local state. Otherwise, the state of the incoming channel on which the marker is received is recorded as the set of computation messages received on that channel after recording the local state but before receiving the marker on that channel. The algorithm can be initiated by any process by executing the “Marker Sending Rule". The algorithm terminates after each process has received a marker on all of its incoming channels. The recorded local snapshots can be put together to create the global snapshot in several ways. One policy is to have each process send its local snapshot to the initiator of the algorithm. Another policy is to have each process send the information it records along all outgoing channels, and to have each process receiving such information for the first time propagate it along its outgoing channels. All the local snapshots get disseminated to all other processes and all the processes can determine the global state. Multiple processes can initiate the algorithm concurrently. If multiple processes initiate the al-

88

gorithm concurrently, each initiation needs to be distinguished by using unique markers. Different initiations by a process are identified by a sequence number.

Correcteness To prove the correctness of the algorithm, we show that a recorded snapshot satisfies conditions C1 and C2. Since a process records its snapshot when it receives the first marker on any incoming channel, no messages that follow markers on the channels incoming to it are recorded in the process’s snapshot. Moreover, a process stops recording the state of an incoming channel when a marker is received on that channel. Due to FIFO property of channels, it follows that no message sent after the marker on that channel is recorded in the channel state. Thus, condition C2 is satisfied. When a process pj receives message mij that precedes the marker on channel Cij , it acts as follows: If process pj has not taken its snapshot yet, then it includes mij in its recorded snapshot. Otherwise, it records mij in the state of the channel Cij . Thus, condition C1 is satisfied.

Complexity The recording part of a single instance of the algorithm requires O(e) messages and O(d) time, where e is the number of edges in the network and d is the diameter of the network.

$600

$550

$550

$630

$630

$170 t4 $0 $0

S1:A $50 $80 S2:B $200 $200 t0 t1 C12

$0

$50

$120 $120 t2 t3 $50 $50

C21

$0

$0

$80

execution message

$0

markers

markers

(1st example)

(2nd example)

Figure 4.4: Timing Diagram of Two Possible Executions of the Banking Example.

4.3.2 Properties of the Recorded Global State The recorded global state may not correspond to any of the global states that occurred during the computation. Consider two possible executions of the snapshot algorithm (shown in Figure 4.4) for the money transfer example of Figure 4.2.

89

1. (Markers shown using dashed-and-dotted arrows.) Let site S1 initiate the algorithm just after t1 . Site S1 records its local state (Account A = $550) and sends a marker to site S2. The marker is received by site S2 after t4 . When site S2 receives the marker, it records its local state (Account B = $170), the state of channel C12 as $0, and sends a marker along channel C21 . When site S1 receives this marker, it records the state of Channel C21 as $80. The $800 amount in the system is conserved in the recorded global state, A = $550, B = $170, C12 = $0, C21 = $80 2. (Markers shown using dotted arrows.) Let site S1 initiate the algorithm just after t0 and before sending the $50 for S2. Site S1 records its local state (Account A = $600) and sends a marker to site S2. The marker is received by site S2 between t2 and t3 . When site S2 receives the marker, it records its local state (Account B = $120), the state of channel C12 as $0, and sends a marker along channel C21 . When site S1 receives this marker, it records the state of Channel C21 as $80. The $800 amount in the system is conserved in the recorded global state, A = $600, B = $120, C12 = $0, C21 = $80 In both these possible runs of the algorithm, the recorded global states never occurred in the execution. This happens because a process can change its state asynchronously before the markers it sent are received by other sites and the other sites record their states. Nevertheless, as we discuss next, the system could have passed through the recorded global states in some equivalent executions. Suppose the algorithm is initiated in global state Si and it terminates in global state St . Let seq be the sequence of events which takes the system from Si to St . Let S ∗ be the global state recorded by the algorithm. Chandy and Lamport [6] showed that there exists a sequence seq ′ which is a permutation of seq such that S ∗ is reachable from Si by executing a prefix of seq ′ and St is reachable from S ∗ by executing the rest of the events of seq ′ . A brief skecth of the proof is as follows: An event e is defined as a prerecording/postrecording event if e occurs on a process p and p records its state after/before e in seq. A postrecording event may occur after a prerecording event only if the two events occur on different processes. It is shown that a postrecording event can be swapped with an immediately following prerecording event in a sequence without affecting the local states of either of the two processes on which the two events occur. By iteratively applying this operation to seq, the above-described permutation seq ′ is obtained. It is then shown that S ∗ , the global state recorded by the algorithm for the processes and channels, is the state after all the prerecording events have been executed, but before any postrecording event. Thus, the recorded global state is a valid state in an equivalent execution and if a stable property (i.e., a property that persists such as termination or deadlock) holds in the system before the snapshot algorithm begins, it holds in the recorded global snapshot. Therefore, a recorded global state is useful in detecting stable properties. A physical interpretation of the collected global state is as follows: Consider the two instants of recording of the local states in the banking example. If the cut formed by these instants is viewed as being an elastic band and if the elastic band is stretched so that it is vertical, then recorded states of all processes occur simultaneously at one physical instant, and the recorded global state occurs in the execution that is depicted in this modified space-time diagram. This is called the rubber-band 90

criterion. For example, consider the two different executions of the snapshot algorithm, depicted in Figure 4.4. For the execution for which the markers are shown using dashed-and-dotted arrows, the instants of the local state recordings are marked by squares. Applying the rubber-band criterion, these can be stretched to be vertical or instantaneous. Similarly for the other execution for which the markers are shown using dotted arrows and the instants of local state rcordings are marked by circles. Note that the system execution would have been like this, had the processors’ speeds and message delays been different. Yet another physical interpretation of the collected global state is as follows: All the recorded process states are mutually concurrent – no recorded process state causally depends upon another. Therefore, logically we can view that all these process states occurred simultaneously even though they might have occurred at different instants in physical time.

4.4 Variations of the Chandy-Lamport Algorithm Several variants of the Chandy-Lamport snapshot algorithm followed. These variants refined and optimized the basic algorithm. For example, Spezialetti and Kearns algorithm [29] optimizes concurrent initiation of snapshot collection and efficiently distributes the recorded snapshot. Venkatesan’s algorithm [32] optimizes the basic snapshot algorithm to efficiently record repeated snapshots of a distributed system that are required in recovery algorithms with synchronous checkpointing.

4.4.1 Spezialetti-Kearns Algorithm There are two phases in obtaining a global snapshot: locally recording the snapshot at every process and distributing the resultant global snapshot to all the initiators. Spezialetti and Kearns [29] provided two optimizations to the Chandy-Lamport algorithm. The first optimization combines snapshots concurrently initiated by multiple processes into a single snapshot. This optimization is linked with the second optimization which deals with the efficient distribution of the global snapshot. A process needs to take only one snapshot, irrespective of the number of concurrent initiators and all processes are not sent the global snapshot. This algorithm assumes bidirectional channels in the system.

Efficient Snapshot Recording In the Spezialetti-Kearns algorithm, a markers carries the identifier of the initiator of the algorithm. Each process has a variable master to keep track of the initiator of the algorithm. When a process executes the “Marker Sending Rule" on the receipt of its first marker, it records the initiator’s identifier carried in the received marker in the master variable. A process that initiates the algorithm records its own identifier in the master variable. A key notion used by the optimizations is that of a region in the system. A region encompasses all the processes whose master field contains the identifier of the same initiator. A region is identified by the initiator’s identifier. When there are multiple concurrent initiators, the system gets partitioned into multiple regions. When the initiator’s identifier in a marker received along a channel is different from the value in the master variable, a concurrent initiation of the algorithm is detected and the sender of the 91

marker lies in a different region. The identifier of the concurrent initiator is recorded in a local variable id-border-set. The process receiving the marker does not take a snapshot for this marker and does not propagate this marker. Thus, the algorithm efficiently handles concurrent snapshot initiations by suppressing redundant snapshot collections – a process does not take a snapshot or propagate a snapshot request initiated by a process if it has already taken a snapshot in response to some other snapshot initiation. The state of the channel is recorded just as in the Chandy-Lamport algorithm (including those that cross a border between regions). This enables the snapshot recorded in one region to be merged with the snapshot recorded in the adjacent region. Thus, even though markers arriving at a node contain identifiers of different initiators, they are considered part of the same instance of the algorithm for the purpose of channel state recording. Snapshot recording at a process is complete after it has received a marker along each of its channels. After every process has recorded its snapshot, the system is partitioned into as many regions as the number of concurrent initiations of the algorithm. Variable id-border-set at a process contains the identifiers of the neighboring regions.

Efficient Dissemination of the Recorded Snapshot The Spezialetti-Kearns algorithm efficiently assembles the snapshot as follows: In the snapshot recording phase, a forest of spanning trees is implicitly created in the system. The initiator of the algorithm is the root of a spanning tree and all processes in its region belong to its spanning tree. If process pi executed the “Marker Sending Rule" because it received its first marker from process pj , then process pj is the parent of process pi in the spanning tree. When a leaf process in the spanning tree has recorded the states of all incoming channels, the process sends the locally recorded state (local snapshot, id-border-set) to its parent in the spanning tree. After an intermediate process in a spanning tree has received the recorded states from all its child processes and has recorded the states of all incoming channels, it forwards its locally recorded state and the locally recorded states of all its descendent processes to its parent. When the initiator receives the locally recorded states of all its descendents from its children processes, it assembles the snapshot for all the processes in its region and the channels incident on these processes. The initiator knows the identifiers of initiators in adjacent regions using id-borderset information it receives from processes in its region. The initiator exchanges the snapshot of its region with the initiators in adjacent regions in rounds. In each round, an initiator sends to initiators in adjacent regions, any new information obtained from the initiator in the adjacent region during the previous round of message exchange. A round is complete when an initiator receives information, or the blank message (signifying no new information will be forthcoming) from all initiators of adjacent regions from which it has not already received a blank message. The message complexity of snapshot recording is O(e) irrespective of the number of concurrent initiations of the algorithm. The message complexity of assembling and disseminating the snapshot is O(rn2 ) where r is the number of concurrent initiations.

4.4.2 Venkatesan’s Incremental Snapshot Algorithm Many applications require repeated collection of global snapshots of the system. For example, recovery algorithms with synchronous checkpointing need to advance their checkpoints periodically. 92

This can be achieved by repeated invocations of the Chandy-Lamport algorithm. Venkatesan [32] proposed the following efficient approach: Execute an algorithm to record an incremental snapshot since the most recent snapshot was taken and combine it with the most recent snapshot to obtain the latest snapshot of the system. The incremental snapshot algorithm of Venkatesan [32] modifies the global snapshot algorithm of Chandy-Lamport to save on messages when computation messages are sent only on a few of the network channels, between the recording of two successive snapshots. The incremental snapshot algorithm assumes bidirectional FIFO channels, the presence of a single initiator, a fixed spanning tree in the network, and four types of control messages: init_snap, snap_completed, regular, and ack. init_snap and snap_completed messages traverse spanning tree edges. regular and ack messages which serve to record the state of non-spanning edges are not sent on those edges on which no computation message has been sent since the previous snapshot. Venkatesan [32] showed that the lower bound on the message complexity of an incremental snapshot algorithm is Ω(u + n) where u is the number of edges on which a computation message has been sent since the previous snapshot. Venkatesan’s algorithm achieves this lower bound in message complexity. The algorithm works as follows: Snapshots are assigned version numbers and all algorithm messages carry this version number. The initiator notifies all the processes the version number of the new snapshot by sending init_snap messages along the spanning tree edges. A process follows the “Marker Sending Rule" when it receives this notification or when it receives a regular message with a new version number. The “Marker Sending Rule" is modified so that the process sends regular messages along only those channels on which it has sent computation messages since the previous snapshot, and the process waits for ack messages in response to these regular messages. When a leaf process in the spanning tree receives all the ack messages it expects, it sends a snap_completed message to its parent process. When a non-leaf process in the spanning tree receives all the ack messages it expects, as well as a snap_completed message from each of its child processes, it sends a snap_completed message to its parent process. The algorithm terminates when the initiator has received all the ack messages it expects, as well as a snap_completed message from each of its child processes. The selective manner in which regular messages are sent has the effect that a process does not know whether to expect a regular message on an incoming channel. A process can be sure that no such message will be received and that the snapshot is complete only when it executes the “Marker Sending Rule" for the next initiation of the algorithm.

4.4.3 Helary’s Wave Synchronization Method Helary’s snapshot algorithm [12] incorporates the concept of message waves in the Chandy-Lamport algorithm. A wave is a flow of control messages such that every process in the system is visited exactly once by a wave control message, and at least one process in the system can determine when this flow of control messages terminates. A wave is initiated after the previous wave terminates. Wave sequences may be implemented by various traversal structures such as a ring. A process begins recording the local snapshot when it is visited by the wave control message. In Helary’s algorithm, the “Marker Sending Rule" is executed when a control message belonging to the wave flow visits the process. The process then forwards a control message to other processes, depending on the wave traversal structure, to continue the wave’s progression. The “Marker Receiving Rule" is modified so that if the process has not recorded its state when a marker is re93

ceived on some channel, the “Marker Receiving Rule" is not executed and no messages received after the marker on this channel are processed until the control message belonging to the wave flow visits the process. Thus, each process follows the “Marker Receiving Rule" only after it is visited by a control message belonging to the wave. Note that in this algorithm, the primary function of wave synchronization is to evaluate functions over the recorded global snapshot. This algorithm has a message complexity of O(e) to record a snapshot (because all channels need to be traversed to implement the wave). An example of this function is the number of messages in transit to each process in a global snapshot, and whether the global snapshot is strongly consistent. For this function, each process maintains two vectors, SENT and RECD. The ith elements of these vectors indicate the number of messages sent to/received from process i, respectively, since the previous visit of a wave control message. The wave control messages carry a global abstract counter vector whose ith entry indicates the number of messages in transit to process i. These entries in the vector are updated using the SENT and RECD vectors at each node visited. When the control wave terminates, the number of messages in transit to each process as recorded in the snapshot is known.

4.5 Snapshot Algorithms for Non-FIFO Channels A FIFO system ensures that all messages sent after a marker on a channel will be delivered after the marker. This ensures that condition C2 is satisfied in the recorded snapshot if LSi , LSj , and SCij are recorded as described in the Chandy-Lamport algorithm. In a non-FIFO system, the problem of global snapshot recording is complicated because a marker cannot be used to delineate messages into those to be recorded in the global state from those not to be recorded in the global state. In such systems, different techniques have to be used to ensure that a recorded global state satisfies condition C2. In a non-FIFO system, either some degree of inhibition (i.e., temporarily delaying the execution of an application process or delaying the send of a computation message) or piggybacking of control information on computation messages to capture out-of-sequence messages, is necessary to record a consistent global snapshot [31]. The non-FIFO algorithm by Helary uses message inhibition [12]. The non-FIFO algorithms by Lai and Yang [18], Li et al. [20], and Mattern [23] use message piggybacking to distinguish computation messages sent after the marker from those sent before the marker. The non-FIFO algorithm of Helary [12] uses message inhibition to avoid an inconsistency in a global snapshot in the following way: When a process receives a marker, it immediately returns an acknowledgement. After a process pi has sent a marker on the outgoing channel to process pj , it does not send any messages on this channel until it is sure that pj has recorded its local state. Process pi can conclude this if it has received an acknowledgement for the marker sent to pj , or it has received a marker for this snapshot from pj . We next discuss snapshot recording algorithms for systems with non-FIFO channels that use piggybacking of computation messages.

94

4.5.1 Lai-Yang Algorithm Lai and Yang’s global snapshot algorithm for non-FIFO systems [18] is based on two observations on the role of a marker in a FIFO system. The first observation is that a marker ensures that condition C2 is satisfied for LSi and LSj when the snapshots are recorded at processes i and j, respectively. The Lai-Yang algorithm fulfills this role of a marker in a non-FIFO system by using a coloring scheme on computation messages that works as follows: 1. Every process is initially white and turns red while taking a snapshot. The equivalent of the “Marker Sending Rule" is executed when a process turns red. 2. Every message sent by a white (red) process is colored white (red). Thus, a white (red) message is a message that was sent before (after) the sender of that message recorded its local snapshot. 3. Every white process takes its snapshot at its convenience, but no later than the instant it receives a red message. Thus, when a white process receives a red message, it records its local snapshot before processing the message. This ensures that no message sent by a process after recording its local snapshot is processed by the destination process before the destination records its local snapshot. Thus, an explicit marker message is not required in this algorithm and the ‘marker’ is piggybacked on computation messages using a coloring scheme. The second observation is that the marker informs process j of the value of {send(mij )| send(mij ) ∈ LSi } so that the state of the channel Cij can be computed as transit(LSi , LSj ). The Lai-Yang algorithm fulfills this role of the marker in the following way: 4. Every white process records a history of all white messages sent or received by it along each channel. 5. When a process turns red, it sends these histories along with its snapshot to the initiator process that collects the global snapshot. 6. The initiator process evaluates transit(LSi , LSj ) to compute the state of a channel Cij as given below: SCij = white messages sent by pi on Cij − white messages received by pj on Cij = {send(mij )|send(mij ) ∈ LSi } − {rec(mij )|rec(mij ) ∈ LSj }. Condition C2 holds because a red message is not included in the snapshot of the recipient process and a channel state is the difference of two sets of white messages. Condition C1 holds because a white message mij is included in the snapshot of process pj if pj receives mij before taking its snapshot. Otherwise, mij is included in the state of channel Cij . Though marker messages are not required in the algorithm, each process has to record the entire message history on each channel as part of the local snapshot. Thus, the space requirements of the algorithm may be large. However, in applications (such as termination detection) where the number of messages in transit in a channel is sufficient, message histories can be replaced by integer counters reducing the space requirement. Lai and Yang describe how the size of the local 95

storage and snapshot recording can be reduced by storing only the messages sent and received since the previous snapshot recording, assuming that the previous snapshot is still available. This approach can be very useful in applications that require repeated snapshots of a distributed system.

4.5.2 Li et al.’s Algorithm Li et al.’s algorithm [20] for recording a global snapshot in a non-FIFO system is similar to the Lai-Yang algorithm. Markers are tagged so as to generalize the red/white colors of the Lai-Yang algorithm to accommodate repeated invocations of the algorithm and multiple initiators. In addition, the algorithm is not concerned with the contents of computation messages and the state of a channel is computed as the number of messages in transit in the channel. A process maintains two counters for each incident channel to record the number of messages sent and received on the channel and reports these counter values with its snapshot to the initiator. This simplification is combined with the incremental technique to compute channel states, which reduces the size of message histories to be stored and transmitted. The initiator computes the state of Cij as: (the number of messages in Cij in the previous snapshot) + (the number of messages sent on Cij since the last snapshot at process pi ) − (the number of messages received on Cij since the last snapshot at process pj ). Snapshots initiated by an initiator are assigned a sequence number. All messages sent after a local snapshot recording are tagged by a tuple < init_id, MKNO >, where init_id is the initiator’s identifier and MKNO is the sequence number of the algorithm’s most recent invocation by initiator init_id; to insure liveness, markers with tags similar to the above tags are explicitly sent only on all outgoing channels on which no messages might be sent. The tuple < init_id, MKNO > is a generalization of the red/white colors used in Lai-Yang to accommodate repeated invocations of the algorithm and multiple initiators. For simplicity, we explain this algorithm using the framework of the Lai-Yang algorithm. The local state recording is done as described by Rules 1-3 of the Lai-Yang algorithm. A process maintains Input/Output counters for the number of messages sent and received on each incident channel after the last snapshot (by that initiator). The algorithm is not concerned with the contents of computation messages and so the computation of the state of a channel is simplified to computing the number of messages in transit in the channel. This simplification is combined with an incremental technique for computing in-transit messages, also suggested independently by Lai and Yang [18], for reducing the size of the entire message history to be locally stored and to be recorded in a local snapshot to compute channel states. The initiator of the algorithm maintains a variable T RANSITij for the number of messages in transit in the channel from process pi to process pj , as recorded in the previous snapshot. The channel states are recorded as described in Rules 4-6 of the Lai-Yang algorithm. 4. Every white process records a history, as Input and Output counters, of all white messages sent or received by it along each channel after the previous snapshot (by the same initiator). 5. When a process turns red, it sends these histories (i.e., Input and Output counters) along with its snapshot to the initiator process that collects the global snapshot. 6. The initiator process computes the state of channel Cij as follows: SCij = transit(LSi , LSj ) = T RANSITij + (# messages sent on that channel since the last 96

snapshot) − (# messages received on that channel since the last snapshot). If the initiator initiates a snapshot before the completion of the previous snapshot, it is possible that some process may get a message with a lower sequence number after participating in a snapshot initiated later. In this case, the algorithm uses the snapshot with the higher sequence number to also create the snapshot for the lower sequence number. The algorithm works for multiple initiators if separate Input/Output counters are associated with each initiator, and marker messages and the tag fields carry a vector of tuples, with one tuple for each initiator. Though this algorithm does not require any additional message to record a global snapshot provided computation messages are eventually sent on each channel, the local storage and size of tags on computation messages are of size O(n), where n is the number of initiators. The Spezialetti and Kearns technique [29] of combining concurrently initiated snapshots can be used with this algorithm.

4.5.3 Mattern’s Algorithm Mattern’s algorithm [23] is based on vector clocks. Recall that in vector clocks, the clock at a process in an integer vector of length n, with one component for each process. Mattern’s algorithm assumes a single initiator process and works as follows: 1. The initiator “ticks" its local clock and selects a future vector time s at which it would like a global snapshot to be recorded. It then broadcasts this time s and freezes all activity until it receives all acknowledgements of the receipt of this broadcast. 2. When a process receives the broadcast, it remembers the value s and returns an acknowledgement to the initiator. 3. After having received an acknowledgement from every process, the initiator increases its vector clock to s and broadcasts a dummy message to all processes. (Observe that before broadcasting this dummy message, the local clocks of other processes have a value 6≥ s.) 4. The receipt of this dummy message forces each recipient to increase its clock to a value ≥ s if not already ≥ s. 5. Each process takes a local snapshot and sends it to the initiator when (just before) its clock increases from a value less than s to a value ≥ s. Observe that this may happen before the dummy message arrives at the process. 6. The state of Cij is all messages sent along Cij , whose timestamp is smaller than s and which are received by pj after recording LSj . Processes record their local snapshot as per rule (5). Any message mij sent by process pi after it records its local snapshot LSi has a timestamp > s. Assume that this mij is received by process pj before it records LSj . After receiving this mij and before pj records LSj , pj ’s local clock reads a value > s, as per rules for updating vector clocks. This implies pj must have already recorded LSj as per rule (5), which contradicts the assumption. Therefore, mij cannot be received by pj before 97

it records LSj . By rule (6), mij is not recorded in SCij and therefore, condition C2 is satisfied. Condition C1 holds because each message mij with a timestamp less than s is included in the snapshot of process pj if pj receives mij before taking its snapshot. Otherwise, mij is included in the state of channel Cij . The following observations about the above algorithm lead to various optimizations: (i) The initiator can be made a “virtual" process; so, no process has to freeze. (ii) As long as a new higher value of s is selected, the phase of broadcasting s and returning the acks can be eliminated. (iii) Only the initiator’s component of s is used to determine when to record a snapshot. Also, one needs to know only if the initiator’s component of the vector timestamp in a message has increased beyond the value of the corresponding component in s. Therefore, it suffices to have just two values of s, say, white and red, which can be represented using one bit. With these optimizations, the algorithm becomes similar to the Lai-Yang algorithm except for the manner in which transit(LSi , LSj ) is evaluated for channel Cij . In Mattern’s algorithm, a process is not required to store message histories to evaluate the channel states. The state of any channel is the set of all the white messages that are received by a red process on which that channel is incident. A termination detection scheme for non-FIFO channels is required to detect that no white messages are in transit to ensure that the recording of all the channel states is complete. One of the following schemes can be used for termination detection: 1. Each process i keeps a counter cntri that indicates the difference between the number of white messages it has sent and received before recording its snapshot. It reports this value to the initiator process along with its snapshot and forwards all white messages, it receives henceforth, to the initiator. Snapshot collection terminates when the initiator has received P cntr number of forwarded white messages. i i

2. Each red message sent by a process carries a piggybacked value of the number of white messages sent on that channel before the local state recording. Each process keeps a counter for the number of white messages received on each channel. A process can detect termination of recording the states of incoming channels when it receives as many white messages on each channel as the value piggybacked on red messages received on that channel.

The savings of not storing and transmitting entire message histories, over the Lai-Yang algorithm, comes at the expense of delay in the termination of the snapshot recording algorithm and need for a termination detection scheme (e.g., a message counter per channel).

4.6 Snapshots in a Causal Delivery System Two global snapshot recording algorithms, namely, Acharya-Badrinath [1] and Alagar-Venkatesan [2] assume that the underlying system supports causal message delivery. The causal message delivery property CO provides a built-in message synchronization to control and computation messages. Consequently, snapshot algorithms for such systems are considerably simplified. For example, these algorithms do not send control messages (i.e., markers) on every channel and are simpler than the snapshot algorithms for a FIFO system. Several protocols exist for implementing causal ordering [5, 6, 26, 28].

98

4.6.1 Process State Recording Both these algorithms use an identical principle to record the state of processes. An initiator process broadcasts a token, denoted as token, to every process including itself. Let the copy of the token received by process pi be denoted tokeni . A process pi records its local snapshot LSi when it receives tokeni and sends the recorded snapshot to the initiator. The algorithm terminates when the initiator receives the snapshot recorded by each process. These algorithms do not require each process to send markers on each channel, and the processes do not coordinate their local snapshot recordings with other processes. Nonetheless, for any two processes pi and pj , the following property (called Property P1) is satisfied: send(mij ) 6∈ LSi :rec(mij ) 6∈ LSj . This is due to the causal ordering property of the underlying system as explained next. Let a message mij be such that rec(tokeni ) −→ send(mij ). Then send(tokenj ) −→ send(mij ) and the underlying causal ordering property ensures that rec(tokenj ), at which instant process pj records LSj , happens before rec(mij ). Thus, mij whose send is not recorded in LSi , is not recorded as received in LSj . Methods of channel state recording are different in these two algorithms and are discussed next.

4.6.2 Channel State Recording in Acharya-Badrinath Algorithm Each process pi maintains arrays SENTi [1, ...N] and RECDi [1, ..., N]. SENTi [j] is the number of messages sent by process pi to process pj and RECDi [j] is the number of messages received by process pi from process pj . The arrays may not contribute to the storage complexity of the algorithm because the underlying causal ordering protocol may require these arrays to enforce causal ordering. Channel states are recorded as follows: When a process pi records its local snapshot LSi on the receipt of tokeni , it includes arrays RECDi and SENTi in its local state before sending the snapshot to the initiator. When the algorithm terminates, the initiator determines the state of channels in the global snapshot being assembled as follows: 1. The state of each channel from the initiator to each process is empty. 2. The state of channel from process pi to process pj is the set of messages whose sequence numbers are given by {RECDj [i] + 1, . . . , SENTi [j]}. We now show that the algorithm satisfies conditions C1 and C2. Let a message mij be such that rec(tokeni ) −→ send(mij ). Clearly, send(tokenj ) −→ send(mij ) and the sequence number of mij is greater than SENTi [j]. Therefore, mij is not recorded in SCij . Thus, send(mij )6∈LSi : mij 6∈SCij . This in conjunction with Property P1 implies that the algorithm satisfies condition C2. Consider a message mij which is the k th message from process pi to process pj before pi takes its snapshot. The two possibilities below imply that condition C1 is satisfied. • Process pj receives mij before taking its snapshot. In this case, mij is recorded in pj ’s snapshot. 99

• Otherwise, RECDj [i] ≤ k ≤ SENTi [j] and the message mij will be included in the state of channel Cij . This algorithm requires 2n messages and 2 time units for recording and assembling the snapshot, where one time unit is required for the delivery of a message. If the contents of messages in channels state are required, the algorithm requires 2n messages and 2 time units additionally.

4.6.3 Channel State Recording in Alagar-Venkatesan Algorithm A message is referred to as old if the send of the message causally precedes the send of the token. Otherwise, the message is referred to as new. Whether a message is new or old can be determined by examining the vector timestamp in the message, which is needed to enforce causal ordering among messages. In Alagar-Venkatesan algorithm [2], channel states are recorded as follows: 1. When a process receives the token, it takes its snapshot, initializes the state of all channels to empty, and returns Done message to the initiator. Now onwards, a process includes a message received on a channel in the channel state only if it is an old message. 2. After the initiator has received Done message from all processes, it broadcasts a T erminate message. 3. A process stops the snapshot algorithm after receiving a T erminate message. An interesting observation is that a process receives all the old messages in its incoming channels before it receives the T erminate message. This is ensured by the underlying causal message delivery property. Causal ordering property ensures that no new message is delivered to a process prior to the token and only old messages are recorded in the channel states. Thus, send(mij )6∈LSi : mij 6∈SCij . This together with Property P1 implies that condition C2 is satisfied. Condition C1 is satisfied because each old message mij is delivered either before the token is delivered or before the T erminate is delivered to a process and thus gets recorded in LSi or SCij , respectively. A comparison of the salient features of the various snapshot recording algorithms discused is given in Table 4.1.

4.7 Monitoring Global State Several applications such as debugging a distributed program need to detect a system state which is determined by the values of variables on a subset of processes. This state can be expressed as a predicate on variables distributed across the involved processes. Rather than recording and evaluating snapshots at regular intervals, it is more efficient to monitor changes to the variables that affect the predicate and evaluate the predicate only when some component variable changes. Spezialetti and Kearns [30] proposed a technique, called simultaneous regions, for the consistent monitoring of distributed systems to detect global predicates. A process whose local variable is a component of the global predicate informs a monitor whenever the value of the variable changes. 100

Algorithms ChandyLamport [6] SpezialettiKearns [29]

Venkatesan [32]

Helary [12]

Lai-Yang [18]

Li et al. [20] Mattern [23]

AcharyaBadrinath [1] Alagar-Venkatesan [2]

Features Baseline algorithm. Requires FIFO channels. O(e) messages to record snapshot and O(d) time. Improvements over [6]: supports concurrent initiators, efficient assembly and distribution of a snapshot. Assumes bidirectional channels. O(e) messages to record, O(rn2 ) messages to assemble and distribute snapshot. Based on [6]. Selective sending of markers. Provides message-optimal incremental snapshots. Ω(n + u) messages to record snapshot. Based on [6]. Uses wave synchronization. Evaluates function over recorded global state. Adaptable to non-FIFO systems but requires inhibition. Works for non-FIFO channels. Markers piggybacked on computation messages. Message history required to compute channel states. Similar to [18]. Small message history needed as channel states are computed incrementally. Similar to [18]. No message history required. Termination detection (e.g., a message counter per channel) required to compute channel states. Requires causal delivery support, Centralized computation of channel states, Channel message contents need not be known. Requires 2n messages, 2 time units. Requires causal delivery support, Distributed computation of channel states. Requires 3n messages, 3 time units, small messages. Table 4.1: A comparison of snapshot algorithms.

n = # processes, u = # edges on which messages were sent after previous snapshot, e = # channels, d is the diameter of the network, r = # concurrent initiators.

101

This process also coerces other processes to inform the monitor of the values of their variables that are components of the global predicate. The monitor evaluates the global predicate when it receives the next message from each of the involved processes, informing it of the value(s) of their local variable(s). The periods of local computation on each process between the ith and the i + 1st events at which the values of the local component(s) of the global predicate are reported to the monitor are defined to be the i + 1st simultaneous regions. The above scheme is extended to arrange multiple monitors hierarchically to evaluate complex global predicates.

4.8 Necessary and Sufficient Conditions for Consistent Global Snapshots Many applications (such as transparent failure recovery, distributed debugging, monitoring distributed events, setting distributed breakpoints, protocol specification and verification, etc.) require that local process states are periodically recorded and analyzed during execution or post martem. A saved intermediate state of a process during its execution is called a local checkpoint of the process. A global snapshot of a distributed system is a set of local checkpoints one from each process and it represents a snapshot of the distributed computation execution at some instant. A global snapshot is consistent, if there is no causal path between any two distinct checkpoints in the global snapshot. Therefore, a consistent snapshot consists of a set of local states that occurred concurrently or had a potential to occur simultaneously. This condition for the consistency of a global snapshot (that no causal path between any two checkpoints) is only the necessary condition but it is not the sufficient condition. In this section, we present the necessary and sufficient conditions under which a local checkpoint or a set of arbitrary collection of local checkpoints can be grouped with checkpoints at other processes to form a consistent global snapshot. Processes take checkpoints asynchronously. Each checkpoint taken by a process is assigned a unique sequence number. The ith (i ≥ 0) checkpoint of process pp is assigned the sequence number i and is denoted by Cp,i. We assume that each process takes an initial checkpoint before execution begins and takes a virtual checkpoint after execution ends. The ith checkpoint interval of process pp consists of all the computation performed between its (i−1)th and ith checkpoints (and includes the (i − 1)th checkpoint but not ith ).

p1 p2 p3

C 1,0 C 2,0 C 3,0

C 1,2

C 1,1 m1 m

C 2,1 2

C 3,1

m3 m4

C 2,3

C 2,2 C3,2

m6

Legend:

m5 C 3,3

A checkpoint

Figure 4.5: An Illustration of zigzag paths. We first show with the help of an example that even if two local checkpoints do not have a causal path between them (i.e., neither happened before the other using Lamport’s happen before 102

relation), they may not belong to the same consistent global snapshot. Consider the execution shown in Figure 4.5. Although neither of the checkpoints C1,1 and C3,2 happened before the other, they cannot be grouped together with a checkpoint on process p2 to form a consistent global snapshot. No checkpoint on p2 can be grouped with both C1,1 and C3,2 while maintaining the consistency. Because of message m4 , C3,2 cannot be consistent with C2,1 or any earlier checkpoint in p2 , and because of message m3 , C1,1 cannot be consistent with C2,2 or any later checkpoint in p2 . Thus no checkpoint on p2 is available to form a consistent global snapshot with C1,1 and C3,2, . To describe the necessary and sufficient conditions for a consistent snapshot, Netzer and Xu [25] defined a generalization of the Lamport’s happen before relation, called a zigzag path. A checkpoint C1 happens before a checkpoint C2 (or a causal path exists between two checkpoints) if a sequence of messages exists from C1 to C2 such that each message is sent after the previous one in the sequence is received. A zigzag path between two checkpoints is a causal path, however, a zigzag path allows a message to be sent before the previous one in the path is received. For example, in Figure 4.5 although a causal path does not exist from C1,1 to C3,2 , a zigzag path does exist from C1,1 to C3,2 . This zigzag path is formed by messages m3 and m4 . This zigzag path means that no consistent snapshot exists in this execution that contains both C1,1 and C3,2 . Several applications require saving or analyzing consistent snapshots and zigzag paths have implication on such applications. For example, the state from which a distributed computation must restart after a crash must be consistent. Consistency ensures that no process is restarted from a state that has recorded the receipt of a message (called an orphan message) that no other process claims to have sent in the rolled back state. Processes take local checkpoints independently and a consistent global snapshot/checkpoint is found from the local checkpoints for a crash recovery. Clearly, due to zigzag paths, not all checkpoints taken by the processes will belong to a consistent snapshot. By reducing the number of zigzag paths in the local checkpoints taken by processes, one can increase the number of local checkpoints that belong to a consistent snapshot, thus minimizing the roll back necessary to find a consistent snapshot1. This can be achieved by tracking zigzag paths online and allowing each process to adaptively take checkpoints at certain points in the execution so that the number of checkpoints that cannot belong to a consistent snapshot is minimized.

4.8.1 Zigzag Paths and Consistent Global Snapshots In this section, we provide a formal definition of zigzag paths and use zigzag paths to characterize condition under which a set of local checkpoints together can belong to the same consistent snapshot. We then present two special cases: First, the conditions for an arbitrary checkpoint to be useful (i.e., a consistent snapshot exists that contains this checkpoint), and second, the conditions for two arbitrary checkpoints to belong to the same consistent snapshot.

A Zigzag Path Recall that if a global snapshot is consistent, then none of its checkpoints happened before the other (i.e., there is no causal path between any two checkpoints in the snapshot). However, as explained earlier using Figure 4.5, if we have two checkpoints such that none of them happened before the other, still it is not sufficient to ensure that they can belong together to the same consistent snapshot. 1

In the worst case, the system would have to restart its execution right from the beginning after repeated rollbacks.

103

This happens when a zigzag path exists between such checkpoints. A zigzag path is defined as a generalization of Lamport’s happened before relation. Definition 1. A zigzag path exists from a checkpoint Cx,i to a checkpoint Cy,j iff there exists messages m1 , m2 , ...mn (n≥1) such that 1. m1 is sent by process px after Cx,i . 2. If mk (1≤k≤n) is received by process pz , then mk+1 is sent by pz in the same or a later checkpoint interval (although mk+1 may be sent before or after mk is received), and 3. mn is received by process py before Cy,j . For example, in Figure 4.5, a zigzag path exists from C1,1 to C3,2 due to messages m3 and m4 . Even though process p2 sends m4 before receiving m3 , it does these in the same checkpoint interval. However, a zigzag path does not exist from C1,2 to C3,3 (due to messages m5 and m6 ) because process p2 sends m6 and receives m5 in different checkpoint intervals. Definition 2. A checkpoint C is involved in a zigzag cycle iff there is a zigzag path from C to itself. For example, in Figure 4.6, C2,1 is on a zigzag cycle formed by messages m1 and m2 . Note that messages m1 and m2 are respectively sent and received in the same checkpoint interval at p1 .

Difference Between a Zigzag Path and a Causal Path It is important to understand differences between a causal path and a zigzag path. A causal path exists from a checkpoint A to another checkpoint B iff there is chain of messages starting after A and ending before B such that each message is sent after the previous one in the chain is received. A zigzag path consists of such a message chain, however, a message in the chain can be sent before the previous one in the chain is received, as long as the send and receive are in the same checkpoint interval. Thus a causal path is always a zigzag path, but a zigzag path need not be a causal path. Figure 4.5 illustrates the difference between causal and zigzag paths. A causal path exists from C1,0 to C3,1 formed by chain of messages m1 and m2 ; this causal path is also a zigzag path. Similarly, a zigzag path exists from C1,1 to C3,2 formed by the chain of messages m3 and m4 . Since the receive of m3 happened after the send of m4 , this zigzag path is not a causal path and C1,1 does not happen before C3,2 . Another difference between a zigzag path and a causal path is that a zigzag path can form a cycle but a causal path never forms a cycle. That is, it is possible for a zigzag path to exist from a checkpoint back to itself, called a zigzag cycle. In contrast, causal paths can never form cycles. A zigzag path may form a cycle because a zigzag path need not represent causality – in a zigzag path, we allow a message to be sent before the previous message in the path is received as long as the send and receive are in the same interval. Figure 4.6 shows a zigzag cycle involving C2,1 , formed by messages m1 and m2 .

104

p1 p

C 1,1

C 1,0 C 2,0

m1

m3 m2

C 2,1

C 2,2

2

p

C1,2 C 2,3

m C 3,0

4

C 3,1

C3,2

3

Figure 4.6: A zigzag cycle, inconsistent snapshot, and consistent snapshot.

Consistent Global Snapshots Netzer and Xu [25] proved that if no zigzag path (or cycle) exists between any two checkpoints from a set S of checkpoints, then a consistent snapshot can be formed that includes the set S of checkpoints and vice versa. For a formal proof, the readers should consult the original paper. Here we give an intuitive explanation. Intuitively, if a zigzag path exists between two checkpoints, and that zigzag path is also a causal path, then the checkpoints are ordered and hence cannot belong to the same consistent snapshot. If the zigzag path between two checkpoints is not a causal path, a consistent snapshot cannot be formed that contains both the checkpoints. The zigzag nature of the path causes any snapshot that includes the two checkpoints to be inconsistent. To visualize the effect of a zigzag path, consider a snapshot line2 through the two checkpoints. Because of the existance of a zigzag path between the two checkpoints, the snapshot line will always cross a message that causes one of the checkpoints to happen before the other, making the snapshot inconsistent. Figure 4.6 illustrates this. Two snapshot lines are drawn from C1,1 to C3,2 . The zigzag path from C1,1 to C3,2 renders both the snapshot lines to be inconsistent. This is because messages m3 and m4 cross either snapshot line in way that orders the two of its checkpoints. Conversely, if no zigzag path exists between two checkpoints (including zigzag cycles), then it is always possible to construct a consistent snapshot that includes these two checkpoints. We can form a consistent snapshot by including the first checkpoint at every process that has no zigzag path to either checkpoint. Note that messages can cross a consistent snapshot line as long as they do not cause any of the line’s checkpoints to happen before each other. For example, in Figure 4.6, C1,2 and C2,3 can be grouped with C3,1 to form a consistent snapshot even though message m4 crosses the snapshot line. As a summary, • The absence of a causal path between checkpoints in a snapshot corresponds to the necessary condition for a consistent snapshot, and the absence of a zigzag path between checkpoints in a snapshot corresponds to the necessary and sufficient conditions for a consistent snapshot. • A set of checkpoints S can be extended to a consistent snapshot if and only if no checkpoint in S has a zigzag path to any other checkpoint in S. 2

A snapshot line is a line drawn through a set of checkpoints.

105

• A checkpoint can be a part of a consistent snapshot if and only if it is not invloved in a Z-cycle.

4.9 Finding Consistent Global Snapshots in a Distributed Computation We now address the problem to determine how individual local checkpoints can be combined with those from other processes to form global snapshots that are consistent. A solution to this problem forms the basis for many algorithms and protocols that must record on-the-fly consistent snapshots or determine post-mortem which global snapshots are consistent. Netzer and Xu [25] proved the necessary and sufficient conditions to construct a consistent snapshot from a set of checkpoints S. However, they did not define the set of possible consistent snapshots and did not present an algorithm to construct them. Manivannan-Netzer-Singhal analyzed the set of all consistent snapshots that can be built from a set of checkpoints S. They proved exactly which sets of local checkpoints from other processes can be combined with those in S to form a consistent snapshot. They also developed an algorithm that enumerates all such consistent snapshots. We define the following notations due to Wang [33, 34]. Definition 3. Let A, B be individual checkpoints and R, S be sets of checkpoints. Let ❀ be a relation defined over checkpoints and sets of checkpoints such that 1. A ❀ B iff a Z-path exists from A to B, 2. A ❀ S iff a Z-path exists from A to some member of S, 3. S ❀ A iff a Z-path exists from some member of S to A, and 4. R ❀ S iff a Z-path exists from some member of R to some member of S. S 6❀ S defines that no Z-path (including Z-cycle) exists from any member of S to any other member of S and implies that checkpoints in S are all from different processes. Using the above notations, the results of Netzer and Xu can be expressed as follows: Theorem 1. A set of checkpoints S can be extended to a consistent global snapshot if and only if S 6❀ S. Corollary 1. A checkpoint C can be part of a consistent global snapshot if and only if it is not involved in a Z-cycle. Corollary 2. A set of checkpoints S is a consistent global snapshot if and only if S 6❀ S and |S| = N, where N is the number of processes.

4.9.1 Finding Consistent Global Snapshots We now discuss exactly which consistent snapshots can be built from a set of checkpoints S. We also present an algorithm to enumerate these consistent snapshots. 106

Extending S to a Consistent Snapshot Given a set S of checkpoints such that S 6❀ S, we first discuss what checkpoints from other processes can be combined with S to build a consistent global snapshot. The result is based on the following three observations. First Observation: None of the checkpoints that have a Z-path to or from any of the checkpoints in S can be used. This is because from Theorem 1, no checkpoints between which a Z-path exists can ever be part of a consistent snapshot. Thus, only those checkpoints that have no Z-paths to or from any of the checkpoints in S are candidates for inclusion in the consistent snapshot. We call the set of all such candidates the Z-cone of S. Similarly, we call the set of all checkpoints that have no causal path to or from any checkpoint in S the C-cone of S. 3 The Z-cone and C-cone help us reason about orderings and consistency. Since a causal path is always Z-path, the Z-cone of S is a subset of the C-cone of S for an arbitrary S, as shown in Figure 4.7. Note that if a Z-path exists from checkpoint Cp,i in process pp to a checkpoint in S, then a Z-path also exists from every checkpoint in pp preceding Cp,i to the same checkpoint in S (because Z-paths are transitive). Likewise, if a Z-path exists from a checkpoint in S to a checkpoint Cq,j in process pq , then a Z-path also exists from the same checkpoint in S to every checkpoint in pq following Cq,j . Causal paths are also transitive and similar results hold for them. S

Edge of C − cone

Z − paths to S Casual paths to S

Edges of Z − cone

Z − unordered with S ( Z − cone) Casually unordered with S (C − cone)

Edge of C − cone

Z − paths from S Casual paths from S

Figure 4.7: The Z-cone and the C-cone associated with a set of checkpoints S. Second Observation: Although candidates for building a consistent snapshot from S must lie 3

These terms are inspired by the so-called light cone of an event e which is the set of all events with causal paths from e (i.e., events in e’s future). Although the light cone of e contains events ordered after e, we define the Z-cone and C-cone of S to be those events with no zigzag or causal ordering, respectively, to or from any member of S.

107

in the Z-cone of S, not all checkpoints in the Z-cone can form a consistent snapshot with S. From Corollary 1, if a checkpoint in the Z-cone is involved in a Z-cycle, then it cannot be part of a consistent snapshot. Lemma 2 below states that if we remove from consideration all checkpoints in the Z-cone that are involved in Z-cycles, then each of the remaining checkpoints can be combined with S to build a consistent snapshot. First we define the set of useful checkpoints with respect to set S. Definition 4. Let S be a set of checkpoints such that S 6❀ S. Then, for each process pq , the set q Susef ul is defined as q Susef ul = {Cq,i | (S 6❀ Cq,i ) ∧ (Cq,i 6❀ S) ∧ (Cq,i 6❀ Cq,i )}.

In addition, we define Susef ul =

[

q Susef ul .

q

Thus, with respect to set S, a checkpoint C is useful if C does not have a zigzag path to any checkpoint in S, no checkpoint in S has a zigzag path to C, and C is not on a Z-cycle. Lemma 2. Let S be a set of checkpoints such that S 6❀ S. Let Cq,i be any checkpoint of process pq such that Cq,i 6∈ S. Then S ∪ {Cq,i } can be extended to a consistent snapshot if and only if Cq,i ∈ Susef ul . We omit the proof of the Lemma and interested readers can refer to the original paper for a proof. Lemma 2 states that if we are given a set S such that S 6❀ S, we are guaranteed that any single checkpoint from Susef ul can belong to a consistent global snapshot that also contains S. Third Observation: However, if we attempt to build a consistent snapshot from S by choosing a subset T of checkpoints from Susef ul to combine with S, there is no guarantee that the checkpoints in T have no Z-paths between them. In other words, although none of the checkpoints in Susef ul has a Z-path to or from any checkpoint in S, Z-paths may exist between members of Susef ul . Therefore, we place one final constraint on the set T we choose from Susef ul to build a consistent snapshot from S: checkpoints in T must have no Z-paths between them. Furthermore, since S 6❀ S, from Theorem 1, at least one such T must exist. Theorem 3. Let S be a set of checkpoints such that S 6❀ S and let T be any set of checkpoints such that S ∩ T = ∅. Then, S ∪ T is a consistent global snapshot if and only if 1. T ⊆ Susef ul , 2. T 6❀ T , and 3. |S ∪ T | = N. We omit the proof of the Theorem and interested readers can refer to the original paper for a proof.

108

4.9.2 Manivannan-Netzer-Singhal Algorithm for Enumerating Consistent Snapshots In the previous section, we showed which checkpoints can be used to extend a set of checkpoints S to a consistent snapshot. We now present an algorithm due to Manivannan-Netzer-Singhal that explicitly computes all consistent snapshots that include a given set a set of checkpoints S. The algorithm restricts its selection of checkpoints to those within the Z-cone of S and it checks for the presence of Z-cycles within the Z-cone. In the next section, we discuss how to detect Z-cones and Z-paths using a graph by Wang [33, 34],

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:

ComputeAllCgs(S) { let G = ∅ if S 6❀ S then let AllP rocs be the set of all processes not represented in S ComputeAllCgsF rom(S, AllP rocs) return G } ComputeAllCgsF rom(T, P rocSet) { if (P rocSet = ∅) then G = G ∪ {T } else let pq be any process in P rocSet q for each checkpoint C ∈ Tusef ul do ComputeAllCgsF rom(T ∪ {C}, P rocSet \ {pq }) } Figure 4.8: Algorithm for computing all consistent snapshots containing S.

The algorithm is shown in Figure 4.8 and it computes all consistent snapshots that include a given set S. The function ComputeAllCgs(S) returns the set of all consistent checkpoints that contain S. The heart of the algorithm is the function ComputeAllCgsF rom(T, P rocSet) which extends a set of checkpoints T in all possible consistent ways, but uses checkpoints only from processes in the set P rocSet. After verifying that S 6❀ S, ComputeAllCgs calls ComputeAllCgsF rom, passing a P rocSet consisting of the processes not represented in S (lines 2–5). The resulting consistent snapshots are collected in the global variable G which is returned (line 6). It is worth nothing that if S = ∅, the algorithm computes all consistent snapshots that exist in the execution. The recursive function ComputeAllCgsF rom(T, P rocSet) works by choosing any process q from P rocSet, say pq , and iterating through all checkpoints C in Tusef ul . From Lemma 2, each such checkpoint extends T toward a consistent snapshot. This means T ∪ C can itself be further extended, eventually arriving at a consistent snapshot. Since this further extension is simply another instance of constructing all consistent snapshots that contain checkpoints from a given set, we make a recursive call (line 14), passing T ∪C and a P rocSet from which process pq is removed. The recursion eventually terminates when the passed set contains checkpoints from all processes 109

(i.e., P rocSet is empty). In this case T is a global snapshot, as it contains one checkpoint from every process, and it is added to G (line 10). When the algorithm terminates, all candidates in Susef ul have been used in extending S, so G contains all consistent snapshots that contain S. The following theorem argues the correctness of the algorithm. Theorem 4. Let S be a set of checkpoints and G be the set returned by ComputeAllCgs(S). If S 6❀ S, then T ∈ G if and only if T is a consistent snapshot containing S. That is, G contains exactly the consistent snapshots that contain S. We omit the proof of the theorem and interested readers can refer to the original paper for a proof.

4.9.3 Finding Z-paths in a Distributed Computation Tracking Z-paths on-the-fly is difficult and remains an open problem. We describe a method for determining the existence of Z-paths between checkpoints in a distributed computation that has terminated or has stopped execution, using the rollback-dependency graph (R-graph) introduced by Wang [33, 34]. First, we present the definition of an R-graph. Definition 5. The rollback-dependency graph of a distributed computation is a directed graph G = (V, E), where the vertices V are the checkpoints of the distributed computation and an edge (Cp,i, Cq,j ) from checkpoint Cp,i to checkpoint Cq,j belongs to E if 1. p = q and j = i + 1, or 2. p 6= q and a message m sent from the ith checkpoint interval of pp is received by pq in its j th checkpoint interval (i, j > 0).

Construction of an R-graph When a process pp sends a message m in its ith checkpoint interval, it piggybacks the pair (p, i) with the message. When the receiver pq receives m in its j th checkpoint interval, it records the existence of an edge from Cp,i to Cq,j . When a process wants to construct the R-graph for finding Z-paths between checkpoints, it broadcasts a request message to collect the existing direct dependencies from all other processes and constructs the complete R-graph. We assume that each process stops execution after it sends a reply to the request so that additional dependencies between checkpoints are not formed while the R-graph is being constructed. For each process, a volatile checkpoint is added; the volatile checkpoint represents the volatile state of the process [33, 34].

Example of an R-graph Figure 4.10 shows the R-graph of the computation shown in Figure 4.9. In Figure 4.10, C1,3 , C2,3 , and C3,3 represent the volatile checkpoints, the checkpoints representing the last state the process attained before terminating. rd We denote the fact that there is a path from C to D in the R-graph by C ❀ D. It only denotes the existence of a path; it does not specify any particular path. For example, in Figure 4.10, 110

C 1,0

p1

C 1,2

C 1,1 m1

C 2,0

p2

m

C 3,0

p3

m3

C 2,1

m4 C 2,2 m5

C 3,1

2

C3,2

m6

Figure 4.9: A distributed computation. C

C

C

C 1,1

1,0

C

2,0

C

1,2

1,3

C 2,3

2,1

Volatile checkpoints

C 2,2

C

3,0

C 3,1

C 3,2

C 3,3

Figure 4.10: The R-graph of the computation in Figure 4.9. rd

C1,0 ❀ C3,2 . When we need to specify a particular path, we give the sequence of checkpoints that constitute the path. For example, (C1,0 , C1,1, C1,2 , C2,1 , C3,1 , C3,2 ) is a path from C1,0 to C3,2 and (C1,0 , C1,1 , C1,2 , C2,1 , C2,2, C2,3 , C3,2 ) is also a path from C1,0 to C3,2 . The following theorem establishes the correspondence between the paths in the R-graph and the Z-paths between checkpoints. This correspondence is very useful in determining whether or not a Z-path exists between two given checkpoints. Theorem 5. Let G = (V, E) be the R-graph of a distributed computation. Then, for any two checkpoints Cp,i and Cq,j , Cp,i ❀ Cq,j if and only if 1. p = q and i < j, or rd

2. Cp,i+1 ❀ Cq,j in G (note that in this case p could still be equal to q). For example, in the distributed computation shown in Figure 4.9, a zigzag path exists from C1,1 rd to C3,1 because in the corresponding R-graph, shown in Figure 4.10, C1,2 ❀ C3,1 . Likewise, C2,1 rd is on a Z-cycle because in the corresponding R-graph, shown in Figure 4.10, C2,2 ❀ C2,1 .

4.10 Chapter Summary Recording global state of a distributed system is an important paradigm in the design of the distributed systems and the design of efficient methods of recording the global state is an important 111

issue. Recording of global state of a distributed system is complicated due to the lack of both a globally shared memory and a global clock in a distributed system. This chapter first presented a formal definition of the global state of a distributed system and exposed issues related to its capture; it then described several algorithms to record a snapshot of a distributed system under various communication models. Table 4.1 gives a comparison of the salient features of the various snapshot recording algorithms. Clearly, the higher the level of abstraction provided by a communication model, the simpler the snapshot algorithm. However, there is no best performing snapshot algorithm and an appropriate algorithm can be chosen based on the application’s requirement. For examples, for termination detection, a snapshot algorithm that computes a channel state as the number of messages is adequate; for checkpointing for recovery from failures, an incremental snapshot algorithm is likely to be the most efficient; for global state monitoring, rather than recording and evaluating complete snapshots at regular intervals, it is more efficient to monitor changes to the variables that affect the predicate and evaluate the predicate only when some component variable changes. As indicated in the introduction, the paradigm of global snapshots finds a large number of applications (such as detection of stable properties, checkpointing, monitoring, debugging, analyses of distributed computation, discarding of obsolete information). Moreover, in addition to the problems they solve, the algorithms presented in this chapter are of great importance to people interested in distributed computing as these algorithms illustrate the incidence of properties of communication channels (FIFO, non-FIFO, causal ordering) on the design of a class of distributed algorithms. We also discussed the necessary and sufficient conditions for consistent snapshots. The noncausal path between checkpoints in a snapshot corresponds to the necessary condition for consistent snapshot, and the non-zigzag path corresponds to the necessary and sufficient conditions for consistent snapshot. Tracking of zigzag path is helpful in forming a global consistent snapshot. The avoidance of zigzag path between any pair of checkpoints from a collection of checkpoints (snapshot) is the necessary and sufficient conditions for a consistent global snapshot. Avoidance of causal paths alone will not be sufficient for consistency. We also presented an algorithm for finding all consistent snapshots containing a given set S of local checkpoints; if we take S = ∅, then the algorithm gives the set of all consistent snapshots of a distributed computation run. We established the correspondence between the Z-paths and the paths in the R-graph which helps in finding the existence of Z-paths between checkpoints.

4.11 Bibliographic Notes The notion of a global state in a distributed system was formalized by Chandy and Lamport [6] who also proposed the first algorithm (CL) for recording the global state, and first studied the various properties of the recorded global state. The space-time diagram which is a very useful graphical tool to visualize distributed executions was introduced by Lamport [19]. A detailed survey of snapshot recording algorithms is given by Kshemkalyani, Raynal, and Singhal [16]. Spezialetti and Kearns proposed a variant of the CL algorithm to optimize concurrent initiations by different processes, and to efficiently distribute the recorded snapshot [29]. Venkatesan proposed a variant that handles repeated snapshots efficiently [32]. Helary proposed a variant of the CL algorithm to incorporate message waves in the algorithm [12]. Helary’s algorithm is adapt112

able to a system with non-FIFO channels but requires inhibition [31]. Besides Helary’s algorithm [12], the algorithms proposed by Lai and Yang [18] Li, Radhakrishnan, and Venkatesh [20], and by Mattern [23] can all record snapshots in systems with non-FIFO channels. If the underlying network can provide causal order of message delivery [5], then the algorithms by Acharya and Badrinath [1] and by Alagar and Venkatesan [2] can record the global state using O(n) number of messages. The notion of simultaneous regions for monitoring global state was proposed by Spezialetti and Kearns [30]. The necessary and sufficient conditions for consistent global snapshots were formulated by Netzer and Xu [25] based on the zigzag paths. These have particular application in checkpointing and recovery. Manivannan, Netzer, and Singhal analyzed the set of all consistent snaspshots that can be built from a given set of checkpoints [21]. They also proposed an algorithm to enumerate all such consistent snapshots. The definition of the R − graph and other notations and framework used by [21] were proposed by Wang [33, 34]. Recording the global state of a distributed system finds applications at several places in distributed systems. For applications in detection of stable properties such as deadlocks, see [17] and for termination, see [22]. For failure recovery, a global state of the distributed system is periodically saved and recovery from a processor failure is done by restoring the system to the last saved global state [15]. For debugging distributed software, the system is restored to a consistent global state [8, 9] and the execution resumes from there in a controlled manner. A snapshot recording method has been used in the distributed debugging facility of Estelle [13, 11], a distributed programming environment. Other applications include monitoring distributed events [30], setting distributed breakpoints [24], protocol specification and verification [4, 10, 14], and discarding obsolete information [12]. We will study snapshot algorithms for shared memory in the Distributed Shared Memory Chapter.

4.12 Exercise Problems 1. Consider the following simple method to collect a global snapshot (it may not always collect a consistent global snapshot): Initiator process takes its snapshot and broadcasts a request to take snapshot. When some other process receives this request, it takes a snapshot. Channels are not FIFO. Prove that such a collected distributed snapshot will be consistent iff the following holds (assume there are n processes in the system and V ti denotes the vector timestamp of the snapshot taken process pi ): (V t1 [1], V t2 [2], ...., V tn [n])= max(V t1 , V t2 , ...., V tn ) Don’t worry about channel states. 2. What good is a distributed snapshot when the system was never in the state represented by the distributed snapshot? Give an application of distributed snapshots. 3. Consider a distributed system where every node has its physical clock and all physical clocks are perfectly synchronized. Give an algorithm to record global state assuming the commu113

nication network is reliable. (Note that your algorithm should be simpler than the ChandyLamport algorithm.) 4. What modifications should be done to the Chandy-Lamport snapshot algorithm so that it records a strongly consistent snapshot (i.e., all channel states are recorded empty). 5. Consider two consistent cuts whose events are denoted by C1 = C1 (1), C1(2), ..., C1 (n) and C2 = C2 (1), C2 (2), ..., C2(n), respectively. Define a third cut, C3 = C3 (1), C3 (2), ..., C3(n) that is the maximum of C1 and C2 ; that is, for every k, C3 (k) = later of C1 (k) and C2 (k). Define a fourth cut, C4 = C4 (1), C4 (2), ..., C4(n) that is the minimum of C1 and C2 ; that is, for every k, C4 (k) = earlierof C1 (k) and C2 (k). Prove that C3 and C4 are also consistent cuts.

114

Bibliography [1] A. Acharya, B. R. Badrinath, Recording Distributed Snapshots Based on Causal Order of Message Delivery, Information Processing Letters, North Holland, 44, 317-321, 1992. [2] S. Alagar, S. Venkatesan, An Optimal Algorithm for Distributed Snapshots with Causal Message Ordering, Information Processing Letters, vol 50, 1994, pp. 311-316. [3] O. Babaoglu, K. Marzullo, Consistent Global States of Distributed Systems: Fundamental Concepts and Mechanisms, Distributed Systems, ACM Press (editor S.J. Mullender), Chapter 4, 1993. [4] O. Babaoglu, Raynal M., Specification and verification of dynamic properties in distributed computations, Journal of Parallel and Distributed Systems, Vol. 28(2), 1995. [5] K. Birman, T. Joseph, Reliable Communication in Presence of Failures, ACM Transactions on Computer Systems, 47-76, 3, 1987. [6] K. Birman, A. Schiper, P. Stephenson, Lightweight Causal and Atomic Group Multicast, ACM Transactions on Computer Systems, 272-314, 9(3), 1991. [7] K.M. Chandy, L. Lamport, Distributed Snapshots: Determining Global States of Distributed Systems, ACM Transactions on Computer Systems, 63-75, 3(1), 1985. [8] R. Cooper and K. Marzullo, Consistent Detection of Global Predicates, Proc. of ACM/ONR Workshop on Parallel and Distributed Debugging, May 1991, pp. 163-173. [9] E. Fromentin, Plouzeau N., Raynal M., An introduction to the analysis and debug of distributed computations, Proc. 1st IEEE Int. Conf. on Algorithms and Architectures for Parallel Processing, Brisbane, Australia, April 1995, pp.545-554. [10] K. Geihs, M. Seifert, Automated Validation of a Cooperation Protocol for Distributed Systems, Proceedings of the 6th International Conference on Distributed Computing Systems, 436-443, 1986. [11] O. Gerstel, Hurfin M., Plouzeau N., Raynal M., Zaks S., On-the-fly replay: a practical paradigm and its implementation for distributed debugging, Proc. 6th IEEE Int. Symposium on Parallel and Distributed Debugging, pages 266-272, Dallas, TX, Oct. 1995. 115

[12] J.-M., Helary, Observing Global States of Asynchronous Distributed Applications, Proceedings of the 3rd International Workshop on Distributed Algorithms, LNCS 392, Springer Verlag, 124-134, 1989. [13] M. Hurfin, Plouzeau N., Raynal M., A debugging tool for distribted Estelle programs, Journal of Computer Communications, Vol. 16 (5), 328-333, may 1993. [14] J. Kamal and M. Singhal, Specification and Verification of Distributed Mutual Exclusion Algorithms, Tech. Report, Dept. of Computer and Information Science, The Ohio State University, Columbus, Fall 1992. [15] R. Koo and S. Toueg, Checkpointing and Rollback-Recovery in Distributed Systems, IEEE Trans. on Software Engineering, January 1987. [16] A. Kshemkalyani, M. Raynal, and M. Singhal. “Global Snapshots of a Distributed System”, Distributed Systems Engineering Journal, Vol 2, No 4, December 1995, pp. 224-233. [17] A. Kshemkalyani and M. Singhal, Efficient Detection and Resolution of Generalized Distributed Deadlocks, IEEE Trans. on Software Engineering, January 1994, Vol 20, No. 1, pp. 43-54. [18] T. H. Lai, T. H. Yang, On Distributed Snapshots, Information Processing Letters, North Holland, 25, 153-158, 1987. [19] L. Lamport, Time, Clocks, and the Ordering of Events in a Distributed System, Communications of the ACM, 558-565, 21(7), July 1978. [20] H. F. Li, T. Radhakrishnan, K. Venkatesh, Global State Detection in Non-FIFO Networks, Proceedings of the 7th International Conference on Distributed Computing Systems, 364-370, 1987. [21] D. Manivannan, R.H.B Netzer, and M. Singhal, Finding Consistent Global Checkpoints in a Distributed Computation, IEEE Trans. of Parallel and Distributed Systems, June 1997, pp. 623-627. [22] F. Mattern, Algorithms for Distributed Termination Detection, Distributed Computing, pp. 161-175, 1987. [23] F. Mattern, Efficient Algorithms for Distributed Snapshots and Global Virtual Time Approximation, Journal of Parallel and Distributed Computing, 18, 423-434, 1993. [24] B. Miller, J. Choi, Breakpoints and Halting in Distributed Programs, Proceedings of the 8th International Conference on Distributed Computing Systems, 316-323, 1988.

116

[25] Robert H. B. Netzer, Jian Xu: Necessary and Sufficient Conditions for Consistent Global Snapshots, IEEE Transactions on Parallel and Distributed Systems, vol. 6, Issue 2 (February 1995), pg 165-169. [26] M. Raynal, A. Schiper, S. Toueg, Causal Ordering Abstraction and A Simple Way to Implement It, Information Processing Letters, 343-350, Sept. 1989. [27] S. Sarin, N. Lynch, Discarding Obsolete Information in a Replicated Database System, IEEE Transactions on Software Engineering, Volume 13, Issue 1, January 1987, (Special issue on distributed systems) pp 39 - 47. [28] A. Schiper, J. Eggli, A. Sandoz, A New Algorithm to Implement Causal Ordering, Proceedings of the 3rd International Workshop on Distributed Algorithms, LNCS 392, Springer Verlag, 219-232, 1989. [29] M. Spezialetti, P. Kearns, Efficient Distributed Snapshots, Proceedings of the 6th International Conference on Distributed Computing Systems, 382-388, 1986. [30] M. Spezialetti, P. Kearns, Simultaneous Regions: A Framework for the Consistent Monitoring of Distributed Systems, Proceedings of the 9th International Conference on Distributed Computing Systems, 61-68, 1989. [31] K. Taylor, The Role of Inhibition in Consistent Cut Protocols, Proceedings of the 3rd International Workshop on Distributed Algorithms, LNCS 392, Springer Verlag, 124-134, 1989. [32] S. Venkatesan, Message-Optimal Incremental Snapshots, Journal of Computer and Software Engineering, Vol. 1, No. 3, pp 211-231, 1993. [33] Yi-Min Wang. “Maximum and Minimum Consistent Global Checkpoints and their Applications”. In Proceedings of the 14th IEEE Symposium on Reliable Distributed Systems, pages 86–95, Bad Neuenahr, Germany, September 1995. [34] Yi-Min Wang. “Consistent Global Checkpoints That Contain a Given Set of Local Checkpoints”. In IEEE Transactions on Computers, 1997.

117

Chapter 5 Terminology and Basic Algorithms In this chapter, we first study a methodical framework in which distributed algorithms can be classified and analyzed. We then consider some basic distributed graph algorithms. We then study synchronizers, which provide the abstraction of a synchronous system over an asynchronous system. Lastly, we look at some practical graph problems, to appreciate the necessity of designing efficient distributed algorithms.

5.1 Topology Abstraction and Overlays The topology of a distributed system can be typically viewed as an undirected graph in which the nodes represent the processors and the edges represent the links connecting the processors. Weights on the edges can represent some cost function we need to model in the application. There are usually three (not necessarily distinct) levels of topology abstraction that are useful in analyzing the distributed system or a distributed application. These are now described using Figure 5.1. To keep the figure simple, only the relevant end hosts participating in the application are shown. The WANs are indicated by ovals drawn using dashed lines. The switching elements inside this WAN, and other end hosts that are not participating in the application, are not shown even though they belong to the physical topological view. Similarly, all the edges connecting all end hosts and all edges connecting to all the switching elements inside the WAN also belong to the physical topology view even though only some edges are shown. • Physical topology. The nodes of this topology represent all the network nodes, including switching elements (also called routers) in the WAN and all the end hosts – irrespective of whether the hosts are participating in the application. The edges in this topology represent all the communication links in the WAN in addition to all the direct links between the end hosts. In Figure 5.1(a), the physical topology is not shown explicitly to keep the figure simple. • Logical topology. This is usually defined in the context of a particular application. The nodes represent all the end hosts where the application executes. The edges in this topology 118

WAN WAN

WAN WAN

participating process(or)

WAN or other network

(a)

(b)

Figure 5.1: Two examples of topological views at different levels of abstraction. are logical channels (also termed as logical links) among these nodes. This view is at a higher level of abstraction than that of the physical topology, and the nodes and edges of the physical topology need not be included in this view. Often, logical links are modeled between particular pairs of end hosts participating in an application to give a logical topology with useful properties. Figure 5.1(b) shows each pair of nodes in the logical topology is connected to give a fully connected network. Each pair of nodes can communicate directly with each other participant in the application using an incident logical link at this level of abstraction of the topology. However, the logical links may also define some arbitrary connectivity (neighborhood-relation) on the nodes in this abstract view. In Figure 5.1(a), the logical view provides each node with a parital view of the topology, and the connectivity provided is some neighborhood connectivity. To communicate with another application node that is not a logical neighbour, a node may have to use a multi-hop path composed of logical links at this level of abstraction of the topology. While the fully connected logical topology in Figure 5.1(b) provides a complete view of the system, updating such a view in a dynamic system incurs an overhead. Neighbourhoodbased logical topologies as in Figure 5.1(a) are easier to manage. We will consider distributed algorithms on logical topologies in this book, Peer-to-peer (P2P) networks that we shall study in Chapter 18 are also defined by a logical topology at the application layer. However, the emphasis of P2P networks is on self-organizing networks with built-in functions, e.g., the implementation of application layer functions such as object lookup and location in a distributed manner. • Superimposed topology. This is a higher-level topology that is superimposed on the logical topology. It is usually a regular structure such as a tree, ring, mesh, or hypercube. The main reason behind defining such a topology is that it provides a specialized path for efficient information dissemination and/or gathering as part of a distributed algorithm.

119

Consider the problem of collecting the sum of variables, one from each node. This can be efficiently solved using n messages by circulating a cumulative counter on a logical ring, or using n − 1 messages on a logical tree. The ring and tree are examples of superimposed topologies on the underlying logical topology – which may be arbirtrary as in Figure 5.1(a) or which may be fully connected as in Figure 5.1(b). We will encounter various examples of these topologies, A superimposed topology is also termed as a topology overlay. This latter term is becoming increasingly popular with the spread of the peer-to-peer computing paradigm. Notation: Whatever the level of topological view we are dealing with, we assume that an undirected graph (N, L) is used to represent the topology. The notation n = |N| and l = |L| will also be used.

5.2 Classifications and Basic Concepts 5.2.1 Application Executions and Control Algorithm Executions The distributed application execution is comprised of the execution of instructions, including the communication instructions, within the distributed application program. The application execution represents the logic of the application. In many cases, a control algorithm also needs to be executed in order to monitor the application execution or to perform various auxiliary functions. The control algorithm performs functions such as: creating a spanning tree, creating a connected dominating set, achieving consensus among the nodes, distributed transaction commit, distributed deadlock detection, global predicate detection, termination detection, global state recording, checkpointing, and also memory consistency enforcement in distributed shared memory systems. The code of the control algorithm is allocated its own memory space. The control algorithm execution is superimposed on the underlying application execution, but does not interfere with the application execution. In other words, the control algorithm execution including all its send, receive, and internal events are transparent to (or not visible by) the application execution. The distributed control algorithm is also sometimes termed as a protocol; although the term protocol is also loosely used for any distributed algorithm. In the literature on formal modeling of network algorithms, the term protocol is more commonly used.

5.2.2 Centralized and Distributed Algorithms In a distributed system, a centralized algorithm is one in which a predominant amount of work is performed by one (or possibly a few) processors, whereas other processors play a relatively smaller role in accomplishing the joint task. The roles of the other processors are usually confined to requesting information or supplying information, either periodically or when queried. A typical system configuration suited for centralized algorithms is the client-server configuration. Presently, much commercial software is written using this configuration, and is adequate. 120

From a theoretical perspective, the single server is a potential bottleneck for both processing and bandwidth access on the links. The single server is also a single point of failure. Of course, these problems are alleviated in practice by using replicated servers distributed across the system, and then the overall configuration is not as centralized any more. A distributed algorithm is one in which each processor plays an equal role in sharing the message overhead, time overhead, and space overhead. It is difficult to design a purely distributed algorithm (that is also efficient) for some applications. Consider the problem of recording a global state of all the nodes. The well-known Chandy-Lamport algorithm which we will study in Chapter ?? is distributed - yet one node, which is typically the initiator, is responsible for assembling the local states of the other nodes, and hence plays a slightly different role. Algorithms that are designed to run on a logical-ring superimposed topology tend to be fully distributed to exploit the symmetry in the connectivity. Algorithms that are designed to run on the logical tree and other asymmetric topologies with a predesignated root node tend to have some asymmetry that mirrors the asymmetric topology. Although fully distributed algorithms are ideal, partly distributed algorithms are sometimes more practical to implement in real systems. At any rate, the advances in peer-to-peer networks, ubiquitous and ad-hoc networks, mobile systems will require distributed solutions.

5.2.3 Symmetric and Asymmetric Algorithms A symmetric algorithm is an algorithm in which all the processors execute the same logical functions. An asymmetric algorithm is an algorithm in which different processors execute logically different (but perhaps partly overlapping) functions. A centralized algorithm is always asymmetric. An algorithm that is not fully distributed is also asymmetric. In the client-server configuration, the clients and the server execute asymmetric algorithms. Similarly, in a tree configuration, the root and the leaves usually perform some functions that are different from each other, and that are different from the functions of the internal nodes of the tree. Applications where there is inherent asymmetry in the roles of the cooperating processors will necessarily have asymmetric algorithms. A typical example is where one processor initiates the computation of some global function (e.g., min, sum).

5.2.4 Anonymous Algorithms An anonymous system is a system in which neither processes nor processors use their process identifiers and processor identifiers to make any execution decisions in the distributed algorithm. An anonymous algorithm is an algorithm which runs on an anonymous system and therefore does not use process identifiers or processor identifiers in the code. An anonymous algorithm possesses structural elegance. However, it is equally hard, and sometimes provably impossible, to design – as in the case of designing an anonymous leader election algorithm on a ring. If we examine familiar examples of multiprocess algorithms, such as the famous Bakery algorithm for mutual exclusion in a shared memory system, or the “wait-wound” 121

or “wound-die” algorithms used for transaction serializability in databases, we observe that the process identifier is used in resolving ties or contentions that are otherwise unresolved despite the symmetric and noncentralized nature of the algorithms.

5.2.5 Uniform Algorithms A uniform algorithm is an algorithm which does not use n, the number of processes in the system, as a parameter in its code. A uniform algorithm is desirable because it allows scalability transparency, and processes can join or leave the distributed execution without intruding on the other processes, except its immediate neighbors that need to be aware of any changes in their immediate topology. Algorithms that run on a logical ring and have nodes communicate only with their neighbours are uniform. In Chapter ??, we will study a uniform algorithm for leader election.

5.2.6 Adaptive Algorithms Consider the context of a problem X. In a system with n nodes, let k, k ≤ n be the number of nodes “participating” in the context of X when the algorithm to solve X is executed. If the complexity of the algorithm can be expressed in terms of k rather than in terms of n, the algorithm is adaptive. For example, if the complexity of a mutual exclusion algorithm can be expressed in terms of the actual number of nodes contending for the critical section when the algorithm is executed, then the algorithm would be adaptive.

5.2.7 Deterministic Versus Nondeterministic Executions A deterministic receive primitive specifies the source from which it wants to receive a message. A nondeterministic receive primitive can receive a message from any source – the message delivered to the process is the first message that is queued in the local incoming buffer, or the first message that comes in subsequently if no message is queued in the local incoming buffer. A distributed program that contains no nondeterministic receives has a deterministic execution; otherwise, if it contains at least one nondeterministic receive primitive, it is said to have a nondeterministic execution. Each execution defines a partial order on the events in the execution. Even in an asynchronous system (defined formally in Section 5.2.9), for any deterministic (asynchronous) execution, repeated re-execution will reproduce the same partial order on the events. This is a very useful property for applications such as debugging, detection of unstable predicates, and for reasoning about global states. Given any nondeterministic execution, any re-execution of that program may result in a very different outcome, and any assertion about a nondeterministic execution can be made only for that particular execution. Different re-executions may result in different partial orders because of variable factors such as (i) lack of an upper bound on message delivery times and unpredictable

122

congestion; (ii) local scheduling delays on the CPUs due to timesharing. As such, nondeterministic executions are difficult to reason with.

5.2.8 Execution Inhibition Blocking communication primitives freeze the local execution 1 until some actions connected with the completion of that communication primitive have occurred. But from a logical perspective, is the process really prevented from executing further? The nonblocking flavors of those primitives can be used to eliminate the freezing of the execution, and the process invoking that primitive may be able to execute further (from the perspective of the program logic) until it reaches a stage in the program logic where it cannot execute further until the communication operation has completed. Only now is the process really frozen! Distributed applications can be analyzed for freezing. Often, it is more interesting to examine the control algorithm for its freezing/inhibitory effect on the application execution. Here, inhibition refers to protocols delaying actions of the underlying system execution for an interval of time. In the literature on inhibition, the term “protocol” is used synonymously with the term “control algorithm”. Protocols that require processors to suspend their normal execution until some series of actions stipulated by the protocol have been performed are termed as inhibitory or freezing protocols. Different executions of a distributed algorithm can result in a different interleavings of the events. Thus, there are multiple executions associated with each algorithm (or protocol). Protocols can be classified as follows, in terms of inhibition. • A protocol is noninhibitory if no system event is disabled in any execution of the protocol. Otherwise, the protocol is inhibitory. • A disabled event e in an execution is said to be locally delayed if there is some extension of the execution (beyond the current state) such that: (i) the event becomes enabled after the extension, and (ii) there is no intervening receive event in the extension, Thus, the interval of inhibition is under local control, A protocol is locally inhibitory if any event disabled in any execution of the protocol is locally delayed. • An inhibitory protocol for which there is some execution in which some delayed event is not locally delayed is said to be globally inhibitory. Thus, in some (or all) execution of a globally inhibitory protocol, at least one event is delayed waiting to receive communication from another processor. An orthogonal classification is that of send inhibition, receive inhibition, and internal event inhibition. • A protocol is send inhibitory if some delayed events are send events. 1

The OS dispatchable entity – the process or the thread – is frozen.

123

• A protocol is receive inhibitory if some delayed events are receive events. • A protocol is internal event inhibitory if some delayed events are internal events. These classifications help to characterize the degree of inhibition necessary to design protocols to solve various problems. Problems can be theoretically analyzed in terms of the possibility or impossibility of designing protocols to solve them under the various classes of inhibition. These classifications also serve as a yardstick to evaluate protocols. The more stringent the class of inhibition, the less desirable is the protocol. When we study algorithms for recording global states and algorithms for checkpointing, we will have the opportunity to analyze the protocols in terms of inhibition.

5.2.9 Synchronous and Asynchronous Systems A synchronous system is a system that satisfies the following properties. • There is a known upper bound on the message communication delay. • There is a known bounded drift rate for the local clock of each processor with respect to real-time. The drift rate between two clocks is defined as the rate at which their values diverge. • There is a known upper bound on the time taken by a process to execute a logical step in the execution. An asynchronous system is a system in which none of the above three properties of synchronous systems are satisfied. Clearly, systems can be designed that satisfy some combination but not all of the criteria that define a synchronous system. The algorithms to solve any particular problem can vary drastically, based on the model assumptions; hence it is important to clearly identify the system model beforehand. Distributed systems are inherently asynchronous; later in this chapter, we will study synchronizers that provide the abstraction of a synchronous execution.

5.2.10 Online versus Offline Algorithms An on-line algorithm is an algorithm that executes as the data is being generated. An off-line algorithm is an algorithm that requires all the data to be available before algorithm execution begins. Clearly, on-line algorithms are more desirable. Debugging and scheduling are two example areas where on-line algorithms offer clear advantages. On-line scheduling allows for dynamic changes to the schedule to account for newly arrived requests with closer deadlines. On-line debugging can detect errors when they occur, as opposed to collecting the entire trace of the execution and then examining it for errors.

124

5.2.11 Failure Models A failure model specifies the manner in which the component(s) of the system may fail. There exists a rich class of well-studied failure models. It is important to specify the failure model clearly because the algorithm used to solve any particular problem can vary dramatically, depending on the failure model assumed. A system is t-fault tolerant if it continues to satisfy its specified behavior as long as no more than t of its components (whether processes or links or a combination of them) fail. The Mean Time between Failures (MTBF) is usually used to specify the expected time until failure, based on statistical analysis of the component/system. Process Failure Models • Fail-stop. In this model, a properly functioning process may fail by stopping execution from some instant thenceforth. Additionally, other processes can learn that the process has failed. This model provides an abstraction – the exact mechanism by which other processes learn of the failure can vary. • Crash. In this model, a properly functioning process may fail by stopping to function from any instance thenceforth. Unlike the fail-stop model, other processes do not learn of this crash. • Receive omission. A properly functioning process may fail by intermittently receiving only some of the messages sent to it, or by crashing. • Send omission. A properly functioning process may fail by intermittently sending only some of the messages it is supposed to send, or by crashing. • General omission. A properly functioning process may fail by exhibiting either or both of send omission and receive omission failures. • Byzantine or malicious failure, with authentication. In this model, a process may exhibit any arbitrary behavior. However, if a faulty process claims to have received a specific message from a correct process, then that claim can be verified using authentication, based on unforgeable signatures. • Byzantine or malicious failure. In this model, a process may exhibit any arbitrary behavior and no authentication techniques are applicable to verify any claims made. The above process failure models, listed in order of increasing severity (except for send omissions and receive omissions which are incomparable with each other), apply to both synchronous and asynchronous systems. Timing failures can occur in synchronous systems, and manifest themselves as some or all of the following at each process. (1) General omission failures. (2) Process clocks violating their prespecified drift rate. (3) The process violating the bounds on the time taken for a step of 125

execution. In term of severity, timing failures are more severe than general omission failures but less severe than Byzantine failures with message authentication. The failure models less severe than Byzantine failures, and timing failures, are considered “benign” because they do not allow processes to arbitrarily change state or send messages that are not to be sent as per the algorithm. Benign failures are easier to handle than Byzantine failures. 5.2.11.1 Communication Failure Models • Crash failure. A properly functioning link may stop carrying messages from some instant thenceforth. • Omission failures. A link carries some messages but not the others sent on it. • Byzantine failures. A link can exhibit any arbitrary behavior, including creating spurious messages and modifying the messages sent on it. The above link failure models apply to both synchronous and asynchronous systems. Timing failures can occur in synchronous systems, and manifest themselves as links transporting messages faster or slower than their specified behavior.

5.2.12 Wait-free algorithms A wait-free algorithm is an algorithm that can execute (synchronization operations) in a (n − 1)process fault tolerant manner, i.e., it is resilient to n − 1 process failures. Thus, if an algorithm is wait-free, then the (synchronization) operations of any process must complete in a bounded number of steps irrespective of the failures of all the other processes. Although the concept of a k-fault-tolerant system is very old, wait-free algorithm design in distributed computing received attention in the context of mutual exclusion synchronization for the distributed shared memory abstraction. The objective was to enable a process to access its critical section, even if the process in the critical section fails or misbehaves by not exiting from the critical section. Wait-free algorithms offer a very high degree of robustness. Designing a wait-free algorithm is usually very expensive and may not even be possible for some synchronization problems, e.g., the simple producer-consumer problem. Wait-free algorithms will be studied in Chapters 12 and 14. Wait-free algorithms can be viewed as a special class of fault-tolerant algorithms.

5.2.13 Communication Channels Communication channels are normally First-In First-Out queues (FIFO). At the network layer, this property may not be satisfied, giving non-FIFO channels. These and other properties such as causal order of messages will be studied in Chapter 6.

126

5.3 Complexity Measures and Metrics The performance of sequential algorithms is measured in terms of the lower bounds (Ω, ω) representing the best case, the upper bounds (O, o) representing the worst case, and the exact bound (θ), on the time and space complexity. For distributed algorithms, the definitions of space and time complexity need to be refined, and additionally, message complexity also needs to be considered for message-passing systems. At the appropriate level of abstraction at which the algorithm is run, the system topology is usually assumed to be an undirected unweighted graph G = (N, L). We denote |N| as n, |L| as l, and the diameter of the graph as d. The diameter of a graph is the minimum number of edges that need to be traversed to go from any node to any other node. More formally, the diameter is maxi,j∈N { length of the shortest path between i and j }. For a tree embedded in the graph, its depth is denoted as h. Other graph parameters, such as eccentricity and degree of edge incidence, can be used when they are required. It is also assumed that identical code runs at each processor; if this assumption is not valid, then different complexities need to be stated for the different codes. The complexity measures are as follows. • Space complexity per node. This is the memory requirement at a node. The best case, average case, and worst case memory requirement at a node can be specified. • Systemwide space complexity. The system space complexity (best-case, average case, or worst case) is not necessarily n times the corresponding space complexity (best-case, average case, or worst case) per node. For example, the algorithm may not permit all nodes to achieve the best case at the same time. We will later study a distributed predicate detection algorithm (Algorithm 11.14 in Chapter 11) for which both the worst-case space complexity per node as well as the worst-case systemwide space complexity are proportional to O(n2 ). If during execution, the worst case occurs at one node, then the worst case will not occur at all the other nodes in that execution. • Time complexity per node. This measures the processing time per node, and does not explicitly account for the message propagation/transmission times, which are measured as a separate metric. • Systemwide time complexity. If the processing in the distributed system occurs at all the processors concurrently, then the system time complexity is not n times the time complexity per node. However, if the executions by the different processes are done serially, as in the case of an algorithm in which only the unique token-holder is allowed to execute, then the overall time complexity is additive. • Message complexity. This has two components – a space component and a time component. – Number of messages. The number of messages contributes directly to the space complexity of the message overhead.

127

– Size of messages. This size, in conjunction with the number of messages, measures the space component on messages. Further, for very large messages, this also contributes to the time component via the increased transmission time. – Message time complexity. The number of messages contributes to the time component indirectly, besides affecting the count of the send events and message space overhead. Depending on the degree of concurrency in the sending of the messages – i.e., whether all messages are sequentially sent (with reference to the execution partial order), or all processes can send concurrently, or something in between – the time complexity is affected. For asynchronous executions, the time complexity component is measured in terms of sequential message hops, i.e., the length of the longest chain in the partial order (E, ≺) on the events. For synchronous executions, the time complexity component is measured in terms of rounds (also termed as steps or phases). It is usually difficult to determine all of the above complexities for most algorithms. Nevertheless, it is important to be aware of the different factors that contribute towards overhead. When stating the complexities, it should also be specified whether the algorithm has a synchronous or asynchronous execution. Depending on the algorithm, further metrics such as the number of send events, or the number of receive events, may be of interest. If message multicast is allowed, it should be stated whether a multicast send event is counted as a single event. Also, whether the message multicast is counted as a single message or as multiple messages needs to be clarified. This would depend on whether or not hardware multicasting is used by the lower layers of the network protocol stack. For shared memory systems, the message complexity is not an issue if the shared memory is not being provided by the distributed shared memory abstraction over a message-passing system. The following additional changes in the emphasis on the usual complexity measures would need to be considered. • The size of shared memory, as opposed to the size of local memory, is important. The justification is that shared memory is expensive, local memory is not. • The number of synchronization operations using synchronization variables is a useful metric because it affects the time complexity.

5.4 Program Structure Hoare, who pioneered programming language support for concurrent processes, designed Concurrent Sequential Processes (CSP) which allows communicating processes to synchronize efficiently. The typical program structure for any process in a distributed application is based on CSP’s repetitive command over the alternative command on multiple guarded commands, is as follows. ∗ [ G1 −→ CL1 || G2 −→ CL2 || · · · || Gk −→ CLk ] The repetitive command (denoted by “*”) denotes an infinite loop. Inside the repetitive command is the alternative command over guarded commands. The alternative command, denoted by a 128

sequence of “||” separating guarded commands, specifies execution of exactly one of its constituent guarded commands. The guarded command has the syntax “G −→ CL” where the guard G is a boolean expression and CL is a list of commands that are executed only if G is true. The guard expression may contain a term to check if a message from a/any other process has arrived. The alternative command over the guarded commands fails if all the guards fail; if more than one guard is true, one of those successful guarded commands is nondeterministically chosen for execution. When a guarded command Gm −→ CLm does get executed, the execution of CLm is atomic with the execution of Gm . The structure of distributed programs has similar semantics to that of CSP although the syntax has evolved to something very different. The format for the pseudo-code used in this book is as indicated below. The algorithm in Figure 5.4 serves to illustrate this format. 1. The process-local variables whose scope is global to the process, and message types, are declared first. 2. Shared variables, if any, (for distributed shared memory systems) are explicitly labeled as such. 3. This is followed by any initialization code. 4. The repetitive and the alternative commands are not explicitly shown. 5. The guarded commands are shown as explicit modules or procedures (e.g., (1) through (4) in Figure 5.4). The guard usually checks for the arrival of a message of a certain type, perhaps with additional conditions on some parameter values and other local variables. 6. The body of the procedure gives the list of commands to be executed if the guard evaluates to true. 7. Process termination may be explicitly stated in the body of any procedure(s). 8. The symbol ⊥ is used to denote an undefined value. When used in a comparison, its value is −∞.

5.5 Elementary Graph Algorithms This section examines elementary distributed algorithms on graphs. The reader is assumed to be familiar with the centralized algorithms to solve these basic graph problems. The distributed algorithms here introduce the reader to the difficulty of designing distributed algorithms wherein each node has only a partial view of the graph (system), that is confined to its immediate neighbors. Further, a node can communicate with only its immediate neighbors along the incident edges. Unless otherwise specified, we assume unweighted undirected edges, and asynchronous execution by the processors. Communication is by message-passing on the edges. 129

(local variables) int visited, depth ←− 0 int parent ←−⊥ set of int N eighbors ←− set of neighbors (message types) QUERY (1) if i = root then (2) visited ←− 1; (3) depth ←− 0; (4) send QUERY to N eighbors; (5) for round = 1 to diameter do (6) if visited = 0 then (7) if any QUERY messages arrive then (8) parent ←− randomly select a node from which QUERY was received; (9) visited ←− 1; (10) depth ←− round; (11) send QUERY to N eighbors \ {senders of QUERYs received in this round}; (12) delete any QUERY messages that arrived in this round.

Figure 5.2: Spanning tree Algorithm 0: The synchronous BFS spanning tree algorithm. The code shown is for processor Pi , 1 ≤ i ≤ n. The first algorithm is a synchronous spanning tree algorithm. The next three are asynchronous algorithms to construct spanning trees. These elementary algorithms are theoretically important from a practical perspective because spanning trees are a very efficient form of information distribution and collection in distributed systems.

5.5.1 Synchronous Single-Initiator Spanning Tree Algorithm Using Flooding: Algorithm 0 The code for all processes is not only symmetrical, but also proceeds in rounds. This algorithm assumes a designated root node, root, which initiates the algorithm. The pseudo-code for each process Pi is shown in Figure 5.2. The root initiates a flooding of QUERY messages in the graph to identify tree edges. The parent of a node is that node from which a QUERY is first received; if multiple QUERYs are received in the same round, one of the senders is randomly chosen as the parent. Exercise 1 asks you to modify the algorithm so that each node identifies not only its parent node but also all its children nodes. Example: Figure 5.3 shows an example execution of the algorithm. The resulting tree is shown in boldface, and the round numbers in which the QUERY messages are sent are indicated next to the messages. The reader should trace through this example for clarity. For example, at the end of round 2, E receives a QUERY from B and F and randomly chooses F as the parent. A total of nine QUERY messages are sent in the network which has eight links. 130

A

(1)

B (2)

C

00000 11111 111 000 111111111 000000000 11111111 00000000 0 1 000000000 111111111 000000000 1 0 1 0 1 0 1 111111111 11111111 00000000 0 1 000000000 111111111 00 11 0 1 0 1 01 1 0 1 0 000000000 111111111 00 11 0 1 0 1 0 1 000000000 111111111 (3) 00 11 0 1 0 1 (3) 01 000000000 00 11 (2) 111111111 01 1 0 (1) 0 1 000000000 111111111 00 11 0 1 0 1 01 000000000 111111111 01 1 0 0 1 000000000 111111111 0 1 0 1 0 000000000 111111111 01 1 0 1 01 1 000000000 111111111 0 (3) 0 1 000000000 111111111 00 11 0 1 0 1 000000000 111111111 00 11 0 01 1 000000000 111111111 00 11 0 1 0 1 000000000 111111111 00 11 0 01 1 000000000 111111111 00 11 0 1 0 1 000000000 111111111 111111111 000000000 0 1 0 1 000000000 111111111 111111111 000000000 111111111 00000000 0 1 0 1 0 000000000 111111111 1111 0000 111 000 0 1 0 1 0 1

F (2)

E (3)

111111 000000

QUERY

D

Figure 5.3: Example execution of the synchronous BFS spanning tree algorithm of Figure 5.2 (Algorithm 0). Termination: The algorithm terminates after all the rounds are executed. It is straightforward to modify the algorithm so that a process exits after the round in which it sets its parent variable (See Exercise 1). Complexity: • The local space complexity at a node is of the order of the degree of edge incidence. • The local time complexity at a node is of the order of (diameter + degree of edge incidence). • The global space complexity is the sum of the local space complexities. • This algorithm sends at least 1 messages per edge, and at most 2 messages per edge. Thus the number of messages is between l and 2l. • The message time complexity is d rounds or message hops. The spanning tree obtained is a Breadth-First Tree (BFS). Although the code is the same for all processes, the predesignated root executes a different logic to being with. Hence, in the strictest sense, the algorithm is asymmetric.

5.5.2 Asynchronous Single-Initiator Spanning Tree Algorithm Using Flooding: Algorithm I This algorithm assumes a designated root node which initiates the algorithm. The pseudo-code for each process Pi is shown in Figure 5.4. The root initiates a flooding of QUERY messages in the graph to identify tree edges. The parent of a node is that node from which a QUERY is first received; an ACCEPT message is sent in response to such a QUERY. Other QUERY messages received are replied to by a REJECT message. Each node terminates its algorithm when it has

131

(local variables) int parent ←−⊥ set of int Children, U nrelated ←− ∅ set of int N eighbors ←− set of neighbors (message types) QUERY, ACCEPT, REJECT (1) When the predesignated root node wants to initiate the algorithm: (1a) if (i = root and parent =⊥) then (1b) send QUERY to all neighbors; (1c) parent ←− i. (2) When QUERY arrives from j: (2a) if parent =⊥ then (2b) parent ←− j; (2c) send ACCEPT to j; (2d) send QUERY to all neighbors except j; (2e) if (Children ∪ U nrelated) = (N eighbors/{parent}) then (2f) terminate. (2g) else send REJECT to j. (3) When ACCEPT arrives from j: (3a) Children ←− Children ∪ {j}; (3b) if (Children ∪ U nrelated) = (N eighbors/{parent}) then (3c) terminate. (4) When REJECT arrives from j: (4a) U nrelated ←− U nrelated ∪ {j}; (4b) if (Children ∪ U nrelated) = (N eighbors/{parent}) then (4c) terminate.

Figure 5.4: Spanning tree Algorithm I: The asynchronous algorithm assuming a designated root that initiates a flooding. The code shown is for processor Pi , 1 ≤ i ≤ n. received from all its non-parent neighbors a response to the QUERY sent to them. The procedures (1), (2), (3), and (4) are each executed atomically. In this asynchronous system, there is no bound on the time it takes to propagate a message, and hence no notion of a message round. Unlike in the synchronous algorithm, each node here needs to track its neighbours to determine which nodes are its children and which nodes are not. This tracking is necessary in order to know when to terminate. After sending QUERY messages on the outgoing links, the sender needs to know how long to keep waiting. This is accomplished by requiring each node to return an “acknowledgement” for each QUERY it receives. The acknowledgement message has to be of a different type than the QUERY type. The algorithm in the figure uses two messages types – called as ACCEPT (+ ack) and REJECT (- ack) – besides the QUERY to distinguish between the child nodes and non-child nodes. 132

Complexity: • The local space complexity at a node is of the order of the degree of edge incidence. • The local time complexity at a node is also of the order of the degree of edge incidence. • The global space complexity is the sum of the local space complexities. • This algorithm sends at least 2 messages (QUERY and its response) per edge, and at most 4 messages per edge (when two QUERIES are sent concurrently, each will have a REJECT response). Thus the number of messages is between 2l and 4l. • The message time complexity is (d + 1) message hops, assuming synchronous communication. In an asynchronous system, we cannot make any claim about the tree obtained, and its depth may be equal to the length of the longest path from the root to any other node, which is bounded only by n − 1 corresponding to a depth-first tree. Termination: The termination condition is given above. Some notes on distributed algorithms are in place. In some algorithms such as this algorithm, it is possible to locally determine the termination condition; however, for some algorithms, the termination condition is not locally determinable and an explicit termination detection algorithm needs to be executed. (1) A 0000 1111

B (4)

C

111 000 000000001 11111111 111111111 00000000 0 1 0 0 000 111 111111111 000000000 0 1 0 1 0 1 111 000 111111111 000000000 111 000 111111111 000000000 111 000 111111111 000000000 111 000 (1) (3) 111111111 000000000 111111111 000000000 111111111 000000000 111111111 000000000 111111111 000000000 (5) 111111111 000000000 00 11 111111111 000000000 00 11 111111111 000000000 00 11 111111111 000000000 00 11 (3) 000000000 111111111 111 000 00 11 111111111 000000000 11111111111111111 00000000 11111111 00000000 11111111 00000000 11111111 00000000 000000000 0 1 0 1 0 1 000000000 111111111 0 1 0 1 0 1 (2)

F

E

111111 000000

QUERY

D

Figure 5.5: Example execution of the asynchronous flooding-based single initiator spanning tree algorithm of Figure 5.4 (Algorithm I). Example: Figure 5.5 shows an example execution of the asynchronous algorithm (i.e., in an asynchronous system). The resulting spanning tree is shown in boldface. The numbers next to the QUERY messages indicate the approximate chronological order in which messages get sent. Recall that each procedure is executed atomically; hence the sending of a message sent at a particular time is triggered by the receipt of a corresponding message at the same time. The same numbering used for messages sent by different nodes implies that those actions occur concurrently and independently. ACCEPT and REJECT messages are not shown to keep the figure simple. It does not matter when the ACCEPT and REJECT messages are delivered. 133

1. A sends a QUERY to B and F. 2. F receives QUERY from A and determines that AF is a tree edge. F forwards the QUERY to E and C. 3. E receives a QUERY from F and determines that FE is a tree edge. E forwards the QUERY to B and D. C receives a QUERY from F and determines that FC is a tree edge. C forwards the QUERY to B and D. 4. B receives a QUERY from E and determines that EB is a tree edge. B forwards the QUERY to A, C, and D. 5. D receives a QUERY from E and determines that ED is a tree edge. D forwards the QUERY to B and C. Each node sends an ACCEPT message (not shown in figure for simplicity) back to the parent node from which it received its first QUERY. This is to enable the parent, i.e., the sender of the QUERY, to recognize that the edge is a tree edge, and to identify its child. All other QUERY messages are negatively acknowledged by a REJECT (also not shown for simplicity). Thus, a REJECT gets sent on each back-edge (such as BA) and each-cross edge (such as BD, BC, and CD) to enable the sender of the QUERY on that edge to recognize that that edge does not lead to a child node. We can also observe that on each tree edge, two messages (a QUERY and an ACCEPT) get sent. On each cross-edge and each back-edge, four messages (two QUERY and two REJECT) get sent. Note that this algorithm does not guarantee a breadth-first tree. Exercise 3 asks you to modify this algorithm to obtain a BFS tree.

5.5.3 Asynchronous Concurrent-Initiator Spanning Tree Algorithm Using Flooding: Algorithm II We modify Algorithm I by assuming that any node may spontaneously initiate the spanning tree algorithm provided it has not already been invoked locally due to the receipt of a QUERY message. The resulting algorithm is shown in Figure 5.6. The crucial problem to handle is that of dealing with concurrent initiations, where two or more processes that are not yet participating in the algorithm initiate the algorithm concurrently. As the objective is to construct a single spanning tree, two options seem available when concurrent initiations are detected. Note that even though there can be multiple concurrent initiations, along any single edge, only two concurrent initiations will be detected. Design 1: When two concurrent initiations are detected by two adjacent nodes that have sent a QUERY from different initiations to each other, the two partially computed spanning trees can be merged. However, this merging cannot be done based only on local knowledge or there might be cycles.

134

(local variables) int parent, myroot ←−⊥ set of int Children, U nrelated ←− ∅ set of int N eighbors ←− set of neighbors (message types) QUERY, ACCEPT, REJECT (1) When the node wants to initiate the algorithm as a root: (1a) if (parent =⊥) then (1b) send QUERY(i) to all neighbors; (1c) parent, myroot ←− i. (2) When QUERY(newroot) arrives from j: (2a) if myroot < newroot then // discard earlier partial execution due to its lower priority (2b) parent ←− j; myroot ←− newroot; Children, U nrelated ←− ∅; (2c) send QUERY(newroot) to all neighbors except j; (2d) if N eighbors = {j} then (2e) send ACCEPT(myroot) to j; terminate. // leaf node (2f) else send REJECT(newroot) to j. // if newroot = myroot then parent is already identified. // if newroot < myroot ignore the QUERY. j will update its root when it receives QUERY(myroot). (3) When ACCEPT(newroot) arrives from j: (3a) if newroot = myroot then (3b) Children ←− Children ∪ {j}; (3c) if (Children ∪ U nrelated) = (N eighbors/{parent}) then (3d) if i = myroot then (3e) terminate. (3f) else send ACCEPT(myroot) to parent. //if newroot < myroot then ignore the message. newroot > myroot will never occur. (4) When REJECT(newroot) arrives from j: (4a) if newroot = myroot then (4b) U nrelated ←− U nrelated ∪ {j}; (4c) if (Children ∪ U nrelated) = (N eighbors/{parent}) then (4d) if i = myroot then (4e) terminate. (4f) else send ACCEPT(myroot) to parent. //if newroot < myroot then ignore the message. newroot > myroot will never occur.

Figure 5.6: Spanning tree Algorithm II (asynchronous) without assuming a designated root. Initiators use flooding to start the algorithm. The code shown is for processor Pi , 1 ≤ i ≤ n. Example: In Figure 5.7, consider that the algorithm is initiated concurrently by A, G, and J. The dotted lines show the portions of the graphs covered by the three algorithms. At this time, the initiations by A and G are detected along edge BD, the initiations by A and J are detected along edge CF, the initiations by G and J are detected along edge HI. If the three 135

A

1 0 0 1

B

1 0 0 1

1 0 0 1

D1 0

E1 0 0 1

1F 0 0 1

1 0 0 1

1 0 0 1

1 0 0 1

0 1

G

C

H

I

1 0 0 1

J

Figure 5.7: Example execution of the asynchronous flooding-based concurrent initiator spanning tree algorithm of Figure 5.6 (Algorithm II). partially computed spanning trees are merged along BD, CF, and HI, there is no longer a spanning tree. Interestingly, even if there are just two initiations, the two partially computed trees may ‘meet’ along multiple edges in the graph, and care must be taken not to introduce cycles during the merger of the trees. Design 2: Suppress the instance initiated by one root and continue the instance initiated by the other root, based on some rule such as tie-breaking using the processor identifier. Again, it must be ensured that the rule is correct. Example: In Figure 5.7, if A’s initiation is suppressed due to the conflict detected along BD, G’s initiation is suppressed due to the conflict detected along HI, and J’s initiation is suppressed due to the conflict detected along CF, the algorithm hangs. The algorithm presented below uses the second option, allowing only the algorithm initiated by the root with the higher processor identifier to continue. To implement this, the messages need to be enhanced with a parameter that indicates the root node which initiated that instance of the algorithm. It is relatively more difficult to use the first option to merge partially computed spanning trees. When a QUERY(newroot) from j arrives at i, there are three possibilities. newroot > myroot: Process i should suppress its current execution due to its lower priority. It reinitializes the data structures and joins j’s subtree with newroot as the root. newroot = myroot: j’s execution is initiated by the same root as i’s initiation, and i has already identified its parent. Hence a REJECT is sent to j.

136

newroot < myroot: j’s root has a lower priority and hence i does not join j’s subtree. i sends a REJECT. j will eventually receive a QUERY(myroot) from i; and abandon its current execution in favour of i’s myroot (or a larger value). When an ACCEPT(newroot) from j arrives at i, there are three possibilities. newroot = myroot: The ACCEPT is in response to a QUERY sent by i. The ACCEPT is processed normally. newroot < myroot: The ACCEPT is in response to a QUERY i had sent to j earlier, but i has updated its myroot to a higher value since then. Ignore the ACCEPT message. newroot > myroot: The ACCEPT is in response to a QUERY i had sent earlier. But i never updates its myroot to a lower value. So this case cannot arise. The three possibilities when a REJECT(newroot) from j arrives at i are the same as for the ACCEPT message. Complexity: The time complexity of the algorithm is O(l) messages, and the number of messages is O(nl). Termination: A serious drawback of the algorithm is that only the root knows when its algorithm has terminated. To inform the other nodes, the root can send a special message along the newly constructed spanning tree edges.

5.5.4 Asynchronous Concurrent-Initiator Depth First Search Spanning Tree Algorithm: Algorithm III As in Algorithm II, this algorithm assumes that any node may spontaneously initiate the spanning tree algorithm provided it has not already been invoked locally due to the receipt of a QUERY message. It differs from Algorithm II in that it is based on a Depth-First Search (DFS) of the graph to identify the spanning tree. The algorithm should handle concurrent initiations (when two or more processes that are not yet participating in the algorithm initiate the algorithm concurrently). The pseudo-code for each process Pi is shown in Figure 5.8. The parent of each node is that node from which a QUERY is first received; an ACCEPT message is sent in response to such a QUERY. Other QUERY messages received are replied to by a REJECT message. The actions to execute when a QUERY, ACCEPT or REJECT arrives are nontrivial and the analysis for the various cases (newroot myroot) are similar to the analysis of these cases for Algorithm II. Complexity: The time complexity of the algorithm is O(l) messages, and the number of messages is O(nl). Termination: The analysis is the same as for Algorithm II.

5.5.5 Broadcast and Convergecast on a Tree A spanning tree is useful for distributing (via a broadcast) and collecting (via a convergecast) information to/from all the nodes. A generic graph with a spanning tree, and the convergecast and 137

(local variables) int parent, myroot ←−⊥ set of int Children ←− ∅ set of int N eighbors, U nknown ←− set of neighbors (message types) QUERY, ACCEPT, REJECT (1) When the node wants to initiate the algorithm as a root: (1a) if (parent =⊥) then (1b) send QUERY(i) to i (itself). (2) When QUERY(newroot) arrives from j: (2a) if myroot < newroot then (2b) parent ←− j; myroot ←− newroot; U nknown ←− set of neighbours; (2c) U nknown ← U nknown/{j}; (2d) if U nknown 6= ∅ then (2e) delete some x from U nknown; (2f) send QUERY(myroot) to x; (2g) else send ACCEPT(myroot) to j; (2h) else if myroot = newroot then (2i) send REJECT to j. // if newroot < myroot ignore the query. // j will update its root to a higher root identifier when it receives its QUERY. (3) When ACCEPT(newroot) or REJECT(newroot) arrives from j: (3a) if newroot = myroot then (3b) if ACCEPT message arrived then (3c) Children ←− Children ∪ {j}; (3d) if U nknown = ∅ then (3e) if parent 6= i then (3f) send ACCEPT(myroot) to parent; (3g) else set i as the root; terminate. (3h) else (3i) delete some x from U nknown; (3j) send QUERY(myroot) to x. // if newroot < myroot ignore the query. Since sending QUERY to j, i has updated its myroot. // j will update its myroot to a higher root identifier when it receives a QUERY initiated by it. // newroot > myroot will never occur.

Figure 5.8: Spanning tree Algorithm III (DFS, asynchronous). The code shown is for processor Pi , 1 ≤ i ≤ n. broadcast operations are illustrated in Figure 5.9. A broadcast algorithm on a spanning tree can be specified by two rules. BC1. The root sends the information to be broadcast to all its children. Terminate. BC2. When a (nonroot) node receives information from its parent, it copies it and forwards it to 138

tree edge cross−edge

initiated by leaves

convergecast

initiated by root

broadcast

root

back−edge

Figure 5.9: A generic spanning tree on a graph. The broadcast and convergecast operations are indicated. its children. Terminate. A convergecast algorithm collects information from all the nodes at the root node in order to compute some global function. It is initiated by the leaf nodes of the tree, usually in response to receiving a request sent by the root using a broadcast. The algorithm is specified as follows. CVC1. Leaf node sends its report to its parent. Terminate. CVC2. At a nonleaf node that is not the root: When a report is received from all the child nodes, the collective report is sent to the parent. Terminate. CVC3. At the root: When a report is received from all the child nodes, the global function is evaluated using the reports. Terminate. Termination: The termination condition for each node in a broadcast as well as in a convergecast is self-evident. Complexity: Each broadcast and each convergecast requires n − 1 messages and time equal to the maximum height of the tree h, which is O(n). An example of the use of convergecast is as follows. Suppose each node has an integer variable associated with the application, and the objective is to compute the minimum of these variables. Each leaf node can report its local value to its parent. When a non-leaf node receives a report from all its children, it computes the minimum of those values, and sends this minimum value to its parent. Another example of the use of convergecast is in solving the leader election problem. Leader election requires that all the processes agree on a common distinguished process, also termed as the leader. A leader is required in many distributed systems and algorithms because algorithms 139

(local variables) int length ←− ∞ int parent ←−⊥ set of int N eighbors ←− set of neighbors set of int {weighti,j , weightj,i | j ∈ N eighbors} ←− the known values of the weights of incident links (message types) UPDATE (1) if i = i0 then length ←− 0; (2) for round = 1 to n − 1 do (3) send UPDATE(i, length) to all neighbors; (4) await UPDATE(j, lengthj ) from each j ∈ N eighbors; (5) for each j ∈ N eighbors do (6) if (length > (lengthj + weightj,i ) then (7) length ←− lengthj + weightj,i ; parent ←− j.

Figure 5.10: The single source synchronous distributed Bellman-Ford shortest path algorithm. The source is i0 . The code shown is for processor Pi , 1 ≤ i ≤ n. are typically not completely symmetrical, and some process has to take the lead in initiating the algorithm; another reason is that we would not want all the processes to replicate the algorithm initiation, to save on resources.

5.5.6 Single Source Shortest Path Algorithm: Synchronous Bellman-Ford Given a weighted graph, with potentially unidirectional links, representing the network topology, the Bellman-Ford sequential shortest path algorithm finds the shortest path from a given node, say i0 , to all other nodes. The algorithm is correct when there are no cyclic paths having negative weight. A synchronous distributed algorithm to compute the shortest path is given in Figure 5.10. It is assumed that the topology (N, L) is not known to any process; rather, each process can communicate only with its neighbors and is aware of only the incident links and their weights. It is also assumed that the processes know the number of nodes |N| = n, i.e., the algorithm is not uniform. This assumption on n is required for termination. The following features can be observed from the algorithm. • After k rounds, each node has its length variable set to the length of the shortest path consisting of at most k hops. The parent variable points to the parent node along such a path. This parent field is used in the routing table to route to i0 . • After the first round, the length variable of all nodes one hop away from the root in the final minimum spanning tree (MST) would have stablized; after k rounds, the length variable of all the nodes up to k hops away in the final MST would have stabilized. 140

• Termination. As the longest path can be of length n − 1, the values of all variables stabilize after n − 1 rounds. • Complexity. The time complexity of this synchronous algorithm is: n − 1 rounds. The message complexity of this synchronous algorithm is: (n − 1)l messages.

5.5.7 Distance Vector Routing When the network graph is dynamically changing, as in a real communication network wherein the link weights model the delays or loads on the links, the shortest paths are required for routing. The classic Distance Vector Routing algorithm (DVR) used in the ARPANET up to 1980, is based on the above synchronous algorithm (Figure 5.10) and requires the following changes. • The outer for loop runs indefinitely, and the length and parent variables never stabilize, because of the dynamic nature of the system. • The variable length is replaced by array LENGT H[1..n], where LENGT H[k] denotes the length measured with node k as source/root. The LENGT H vector is also included on each UPDATE message. Now, the k th component of the LENGT H received from node m indicates the length of the shortest path from m to the root k. For each destination k, the triangle inequality of the Bellman-Ford algorithm is applied over all the LENGT H vectors received in a round. • The variable parent is replaced by array P ARENT [1..n], where P ARENT [k] denotes the next hop to which to route a packet destined for k. The array P ARENT serves as the routing table. • The processes exchange their distance vectors periodically over a network that is essentially asynchronous. If a message does not arrive within the period, the algorithm assumes a default value, and moves to the next round. This makes it virtually synchronous. Besides, if the period between exchanges is assumed to be much larger than the propagation time from a neighbor and the processing time for the received message, the algorithm is effectively synchronous.

5.5.8 Single Source Shortest Path Algorithm: Asynchronous Bellman-Ford The asynchronous version of the Bellman-Ford algorithm is shown in Figure 5.11. It is assumed that there are no negative weight cycles in (N, L). The algorithm does not give the termination condition for the nodes. Exercise 14 asks you to modify the algorithm so that each node knows when the length of the shortest path to itself has been computed. This algorithm, unfortunately, has been shown to have an exponential Ω(cn ) number of messages and exponential Ω(cn · d) time complexity in the worst case, where c is some constant (See Exercise 16). 141

(local variables) int length ←− ∞ set of int N eighbors ←− set of neighbors set of int {weighti,j , weightj,i | j ∈ N eighbors} ←− the known values of the weights of incident links (message types) UPDATE (1) if i = i0 then (1a) length ←− 0; (1b) send UPDATE(i0 , 0) to all neighbours; terminate. (2) When UPDATE(i0 , lengthj ) arrives from j: (2a) if (length > (lengthj + weightj,i )) then (2b) length ←− lengthj + weightj,i ; parent ←− j; (2c) send UPDATE(i0 , length) to all neighbors;

Figure 5.11: The asynchronous distributed Bellman-Ford shortest path algorithm for a given source i0 . The code shown is for processor Pi , 1 ≤ i ≤ n. If all links are assumed to have equal weight, the algorithm which computes the shortest path effectively computes the minimum-hop path; the minimum-hop routing tables to all destinations are computed using O(n2 · l) messages. (See Exercise 17).

5.5.9 All Sources Shortest Paths: Asynchronous Distributed Floyd-Warshall The Floyd-Warshall algorithm computes all-pairs shortest paths in a graph in which there are no negative weight cycles. It is briefly summarized first, before a distributed version is studied. The centralized algorithm uses n × n matrices LENGT H and V IA. LENGT H[i, j] is the length of the shortest path from i to j. LENGT H[i, j] is initialized to the initial known conditions: (i) weighti,j if i and j are neighbours, (ii) 0 if i = j, and (iii) ∞ otherwise. V IA[i, j] is the first hop on the shortest path from i to j. V IA[i, j] is initialized to the initial known conditions: (i) j if i and j are neighbours, (ii) 0 if i = j, and (iii) ∞ otherwise. After pivot iterations of the outer loop, the following invariant holds: “LENGT H[i, j] is the shortest path going through intermediate nodes from the set {i, . . . , pivot}. V IA[i, j] is the corresponding first hop.” Convince yourself of this invariant using the code in Figure 5.12 and Figure 5.13. In this figure, the LEN is for the paths that pass through nodes from {1 . . . pivot − 1}. The time complexity of the centralized algorithm is O(n3 ). 142

(1) for pivot = 1 to n do (2) for s = 1 to n do (3) for t = 1 to n do (4) if LEN GT H[s, pivot] + LEN GT H[pivot, t] < LEN GT H[s, t] then (5) LEN GT H[s, t] ←− LEN GT H[s, pivot] + LEN GT H[pivot, t]; (6) V IA[s, t] ←− V IA[s, pivot].

Figure 5.12: The centralized Floyd-Warshall all-pairs shortest paths routing algorithm. The distributed asynchronous algorithm is shown in Figure 5.14. Row i of the LENGT H and V IA data structures is stored at node i which is responsible for updating this row. To avoid ambiguity, we rename these data structures as LEN and P ARENT , respectively. When the algorithm terminates, the final values of row i of LENGT H is available at node i as LEN.

s

passes through nodes in {1,2,...,pivot−1}

d

d

LEN[s,d] VIA((VIA(s,d), d)

LEN[s,pivot]

LEN[pivot,d]

passes through nodes in passes through nodes in {1,2,...,pivot−1} {1,2,...,pivot−1}

pivot

VIA(s,d) s (b)

(a)

Figure 5.13: The all-pairs shortest paths algorithm by Floyd-Warshall. (a) Triangle inequality used in iteration pivot uses paths via {1, . . . , pivot − 1}. (b) The V IA relationships along a branch of the sink tree for a given (s, d) pair. There are two challenges in making the Floyd-Warshall algorithm distributed. 1. How to access the remote datum LENGT H[pivot, t] for each execution of line (4) in the centralized algorithm of Figure 5.12, now being executed by i? 2. How to synchronize the execution at the different nodes? If the different nodes are not executing the same iteration of the outermost loop of Figure 5.12, the distributed algorithm becomes incorrect. The problem of accessing the remote datum LENGT H[pivot, t] is solved by using the idea of the distributed sink tree. In the centralized algorithm, after each iteration pivot of the outermost loop, if LENGT H[s, t] 6= ∞, then V IA[s, t] points to the parent node on the path to t and this is the shortest path going through nodes {1 . . . pivot}. Observe that V IA[V IA[s, t], t] will also point to V IA[s, t]’s parent node on the shortest path to t, and so on. Effectively, tracing through the V IA nodes gives the shortest path to dest; this path is acyclic because of the “shortest path” property 143

(local variables) array of int LEN [1..n]

// LEN [j] is the length of the shortest known path from i to node j. // LEN [j] = weightij for neighbor j, 0 for j = i, ∞ otherwise array of int P AREN T [1..n] // P AREN T [j] is the parent of node i (myself) on the sink tree rooted at j. // P AREN T [j] = j for neighbor j, ⊥ otherwise set of int N eighbours ←− set of neighbors int pivot, nbh ←− 0 (message types) IN_TREE(pivot), NOT_IN_TREE(pivot), PIV_LEN(pivot, P IV OT _ROW [1..n]) // P IV OT _ROW [k] is LEN [k] of node pivot, which is LEN [pivot, k] in the central algorithm // the PIV_LEN message is used to convey P IV OT _ROW . (1) for pivot = 1 to n do (2) for each neighbour nbh ∈ N eighbours do (3) if P AREN T [pivot] = nbh then (4) send IN_TREE(pivot) to nbh; (5) else send NOT_IN_TREE(pivot) to nbh; (6) await IN_TREE or NOT_IN_TREE message from each neighour; (7) if LEN [pivot] 6= ∞ then (8) if pivot 6= i then (9) receive PIV_LEN(pivot, P IV OT _ROW [1..n]) from P AREN T [pivot]; (10) for each neighbour nbh ∈ N eighbours do (11) if IN_TREE message was received from nbh then (12) if pivot = i then (13) send PIV_LEN(pivot, LEN [1..n]) to nbh; (14) else send PIV_LEN(pivot, P IV OT _ROW [1..n]) to nbh; (15) for t = 1 to n do (16) if LEN [pivot] + P IV OT _ROW [t] < LEN [t] then (17) LEN [t] ←− LEN [pivot] + P IV OT _ROW [t]; (18) P AREN T [t] ←− P AREN T [pivot].

Figure 5.14: Toueg’s asynchronous distributed Floyd-Warshall all-pairs shortest paths routing algorithm. The code shown is for processor Pi , 1 ≤ i ≤ n. (see invariant). Thus, all nodes s for which LENGT H[s, t] 6= ∞ are part of a tree to t, and this tree is termed as a sink tree, with t as the root or the sink node. In the distributed algorithm, the parent of any node on the sink tree for dest is stored in P ARENT [t]. Applying the sink tree idea to node pivot in iteration pivot of the distributed algorithm, we have the following observations for any node i in any iteration pivot. • If LEN[pivot] = ∞, then i will not update its LEN and P ARENT arrays in this iteration. Hence there is no need for i to receive the remote data P IV _ROW [1, . . . , n]. In fact, there is no known path from i to pivot at this stage. • If LEN[pivot] 6= ∞, then the remote data P IV OT _ROW [1, . . . , n] is distributed to all the 144

nodes lying on the sink tree of pivot. Observe that i necessarily lies on the sink tree of pivot. The parent of i, and its parent’s parent, and so on, all lie on that sink tree. The asynchronous distributed algorithm proceeds as follows. It iteration pivot, node pivot broadcasts its LEN vector along its sink tree. To implement this broadcast, the parent-child edges of the sink tree need to be identified. Note that any node on the sink tree of pivot does not know which of its neighbours are its children. Hence, each node awaits a IN_TREE or NOT_IN_TREE message from each of its neighbours (lines (2)-(6)) to identify it children. These flows seen at node i are illustrated in Figure 5.15. The broadcast of the pivot’s LEN vector is initiated by node pivot in lines (10)-(13). For example, consider the first iteration, where pivot = 1. Node 1. The node executes (1), (2)-(5) by sending NOT_IN_TREE, (6) in which it gets IN_TREE messages from its neighbours, (10)-(13) wherein the node sends its LEN vector to its neighbours. Node > 1. In line (1)-(4), the neighbours of node 1 send IN_TREE to node 1. In line (9), the neighbours receive P IV OT _LEN from the pivot, i.e., node 1. The reader can step through the remainder of the protocol. When i receives P IV _LEN message containing the pivot’s P IV OT _ROW [1..n] from its parent (line (9)), it forwards it to its children (lines (10)-(12) and (14)). The two inner loops of the centralized algorithm are then executed in lines (15)-(18) of the distributed algorithm. The inherent distribution of P IV OT _ROW in lines (9)-(12) via the receive from the parent (line (9)) and send to the children (line (14)), as well as the synchronization of the send (lines (4)(5)) and receive (line (6)) of IN_TREE and NOT_IN_TREE messages among neighbour nodes ensures that the asynchronous execution of the nodes gets synchronized and all nodes are forced to execute the innermost nested iteration concurrently with each other. Notice also the dependence between the send of lines (4)-(5) and receive of line (6), and between the receive of line (9) and the send of lines (13) or (14). The techniques for synchronization used here will be formalized in Section 5.6 under the subject of synchronizers. Complexity: In each of the n iterations of the outermost loop, two IN_TREE or NOT_IN_TREE messages are sent per edge, and at most n − 1 PIV_LEN messages are sent. The overall number of messages is n · (2l + n). The PIV_LEN is of size n while the IN_TREE and NOT_IN_TREE messages are of size O(1). The execution time complexity per node is O(n2 ), plus the time for n convergecast-broadcast phases.

5.5.10 Asynchronous and Synchronous Constrained Flooding (w/o a Spanning Tree) Asynchronous algorithm (Figure 5.16). This algorithm allows any process to initiate a broadcast via (constrained) flooding along the edges of the graph. It is assumed that all channels are FIFO. Duplicates are detected by using sequence numbers. Each process uses the 145

B NOT_IN_TREE(pivot)

NOT_IN_TREE(pivot) NOT_IN_TREE(pivot)

C

NOT_IN_TREE(pivot)

i

IN_TREE(pivot)

A

IN_TREE(pivot)

Figure 5.15: Message flows to determine how to selectively distribute P IV _ROW in iteration pivot in Toueg’s distributed Floyd-Warshall algorithm. SEQNO[1..n] vector, where SEQNO[k] tracks the latest sequence number of the update initiated by process k. If the sequence number on a newly arrived message is not greater than the sequence numbers already seen for that initiator, the message is simply discarded; otherwise, it is flooded on all other outgoing links. This mechanism is used by the Link State Routing protocol in the Internet to distribute any updates about the link loads and the network topology. Complexity: The message complexity is: 2l messages in the worst case, where each message is of size |M|. The time complexity is: diameter d number of sequential hops. Synchronous algorithm (Figure 5.17). This algorithm allows all processes to flood a local value throughout the network. The local array ST AT EV EC[1..n] is such that ST AT EV EC[k] is the estimate of the local value of process k. After d number of rounds, it is guaranteed that the local value of each process has propagated throughout the network. Complexity: The time complexity is: diameter d rounds, and the message complexity is: 2l · d messages, each of size n.

5.5.11 Minimum Weight Spanning Tree (MST) Algorithm in a Synchronous System A minimum spanning tree (MST) minimizes the cost of transmission from any node to any other node in the graph. The classical centralized MST algorithms such as those by Prim, Dijkstra, and Kruskal assume that the entire weighted graph is available for examination. • Kruskal’s algorithm begins with a forest of graph components. In each iteration, it identifies the minimum-weight edge that connects two different components, and uses this edge to merge two components. This continues until all the components are merged into a single component. 146

(local variables) array of int SEQN O[1..n] ←− 0 set of int N eighbors ←− set of neighbors (message types) UPDATE (1) To send a message M : (1a) if i = root then (1b) SEQN O[i] ←− SEQN O[i] + 1; (1c) send UPDATE(M, i, SEQN O[i]) to each j ∈ N eighbors. (2) When UPDATE(M, j, seqnoj ) arrives from k: (2a) if SEQN O[j] < seqnoj then (2b) Process the message M ; (2c) SEQN O[j] ←− seqnoj ; (2d) send UPDATE(M, j, seqnoj ) to N eighbors/{k} (2e) else discard the message.

Figure 5.16: The asynchronous flooding algorithm. The code shown is for processor Pi , 1 ≤ i ≤ n. Any and all nodes can initiate the algorithm spontaneously. (local variables) array of int ST AT EV EC[1..n] ←− 0 set of int N eighbors ←− set of neighbors (message types) UPDATE (1) ST AT EV EC[i] ←− local value; (2) for round = 1 to diameter d do (3) send UPDATE(ST AT EV EC[1..n]) to each j ∈ N eighbors; (4) for count = 1 to |N eighbors| do (5) await UPDATE(SV [1..n]) from some j ∈ N eighbors; (6) ST AT EV EC[1..n] ←− max(ST AT EV EC[1..n], SV [1..n]).

Figure 5.17: The synchronous flooding algorithm for learning all node’s identifiers. The code shown is for processor Pi , 1 ≤ i ≤ n. • In Prim’s algorithm and Dijkstra’s algorithm, a single-node component is selected. In each iteration, a minimum weight edge incident on the component is identified, and the component expands to include that edge and the node at the other end of that edge. After n − 1 iterations, all the nodes are included. The MST is defined by the edges that are identified in each iteration to expand the initial component. In a distributed algorithm, each process can communicate only with its neighbors and is aware of only the incident links and their weights. It is also assumed that the processes know the value of 147

|N| = n. The weight of each edge is unique in the network, which is necessary to guarantee a unique MST. (If weights are not unique, the IDs of the nodes on which they are incident can be used as tie-breakers by defining a well-formed order.) A distributed algorithm that generalizes the strategy of Kruskal’s centralized algorithm is given after reviewing some definitions. A forest (i.e., a disjoint union of trees) is a graph in which any pair of nodes is connected by at most one path. A spanning forest of an undirected graph (N, L) is a maximal forest of (N, L), i.e., an acyclic and not necessarily connected graph whose set of vertices is N. When a spanning forest is connected, it becomes a spanning tree. A spanning forest of G is a subgraph G′ of G having the same node set as G; the spanning forest can be viewed as a set of spanning trees, one spanning tree per “connected component” of G′ . All MST algorithms begin with a spanning forest having n nodes (or connected components) and without any edges. They then add a ‘minimum weight outgoing edge’ (MWOE) between two components. 2 The spanning trees of the combining conencted components combine with the MWOE to form a single spanning tree for the combined connected component. The addition of the MWOE is repeated until a spanning tree is produced for the entire graph (N, L). Such algorithms are correct because of the following observation. Observation 1. For any spanning forest {(Ni , Li ) | i = 1 . . . k} of a weighted undirected graph G, consider any component (Nj , Lj ). Denote by λj , the edge having the smallest weight among those that are incident on only one node in Nj . Then an MST for the graph G that includes all the edges in each Li in the spanning forest, must also include edge λi . This observation says that for any “minimum weight” component created so far, when it grows by joining another component, the growth must be via the MWOE for that component under consideration. Intuitively, the logic is as follows. For any component containing node set Nj , if edge x is used instead of the MWOE λj to connect with nodes in N \ Nj , then the resulting tree cannot be a MST because edge x can always be replaced with the MWOE that was not chosen to yield a lower cost tree. Consider Figure 5.18(a) where three components have been identified and are encircled. The MWOE for each component is marked by an outgoing edge (other outgoing edges are not shown). Each of the three components shown must grow only by merging with the component at the other end of the MWOE. In a distributed algorithm, the addition of the edges should be done concurrently by having all the components identify their respective minimum-weight outgoing edge. The synchronous algorithm of Gallagher-Humblet-Spira uses this above observation, and is given in Figure 5.19. Initially, each node is the leader of its component which contains only that node. The algorithm uses log(n) iterations. In each iteration, each component merges with at least one other component. Hence, log(n) iterations guarantee termination with a single component. Each iteration goes through a broadcast-convergecast-broadcast sequence to identify the MWOE of the component, and to select the leader for the next iteration. The MWOE is identified after the 2

Note that this is an undirected graph. The direction of the “outgoing” edge is logical in the sense that it identifies the direction of expansion of the connected component under consideration.

148

A

A

B

C

B

C

(a)

(b)

Figure 5.18: Merging of MWOE components. (a) A cycle of length 2 is possible. (b) A cycle of length greater than 2 is not possible. broadcast (Steps 1 and 2) and convergecast (Step 3) by the current leader, which then does a second broadcast (Step 4). The leader is selected at the end of this second broadcast (Step 4); among all the components that merge in an iteration, a single leader is selected, and it identifies itself among all the nodes in the newly-forming component by doing a third broadcast (Step 5). This sequence of steps can be visualized using the connected component enclosed within a rectangle in Figure 5.20, using the following narrative. (a) Root broadcasts SEARCH_MWOE; (b) Convergecast REPLY_MWOE occurs. (c) Root broadcasts ADD_MWOE; (d) If the MWOE is also chosen as the MWOE by the component at the other end of the MWOE, the incident process with the higher ID is the leader for the next iteration and broadcasts NEW_LEADER. The correctness of the above algorithm hinges on the fact that in any iteration, when each component of the spanning forest joins with one or more other components of the spanning forest, the result is still a spanning forest! Observe that each component picks exactly one MWOE with which it connects to another component. However, more than two components can join together in one iteration. If multiple components join, we need to observe that the resulting component is still a spanning forest. To do so, model a directed graph (P, M) where P is the set of components at the start of an iteration and M is the set of |P | MWOE edges chosen by the components in P . In this graph, there is exactly one outgoing edge from each node in P . Recall that the direction of the MWOE is logical; the underlying graph remains undirected. If component A chooses to include a MWOE leading to component B, then directed edge (A, B) exists in (P, M). By tracing any path in this graph, observe that MWOE weights must be monotonically decreasing. To see that (i) the merging of components retains the spanning forest property, and that (ii) there is a unique leader in each component after the merger in the previous round, consider the following two cases. 1. If two components join, then each must have picked the other to join with, and we have a cycle of length two. As each component was a spanning forest, joining via the common MWOE still retains the spanning forest property, and there is a unique leader in the merged component. 149

(message types:) SEARCH_MWOE(leader) EXAMINE(leader) REPLY_MWOES(local_ID, remote_ID) ADD_MWOE(local_ID, remote_ID) NEW_LEADER(leader)

leader = i; for round = 1 to log(n) do

// broadcast by current leader on tree edges // sent on non-tree edges after receiving SEARCH_MWOE // details of potential MWOEs are convergecast to leader // sent by leader to add MWOE and identify new leader // broadcast by new leader after merging components

// each merger in each iteration involves at least two components

1. if leader = i then broadcast SEARCH_MWOE(leader) along marked edges of tree (Sect. 5.5.5). 2. On receiving a SEARCH_MWOE(leader) message that was broadcast on marked edges: (a) Each process i (including leader) sends an EXAMINE message along unmarked (i.e., non-tree) edges to determine if the other end of the edge is in the same component (i.e., whether its leader is the same). (b) From among all incident edges at i, for which the other end belongs to a different component, process i picks its incident MWOE(localID,remoteID). 3. The leaf nodes in the MST within the component initiate the convergecast (Sect. 5.5.5) using REPLY_MWOEs, informing their parent of their MWOE(localID,remoteID). All the nodes participate in this convergecast. 4. if leader = i then await convergecast replies along marked edges. Select the minimum MWOE(localID,remoteID) from all the replies. broadcast ADD_MWOE(localID,remoteID) along marked edges of tree (Sect. 5.5.5). // To ask process localID to mark the (localID, remoteID) edge, // i.e., include it in MST of component. 5. if an MWOE edge gets marked by both the components on which it is incident then (a) Define new_leader as the process with the larger ID on which that MWOE is incident (i.e., process whose ID is max(localID, remoteID)). (b) new_leader identifies itself as the leader for the next round. (c) new_leader broadcasts NEW_LEADER in the newly formed component along the marked edges (Sect. 5.5.5) announcing itself as the leader for the next round.

Figure 5.19: The synchronous MST algorithm by Gallagher-Humblet-Spira (GHS algorithm). The code shown is for processor Pi , 1 ≤ i ≤ n. 2. If three or more components join, then two sub-cases are possible. • There is some cycle of length three or more (see Figure 5.18(b)). But as any path in (P, M) follows MWOEs of monotonically decreasing weights, this implies a contra150

54 16

88

112

1 0 0 1

43

27

21

87

44 13

14

34

tree edge cross edge

11 (MWOE)

1 0 0 1

16

out−edge root of component

Figure 5.20: The phases within an iteration in a component. diction because at least one node must have chosen an incorrect MWOE. • There is no cycle of length 3 or more, and at least one node in (P, M) will have two or more incoming edges (component C in Figure 5.18(a)). Further, there must exist a cycle of length two. Exercise 22 asks you to prove this formally. As the graph has a cycle of length at most two (Case (1)), the resulting component after the merger of all the involved components is still a spanning component, and there is a unique leader in the merged component. That leader is the node with the larger PID incident on the MWOE that gets marked by both components on which it is incident. Complexity: • In each of the log(n) iterations, each component merges with at least one other component. So after the first iteration, there are at most n/2 components, after the second, at most n/4 components, and so on. Hence, at most log(n) iterations are needed and the number of nodes in each component after iteration k is at least 2k . In each iteration, the time complexity is O(n) because the time complexity for broadcast and convergecast is bounded by O(n). Hence the time complexity is O(n · log(n)) • In each of the log(n) iterations, O(n) messages are sent along the marked tree edges (steps (1), (3), (4), and (5)). There may be up to l = |L| EXAMINE messages to determine the MWOEs in step (2) of each iteration. Hence, the total message complexity is O((n + l) · log(n)). The correctness of the GHS algorithm hinges on the fact that the execution occurs in synchronous rounds. This is necessary in Step (2), where a process sends EXAMINE messages to its unmarked neighbors to determine whether those neighbors belong to the same or a different component than itself. If the neighbor is not synchronized, problems can occur. For example, 151

consider edge (j, k), where j and k become a part of the same component in ‘iteration’ x. From j’s perspective, the neighbor k may not yet have received its leader’s ID that was broadcast in step (5) of the previous iteration; hence k replies to the EXAMINE message sent by j based on an older ID for its leader. The testing process j may (incorrectly) include k in the same component as itself, thereby creating cycles in the graph. As the distance from the leader to any node in its component is not known, this needs to be dealt with even in a synchronous system. One way to enforce the synchronicity is to wait for O(n) number of communication steps; this way, all communication within the round would have completed in the synchronous model.

5.5.12 Minimum Weight Spanning Tree (MST) in an Asynchronous System There are two approaches to designing the asynchronous MST algorithm. In the first approach, the synchronous GHS algorithm is simulated in an asynchronous setting. In such a simulation, the same synchronous algorithm is run, but is augmented by additional protocol steps and control messages to provide the synchronicity. Observe from the synchronous GHS that the difficulty in making it asynchronous lies in Step (2). If the two nodes at the ends of an unmarked edge are in different levels, the algorithm can go wrong. Two possible ways to deal with this problem are as follows. • After each round, an additional broadcast and convergecast on the marked edges are serially done. The newly identified leader broadcasts its ID and round number on the tree edges; the convergecast is then initiated by the leaves to acknowledge this broadcast. When the convergecast completes at the leader, it then begins the next round. Now in Step (2), if the recipient of an EXAMINE message is in an earlier round, it simply delays the response to the EXAMINE, thus forcing synchrony. This costs n · log(n) extra messages and increases the message complexity. • When a node gets involved in a new round, it simply informs each neighbor (reachable along unmarked or non-tree edges) of its new level. Only when the neighbors along unmarked edges are all in the same round that the node sends the EXAMINE messages in Step (2). This costs |L| · log(n) extra messages and increases the message complexity. The second approach to designing the asynchronous MST is to directly address all the difficulties that arise due to lack of synchrony. The original asynchronous GHS algorithm uses this approach even though it is patterned along the synchronous GHS algorithm. By carefully engineering the asynchronous algorithm, it achieves the same message complexity O(n · log(n) + l) and the same time complexity O(n · log(n) · (l + d)) as the synchronous algorithm. We do not present the algorithm here because it is a well-engineered algorithm with intricate details; rather, we only point out some of the difficulties in designing this algorithm. • In step (2), if the two nodes are in different components or in different levels, there needs to be a mechanism to determine this. 152

• If the combining of components at different levels is permitted, then some component may keep combining with only single-node components in the worst-case, thereby increasing the complexity by changing the log(n) factor to the factor n. • The search for MWOEs by adjacent components at different levels needs to be coordinated carefully. Specifically, the rules for merging such components, as well as the rules for the concurrent search for the MWOE by these two components need to be specified.

5.6 Synchronizers General Observations on Synchronous and Asynchronous Algorithms: From the spanning tree algorithms, shortest path routing algorithms, constrained flooding algorithms, and the MST algorithms, it can be observed that it is much more difficult to design the algorithm for an asynchronous system, than for a synchronous system. This can be generalized to all algorithms, with few exceptions. The example algorithms also suggest that simulating synchronous behavior (of an algorithm designed for a synchronous system) on an asynchronous system is often a direct way to realize the algorithms on asynchronous systems. Given that typical distributed systems are asynchronous, the logical question to address is whether there is a general technique to convert an algorithm designed for a synchronous system, to run on an asynchronous system. The generic class of transformation algorithms to run synchronous algorithms on asynchronous systems are called synchronizers. We make the following observations. (1) We consider only failure-free systems, whether synchronous or asynchronous. We will later see (in Chapter ??) that such transformations may not be possible in asynchronous systems in which either processes fail or channels are unreliable. (2) Using a synchronizer provides a sure way to obtain an asynchronous algorithm. However, such an algorithm may have high complexity. Although more difficult, it may be possible to design more efficient asynchronous algorithms from scratch, rather than transforming the synchronous algorithms to run on asynchronous systems. (This was seen in the case of the GHS algorithm.) Thus, the field of systematic algorithm design for asynchronous systems is an open and challenging field. Practically speaking, in an asynchronous system, a synchronizer is a mechanism that indicates to each process when it is safe to proceed to the next round of execution of the “synchronous” algorithm. Conceptually, the synchronizer signals to each process when it is sure that all messages to be received in the current round have arrived. The mesage complexity Ma and time complexity Ta of the asynchronous algorithm are as follows. Ma = Ms + (Minit + rounds · Mround ) (5.1) Ta = Ts + Tinit + rounds · Tround • Ms is the number of messages in the synchronous algorithm. • rounds is the number of rounds in the synchronous algorithm. 153

(5.2)

Minit Tinit Mround Tround

Simple synchronizer 0 d 2|L| 1

α synchronizer 0 0 O(|L|) O(1)

β synchronizer O(n · log(n) + |L|) O(n) O(n) O(n)

γ synchronizer O(kn2 ) n · log(n)/log(k) O(Lc ) (≤ O(kn)) O(hc ) (≤ O(log(n)/log(k)))

Table 5.1: The message and time complexities for the simple, α, β, and γ synchronizers. hc is the greatest height of a tree among all the clusters. Lc is the number of tree edges and designated edges in the clustering scheme for the γ synchronizer. d is the graph diameter. • Ts is the time for the synchronous algorithm. Assuming one unit (message hop) per round, this equals rounds. • Mround is the number of messages needed to simulate a round, • Tround is the number of sequential message hops needed to simulate a round. • Minit and Tinit are the number of messages and the number of sequential message hops, respectively, in the initialization phase in the asynchronous system. We now look at four standard synchronizers: the simple, the α, the β, and the γ synchronizers, proposed by Awerbuch. The message and time complexities of these are summarized in Table 5.1. The α, β, and γ synchronizers use the notion of process safety, defined as follows. A process i is said to be safe in round r if all messages sent by i in round r have been received. The α, β synchronizers are extreme cases of the γ synchronizer and form its building blocks. A Simple Synchronizer: This synchronizer requires each process to send every neighbor one and only one message in each round. If no message is to be sent in the synchronous algorithm, an empty dummy message is sent in the asynchronous algorithm; if more than one message are sent in the synchronous algorithm, they are combined into one message in the asynchronous algorithm. In any round, when a process receives a message from each neighbor, it moves to the next round. We make the following observations about this synchronizer. • In physical time, any two processes may be only one round apart. Thus, if process i is in round roundi, any other adjacent process j must be in rounds roundi − 1, roundi, or roundi + 1 only. • When process i is in round roundi, it can receive messages only from rounds roundi or roundi + 1 from its neighbours. • Initialization: Any process may start round i. Within d time units, all processes will participate in that round. Hence, Tinit = d. Minit = 0 because no explicit messages are required solely for initialization. 154

• Complexity: Each round requires a message to be sent on each incident link in each direction. Hence, Mround = 2|L| and Tround = 1.

1 B0 0 1 1

1

2

2

1

2

C

0000000000000 01111111111111 1 0 1 0 1 0 1 0 1 0 1

E2

1

A

3

0 B1 0 1

3

3

A

C

3

00000000000000 11111111111111 0 1 0 1 0 1 0 1 0 1 0 1

E

3

3

3

3

D 1 0 0 1

D 0 1 0 1 execution message

acknowledgement

(a)

"safe"

(b)

Figure 5.21: An example showing steps of the α synchronizer. (a) Execution messages (Step (1)) and their acknowledgements (Step (2)). (b) “I am safe” messages (Step (3)). The α-synchronizer: At any process i, the α synchronizer in round r moves the process to the next round r + 1 if all the neighboring processes are safe for round r. A process can learn about the safety of its neighbor if any message sent by this process is required to be acknowledged. Once a neighbor j has received acknowledgements for all the messages it sent, it sends a message informing i (and all its other neighbors) that it is safe. Example: The operation is illustrated in Figure 5.21. (1) Node A sends a message to nodes C and E, and receives messages from B and E in the same round. (2) These messages are acknowledged after they are received. (3) Once node A receives the acknowledgements from C and E, it sends a message to all its neighbours to notify them that node A is safe. This allows the neighbours to not wait on A before proceeding to the next round. Node A itself can proceed to the next round only after it receives a safety notification from each of its neighbours, whether or not there was any exchange of application execution messages with them in that round. Complexity: For every message sent (≤ |L|) in a round, an ack is required. If l′ (< |L|) messages are sent in a round, l′ acks are needed, giving a message overhead of 2l′ thus far; but it is assumed that an underlying transport layer (or equivalent) protocol uses acks, and hence these come for free. But additionally, 2|L| messages are required so that each process can inform all its neighbors that it is safe. Thus the message complexity Mround = 2|L| + 2l′ = O(|L|). The time complexity Tround = O(1). Initialization: No explicit initialization is needed. A process that spontaneously wakes up 155

and initializes the algorithm sends messages to (some of) its neighbours, who then acknowledge any message received, and also reply that they are safe. The β-synchronizer: This synchronizer assumes a rooted spanning tree. Safe leaf nodes initiate a convergecast; an intermediate node propagates the convergecast to its parent when all the nodes in its subtree, including itself, are safe. When the root becomes safe and receives the convergecast from all its children, it uses a tree broadcast to inform all the nodes to move to the next phase. Example: Compared to the α-synchronizer, steps (1) and (2) as described with respect to Figure 5.21 are the same to determine when to notify others about safety. The actual notification about safety uses the convergecast-broadcast sequence on a pre-established tree, instead of using Step (3) of Figure 5.21. Complexity: Just as for the α synchronizer, an ack is required by the β-synchronizer for each message of the l′ messages sent in a round; hence l′ acks are required, but these can be assumed to come for free, thanks to the transport layer or an equivalent lower layer protocol. Now instead of 2l further messages as in the α synchronizer, only 2(n − 1) further messages are required for the convergecast and broadcast. Hence, Mround = 2(n − 1). For each round, there is an average case 2 · log(n) delay for Tround and worst-case 2n delay for Tround , incurred by the convergecast and the broadcast. Initialization: There is an initialization cost, incurred by the set up of the spanning tree (Algorithms in Section 5.5). As studied earlier in Section 5.5, this cost is: O(n·log(n)+|L|) messages and O(n) time. 1 0 0 1

F

1 0 0 1

A

1 0 0 1

1 0 0 1

B

1 0 0 1

E

tree edge designated (inter−cluster) edge

C

1111 0000 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0 0000 1111 D1 0 1 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111

1 0 0 root 1

Figure 5.22: Cluster organization for the γ synchronizer, showing six clusters A-F. Only the tree edges within each cluster, and the inter-cluster designated edges are shown. The γ-synchronizer: The network is organized into a set of clusters, as shown in Figure 5.22. Within a cluster, a spanning tree hierarchy exists with a distinguished root node. The height 156

(message types) Subtree_safe This_cluster_safe My_cluster_safe Neighboring_cluster_safe Next_round

// β synchronizer phase’s convergecast within cluster // β synchronizer phase’s broadcast within cluster // embedded inter-cluster α synchronizer’s messages across cluster boundaries // Convergecast following inter-cluster α synchronizer phase // Broadcast following inter-cluster α synchronizer phase

for each round do 1. (β synchronizer phase:) This phase aims to detect when all the nodes within a cluster are safe, and inform all the nodes in that cluster. (a) Using the spanning tree, leaves initiate the convergecast of the ‘Subtree_safe’ message towards the root of the cluster. (b) After the convergecast completes, the root initiates a broadcast of ‘This_cluster_safe’ on the spanning tree within the cluster. (c) (Embedded α synchronizer:) i. During this broadcast in the tree, as the nodes get engaged, the nodes also send ‘My_cluster_safe’ messages on any incident designated inter-cluster edges. ii. Each node also awaits ‘My_cluster_safe’ messages along any such incident designated edges. 2. (Convergecast and broadcast phase:) This phase aims to detect when all neighboring clusters are safe, and to inform every node within this cluster. (a) (Convergecast:) i. After the broadcast of the earlier phase (1b) completes, the leaves initiate a convergecast using ‘Neighboring_cluster_safe’ messages once they receive any expected ‘My_cluster_safe’ messages (step (1c)) on all the designated incident edges. ii. An intermediate node propagates the convergecast once it receives the ‘Neighboring_cluster_safe’ message from all its children, and also any expected ‘My_cluster_safe’ message (as per step (1c)) along designated edges incident on it. (b) (Broadcast:) Once the convergecast completes at the root of the cluster, a ‘Next_round’ message is broadcast in the cluster’s tree to inform all the tree nodes to move to the next round.

Figure 5.23: The γ synchronizer. of a clustering scheme, h(c), is the maximum height of the spanning trees across all of the clusters. Two clusters are neighbors if there is at least one edge between one node in each of the two clusters; one of such multiple edges is the designated edge for that pair of clusters. Within a cluster, the β-synchronizer is executed; once a cluster is ‘stabilized’, the α-synchronizer is executed among the clusters, over the designated edges. To convey the results of the stabilization of the inter-cluster α synchronizer, within each cluster, a convergecast and broadcast phase is then executed. Over the designated inter-cluster edges, two types of messages are exchanged for the α synchronizer: My_cluster_safe, and Neighboring_cluster_safe, with the self-evident semantics. The details of the algorithm are given in Figure 5.23. 157

Complexity: • Let Lc be the total number of tree edges plus designated edges in the clustering scheme. In each round, there are four messages – Subtree_safe, This_cluster_safe, Neighboring_cluster_safe, and Next_round – per tree edge, and two My_cluster_safe messages over each designated edge. Hence, Mround is O(Lc ). • Let hc be the maximum height of any tree among the clusters, then the time complexity component Tround is O(hc ). This is due to the four phases – convergecast, broadcast, convergecast, and broadcast – contributing 4hc time, the 2 units of time needed for all processes to become safe, and 1 unit of time needed for the inter-cluster messages My_cluster_safe. Exercise 25 asks you to work out a formal design of how to partition the nodes into clusters, how to choose a root and a spanning tree of appropriate depth for each cluster, and how to designate the preferred edges. The requirements on the design scheme are to be able to control the complexity by suitably tuning a parameter k. The γ(k)-synchronizer reduces to the α-synchronizer when k = n − 1, i.e., each cluster contains a single node. The γ(k)synchronizer reduces to the β-synchronizer when k = 2, i.e., there is a single cluster. The construction will allow the γ(k)-synchronizer to be viewed as a parameterized synchronizer based on clustering.

5.7 Maximal Independent Set (MIS) For a graph (N, L), an independent set of nodes N ′ , where N ′ ⊂ N, is such that for each i and j in N ′ , (i, j) 6∈ L. An independent set N ′ is a maximal independent set if no strict superset of N ′ is an independent set. A graph may have multiple maximal independent sets; all of which may not be of the same size.3 The maximal independent set problem requires that adjacent nodes must not be chosen. This has application in wireless broadcast where it is required that transmitters must not broadcast on the same frequency within range of each other. More generally, for any shared resources (radio frequency bandwidth in the above example) to allow a maximum concurrent use while avoiding interference or conflicting use, a maximal independent set is required. Computing a maximum independent set in a distributed manner is challenging. The problem becomes further interesting when a maximal independent set must be maintained when processes join and leave, and links can go down, or new links between existing nodes can be established. A simple and elegant distributed algorithm for the MIS problem in a static system, proposed by Luby, is presented in Figure 5.24 for an asynchronous system. The idea is as follows. In each iteration, each node Pi selects a random number randomi and exchanges this value with its neighbours using the RANDOM message. If randomi is less than the random numbers chosen by 3

The problem of finding the largest sized independent set is the maximum independent set problem. This is NPhard.

158

(variables) set of integer N eighbours // set of neighbours real randomi // random number from a sufficiently large range boolean selectedi // becomes true when Pi is included in the MIS boolean eliminatedi // becomes true when Pi is eliminated from the candidate set (message types) RANDOM(real random) // a random number is sent SELECTED(integer pid, boolean indicator) // whether sender was selected in MIS ELIMINATED(integer pid, boolean indicator) // whether sender was removed from candidates (1a) repeat (1b) if N eighbours = ∅ then (1c) selectedi ←− true; exit(); (1d) randomi ←− a random number; (1e) send RANDOM(randomi) to each neighbour; (1f) await RANDOM(randomj ) from each neighbour j ∈ N eighbours; (1g) if randomi < randomj (∀j ∈ N eighbours) then (1h) send SELECTED(i, true) to each j ∈ N eighbours; (1i) selectedi ←− true; exit(); // in MIS (1j) else (1k) send SELECTED(i, f alse) to each j ∈ N eighbours; (1l) await SELECTED(j, ⋆) from each j ∈ N eighbours; (1m) if SELECTED(j, true) arrived from some j ∈ N eighbours then (1n) for each j ∈ N eighbours from which SELECTED(⋆, f alse) arrived do (1o) send SELECTED(i, true) to j; (1p) eliminatedi ←− true; exit(); // not in MIS (1q) else (1r) send ELIMINATED(i, f alse) to each j ∈ N eighbours; (1s) await ELIMINATED(j, ⋆) from each j ∈ N eighbours; (1t) for all j ∈ N eighbours do (1u) if ELIMINATED(j, true) arrived then (1v) N eighbours ←− N eighbours \ {j}; (1w) forever.

Figure 5.24: Luby’s algorithm for the Maximal Independent Set in an asynchronous system. Code shown is for process Pi , 1 ≤ i ≤ n. all its neighbours, the node includes itself in the MIS and exits. However, whether or not a node gets included in the MIS, it informs its neighbours via the indicator parameter on the SELECTED message. On receiving SELECTED messages from all the neighbours, if a node finds that at least one of its neighbours has been selected for inclusion in the MIS, the node eliminates itself from the candidate set for inclusion. However, whether or not an unselected node eliminates itself from the candidate set, it informs its neighbours via the indicator parameter on the ELIMINATED message. If a node learns that a neighbour j is eliminated from candidature, the node deletes j from Neighbours, and proceeds to the next iteration. 159

7

A

E

G 2

H

1

4

5

A

G

E

H

2 F

B 6

2

D

F

I

J

K

0

8

I D

6

1 C

B

9 J

C 5

6

K 1

(b)

(a)

Figure 5.25: An example showing the execution of the MIS algorithm. (a) Winners and losers in round 1. (b) Winners up to round 2, and the losers in round 2. The algorithm constructs an IS because once a node is selected to be in the IS, all its neighbours are deleted from the set of remaining candidate nodes for inclusion in the IS. The algorithm constructs an MIS because only the neighbours of the selected nodes are eliminated from being candidates. Example: Figure 5.25(a) and (b) show the first two rounds in the execution of the MIS algorithm. The winners have a check mark and the losers have a cross next to them. In the third round, the node labeled I includes itself as a winner. The MIS is {C, E, G, I, K}. Complexity: It is evident that in each iteration, at least one node will be included in the MIS, and at least one node will be eliminated from the candidate set. So at most n/2 iterations of the repeat loop are required. In fact, the expected number of iterations is O(log n). The reader is referred to the paper by Luby for the proof of this bound.

5.8 Connected Dominating Set A dominating set of graph (N, L) is a set N ′ ⊆ N such that each node in N \N ′ has an edge to some node in N ′ . Determining whether there exists a dominating set of size k < |N| is NP-complete. A connected dominating set (CDS) of (N, L) is a dominating set N ′ such that the subgraph induced by the nodes in N ′ is connected. Finding the miminum connected dominating set (MCDS) is NP-complete, and hence polynomial time heuristics are used to design approximation algorithms. In addition to the time and message complexities, the approximation factor becomes an important metric. The approximation factor is the worst case ratio of the size of the CDS obtained by the algorithm to the size of the MCDS. Another useful metric is the stretch factor. This is the worst-case ratio of the length of the shortest route between the dominators of two nodes in the CDS overlay, to the length of the shortest routes between the two nodes in the underlying graph. The connected dominating set can form a backbone along which a broadcast can be performed. All nodes are guaranteed to be within range of the backbone and can hence receive the broad160

cast. The set is thus useful for routing, particularly in the wide-area network and also in wireless networks. A simple heuristic is to create a spanning tree and delete the edges to the leaf nodes to get a CDS. Another heuristic is to create a MIS and add edges to create a CDS. However, designing an algorithm with a low approximation factor is non-trivial. The Bibliographic Notes point to a couple of references for efficient distributed CDS algorithms.

5.9 Compact Routing Tables Routing tables are traditionally as large as the number of destinations n. This can have high storage requirements as well as table lookup and processing overheads when routing each packet. If the table can be reorganized such that it is indexed by the incident incoming link, and the table entry gives the outgoing link, then the table size becomes the degree of the node, which can be much smaller than n. Further efficiency would depend on how the destinations reachable per channel are represented and accessed. Some of the approaches to designing compact routing tables include the following. • Hierarchical routing schemes: The network graph is organized into clusters in a hierarchical manner, with each cluster having one clusterhead designated node that represents the cluster at the next higher level in the hierarchy. There is detailed information about routing within a cluster, at all the routers within that cluster. If the destination does not lie in the same cluster as the source, the packet is sent to the clusterhead and up the hierarchy as appropriate. Once the clusterhead of the destination is found in the routing tables, then the packet is sent across the network at that level of the hierarchy, and then down the hierarchy in the destination cluster. This form of routing is widely used in the Internet. • Tree-labeling schemes: This family of schemes uses a logical tree topology for routing. The routing scheme requires labeling the nodes of the graph in such a way that all the destinations reachable via any link can be represented as a range of contiguous addresses [x, y]. A node with degree deg need only maintain deg entries in its routing table, where each entry is a range of contiguous addresses. For all the address intervals [x, y] except at most one, the scheme must satisfy x < y. Example: Figure 5.26 shows a tree labeling. on a tree with 7 nodes. The tree edge labels are enclosed in rectangles. Non-tree edges are in dashed lines. Tree-labeling can provide great savings, compared to a table of size n at each node. Unfortunately, all traffic is confined to the logical tree edges. Exercise 26 asks you to show that it is always possible to generate a tree-labeling scheme. • Interval routing schemes: The tree-labeling schemes suffer from the fact that data can be sent only over tree edges, wasting the remaining bandwidth in the system. Interval routing extends the tree labeling so that the data packets need not be sent only on the edges of a tree. 161

4 1−3

5−7

4−7 1−1

2−7

1

1−4

2

6 5−5

3−3 4−2

3

6−4

5

7−7 1−6

7

Figure 5.26: Tree labeling on a graph with 7 nodes. Formally, given a graph (N, L), an interval routing scheme is a tuple (B, I), where: 1. Node labeling: B is a 1:1 mapping on N, that assigns labels to nodes.

2. Edge labeling: The mapping I labels each edge in L by some subset of node labels B(N) such that for any node x, all destinations are covered (∪y∈N eighbours I(x, y) ∪ B(x) = N) and there is no duplication of coverage (I(x, w) ∩ I(x, y) = ∅ for w, y ∈ Neighbours).

3. For any source s and destination t nodes, there must exist a sequence of nodes hs = x0 , x1 . . . xk−1 , xk = ti where B(t) ∈ I(xi−1 , xi ) for each i between 1 and k. Therefore, for each source and destination pair, there must exist a path under the new mapping.

To show that an interval labeling scheme is possible for every graph, a tree with the following property is constructed: “there are no cross-edges in the corresponding graph”. The tree generated by a depth-first traversal always satisfies this property. Nodes are labeled by a preorder traversal whereas the edges are labeled by a more detailed scheme, see [31]. Two drawbacks of interval routing schemes are that: (i) they do not give any guarantees on the efficiency (lengths) of the routing paths that get chosen, and (ii) they are not robust to small changes in the topology. • Prefix routing schemes: Prefix routing schemes overcome the drawbacks of interval routing. (This prefix routing is not to be confused with the CIDR routing used in the internet. CIDR also uses the prefixes of the destination IP address.) In prefix routing, the node labels as well as the channel labels are drawn from the same domain and are viewed as strings. The routing decision at a router is as follows: identify the channels whose label is the longest prefix of the address of the destination. This is the channel on which to route the packet for that particular destination. distancer (i,j) }. This is an The stretch factor of a routing scheme r is defined as maxi,j∈N { distance opt (i,j) important metric in evaluating a compact routing scheme.

162

All the above approaches for compact routing are rich in distributed graph algorithmic problems and challenges, including identifying and proving bounds on the efficiency of computed routes. Different graph topologies yield interesting results for these routing schemes.

5.10 Leader Election We have seen the role of a leader process in several algorithms such as the Minimum spanning tree and broadcast/convergecast to compute a function over all the participating processes. Leader election requires that all the processes agree on a common distinguished process, also termed as the leader. A leader is required in many distributed systems because algorithms are typically not completely symmetrical, and some process has to take the lead in initiating the algorithm; another reason is that we would not want all the processes to replicate the algorithm initiation, to save on resources. Typical algorithms for leader election assume a ring topology is available. Each process has a left neighbour and a right neighbour. The Lelang, Chang, and Roberts (LCR) algorithm assumes an asynchronous unidirectional ring. It also assumes that all processes have unique identifiers. Each process in the ring sends its identifier to its left neighbour. When a process Pi receives the identifier k from its right neighbour Pj , it acts as follows. • i < k: forward the identifier k to its left neighbour • i > k: ignore the message received from neighbour j • i = k: due to the assumption on nonanonymity, Pi ’s identifier must have circluated across the entire ring. Hence Pi can declare itself the leader. Pi can then send another message around the ring announcing that it has been chosen as the leader. The algorithm is given in Figure 5.27. Complexity: The LCR algorithm in Figure 5.27 is in its simplest form. Several optimizations are possible. For example, if i has frowarded a probe with value z and a probe with value X, where i < x < z arrives, no forwarding action on the probe needs to be taken. Despite this, it is straightforward to see that the message complexity of this algorithm is n · (n − 1)/2 and the time complexity is O(n). The O(n2 ) message cost can be reduced to O(n logn) by using a binary search in both directions. In round k, the token is circulated to 2k neighbours on both the left and right sides. To cover the entire ring, a logarithmic number of steps are needed. Consider that in each round, a process tries to become a leader, and only the winners in round k can proceed to round k + 1. In effect, a process i is a leader in round k if and only if i is the highest identifier among 2k neighbours in both directions. Hence, any pair of leaders after round k are at least 2k apart. Hence the number of leaders diminishes logarithmically as n/2k Observe that in each round, therer are at most n messages sent, using the suprewssion technique of the LCR algorithm. Thus the overall complexity is O(n · log n). 163

(variables) boolean participate ← f alse (message types) PROBE integer SELECTED integer

// becomes true when Pi is included in the MIS // contains a node identifier // announcing the result

(1) When a process wakes up to participate in leader eleciton: (1a) send PROBE(i) to right neighbor; (1b) participate ←− true. (2) When a PROBE(k) message arrives from the left neighbour Pj : (2a) if participate = f alse then execute step (1) first. (2b) if i > k then (2c) discard the probe; (2d) else forward if i < PROBE(k) k then (2e) to right neighbour; (2f) else if i = k then (2g) declare i is the leader; (2h) circulate SELECTED(i) to right neighbour; (3) When a SELECTED(x) message arrives from left neighbour: (3a) if x (3b) note x as the leader and forward message to right neighbour; (3c) else do not forward the SELECTED message.

Figure 5.27: The LCR leader election algorithm in a synchronous system. Code shown is for process Pi , 1 ≤ i ≤ n. It has been shown that there cannot exist a determinisitc leader election algorithm for anonymous rings. Hence, the assumption about uniform node identifiers is necessary in this model. However, the algorithm can be uniform, i.e., thre total number of porcesses need not be known.

5.11 Challenges in Designing Distributed Graph Algorithms We have thus far considered some elementary but important graph problems, and seen how to solve them in distributed algorithms. The algorithms either fail or require a more complicated redesign if we assume that the graph topology changes dynamically, which happens in mobile systems. • The graph (N, L) changes dynamically in the normal course of execution of a distributed execution. An example is the load on a network link, which is really determined as the aggregate of many different flows. It is unrealistic to expect that this will ever be static. All of a sudden, the MST algorithms (and others) need a complete overhaul. • The graph can change if either there are link or node failures, or worse still, partitions in the network. The graph can also change when new links and new nodes are added to the network. Again, the algorithms seen thus far need to be redesigned to accommodate such changes. The challenge posed by mobile systems additionally needs to deal with the new communication model. Here, each node is capable of transmitting data wirelessly, and all nodes within a certain radius can receive it. This is the unit-disk radius model.

164

5.12 Object Replication Problems We now describe a real-life graph problem based on web/data replication, and that also requires dynamic distributed solutions. 1. Consider a weighted graph (N, L), wherein k users are situated at some Nk ⊆ N nodes, and r replicas of a data item can be placed at some Nr ⊆ N. What is the optimal placement of the replicas if k > r and the users access the data item in read-only mode? A solution requires evaluating all placements of Nr among the nodes in N to identify P min( i∈Nk ,ri ∈Nr disti,ri ), where disti , ri is the cost from node i to ri , the replica nearest to i. 2. If we assume that the Read accesses from each of the users in Nk have a certain frequency (or weight), the minimization function would change. 3. If each edge has a certain bandwidth or capacity, that too has to be taken into account in identifying a feasible solution. 4. Now assume that a user access to the shared data is a Read operation with probability x, and an Update operation with probability 1 − x. An Update operation also requires all replicas to be updated. What is the optimal placement of the replicas if k > r? Many such graph problems do not always have polynomial solutions even in the static case. With dynamically changing input parameters, the case appears even more hopeless for an optimal solution. Fortunately, heuristics can often be used to provide good solutions.

5.12.1 Problem Definition In a large distributed system, data replication is useful for rapid access to data and for faulttolerance. Here we look at Wolfson, Jajodia, and Huang’s optimal data replication strategy that is dynamic in that it adapts to the read and write patterns from the different nodes. Let the network be modeled by the graph (V, E), and let us focus on a single object for simplicity. Define a replication scheme as a subset R of V such that each node in R has a replica of the object. Let ri and wi denote the rates of reads and writes issued by node i. Let cr (i) and cw (i) denote the cost of a read and write issued by node i. Let R denote the set of all possible replication schemes. The goal is to minimize the cost of the replication scheme: X X min[ ri · cr (i) + wi · cw (i)] (5.3) R∈R

i∈V

i∈V

The algorithm assumes one copy serializability which can be implemented by the Read-One-WriteAll (ROWA) policy. ROWA can be strictly implemented in conjunction with a concurrency control mechanism such as two-phase locking; however, lazy propagation can also be used for weaker semantics. 165

5.12.2 Algorithm Outline For arbitrary graph topologies, minimizing the cost as in Equation (5.3) is NP-complete. So we assume a tree topology T , as shown in Figure 5.28. The nodes inthe replication scheme R are shown in the ellipse. If T is allowed to be a tree overlay T on the network topology, then all algorithm communication is confined to the overlay. Conceptually, the set of nodes R containing the replicas is an amoeba-like connected subgraph that moves around the overlay tree T towards the “center of gravity” of the read and write activity. The amoeba-like subgraph expands when the relative cost of the reads is more than that of writes, and shrinks as the relative cost of writes is more than that of reads, reaching an equilibrium under steady state activity. This equilibrium-state subgraph for the replication scheme is optimal. The algorithm executes in steps that are separated by predetermined time periods or “epochs”. Irrespective of the initial replication scheme, the algorithm converges to the optimal replication scheme in (diameter + 1) number of steps once the read-and-write pattern stabilizes.

R A 1 0 0 1

B

0 C1 0 1

D

1 0 0 R−fringe 1

E1 0 0 1

R−neighbour 1 0 0 R−neighbour 1

and R−fringe

Figure 5.28: The tree topology and the replication scheme R. Nodes inside the ellipse belong to the replication scheme.

5.12.3 Reads and Writes Read: A read operation is performed from the closest replica on the tree T . If the node issuing the read query or receiving a forwarded read query is not in R, it forwards the query towards the nodes in R along the tree edges – for this, it suffices that a parent pointer point in the direction of the subgraph R. Once the query reaches a node in R, the value read is returned along the same path. Write: A write is performed to every replica in the current replication scheme R. If a write operation is issued by a node not in R, the operation request is propagated to the closest node in R, like for the read operation request. Once a write operation reaches a node i in R, the local replica is updated, and the operation is propagated to all neighbours of i that belong to R. To implement this, a node needs to track the set of its neighbours that belong to R. This is done using a variable, R-neighbours.

166

Implementation: To execute a read or write operation, a node needs to know (i) whether it is in R (so it can read/write from the local replica), (ii) which of its neighbours are in R (to propagate write requests), and (iii) if the node is not in R, then which of its neighbours is the unique node that leads on the tree to R (so it can propagate read and write requests). After appropriate initialization, this information is always locally available by tracking the status of the neighbour nodes.

5.12.4 Converging to an Replication Scheme Within the replication scheme R, three types of nodes are defined. R-neighbour: Such a node i belongs to R but has at least one neighbour j that does not belong to R. R-fringe: Such a node i belongs to R and has only one neighbour j that belongs to R. Thus, i is a leaf node in the subgraph of T induced by R and j is the parent of i. singleton: |R| =1 and i ∈ R. Example: In Figure 5.28, node C is an R-fringe node, nodes A and E are both R-fringe and R-neighbour nodes, and node D is an R-neighbour node. The algorithm uses the following three tests to adjust the replication scheme to converge to the optimal scheme.

r j w (a)

1 0 0i 1

r+w

w

j

1 0 0 1

r

1 0 0i 1

(b)

1 0 0 1

j

i

r+w

(c)

Figure 5.29: Adaptive data replication tests executed by node i. (a) Expansion test. (b) Contraction test. (c) Switch test. Expansion test: An R-neighbour node i examines each such neighbour j to determine whether j can be included in the replication scheme, using an expansion test. Node j is included in the replication scheme if the volume of reads coming from and via j is more than the volume of writes that would have to be propagated to j from i if j were included in the replication scheme. Example (Figure 5.29(a)): Node i includes j in the replication scheme if r > w. Contraction test: An R-fringe node i examines whether it can exclude itself from the replication scheme, using a contraction test. Node i excludes itself from the replication scheme if the volume of writes being propagated to it from j is more than the volume of reads that i would 167

have to forward to j if i were to exit the replication scheme. Before exiting, node i must seek permission from j to prevent a situation where R = {i, j} and both i and j simultaneously have a successful contraction test and exit, leaving no copies of the object. Example (Figure 5.29(b)): Node i excludes itself from the replication scheme if w > r. Switch test: A singleton node i executes the switch test to determine if it can transfer its replica to some neighbour to optimize the objective function. A singleton node transfers its replica to a neighbour j if the volume of requests being forwarded by that neighbour is greater than the volume of requests the node would have to forward to that neighbour if the replica were shifted from itself to that neighbour. If such a node j exists, observe that it is uniquely identified among the neighbours of node i. Example (Figure 5.29(c)): Node i transfers its replica to j if r + w being forwarded by j is greater than r + w that node i receives from all other nodes. The various tests are executed at the end of each “epoch”. A R-neighbour node may also be a R-fringe node or a singleton node; in either case, the expansion test is executed first and if it fails, then the contraction test or the switch test is executed. Note that a singleton node cannot be a R-fringe node. The code is given in Figure 5.30. Implementation: Each node needs to be able to determine whether it is in R, whether it is a Rneighbour node, a R-fringe node, or a singleton node. This can be determined if a node knows whether it is in R, the set of neighbour nodes, and for each such neighbour, whether it is in R. This is a subset of the information required for implementing read and write operations, and can be tracked easily using local exchanges. Hence, these operations are not shown in the code in Figure 5.30. The actions to service read and write requests described earlier are also straightforward and are not shown code. Correctness: Given an initial connected replication scheme, the replication scheme after each epoch remains connected, and the replication schemes in two consecutive epochs either intersect or are adjacent singletons. This property follows from the fact that for each node i ∈ R, in each epoch, at most one of the three tests – expansion, contraction, and switch – succeeds, and the corresponding transformation satisfies the above property. Given two disconnected components of a replication scheme, it is easy to see that by adding nodes to combine the components can never increase the cost (Equation (5.3)) of the replication scheme. Once the read-write pattern stabilizes, the replication scheme stabilizes withing diameter + 1 number of epochs, and the resulting replication scheme is optimal. The proof if fairly complex; below are the main steps to show termination, and these can be validated intuitively. For the optimality argument, note that each change in an epoch reduces the cost. The proof that the replication scheme on termination is globally optimal and not just locally optimal is given in the full paper [31]. Termination:

168

• After a switch test succeeds, no other expansion test can succeed. • If a node exits the replication scheme in a contraction test, it cannot re-enter the replication scheme via an expansion test. • If a node exits the replication scheme in a switch test, it cannot re-enter the replication scheme again. Thus, if a node exits the replication scheme, it can re-enter only by a switch test, and that too if the exit was via a contraction test. But then, no further expansion test can succeed. Hence, a node can exit the replication scheme at most once more – via a switch test. Each node can exit the replication scheme at most twice, and after the first switch test, no expansion can occur. Hence the replication scheme stabilizes. It can be seen that the replication scheme first expands wherever possible, and then contracts. If it becomes a singleton, then the only changes possible are switches. Arbitrary graphs: The algorithm so far assumes the graph was a tree, on which the replication scheme “amoeba” moves into optimal position. For arbitrary graphs, a tree overlay can be used. However, the tree structure also has to change dynamically because the shortest path in the spanning tree between two arbitrary nodes is not always the shortest path between the nodes in the graph. Modified versions of the three tests can now be used, but the structure of the graph does not guarantee the global optimum solution, but only that a loal optimum is reached.

5.13 Chapter Summary This chapter first examined various views of the distributed system at different levels of abstraction of the topology of the system graph. It then introduced basic terminology for classifying distributed algorithms and distributed executions. This covered failure models of nodes and links. It then examined several performance metrics for distributed algorithms. The chapter then examined several traditional distributed algorithms on graphs. The most basic of such algorithms are the spanning tree, minimum weight spanning tree, and the shortest path algorithms - both single source and multi-source. The importance of these algorithms lies in the fact that spanning trees are used for information distribution and collection via broadcast and convergecast, respectively, and these functions need to be performed by a wide range of distributed applications. The convergecast and broadcast performed on the spanning trees also allow the reP peated computation of a global function such as min, max, and . Some of the shortest path routing algorithms studied are seen to be used in the Internet at the network layer. In all cases, the synchronous version and then the asynchronous version of the algorithms were examined. The various examples of algorithm design showed that it is often easier to construct an algorithm for a synchronous system than it is for an asynchronous system. The chapter then studied synchronizers, which are transformations that allow any algorithm designed for a synchronous system to run in an asynchronous system. Specifically, four synchronizers, in the order of increasing

169

complexity, were studied – the simple synchronizer, the α synchronizer, the β synchronizer, and the γ synchronizer. A distributed randomized algorithm for the Maximal Independent Set problem was studied, and then the problem of determining a a Connected Dominating Set was examined. The chapter then examined several compact routing schemes. These aim to trade-off routing table size for slightly longer routes. The chapter concluded by taking a look at some of the challenges introduced in re-engineering or re-designing these distributed graph algorithms in the face of mobility as well as the wireless communication model.

5.14 Bibliographic Notes The discussion on the classification of distributed algorithms is based on the vast literature, and many of the definitions are difficult to attribute to a particular source. The discussion on execution inhibition is based on Critchlow and Taylor [3]. The discussion on failure models is based on Hadzilacos and Toueg [14]. Crash failures were proposed by Lamport and Fischer [21]. Failstop failures were introduced by Schlichting and Schneider [24]. Send omission failures were introduced by Hadzilacos [16]. General omission failures and timing failures were introduced by Perry and Toueg [25] and Christian et al.[8], respectively. The notion of wait-freedom was introduced by Lamport [18] and later developed by Herlihy [18]. The notions of the space, message, and time complexities appear to be part of the folklore. The time and message complexity measures were formalized by Peterson and Fischer [24] and later by Awerbuch [3]. The various spanning tree algorithms are common knowledge and have used informally in many contexts. Broadcast, convergecast, and distributed spanning trees are listed as part of a suite of elmentary algorithms [13]. Segall [28] formally presented the broadcast and convergecast algorithms, and the breadth-first search spanning tree algorithm, on which Algorithm 0 is based. Algorithms II and III which compute flooding-based and depth-first search based spanning trees, respectively, in the face of concurrent initiators, use the technique of supressing lower priority initiations. This technique has been used in many other contexts in computer science (e.g., database transaction serialization, deadlock detection). An asynchronous DFS algorithm with a specified root was given by Cheung [7]. Algorithm III adapts this to handle concurrent initiators. The solution to Problem 9 which asks for a linear-time DFS tree was given by Awerbuch [2]. The synchronous Bellman-Ford algorithm is derived from the Bellman-Ford shortest path algorithm [4, 12]. The asynchronous Bellman-Ford was formalized by Chandy and Misra [5]. The Distance Vector Routing algorithm and Synchronous flooding algorithm of Figure 5.17 are based on the Arpanet protocols [29]. The Floyd-Warshall algorithm is from [9] and its distributed version was given by Toueg [30]. The asynchronous flooding algorithm outlined in Figure 5.16 is based on the the Link State Routing protocol used in the Internet [29]. The synchronous distributed minimum spanning tree algorithm was given by Gallagher, Humblet, and Spira [14]. Its asynchronous version was also proposed by the same authors. The notion of synchronizers, and the α, β, and γ synchronizers were introduced by Awerbuch [3]. The ran170

domized algorithm for the Independent Maximal Set (MIS) was proposed by Luby [23]. Several distributed algorithms to create connected dominating sets with a low approximation factor are surveyed by Wan, Alzoubi and Frieder [32]. The randomized algorithm for connected dominating set by Dubhashi, Mei, Panconesi, Radhakrishnan, and Srinivasan [11] has an approximation factor of O(log∆), where ∆ is the maximum degree of the network. This algorithm also has a stretch factor of O(log n). Compact routing based on the tree topology was introduced by Santoro and Khatib [27]. Its generalization to interval routing was introduced by van Leeuwen and Tan [31]. A survey of interval routing mechanisms is given by Gavoille [15]. The LCR algorithm for leader election was proposed by LeLann [22] and Chang and Roberts who provided several optimizations [6]. The O(n log n) alogrithm for leader election was given by Hirschberg and Sinclair [19]. The result on the impossibility of election on anonymous rings was shown by Angluin [1]. The adaptive replication algorithm was proposed by Wolfson, Jajodia, and Huang [31].

171

(variables) array of integer N eighbours[1 . . . bi ]; array of integer Read_Received[1 . . . |bi |]; array of integer W rite_Received[1 . . . |bi |]; integer writei , readi ; boolean success;

// bi neighbours in tree T topology // jth element gives # reads from N eighbours[j] // jth element gives # writes from N eighbours[j] // # writes and # reads issued locally

(1) Pi determines which tests to execute at the end of each epoch: (1a) if i is R-neighbour and R-fringe then (1b) if expansion test fails then (1c) reduction test (1d) else if i is R-neighbour and singleton then (1e) if expansion test fails then (1f) switch test (1g) else if i is R-neighbour and not R-fringe and not singleton then (1h) expansion test (1i) else if i is R − neighbour and R-fringe then (1j) contraction test. (2) Pi executes expansion test: (2a) for j from 1 to bi do (2b) if N eighbours[j] not in R then P (2c) if Read_Received[j] > (writei + k=1...bi ,k6=j W rite_Received[k]) then (2d) send a copy of the object to N eighbours[j]; success ←− 1; (2e) return(success). (3) Pi executes contraction test: (3a) let N eighbours[j] be the only neighbour P in R; (3b) if W rite_Received[j] > (readi + k=1...bi ,k6=j Read_Received[k]) then (3c) seek permission from N eighbours[j] to exit from R; (3d) if permission received then (3e) success ←− 1; inform all neighbours; (3f) return(success). (4) Pi executes switch test: (4a) for j from 1 to bi do (4b) ifP(Read_Received[j] + W rite_Received[j]) > [ k=1...bi ,k6=j (Read_Received[k] + W rite_Received[k]) + readi + writei ] then (4c) transfer object copy to N eighbours[j]; success ←− 1; inform all neighbours; (4d) return(success).

Figure 5.30: Adaptive Data Replication algorithm executed by a node Pi in replication scheme R. All variables except Neighbours are reset at the end of each epoch. R stabilizes in diameter + 1 epochs after the read-write rates stabilize.

172

Bibliography [1] D. Angluin, Local and global properties in networks of processors, Proc. 12th ACM Symposium on Theory of Computing, 82-93, 1980. [2] B. Awerbuch, Optimal distributed algorithms for minimum weight spanning tree, counting, leader election, and related problems, Proc. 19th ACM Symposium on Principles of Theory of Computing (STOC), 230-240, 1987. [3] B. Awerbuch, Complexity of network synchronization, Journal of the ACM, 32(4): 804-823, October 1985. [4] R. Bellman, Dynamic Programming, Princeton, NJ, Princeton University Press, 1957. [5] K. M. Chandy, J. Misra, Distributed computations on graphs: Shortest path algorithms, Communications of the ACM, 25(11): 833-838, 1982. [6] E. Chang, R. Roberts, An improved algorithm for decentralized extrema-finding in circular configurations of processes, Communications ofthe ACM, 22(5): 281-283, 1979, [7] T.-Y. Cheung, Graph traversal techniques and the maximum flow problem in distributed computation, IEEE Transactions on Software Engineering, 9(4): 504-512, July 1983. [8] F. Christian, H. Aghili, H. Strong, D. Dolev, Atomic broadcast: From simple message diffusion to Byzantine agreement, Proc. 15th International Symposium on Fault-Tolerant Computing, 200-206, 1985. [9] T. Cormen, C. Lieserson, R. Rivest, C. Stein, An Introduction to Algorithms, 2nd edition, 2001. [10] C. Critchlow, K. Taylor, The inhibition spectrum and the achievement of causal consistency, Distributed Computing, 10(1): 11-27, 1996. [11] D. Dubhashi, A. Mei, A. Panconesi, J. Radhakrishnan, A. Srinivasan, Fast distributed algorithms for (weakly) connected dominating sets and linear-size skeletons, Proc. 14th Annual Symposium on Discrete Algorithms, 717-724, 2003. [12] L. Ford, D. Fulkerson, Flows in Networks, Princeton, NJ, Princeton University Press, 1962. 173

[13] E. Gafni, Perspectives on distributed network protocols,: A case for building blocks, Proc. IEEE MILCOM, 1986. [14] R. Gallagher, P. Humblet, P. Spira, A distributed algorithm for minimum-weight spanning trees, ACM Transactions on Programming Languages and Systems, 5(1): 66-77, Jan. 1983. [15] C. Gavoille, A survey on interval routing, Theoretical Computer Science, 245(2): 217-253, 2000. [16] V. Hadzilacos, Issues of Fault Tolerance in Concurrent Computations, Ph.D. dissertation, Harvard University, Computer Science Tech. Report 11-84, 1984. [17] V. Hadzilacos, S. Toueg, Fault-tolerant broadcasts and related problems, pp. 97-146, In: Distributed Systems, Ed: S. Mullender, Addison-Wesley, 1993. [18] M. Herlihy, Wait-free synchronization, ACM Transactions on Programming Languages and Systems, 15(5): 745-770, Nov. 1991. [19] D. Hirschberg, J. Sinclair, Decentralized extrema-finding in circular configurations of processors. Communications of the ACM, 23(11): 627-628, 1980. [20] L. Lamport, Concurrent reading and writing, Communications of the ACM, 20(11): 806-811, 1977. [21] L. Lamport, M. Fischer, Byzantine Generals and Transaction Commit Protocols, SRI International, Technical Report 62, 1982. [22] G. LeLann, Distributed Systems, Towards a formal approach, IFIP Congress Proceedings, 155-160, 1977. [23] M. Luby, A simple parallel algorithm for the maximal independent set problem, SIAM J. Comput. 15(4): 1036-1053 (1986) [24] R. Schlichting, F. Schneider, Fail-stop processors: An approach to designing fault-tolerant computing systems, ACM Transactions on Computer Systems, 1(3): 222-238, 1983. [25] K. Perry, S. Toueg, Distributed agreement in the presence of processor and communication faults, IEEE Transactions on Software Engineering, 12(3): 477-482, March 1986. [26] G. Peterson, M. Fischer, Economical solutions for the critical section problem in a distributed system, Proc. 9th ACM Symposium on Theory of Computing, 91-97, 1977. [27] N. Santoro, R. Khatib, Labelling and implicit routing in networks, The Computer journal, Vol. 28, 5-8, 1985. [28] A. Segall, Distributed network protocols, IEEE Transactions on Information Theory, 29(1): 23-35, 1983. 174

[29] A. Tanenbaum, Computer Networks, 3rd edition, Prentice-Hall PTR, NJ, 1996. [30] S. Toueg, An all-pairs shortest path distributed algorithm, IBM Technical Report RC 8327, 1980 [31] J. van Leeuwen, R. Tan, Interval routing, The Computer Journal, Vol. 30, 298-307, 1987. [32] P. Wan, K. Alzoubi, O. Frieder, Distributed construction of connected dominating set in wireless ad-hoc networks, Proc. IEEE Infocom 2002.

5.15 Exercise Problems 1. Adapt the synchronous BFS spanning tree algorithm shown in Figure 5.2 to satisfy the following properties. • The root node can detect after the entire algorithm has terminated. The root should then terminate. • Each node is able to identify its child nodes without using any additional messages. What is the resulting space, time, and message complexity? 2. What is the exact number of messages sent in the spanning tree algorithm shown in Figure 5.4. You may want to use additional parameters to characterize the graph. Is it possible to reduce the number of messages to exactly 2l? 3. Modify the algorithm of Figure 5.4 to obtain a BFS tree win the asynchronous system, while retaining the framework of the flooding mechanism. 4. Modify the asynchronous spanning tree algorithm of Figure 5.4 to eliminate the use of REJECT messages. What is the message overhead of the modified algorithm? 5. What is the maximum distance between any two nodes in the tree obtained by running the algorithm in Figure 5.6? 6. For the algorithm in Figure 5.6, show each of the performance complexities introduced in Section 5.3. 7. For the algorithm in Figure 5.8, show each of the performance complexities introduced in Section 5.3. 8. (based on Cheung [7]) Simplify the algorithm in Figure 5.8 to deal with only a single initiator. What is the message complexity and the time complexity of the resulting algorithm? 9. (based on [2]) Modify the algorithm derived in Exercise 8 to obtain a depth-first search tree but with time complexity O(n). (Assuming a single intiator for simplicity does not reduce the time complexity. A different strategy needs to be used.) 175

10. Formally write the convergeecast algorithm of Section 5.5.5 using the style for the other algorithms in this chapter. Modify your algorithm to satisfy the following property. Each node has a sensed temperature reading. The maximum temperature reading is to be collected by the root. 11. Modify the synchronous flooding algorithm of Figure 5.17 so as to reduce the complexity, assuming that all the processes only need to know the highest process identifier among all the processes in the network. For this adapted algorithm, what are the lowered complexity measures? 12. Adapt Algorithms 5.10 and 5.17 to design a synchronous algorithm that achieves the following property: “In each round, each node may or may not generate a new update that it wants to distribute throughout the network. If such an update is locally generated within a round, it should be synchronously propagated in the network.” 13. In the synchronous distributed Bellman-Ford algorithm in Figure 5.10, the termination condition for the algorithm assumed that each process knew the number of nodes in the graph. If this number is not known, what can be done to find it? 14. In the asynchronous Bellman-Ford algorithm of Figure 5.11 what can be said about the termination conditions when (i) n is not known, and when (ii) n is known? For each of these two cases, modify the asynchronous Bellman-Ford algorithm of Figure 5.11 to allow each process to determine when to terminate. 15. Modify the asynchronous Bellman-Ford algorithm to devise the Distance Vector Routing algorithm outlined in Section ??. 16. For the asynchronous Bellman-Ford algorithm of Figure 5.11 show that it has an exponential Ω(cn ) number of messages and exponential Ω(cn ·d) time complexity in the worst case, where c is some constant 17. For the asynchronous Bellman-Ford algorithm of Figure 5.11, if all links are assumed to have equal weight, the algorithm effectively computes the minimum-hop path. Show that under this assumption, the minimum-hop routing tables to all destinations are computed using O(n2 · l) messages. 18. For the asynchronous Bellman-Ford algorithm of Figure 5.11, (a) if some of the links may have negative weights, what would be the impact on the shortest paths? Explain your answer. (b) if the link weights can keep changing (as in the Internet), can cycles be formed during routing based on the computed next hop?

176

19. In the distributed Floyd-Warshall algorithm of Figure 5.14, consider iteration k at node i and iteration k + 1 at node j. Examine the dependencies in the code of i and j in these two iterations. 20. In the distributed Floyd-Warshall algorithm of Figure 5.14, (a) show that the parameter pivot is redundant on all the message types when the communication channels are FIFO. (b) show that the parameter pivot is required on all the message types when the communication channels are non-FIFO. 21. In the synchronous distributed GHS algorithm, it was assumed that all the edge weights were unique. Explain why this assumption was necessary, and give a way to make the weights unique if they are not so. 22. In the synchronous GHS MST algorithm, prove that when several components join to form a single component, there must exist a cycle of length two in the component graph of MWOE edges. 23. Identify how the complexity of the synchronous GHS algorithm can be reduced from O((n+ |L|)log n) to O((n log n) + |L|). Explain and prove your answer. 24. Consider the simple, the α, and the β synchronizers. Identify some algorithms or application areas where you can identify one synchronizer as being more efficient than the others. 25. For the γ-synchronizer, significant flexibility can be achieved by varying a parameter k that is used to give a bound on Lc (sum of the number of tree edges and clustering edges) and hc (maximum height of any tree in any cluster). Visually, this parameter determines the flatness of the cluster hierarchy. Show that for every k, 2 ≤ k < n, a clustering scheme can be designed so as to satisfy the following bounds: (1) Lc < k · n, and (2) hc ≤ (log n)/(log k). 26.

(a) For the tree labeling scheme for compact routing, show that a pre-order traversal of the tree generates a numbering that always permits tree-labeled routing. (b) Will post-order traversal always generate a valid tree labeling scheme? (c) Will in-order traversal always generate a valid tree-labeling scheme?

27.

(a) For the tree labeling schemes, show that there is no uniform bound on the dialation, which is defined as the ratio of the length of the tree path to the optimal path, between any pair of nodes and an arbitrary tree. (b) Is it possible to bound the dialation by choosing a tree for any given graph? Explain your answer.

177

28. Examine all the algorithms in this chapter, and classify them using the classifications introduced in Section 5.2 ( 5.2.1- 5.2.10). 29. Examine the impact of both fail-stop process failures and of crash process failures on all the algorithms described in this chapter. Explain your answers in each case. 30. (Adaptive Data Replication.) In the adaptive data replication scheme (Section 5.12), consider a node that is both an R-neighbour and a R-fringe node. • Can the expansion test and the reduction test both be successful? Prove your answer. • The algorithm first performs the expansion test, and if it fails, then it performs the reduction test. Is it possible to restructure the algorithm to perform the reduction test first, and then the expansion test? Prove your answer. 31. Modify the rules of the expansion, contraction, and switch tests in the adaptive dynamic replication algorithm of Section 5.12 to adapt to tree overlays on arbitrary graphs, rather than to tree graphs. Justify the correctness of the modified tests.

178

Chapter 6 Message Ordering and Group Communication Inter-process communication via message-passing is at the core of any distributed system. In this chapter, we will study the non-FIFO, the FIFO, causal order, and synchronous order communication paradigms for ordering messages. We will then examine protocols that provide these message orders. We will also examine several semantics for group communication with multicast – in particular, causal ordering and total ordering. We will then look at how exact semantics can be specified for the expected behaviour in the face of processor or link failures. Multicasts are required at the application layer when superimposed topologies or overlays are used, as well as at the lower layers of the protocol stack. We will examine some popular multicast algorithms at the network layer. An example of such an algorithm is the Steiner tree algorithm, which is useful for setting up multi-party teleconferencing and videoconferencing multicast sessions. Notations: As before, we model the distributed system as a graph (N, L). The following notations are used to refer to messages and events. • When referring to a message without regard for the identity of the sender and receiver processes, we use mi . For message mi , its send and receive events are denoted as si and r i , respectively. • More generally, send and receive events are denoted simply as s and r. When the relationship between the message and its send and receive events is to be stressed, we also use M, send(M), and receive(M), respectively. For any two events a and b, where each can be either a send event or a receive event, the notation a ∼ b denotes that a and b occur at the same process, i.e., a ∈ Ei and b ∈ Ei for some process i. The send and receive event pair for a message is said to be a pair of corresponding events. The send event corresponds to the receive event, and vice-versa. For a given execution E, let the set of all send-receive event pairs be denoted as T = {(s, r) ∈ Ei × Ej | s corresponds to r}. When dealing with message ordering definitions, we will consider only send and receive events, but not internal events, because only communication events are relevant. 179

6.1 Message Ordering Paradigms The order of delivery of messages in a distributed system is an important aspect of system executions because it determines the messaging behavior that can be expected by the distributed program. Distributed program logic greatly depends on this order of delivery. To simplify the task of the programmer, programming languages in conjunction with the middleware provide certain well-defined message delivery behavior. The programmer can then code her logic with respect to this behavior. Several orderings on messages have been defined: (i) non-FIFO, (ii) FIFO, (iii) causal order, and (iv) synchronous order. There is a natural hierarchy among these orderings. This hierarchy represents a trade-off between concurrency and ease of use and implementation. After studying the definitions of and the hierarchy among the ordering models, we will study some implementations of these orderings in the middleware layer.

6.1.1 Asynchronous Executions Definition 6. (A-execution.) An asynchronous execution (or A-execution) is an execution (E, ≺) for which the causality relation is a partial order. There can not exist any causality cycles in any real asynchronous execution because cycles lead to the absurdity that an event causes itself. On any logical link between two nodes in the system, messages may be delivered in any order, not necessarily First-In First-Out. Such executions are also known as non-FIFO executions. Although each physical link typically delivers the messages sent on it in FIFO order due to the physical properties of the medium, a logical link may be formed as a composite of physical links and multiple paths may exist between the two end points of the logical link. As an example, the mode of ordering at the Network Layer in connectionless networks such as IPv4 is non-FIFO. Figure 6.1(a) illustrates an A-execution under non-FIFO ordering.

P1 P2

r2 r1 m2 m1

r3 m3

s1 s2 s3

r1 r2 m1

s1

(a)

m2

s2 (b)

Figure 6.1: Illustrating FIFO and non-FIFO executions. (a) an A-execution that is not a FIFO execution. (b) an A-execution thatis also a FIFO execution.

6.1.2 FIFO Executions Definition 7. (FIFO executions.) A FIFO execution is an A-execution in which: for all (s, r) and (s′ , r ′ ) ∈ T , (s ∼ s′ and r ∼ r ′ and s ≺ s′ ) = : r ≺ r ′ 180

r3 r1

P1 P2 P3

s3

m3

r2 m2 m1

s1 s2 (a)

r1 m3

r2 s3

m2

m1

s3 m3

r3

r1 r3

s2

m1

r3 m3

r2 3 m2 s

r1 m1

m2

s1 s2

s1 r2 (c)

(b)

s2 s1 (d)

Figure 6.2: Illustration of causally ordered executions. (a) Not a CO execution. (b,c,d) CO executions. On any logical link in the system, messages are necessarily delivered in the order in which they are sent. Although the logical link is inherently non-FIFO, most network protocols provide a connection-oriented service at the transport layer. Therefore, FIFO logical channels can be realistically assumed when designing distributed algorithms. A simple algorithm to implement a FIFO logical channel over a non-FIFO channel would use a separate numbering scheme to sequence the messages on each logical channel. The sender assigns and appends a h sequence_num, connection_id i tuple to each message. The receiver uses a buffer to order the incoming messages as per the sender’s sequence numbers, and accepts only the “next” message in sequence. Figure 6.1(b) illustrates an A-execution under FIFO ordering.

6.1.3 Causally Ordered (CO) Executions Definition 8. (Causal order (CO)). A CO execution is an A-execution in which, for all (s, r) and (s′ , r ′ ) ∈ T , (r ∼ r ′ and s ≺ s′ ) = :r ≺ r ′ If two send events s and s′ are related by causality ordering (not physical time ordering), then a causally ordered execution requires that their corresponding receive events r and r ′ occur in the same order at all common destinations. Note that if s and s′ are not related by causality, then CO is vacuously satisfied becuase the antecedent of the implication is false. Example: Figure 6.2(a) shows an execution that violates CO because s1 ≺ s3 and at the common destination P1 , we have r 3 ≺ r 1 . Figure 6.2(b) shows an execution that satisfies CO. Only s1 and s2 are related by causality but the destinations of the corresponding messages are different. Figure 6.2(c) shows an execution that satisfies CO. No send events are related by causality. Figure 6.2(d) shows an execution that satisfies CO. s2 and s1 are related by causality but the destinations of the corresponding messages are different. Similarly for s2 and s3 . 181

Causal order is useful for applications requiring updates to shared data, implementing distributed shared memory, and fair resource allocation such as granting of requests for distributed mutual exclusion. Some of these uses will be discussed in detail in Section 6.5 on ordering message broadcasts and multicasts. To implement CO, we distinguish between the arrival of a message and its delivery. A message m that arrives in the local OS buffer at Pi may have to be delayed until the messages that were sent to Pi causally before m was sent (the “overtaken” messages) have arrived and are processed by the application. The delayed message m is then given to the application for processing. The event of an application processing an arrived message is referred to as a delivery event (instead of as a receive event) for emphasis. Example: Figure 6.2(a) shows an execution that violates CO. To enforce CO, message m3 should be kept pending in the local buffer after it arrives at P1 , until m1 arrives and m1 is delivered. Definition 9. (Definition of causal order (CO) for implementations). If send(m1 ) ≺ send(m2 ) then for each common destination d of messages m1 and m2 , deliverd (m1 ) ≺ deliverd (m2 ) must be satisfied. Observe that if the definition of causal order is restricted so that m1 and m2 are sent be the same process, then the property degenerates into the FIFO property. In a FIFO execution, no message can be overtaken by another message between the same (sender, receiver) pair of processes. The FIFO property which applies on a per-logical channel basis can be extended globally to give the CO property. In a CO execution, no message can be overtaken by a chain of messages between the same (sender, receiver) pair of processes. Example: Figure 6.2(a) shows an execution that violates CO. Message m1 is overtaken by the messages in the chain hm2 , m3 i. CO executions can also be alternatively characterized by Definition 10 by simultaneously dropping the requirement from the implicand of Definition 8 that the receive events be on the same process, and relaxing the consequence from (r ≺ r ′ ) to ¬(r ′ ≺ r), i.e., the message m′ sent causally later than m is not received causally earlier at the common destination. This ordering is known as Message Ordering (MO). Definition 10. (Message order (MO)). A MO execution is an A-execution in which, for all (s, r) and (s′ , r ′ ) ∈ T , s ≺ s′ = :¬(r ′ ≺ r) Example: Consider any message pair, say m1 and m3 in Figure 6.2(a). s1 ≺ s3 but ¬(r 3 ≺ r 1 ) is false. hence, the execution does not satisfy MO. You are asked to prove the equivalence of MO executions and CO executions in Exercise 1. This will show that in a CO execution, a message cannot be overtaken by a chain of messages. Another characterization of a CO execution in terms of the partial order (E, ≺) is known as the Empty-Interval (EI) property. Definition 11. (Empty-Interval execution.) An execution (E, ≺) is an Empty-Interval (EI) execution if for each pair of events (s, r) ∈ T , the open interval set {x ∈ E | s ≺ x ≺ r} in the partial order is empty. 182

P

s2 s3

1

P

m3

m2

r1

2

P

3

m5

s6

r3 s5 m1 s1

s2

s 4 r5

r2

s3 m 3

m4

r1 m1

m6 r4r6

s1

r3 2 m r2

s4

r5

m5 m4 r4

s6 s5

m6 r6

(b)

(a)

Figure 6.3: Illustration of a synchronous communication. (a) Execution in an asynchronous system. (b) Equivalent instantaneous communication. Example: Consider any message, say m2 , in Figure 6.2(b). There does not exist any event x such that s2 ≺ x ≺ r 2 . This holds for all messages in the execution. Hence, the execution is EI. You are asked to prove the equivalence of EI executions and CO executions in Exercise 1. A consequence of the EI-property is that for an empty interval hs, ri, there exists some linear extension 1 < such that the corresponding interval {x ∈ E | s < x < r} is also empty. An empty hs, ri interval in a linear extension indicates that the two events may be arbitrarily close and can be represented by a vertical arrow in a timing diagram, which is a characteristic of a synchronous message exchange. Thus, an execution E is CO if and only if for each message, there exists some space-time diagram in which that message can be drawn as a vertical message arrow. This, however, does not imply that all messages can be drawn as vertical arrows in the same space-time diagram. If all messages could be drawn vertically in an execution, all the hs, ri intervals would be empty in the same linear extension and the execution would be synchronous. Another characterization of CO executions is in terms of the causal past/future of a send event and its corresponding receive event. The following corollary can be derived from the EI characterization above (Definition 11). Corollary 3. An execution (E, ≺) is CO if and only if for each pair of events (s, r) ∈ T and each event e ∈ E, • Weak common past: e ≺ r = :¬(s ≺ e) • Weak common future: s ≺ e = :¬(e ≺ r) Example: Corollary 3 can be observed for the executions in Figures 6.2(b)–(d). If we require that the past of both the s and r events are identical (and analogously for the future), viz., e ≺ r = :e ≺ s and s ≺ e = :r ≺ e, we get a subclass of CO executions, called synchronous executions. 1

A linear extension of a partial order (E, ≺) is any total order (E, |V| − 1 for any connected network. The algorithm is asymptotically optimal in the number of messages.

7.7 Termination Detection in a Very General Distributed Computing Model So far we assumed that the reception of a single message is enough to activate a passive process. Now we consider a general model of distributed computing where a passive process does not necessarily become active on the receipt of a message. Instead, the condition of activation of a passive process is more general and a passive process requires a set of messages to become active. 245

This requirement is expressed by an activation condition defined over the set DSi of processes from which a passive process Pi is expecting messages. The set DSi associated with a passive process Pi is called the dependent set of Pi . A passive process becomes active only when its activation condition is fulfilled.

7.7.1 Model Definition and Assumptions The distributed computation consists of a finite set P of processes Pi , i=1, ...,n, interconnected by unidirectional communication channels. Communication channels are reliable, but they do not obey FIFO property. Message transfer delay is finite but unpredictable. A passive process that has terminated its computation by executing for example an end or stop statement is said to be individually terminated; its dependent set is empty and therefore, it can never be activated. AND, OR, and ANO-OR Models There are several request models, such as AND, OR, AND-OR models. In the AND model, a passive process Pi can be activated only after a message from every process belonging to DSi has arrived. In the OR model, a passive process Pi can be activated when a message from any process belonging to DSi has arrived. In the AND-OR model, the requirement of a passive process Pi is defined by a set Ri of sets DSi 1 ,DSi 2 ,...DSiqi , such that for all r, 1≤ r≤ qi , DSi r ⊆P. The dependent set of Pi is DSi = DSi 1 ∪DSi 2 ∪...DSi qi . Process Pi waits for messages from all processes belonging to DSi 1 or for messages from all processes belonging to DSi 2 or...or for messages from all processes belonging to DSi qi . The k out of n Model In the k out of n model, the requirement of a passive process Pi is defined by the set DSi and an integer ki , 1≤ki ≤ |DSi | = ni and process Pi becomes active when it has received messages from ki distinct processes in DSi . Note that a more general k out of n model can be constructed as disjunctions of several k out of n requests. Predicate fulfilled To abstract the activation condition of a passive process Pi , a predicate fulfilledi(A) is introduced, where A is a subset of P. Predicate fulfilledi(A) is true if and only if messages arrived (and not yet consumed) from all processes belonging to set A are sufficient to activate process Pi .

7.7.2 Notations The following notations will be used to define termination of a distributed computation: • passivei : true iff Pi is passive 246

• empty(j, i): true iff all messages sent by Pj to Pi have arrived at Pi ; the messages not yet consumed by Pi are in its local buffer. • arri (j): true iff a message from Pj to Pi has arrived at Pi and has not yet been consumed by Pi . • ARRi = {processes Pj such that arri (j)}. • NEi = {processes Pj such that ¬ empty(j, i)}.

7.7.3 Termination Definitions Two different types of terminations are defined, dynamic termination and static termination: • Dynamic termination: The set of processes P is said to be dynamically terminated at some instant if and only if the predicate Dterm is true at that moment where: Dterm≡∀Pi ∈ P: passivei ∧ ¬ f ulf illedi (ARRi ∪ NEi ). Dynamic termination means that no more activity is possible from processes, though messages of the underlying computation can still be in transit. This definition is useful in "early" detection of termination as it allows concluding a computation has terminated even if some of its messages have not yet arrived. Note that dynamic termination is a stable property because once Dterm is true, it remains true. • Static termination: The set of processes P is said to be statically terminated at some instant if and only if the predicate Sterm is true at that moment where: Sterm ≡ ∀ Pi ∈ P: passivei ∧ (NEi =∅) ∧ ¬ f ulf illedi (ARRi ) Static termination means all channels are empty and none of the processes can be activated. Thus, static termination is focused on the state of both channels and processes. When compared to Dterm, the predicate Sterm corresponds to “late" detection as, additionally, all channels must be empty.

7.7.4 A Static Termination Detection Algorithm Informal Description A control process Ci , called controller, is associated with each application process Pi . Its role is to observe the behavior of process Pi and to cooperate with other controllers Cj to detect occurrence 247

of the predicate Sterm. In order to detect static termination, a controller, say Ca , initiates detection by sending a control message query to all controllers (including itself). A controller Ci responds with a message reply(ldi ), where ldi is a Boolean value. Ca combines all the Boolean values ^ received in reply messages to compute td:= ldi . If td is true, Ca concludes that termination 1≤i≤n

has occurred. Otherwise, it sends new query messages. The basic sequence of sending of query messages followed by the reception of associated reply messages is called a wave. The core of the algorithm is the way a controller Ci computes the value ldi sent back in a reply message. To ensure safety, the values ld1 ,...ldn must be such that: ^

ldi : Sterm

1≤i≤n

: ∀Pi ∈ P: passivei ∧ (NEi = ∅) ∧ ¬ f ulf illedi (ARRi ).

A controller Ci delays a response to a query as long as the following locally evaluable predicate is false: passivei ∧ (notacki = 0) ∧ ¬ f ulf illedi (ARRi ). When this predicate is false, the static termination cannot be guaranteed. For correctness, the values reported by a wave must not miss activity of processes “in the back" of the wave. This is achieved in the following manner: each controller Ci maintains a Boolean variable cpi (initialized to true iff Pi is initially passive) in the following way: • When Pi becomes active, cpi is set to false. • When Ci sends a reply message to Ca , it sends the current value of cpi with this message, and then sets cpi to true. Thus, if a reply message carries value true from Ci to Ca , it means that Pi has been continuously passive since the previous wave, and the messages arrived and not yet consumed are not sufficient to activate Pi , and all output channels of Pi are empty.

Formal Description The algorithm for static termination detection is as follows. By a message, we mean any message of the underlying computation; queries and replies are called control messages. S1: When Pi sends a message to Pj notacki := notacki + 1 S2: When a message from Pj arrives to Pi send ack to Cj S3: When Ci receives ack from Cj notacki = notacki -1 248

S4: When Pi becomes active cpi := false (* A passive process can only become active when its activation condition is true; this activation is under the control of the underlying operating system, and the termination detection algorithm only observes it. *) S5: When Ci receives query from Cα (* Executed only by Cα *) Wait until ((passivei ∧ (notacki = ∅ ) ¬ f ulf illedi (ARRi )); ldi := cpi ; cpi :=true; send reply(ldi ) to Cα S6: When controller Ca decides to detect static termination repeat send query to all Ci; receive^reply(ldi ) from all Ci ; td:= ldi ; 1≤i≤n

until td; claim static termination

Performance The efficiency of this algorithm depends on the implementation of waves. Two waves are in general necessary to detect static termination. A wave needs two types of messages: n queries and n replies, each carrying one bit. Thus, 4n control messages of two distinct types carrying at most one bit each are used to detect the termination once it has occurred. If waves are supported by a ring, this complexity reduces to 2n. The detection delay is equal to duration of two sequential wave executions.

7.7.5 A Dynamic Termination Detection Algorithm Recall that a dynamic termination can occur before all messages of the computation have arrived. Thus, termination of the computation can be detected sooner than in static termination.

249

Informal Description Let Cα denote the controller that launches the waves. In addition to cpi , each controller Ci has the following two vector variables, denoted as si and ri , that count messages, respectively, sent to and received from every other process. si [j] denotes the number of messages sent by Pi to Pj . ri [j] denotes the number of messages received by Pi from Pj . Let S denote an n×n matrix of counters used by Cα ; entry S[i, j] represents Cα ’s knowledge about the number of messages sent by Pi to Pj . First, Ca sends to each Ci a query message containing the vector (S[1,i],...,S[n,i]), denoted by S[.,i]. Upon receiving this query message, Ci computes the set ANEi of its non-empty channels. This is an approximate knowledge but is sufficient to ensure correctness. Then Ci computes ldi which is true if and only if Pi has been continuously passive since the previous wave and its requirement cannot be fulfilled by all the messages arrived and not yet consumed (ARRi ) and all messages potentially in its input channels (ANEi ). Ci sends to Cα a reply message carrying the values ldi and vector^si . Vector si is used by Cα to update row S[i,.] and thus gain more accurate knowledge. If ldi evaluates to true, Ca claims dynamic termination of the underlying 1≤i≤n

computation. Otherwise, Cα launches a new wave by sending query messages. Vector variables si and ri allow Cα to update its (approximate) global knowledge about messages sent by each Pi to each Pj and get an approximate knowledge of the set of non-empty input channels.

Formal Description All controllers Ci execute statements S1 to S4. Only the initiator Cα executes S5. Local variables si , ri and S are initialized to 0. S1: When Pi sends a message to Pj si [j] := si [j] + 1 S2: When a message from Pj arrives at Pi ri [j]:= ri [j] + 1 S3: When Pi becomes active cpi := false S4: When Ci receives query(VC[1..n]) from Cα (* VC[1...n] = S[1...n,i] is the i-th column of S *) ANEi := {Pj : VC[j] > ri [j]}; 250

ldi := cpi ∧ ¬ f ulf illedi (ARRi ∪ NEi ); cpi := (statei = passive); send reply(ldi ,si ) to Cα S5: When controller Cα decides to detect dynamic termination repeat for each Ci send query(S[1..n,i]) to Ci ; (* the i-th column of S is sent to Ci *) receive reply(ldi ,si ) from all Ci ; ∀ i ∈ [1..n] ^ : S[i,.]:= si ; td := ldi 1≤i≤n

until td; claim dynamic termination

Performance The dynamic termination detection algorithm needs two waves after dynamic termination has occurred to detect it. Thus, its message complexity is 4n which is lower than the static termination detection algorithm since no acknowledgements are necessary. However, messages are composed of n monotonically increasing counters. As waves are sequential, query (and reply) messages between Cα and each Ci are received and processed in their sending order; this FIFO property can be used in conjunction with the Singhal-Kshemkalyani’s differential technique to decrease the size of control messages. The detection delay is two waves but is shorter than the delay of the static termination algorithm as acknowledgements are not used.

7.8 Termination Detection in the Atomic Computation Model Mattern [12] developed several algorithm for termination detection in the atomic computation model.

Assumptions 1. Processes communicate solely by messages. Messages are received correctly after an arbitrary but finite delay. Messages sent over the same communication channel may not obey the FIFO rule. 2. A time cut is a line crossing all process lines. A time line can be a straight vertical line or a zigzag line, crossing all process lines. Time cut of a distributed computation is a set of actions characterized by a fact that whenever an action of a process belongs to that set, all previous actions of the same process also belongs to the set. 251

3. We assume that all atomic actions are totally globally ordered i.e., no two actions occur at the same time instant.

7.8.1 The Atomic Model of Execution In the atomic model of the distributed computation, a process may at any time take any message from one of its incoming communication channels, immediately change its internal state and at the same instant send out zero or more messages. All local actions at a process are performed in zero time. Thus, consideration of process states is eliminated when performing termination detection. In the atomic model, a distributed computation has terminated at time instant t if at this instant all communications channels are empty. This is because execution of an internal action at a process is instantaneous. A dedicated process, P1 , the initiator, determines if the distributed computation has terminated. The initiator P1 starts termination detection by sending control messages directly or indirectly to all other processes. Let us assume that processes P1 ,...,Pn are ordered in sequence of the arrival of the control message.

7.8.2 A Naive Counting Method To find out if there are any messages in transit, an obvious solution is to let every process count the number of basic messages sent and received. We denote the total number of basic messages Pi has sent at (global) time instant t by si (t), and the number of messages received by ri (t). The values of the two local counters are communicated to the initiator upon request. Having directly or indirectly received these values from all processes, the initiator can accumulate the counters. Figure 7.9 shows an example, where the time instants at which the processes receive the control messages and communicate the values of their counters to the initiator are symbolized by striped dots. These are connected by a line representing a “control wave", which induces a time cut.

Pn

P

P

3

2

P 1

Control Wave 111111111111111 000000000000000 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 0 1 0 1 000000 111111 000000000000000 111111111111111 0 1 0 1 000000 111111 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 0 1 0 1 000000 111111 000000000000000 111111111111111 0 1 0 1 000000 111111 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 0 1 000000 111111 000000000000000 111111111111111 000000 111111 0 1 0 1 000000 111111 000000000000000 111111111111111 000000 111111 0 1 0 1 000000000000000 111111111111111 000000 111111 0 1 000000000000000 111111111111111 000000 111111 0 1 000000000000000 111111111111111 000000 111111 0 1 000000000000000 111111111111111 0 1 000000 111111 0 1 000000000000000 111111111111111 0 1 000000000000000 111111111111111 000000000000000 111111111111111

Figure 7.9: An example showing a control wave with a backward communication. If the accumulated values at the initiator indicate that the sum of all the messages received by 252

all processes is the same as the sum of all messages sent by all processes, it may give an impression that all the messages sent have been received, i.e., there is no message in transit. Unfortunately because of the time delay of the control wave, this simple method is not correct. The example in Figure 7.9 shows that the counters can become corrupted by messages “from the future", crossing from the right side of the control wave to its left. The accumulated result indicates that one message was sent and one received although the computation has not terminated. This misleading result is caused by the fact that the time cut is inconsistent. A time cut is considered to be inconsistent, if when the diagonal line representing it is made vertical, by compressing or expanding the local time scales, a message crosses the control wave backwards. However, this naive method for termination detection works if the time cut representing the control wave is consistent. Various strategies can be applied to correct the deficiencies of the naive counting method: • If the time cut is inconsistent, restart the algorithm later. • Design techniques that will only provide consistent time cuts. • Do not lump the count of all messages sent and all messages received. Instead, relate the messages sent and received between pairs of processes. • Use techniques like freezing the underlying computation.

7.8.3 The Four Counter Method A very simple solution consists of counting twice using the naive counting method and comparing the results. After the initiator has received the response process and accumulated the X from the last X values of the counters R* and S* (where R*:= ri (ti ) and S*:= si (ti )), it starts a second ∀i

∀i

control wave (see Figure 7.10), resulting in values R’* and S’*. The system is terminated, if values of the four counters are equal, i.e., R* = S* = R’* = S’*. In fact, a slightly stronger result exists: If R* = S’*, then the system had terminated at the end of the first wave (t2 in Figure 7.10). Let t2 denote the time instant at which the first wave is finished, and t3 (≥ t2) denote the starting time of the second wave (see Figure 7.10).

1. Local message counters are monotonic, t ≤ t′ implies si (t)≤si (t′ ) and ri (t)≤ri (t′ ). This follows from the definition. 2. The total number of messages sent or received is monotonic, that is, t ≤ t′ implies S(t)≤S(t′ ) and R(t)≤R(t′ ). 3. R*≤ R(t2 ). This follows from (1) and the fact that all values ri are collected before t2 .

253

Pn

P

P

3

2

P 1

First Wave Second Wave 0000000000 1111111111 00000 11111 0000000000 1111111111 00000 11111 0000000000000000000000000000000 1111111111111111111111111111111 0 1 0 1 0000000000 1111111111 00000 11111 0 1 0 1 0000000000 1111111111 00000 11111 0000000000 1111111111 00000 11111 0000000000 1111111111 00000 11111 0000000000 1111111111 00000 11111 0000000000 1111111111 00000 11111 0000000000 1111111111 00000 11111 0000000000 1111111111 00000 11111 00 11 1111111111111111111111111111111 0000000000000000000000000000000 0 1 0 0000000000 1111111111 00000 11111 001 11 0 1 0 0000000000 1111111111 001 11 0000000000 1111111111 00 11 0000000000 1111111111 00 11 0000000000 1111111111 00 11 0000000000 1111111111 1111111111111111111111111111111 0000000000000000000000000000000 00 11 00 11 0 1 0 1 0000000000 1111111111 00 11 0 1 0 1 0000000000 1111111111 00 11 0000000000 1111111111 00 11 0000000000 1111111111 00 11 1111111111111111111111111111111 0000000000000000000000000000000 0000000000 1111111111 0 1 0 1 0 1 00 11 0000000000 1111111111 0 1 0 1 0 1 0000000000 1111111111 0 1 t 1

t 2

t 3

t 4

Figure 7.10: An example showing two control waves. 4. S’*≥ S(t3 ). This follows from (1) and the fact that all values si are collected after t3 . 5. For all t: R(t)≤ S(t). This is because the number of messages in transit D(t):= S(t) - R(t) ≥ 0. Now we show that if R* = S’*, then the computation had terminated at the end of the first wave. R∗ = S ′ ∗ : R(t2 )≥S(t3 ) : R(t2 )≥S(t2 ) :R(t2 ) = S(t2 ) That is, the computation terminated at t2 (at the end of the first wave). If the system terminated before the start of the first wave, it is trivial that all messages arrived before the start of the first wave, and hence the values of the accumulated counters will be identical. Therefore, termination is detected by the algorithm in two “rounds" after it had occurred. Note that the second wave of an unsuccessful termination test can be used as the first wave of the next termination test. However, a problem with this method is to decide when to start the next wave after an unsuccessful test - there is a danger of an unbounded control loop.

7.8.4 The Sceptic Algorithm Note that the values of the counters obtained by the first wave of the four counter method can become corrupted if there is some activity at the right of the wave. To detect such activity, we use flags which are initialized by the first wave, and set by the processes when they receive (or alternatively when they send) messages. The second wave checks if any of the flags have been set, in which case a possible corruption is indicated. A general drawback is that at least two waves are necessary to detect the termination. It is possible to devise several variants based on the logical control topology. If the initiator asks every process individually, it corresponds to a star topology. It is possible to implement the sceptic algorithm on a ring, however, symmetry is not easily achieved since different waves may interfere when single flag is used at each process. Spanning tree is also an interesting control configuration. 254

Echo algorithms used as a parallel graph traversal method induce two phases. The “down" phase is characterized by the receipt of a first control message which is propagated to all other neighbors, and the “up" phase by the receipt of the last of the echoes from its neighboring nodes. These two phases can be used as two necessary waves of the sceptice method for termination detection.

7.8.5 The Time Algorithm The time algorithm is a single wave detection algorithm where termination can be detected in one single wave after its occurrence at the expense of increased amount of control information or augmenting every message with a timestamp. In the time algorithm, each process has a local clock represented by a counter initialized to 0. A control wave started by the initiator at time i, accumulates the values of the counters and “synchronizes" the local clocks by setting them to i+1. Thus, the control wave separates “past" from “future". If a process receives a message whose timestamp is greater than its own local time, the process has received a message from the future (i.e., the message crossed the wave from right to left) and the message has corrupted the counters. After such a message has been received, the current control wave is nullified on arrival at the process.

Formal Description Every process Pj (1 ≤ j ≤ n) has a local message counter COUNT (initialized to 0) that holds the value sj - rj , a local discrete CLOCK (initialized to 0), and a variable TMAX (also initialized to 0) that holds the latest send time of all messages received by Pj . The psuedo code for process Pj is as follows: (a) When sending a basic message to Pi : 1. COUNT ←COUNT +1; 2. send to Pi ; /* time-stamped basic message */ (b) When receiving a basic message : 3. COUNT←COUNT -1; 4. TMAX←max(TSTAMP, TMAX); 5. /* process the message */ (c) When receiving a control message : 6. CLOCK← max(TIME, CLOCK): /* synchronize the local closk */ 7. if INIT = j /* complete round? */ 8. then if ACCU = 0 and not INVALID 9. then "terminated" else "try again"; 10. endif; 11. else send 255

to P(j mod n)+1 ; 12.

end_if;

(d) When starting a control round: 13. CLOCK←CLOCK +1; 14. send to P(j mod n)+1 ; A control message consists of four parameters, the (local) time at which the control round was started, the accumulator for the message counters, a flag which is set when a process has received a basic message from future (TMAX≥TIME) and the identification of the initiating process. The first component of a basic message is always the timestamp. For each single control wave, any basic message that crosses the wave from the right side of its induced cut to its left side is detected. Note that different control waves do not interfere; they merely advance the local clocks further. Once the system is terminated, the values of the TMAX variables remain fixed and since for every process Pj , T MAXj ≤max CLOCKi (1 ≤ i ≤ n), the process with the maximum clock value can detect global termination in one round. Other processes may need more rounds.

7.8.6 Vector Counters Method Vector counters method of termination detection consists of counting messages in such a way, that it is not possible to mislead the accumulated counters. The configuration used is the ring with n processes where every process Pj (1 ≤ j ≤ n) has a COUNT vector of length n, where COUNT[i] (1 ≤ i ≤ n) denotes the i-th component of the vector. A circulating control message also consists of a vector of length n. For each process Pj , the local variable COUNT[i] (i 6= j) holds the number of basic messages which have been sent to process Pi since the last visit of the control message. Likewise, the negative value of COUNT[j] indicates how many messages have been received from any other process. At any (global) time instant, the sum of the k-th components of all n COUNT vectors including the circulating control vector equals the number of messages currently on their way to process Pk , 1 ≤ k ≤ n. This property is maintained invariant by the implementation given below. For simplicity, we assume that no process communicates with itself, Pn+1 is identical to P1 , an operation on a vector is defined by the operating on each of its components, and 0* denotes the null vector. The psuedo code for process Pj is as follows: COUNT is initialized to 0* (a) When sending a basic message to Pi (i6=j): 1. COUNT[i] ← COUNT[i]+1; (b) The following instructions are executed at the end of all local actions triggered by the receipt of a basic message: 256

2. 3. 4. 5. 6. 7. 8.

COUNT[j]←COUNT[j]-1; if COUNT[j] = 0 then if COUNT = 0* then "system terminated" else send accumulate to Pj+1; COUNT←0*; end_if; end_if;

(c) When receiving a control message ’accumulate ’: 9. COUNT←COUNT + ACCU; 10. if COUNT [j] ≤ 0 then 11. if COUNT = 0* then “system terminated" 12. else send accumulate to Pj+1; 13. COUNT←0*; 14. end_if; 15. end_if;

An initiator Pi starts the algorithm by sending the control message ’accumulate ’ to Pi+1 . A mechanism is needed to ensure that every process is visited at least once by the control message, i.e., that the control vector makes at least one complete round after the start of the algorithm. Every process counts the number of outgoing messages individually by incrementing the counter indexed by the receiver’s process number (line 1); the counter indexed by its own number is decremented on receipt of a message (line 2). When a process receives the circulating control message, it accumulates the values in the message to its COUNT vector (line 9). A check is then made (line 10) to determine whether any basic messages known to the control message have still not arrived at Pj . If this is the case (COUNT[j]>0), the control message is removed from the ring and regenerated at later time (line 5) when all expected messages have been received by Pj . For this purpose, every time a basic message is received by a process Pj , a test is made to check whether COUNT[j] is equal to 0 (line 3). Note that lines 4-15 are only executed when the control vector is at Pj . Note that there is at most one process Pj with COUNT[j]>0, and if this is the case at Pj , the control vector “waits" at process Pj (lines 11 to 13 are not executed and the control vector remains at Pj ). If it is not required that the control message waits at nodes for outstanding basic messages, the algorithm can be simplified considerably by removing lines 3-8 as well as lines 10 and 15.

257

Performance The number of control messages exchanged by this algorithm is bounded by n(m+1), m denotes the number of basic messages, because at least one basic message is received in every round of the control message, excluding the first round. Therefore, the worst case communication complexity for this algorithm is O(mn).

7.8.7 A Channel Counting Method The channel counting method is a refinement of the vector counter method in the following way: a process keeps track of the number of messages sent to each process and keeps track of the number of messages received from each process, using appropriate counters. + + − − Each process Pj has n counters, Cj1 ,..., Cjn , for outgoing messages and n counters, C1j ,...,Cnj , − + for incoming messages. Cij is incremented when Pj receives a message from process Pi , and Cjk is incremented when Pj sends a message to Pk . Upon demand, each process informs the values of the counters to the initiator. The initiator reports termination if Cij− = Cij+ for all i,j. The method becomes more practical if it is combined with the echo algorithm, where test messages flow down on every edge of the graph and echoes proceed in the opposite direction. The value of Cij− is transmitted upwards from process Pj to Pi in an echo; whereas, a test message sent by Pi to Pj carries the value of Cij+ with it. A process receiving a test message from another process (the activator), propagates it in parallel to any other process to which it sent basic messages whose receipts have not yet been confirmed. If it has already done this, or if all basic messages sent out have been confirmed, an echo is immediately sent to the activator. There are no special acknowledgement messages. A process Pi receiving the value of Cij− in an echo, knows that all messages it sent to Pj have arrived if the value of Cij− equals the value of its own counter Cij+ . An echo is only propagated towards the activator if an echo has been received from each subtree and all channels in the subtrees are empty.

Formal Description Each process Pj has the following arrays of counters: 1. OUT[i]: counts the number of basic messages sent to Pi . 2. IN[i]: counts the number of basic messages received from Pi . 3. REC[i]: records the number of its messages Pj is aware have been received by Pi . + OUT[i] corresponds to Cji and IN[i] to Cij− . A variable ACTIVATOR is used to hold the index number of the activating process and a counter DEGREE indicates how many echoes are still missing.

The psuedo code for process Pj is as follows: 258

{OUT, IN, REC are initialized to 0* and DEGREE to 0.} (a) When sending a basic message to Pi : 1. OUT[i]→OUT[i]+1; (b) When receiving a basic message from Pi : 2. IN[i]←IN[i]+1; (c) On the receipt of a control message test < m > from Pi where m≤IN[i]: 3. if DEGREE > 0 or OUT = REC /* already engaged or subtree is quiet */ 4. then send echo to Pi ; 5. else ACTIVATOR←i; /* trace activating process */ 6. PROPOGATE /* and test all subtrees */ 7. end_if; (d) On the receipt of a control message echo < m > from Pi : 8. REC[i]←m; 9. DEGREE←DEGREE - 1; /* decrease missing echoes counter */ 10. if DEGREE=0 then /* last echo checks whether all subtrees are quiet */ 11. PROPAGATE; 12. end_if; 13. if DEGREE=0 then /*all echoes arrived, everything quiet */ 14. send echo to PACT IV AT OR ; 15. end_if; (e) The procedure PROPAGATE called at lines 6 to 11 is defined as follows: 16. procedure PROPAGATE: 17. loop for K = 1 to n do 18. if OUT[K]6=REC[K] then /* confirmation missing */ 19. send test to Pk ; /* check subtree */ 20. DEGREE←DEGREE + 1; 21. end_if; 22. end_loop; 23. end_procedure; Varibale DEGREE is incremented when a process sends a test message (line 20) and it is decremented when a process receives an ECHO message (line 9). If DEGREE >0, it means the node is “engaged" and a test message is immediately responded to with an echo message (line 4). An echo is also returned for a test message if OUT = REC (line 3), i.e., if process sent no messages at all

259

or if all messages sent out by it have been acknowledged. Lines 10-15 insure that an echo is only returned if the arrival of all basic messages has been confirmed and all computations in the subtree finished. This is done by sending further test messages (via procedure PROPAGATE) after the last echo has arrived (lines 10-12). These test messages visit any of the subtree root processes which have not yet acknowledged all basic messages sent to them. The procedure PROPAGATE increases the value of the variable DEGREE if any processes are visited, thus preventing the generation of an echo (lines 13-15). To minimize the number of control messages, test messages should not overtake basic messages. To achieve this, test messages carry with them a count of the number of basic messages sent over the communication channel (line 19). If a test messages overtakes some basic messages (and it is not overtaken by basic messages), its count will be greater than the value of the IN-counter of the receiver process. In this case, the test message is put on hold and delivered later when all basic messages with lower count have been received (guard m ≤IN[i] in point(c) insures this). The initiator starts the termination test only once, as if it had received a test < 0 > message from some imaginary process P0 . On termination detection, instead of eventually sending an echo to P0 , it reports termination. Test messages only travel along channels which were used by basic messages; processes that did not participate in the distributed computation are not visited by test messages. For each test message, an echo is eventually sent in the opposite direction.

Performance At least one basic message must have been sent between the send of two test messages along the same channel. This results in an upper bound of 2m control messages, where m denotes the number of basic messages. Hence, the worst case communication complexity is O(m). However, The worst case should rarely occur, specially, if the termination test is started well after the computation started. In many situations, the number of control messages should be much smaller than m. The exact number of control messages involved in channel counting is difficult to estimate because it is highly dependent on communication patterns of the underlying computation.

7.9 Termination Detection in a Faulty Distributed System An algorithm is presented that detects termination in distributed systems in which processes fail in a fail-stop manner. The algorithm is based on the weight-throwing method. In such a distributed system, a computation is said to be terminated if and only if each healthy process is idle and there is no basic message in transit whose destination is a healthy process. This is independent of faulty processes and undeliverable messages (i.e., whose destination is a faulty process). Based on the weight-throwing scheme, a scheme called flow detecting scheme is developed by Tseng [20]to derive a fault-tolerant termination detection algorithm.

260

Assumptions Let S = P1 , P2 , ..., Pn be the set of processes in the distributed computation. Cij represents the bidirectional channel between Pi and Pj . Communication network is asynchronous. Communications channels are reliable, but they are non-FIFO. At any time, an arbitrary number of processes may fail. However, the network remains connected in the presence of faults. Fail-stop model implies that a failed process stops all activities and can not rejoin the computation in the current session. Detection of faults takes a finite amount of time.

7.9.1 Flow Detecting Scheme Weights may be lost because a process holding non-zero weight may crash or a message destined to a crashed process is carrying a weight. Thus, due to faulty processes and undeliverable messages carrying weights, it may not be possible for the leader to accumulate the total weight of 1 to declare termination. Thus, in case of a process crash, the lost weight must be calculated. To solve this problem, concept of flow invariant is used.

The Concept of Flow Invariant Define H ⊆ S the set of all healthy processes. Define subsystem H to be part of the system containing all processes in H and communication channels connecting two processes in H. According to the concept of flow invariant, weight change of the subsystem during the time interval I, during which the system is doing computation, is equal to (weights flowing into H during I) − (weights flowing out of H during I). To implement this concept, a variable called neti is assigned to each process Pi belonging to H. This variable records the total weight flowing into and out of the subsystem H. Initially, ∀i neti = 0. The following flow-detecting rules are defined. Rule 1: Whenever a process Pi which belongs to H receives a message with weight x from another process Pj which does not belong to H, x is added to neti. Rule 2: Whenever a process Pi which belongs to H sends a message with weight x to a process Pj which does not belong to H, x is subtracted from neti . Let WH be the sum of the weights of all processes in H and all in-transit messages transmitted between processes in H. WH =

X

(neti + 1/n)

Pi ∈H

where, 1/n is the initial weight held by each process Pi . Let H = S–H be the set of faulty processes. The distribution of weights is divided into four

261

parts: WH : weights of processes in H. WH : weights of processes in H. WH→H : weights held by in-transit messages from H to H. WH→H : weights held by in-transit messages from H to H.

WH

H

W

WH W

H

H

H

H

H

Figure 7.11: Healthy and faulty process sets and message flow between them. This is shown in Figure 7.11. WH and WH→H are lost and can not be used in the termination detection.

7.9.2 Taking Snapshots In distributed systems, due to the lack of a perfectly synchronized global clock, it is not possible to get a global view of the subsystem H and hence it may not possible to determine WH . We obtain W H , which is an estimated value of WH by taking snapshots on the subsystem H and by using the above equation for WH . However, note that weights in WH→H carried by in-transit messages may join H and change WH . To obtain a stable value of WH , channels from H to H are disconnected before taking snapshots of H. Once a channel is disconnected, a healthy process can no longer send or receive messages along this channel. A snapshot on H is the collection of neti ’s from all processes in H. A snapshot is said to be consistent if all channels from H to H are disconnected before taking the snapshot (i.e., recording the values of neti ). A snapshot is taken upon a snapshot request by the leader process. The leader uses the information in a consistent snapshot and equation to compute WH to calculate W H . Snapshots are requested when a new faulty process is found or when a new leader is elected. It should be noted that W H is an estimate of the weight that is remaining in the system. This is because processes can fail and stop any time and there may not exist any point in real time in the computation where H is the healthy set of processes. Suppose H ′ is the set of healthy processes at some point in time in the computation after taking the snapshot. If H = H ′ , then W H = WH ′ ; otherwise, W H ≥ WH ′ must 262

be true, because of the fail stop model of processes. This eliminates the possibility of declaring termination falsely. Thus, the leader can safely declare termination after it has collected W H of weight.

7.9.3 Description of the Algorithm The algorithm combines the weight-throwing scheme, the flow detecting scheme and a snapshotrecording scheme. Process Pi elects itself the leader if it knows that all Pj , j < i, are faulty. The leader process takes snapshots and estimates remaining weight in the system. Data Structures The following data structures are used at process a Pi , i=1, ...,n: • li is the id of the leader known to Pi . Initially li =1 • wi is the weight currently held by Pi . Initially wi = 1/n. • si is the systems total weight assumed by Pi . Pi will try to collect this amount of weight. Initially, si =1. • NETi [1,. . . ,n] is an array real numbers. NETi [j] keeps track of the total weight flowing into Pi from Pj . Initially, NETi [j]=0 for all j=1,. . . ,n. • Fi is a set of faulty processes. A process Pj belongs to Fi if and only if Pi knows that Pj is faulty and Pi has disconnected its channel to Pj . Initially, Fi is a null set. • SNi is a set of processes. When Pi initiates a snapshot, SNi is a set of processes to which Pi sends snapshot requests. A process Pj belonging to SNi is removed from SNi if Pi receives a reply from Pj or if Pi finds Pj is faulty. No new snapshot is started unless SNi is an empty set. Initially, SNi is a null set, which implies no snapshot is in progress. • ti is used for temporarily calculating the total remaining weight while a snapshot is in progress. • ci a boolean, used for temporarily calculating the consistency of a snapshot. Types of Messages The following four types of messages are exchanged by the algorithm: • B(x) is a basic message B with weight x. • C(x) is a control message that is used to report weight x to the leader process.

263

• Request(Fi ) is a snapshot requesting message sent by the leader process Pi . The set Fi is to inform the receiver the set of faulty processes known to Pi . • Reply(Fj , NETj ) is the state reporting message sent by Pj in reply to the leader’s Request() message. The Algorithm The algorithm is described for process Pi . The algorithm consists of nine event-driven atomic actions, each having the format “(guard) → (actions)”. Actions are triggered by sending/receiving messages, changing local states, or detecting new faulty processes. The following actions from A1 to A5 implement weight throwing and flow detecting schemes. A1: (Pi sending a basic message B to Pj ) → wi is partitioned into x and y such that x>0,y>0 and x+y =wi ; B(x) is sent to Pj ; NETi [j]:= NETi [j] – x; wi := y; A2: (Pi receiving a basic message B(x) from Pj ) → NETi [j]:= NETi [j] + x; wi := wi + x; pass the basic message to the underlying system; A3: (Pi becoming idle) → if li 6= i then send C(wi ) to Pli ; NETi [li ]:= NETi [li ] – wi ; wi := 0; end if; A4: (Pi receiving a control message C(x) from Pj ) → NETi [j]:= NETi [j] + x; wi := wi + x; A5: (Pi is idle) ∧ (si = wi ) → announce “termination”; A1 is activated when Pi sends a basic message to another process. A2 is triggered by receiving a basic message. A3 is the weight reporting action. When Pi is not the leader, it sends its weight to the leader process in a control message. A4 describes Pi ’s response on receiving a con-

264

trol message. In all actions A1-A4, NETi [1. . . n] records the weight flowing information. In A5, leader Pi announces the termination. The following actions from F1 to F4 deal with faults and take snapshots of the system. (* Actions for detecting a fault when no snapshot is in progress *) F1: (Pi detecting Pj faulty) ∧ (Pj 6∈ Fi ) ∧ (SNi = ∅) → disconnect the channel from Pi to Pj ; Fi := Fi ∪ {Pj }; li = min{k | Pk ∈ S – Fi }; if (li = i), then call snapshot(); end if; (* Actions on receiving a snapshot request *) F2: (Pi receiving Request(Fj ) from Pj ) → li := j; for every Pf belonging to Fj − Fi , disconnect the channel Ci,f ; Fi := Fi ∪ Fj ; Send a Reply(Fi , NETi [1. . . n]) to Pj ; (* Actions on receiving a snapshot response *) F3: (Pi receiving Reply(Fj , NETj [1. . . n] from Pj ) → if (Fi 6= Fj ) ∨ ¬ci then for every Pf belonging to Fj − Fi , disconnect the channel Ci,f ; Fi = Fi ∪ Fj ; ci = false; else P ti = ti + 1/n + Pf ∈Fj NETj [f ]; end if; SNi = SNi – {Pj }; if SNi = ∅ then if ci then si:= ti else call snapshot(); end if; end if; (* Actions for detecting a fault when a snapshot is in progress *) F4: (Pi detecting Pj faulty) ∧ (SNi 6= ∅) → Disconnect the channel Ci,j ; Fi := Fi ∪ {Pj }; ci := false; SNi := SNi – {Pj }; if SNi = ∅ then call snapshot(); end if; 265

(* Snapshot taking procedure *) Procedure snapshot() (* assuming the caller is Pi *) Begin SNi = S – Fi – {Pi }; (* processes that will receive requests *) ∀ Pk ∈ SNiX , send a Request(Fi ) to Pk ; ti := 1/n + NETi [f ]; ci := true;

Pf ∈Fi

end; F1 is triggered when Pi detects for the first time that a process Pj is faulty and no snapshot is currently in progress. The channel from Pi to Pj is disconnected. Then Pi elects a healthy process with least id as its leader. If process Pi itself is the leader, then it invokes snapshot procedure to initiate a snapshot. In the snapshot() procedure, first SNi is set to the set of processes to which Request()’s are to be sent and sends a Request() to these processes. This prevents F1 from being executed until the snapshot finishes. Assuming that the current healthy process set is S – Fi and this snapshot is consistent, more weight is added to ti as Pi receives Reply() messages from other processes. F2 describes Pi ‘s response on receiving a Request() message from Pj . Pi disconnects channels to faulty processes and sends a Reply() message to Pj , that sent the Request() message. The initiator of the snapshot Pi waits for each Pj belonging to SNi for either a Reply() coming from Pj or Pj being detected as faulty. If a Reply() is received from Pj , F3 is executed. F3 describes Pi ’s actions on receiving such a snapshot response. The consistency of the snapshot is checked. If the snapshot is still consistent, ti is updated. Then the barrier SNi is reduced by one. If the barrier becomes null and the snapshot is consistent, si is updated to ti. If the snapshot is not consistent, another snapshot is initiated. The snapshot initiator Pi executes F4 when it detects a process Pj ∈ SNi , is faulty and a snapshot is in progress. Another snapshot is started only when SNi =∅. Such a procedure is repeated until a consistent snapshot is obtained. Because of the fail stop model of processes, the number of healthy processes is a non-increasing function of time and eventually the procedure will terminate.

7.9.4 Performance Analysis If k processes become faulty, at most 2k snapshots will be taken. Each snapshot costs at most n–1 Request()s and n–1 Reply()s. Thus, the message overhead due to snapshots is bounded by 4kn. 266

If M basic messages are issued, processes will be activated by at most M times. So processes will not turn idle more than M + n times. So at most M + n control messages C(x) will be issued. Thus, the message complexity of the algorithm is O(M + kn + n). The termination detection delay is bounded by O(k+1). The termination detection delay is defined as the maximum number of message hops needed, after the remination has occurred, by the algorithm to detect the termination.

7.10 Bibliographic Notes The termination detection problem was brought to prominence in 1980 by Francez [5] and by Dijkstra and Scholten [4]. Since then, a large number of termination detection algorithms having different features and for a variety of logical system configurations have been developed. A termination detection algorithm that uses distributed snapshot is discussed in [8]. A termination detection algorithm based on weight throwing is discussed in [9]. A termination detection algorithm based on weight throwing was first developed by Mattern [13]. Dijkstra et al. [3] present a ring-based termination detection algorithm. Topor [19] adapts this algorithm to a spanning tree configuration. Chandrasekaran and Venkatesan [2] present a message optimal termination detection algorithm. Brzezinski et al. [1] define a very general model of the termination problem, introduce the concept of static and dynamic terminations, and develop algorthms to detect static and dynamic terminations. Mattern developed [12] several algorithms for termination detection for the atomic model of computation. An algorithm for termination detection under faulty processes is given by Tseng [20]. Mayo and Kearns [14, 15] present efficient termination detection based on roughly synchronized clocks. Other algorithms for termination detction can be found in [6, 10, 11, 16, 17, 18, 21]. Many termination detection algorithms use a spanning tree configuration. An efficient distributed algorithm to construct a minimum weight spanning tree is given in [7].

7.11 Exercise Problems 1. Haung’s termination detection algorithm could be redesigned using a counter to avoid the need of splitting weights. Present an algorithm for termination detection that uses counters instead of weights. 2. Design a termination detection algorithm that is based on the concept of weight throwing and is tolerant to message losses. Assume that processe do not crash. 3. Termination detection algorithms assume that an idle process can only be activated on the reception of a message. Consider a system where an idle process can become active spontaneously without receiving a message. Do you think a termination detection algorithm can be designed for such a system? Give reasons for your answer.

267

4. Design an efficient termination detection algorithm for a system where communication delay is zero. 5. Design an efficient termination detection algorithm for a system where computation at a process is instantaneous (that is, all proceses are always in the idle state.)

268

Bibliography [1] J. Brzezinski, J.M. Helary and M. Raynal, "Termination detection in a very general distributed computing model". in Proc. of International Conf. on Distributed Computing Systems, Poland, 1993, pp. 374-381. [2] S. Chandrasekaran and S. Venkatesan, “A Message-Optimal Algorithm for Distributed Termination Detection". J. of Parallel and Distributed Computing, 1990, pp. 245-252. [3] E.W.Dijikstra, WH.J.Feijen and A.J.M.van Gasteren, “Derivations of a termination detection algorithm for distributed computations". Information Processing Letters, 16, 5, June 1983, pp. 217-219. [4] E.W. Dijkstra and C.S. Scholten, “Termination Detection for Distributed Computations", Information Processing Letters, 11, 1, 1980, pp. 1-4. [5] N. Francez, “Distributed Termination", ACM Trans. on Programming Langauges, 2(1), 1980, pp. 42-55. [6] N. Francez and M. Rodeh, “Achieving distributed termination without freezing", IEEE Trans. on Software Engineering, May 1982, pp. 287-292. [7] R.G. Gallager, P. Humblet, and P. Spira, “A Distributed Algorithm for Minimum Weight Spanning Trees", ACM Trans. on Programming Langauges and Systems, January 1983, pp. 66-77. [8] Shing-Tsaan Huang, “Termination detection by using distributed snapshots". Information Processing Letters, 32, August 1989, pp. 113-119. [9] S. T. Huang, “Detecting Termination of Distributed Computations by External Agents", Proc. of the 9th International Conf. on Distributed Computing Systems, 1989, pp. 79-84. [10] D. Kumar, “A Class of Termination Detection Algorithms for Distributed Computations", 5th Conf. on Foundation of Software Technology and Theoretical Computer Science, New Delhi, Springer-Verlag, LNCS 206, 1985, pp. 73-100. [11] T.H. Lai, “Termination Detection for Dynamically Distributed Systems with non-first-in-firstout Communication", J. of Parallel and Distributed Computing, December 1986, pp. 577-599. 269

[12] Friedemann Mattern, “Algorithms for distributed termination detection". Distributed Computing, Vol 2, 1987, pp. 161-175. [13] F. Mattern, “Global quiescence detection based on credit distribution and recovery", Information Processing Letters, 30, 4, 1989, pp. 195 - 200. [14] Jean Mayo and Phil Kearns, Distributed Termination Detection with Roughly Synchronized Clocks. Inf. Process. Letters, 52(2), 105-108, (1994). [15] Jean Mayo and Phil Kearns, Efficient Distributed Termination Detection with Roughly Synchronized Clocks, Parallel and Distributed Computing and Systems, 1995, 305-307. [16] J. Misra and K.M. Chandy, “Termination Detection of Diffusing Computations in Communication Sequential Processes", ACM Trans. on Programming Languages and Systems, January 1982, pp. 37-42. [17] S.P. Rana, “A Distributed Solution of the Distributed Termination Problem", Information Processing Letters, 17, 1, pp. 43-46. [18] Stefan Ronn and Heikki Saikkonen, “Distributed Termination Detection with Counters". Information Processing Letters, 34, 5, 1990, pp. 223-227. [19] Rodney W. Topor, “Termination detection for distributed computations". Information Processing Letters, 18, 1, January 1984, pp. 33-36. [20] Yu-Chee Tseng, “Detecting Termination by Weight-Throwing in a Faulty Distributed System". J. Parallel Distrib. Computing, 25(1), (1995), pp. 7-15. [21] Yu-Chee Tseng, Cheng-Chung Tan, “On Termination Detection Protocols in a Mobile Distributed Computing Environment", Proc. of ICPADS, 1998, pp. 156-163.

270

Chapter 8 Reasoning with Knowledge In a distributed system, processes make local decisions based on their limited view of the system state. A process learns of new facts when it receives messages from other processes, and can reason only with the additional knowledge available to it. This chapter provides a formal framework in which it is easier to understand the role of knowledge in the system, and how processes can reason with such knowledge. The logic of knowledge, classically termed as epistemic logic, is the formal logical analysis of reasoning about knowledge. Epistemic knowledge first received much attention from philosophers in the mid-twentieth century.

8.1 The Muddy Children Puzzle Consider the classical “muddy children” puzzle of Halpern and Moses. Imagine there are n children who return from playing outdoors, and k, k ≥ 1, of the n children have mud on their foreheads. Let Ψ denote the fact “at least one child has a muddy forehead.” Assume that each child can see all other children and their foreheads, but not his/her own forehead. We also assume that the children are intelligent and truthful, and answer any question asked of them, simultaneously. We now consider two scenarios. In Scenario A, the father who now shows up on the scene, first makes a statement announcing Ψ. We assume that this announcement is heard by everyone, and that everyone is aware that the announcement is being made in their common presence. The father now repeatedly asks the children, “Do you have mud on your forehead?” The first k − 1 times that the father asks the question, all the children will say “No” and the k th time the father asks the question, the children with mud on their foreheads (henceforth, referred to as the muddy children) will all say “Yes.” This can be proved by induction on k. • If k = 1, the single muddy child, seeing no other muddy children and knowing the announcement of Ψ, will conclude on hearing the father’s question that he himself is the muddy child. • If k = 2, let the two muddy children be m1 and m2. The first time the question is asked, neither can answer in the affirmative. But when m1 hears the negative answer of m2, m1 can 271

reason that m1 himself must be muddy because otherwise m2 would have answered “Yes” in the first round using the logic for the k = 1 case. Hence, m1 answers “Yes” the second time, and m2 who uses analogous reasoning, also answers “Yes.” • We assume the induction hypothesis is true for k = x muddy children. • For k = x + 1 muddy children, the proof is as follows. Each muddy child reasons in the following manner. “If there were x muddy children, then they would all have answered ‘Yes’ when the question is asked for the xth time. As that did not happen, there must be more than x muddy children, and as I can see only x other muddy children, I myself must also be muddy. So I will answer ‘Yes’ when the question is asked for the x + 1th time.” In Scenario B, the father who now shows up on the scene, does not make the announcement of Ψ, but repeatedly asks the children, “Do you have mud on your forehead?” All the children repeatedly respond with a “No.” This can be shown by induction on q, the number of times the father asks the question, that “no matter how many muddy children there are, all children answer ‘No’ to the first q questions.” For q = 1, each child answers “No” because he cannot distinguish the two situations wherein he has and does not have mud on his forehead. Assume the hypothesis is true for q = x. For q = x + 1, the situation is unchanged because each child has no further knowledge to distinguish the two situations wherein he has and does not have mud on his forehead. In Scenario A, the father announced Ψ whereas in Scenario B, the father did not announce Ψ, and the responses of the children were very different. The announcement of Ψ effectively made Ψ common knowledge among the children, and this enabled the children to reason differently. The above puzzle introduces the notions of knowledge, levels of knowledge, and common knowledge in a system. We now define these formally and consider how such logic can be adapted to computing systems.

8.2 Logic of Knowledge 8.2.1 Knowledge Operators A definition of knowledge requires the identification of an appropriate set of possible worlds (also called possible universes or possible configurations), and a family of possible relations between those worlds. In a given global state, the possible worlds at a process denote all the global states that the process believes may be consistent with its local state. These states are expressible as logical formulas. Fact φ can be a primitive proposition or a formula using the usual logical connectives (∧, ∨, ¬ on primitive propositions, the “knowledge operator” K, and the “everyone knows” operator E. Propositional logic is adequate to cover many interesting scenarios that occur in distributed executions, although first-order and higher-order logics can also be used. The traditional semantics of knowledge, using the K and E operators, were first based on timed executions. Intuitively, a

272

process i that knows a fact φ is said to have knowledge Ki (φ), and if “every process in the sysV tem knows φ”, then the system exhibits knowledge E 1 (φ) = i∈N Ki (φ). A knowledge level of E 2 (φ) indicates that every process knows E 1 (φ), i.e., E 2 (φ) = E (E 1 (φ)). Inductively, E k (φ) = E k−1 (E 1 (φ)) for k > 1. Thus, a hierarchy of levels of knowledge E j (φ) (j ∈ Z∗) gets defined, where Z∗ is used to denote the set of whole numbers {0, 1, 2, 3, . . .}. It can be seen that E k+1(φ) = : E k (φ). Each level in the hierarchy represents a different level of group knowledge among the processes. V In the limiting case, we have the j∈Z∗ E j (φ). Informally, this knowledge of a fact φ stands for ”everyone knows that everyone knows that everyone knows . . . (infinitely often) the fact φ.” This limit is informally called common knowledge of φ. Strictly speaking, the epistemic logic is finitary and hence does not allow such infinite conjunctions. On a more formal note, common knowledge of φ, denoted as C(φ), is defined as the knowledge X which is the greatest fixed point of E(φ ∧ X). Stated differently, common knowledge is a state of knowledge X satisfying the equality, X = E(φ ∧ X). The theory of fixed points is quite intricate. For our purposes, it V suffices if we informally view the fixed point as implying the infinite conjunction j∈Z∗ E aj (φ). Common knowledge of a fact captures the notion of everyone agreeing on the fact, and is therefore an important notion in distributed systems.

8.2.2 The Muddy Children Puzzle Again indexmuddy children puzzle We now revisit the muddy children puzzle. Assume there are k children m1, . . . mk with mud on their forehead. In this system, E k−1 (Ψ) is true, but not E k (Ψ). In Scenario A, we have the following. • Consider k = 1. Here, the child with the mud on the forehead does not see any muddy child, and hence E(Ψ) is false. • Consider k = 2. Here, E 1 (Ψ) is true because every child can see at least one muddy child, and thus ∧i∈N Ki (Ψ). However, m1 can see only one muddy child m2 and therefore in some possible world, m1 believes that at m2, Km2 (Ψ) may be false, i.e., ¬Km1 Km2 (Ψ), and hence E 2 (Ψ) is false. • Generalizing this reasoning for k muddy children, E k−1 (Ψ) is true because Ki1 . . . Kik−1 (Ψ) is true for all instantations of i1 , . . . ik−1 . This is so because everyone can see at least k − 1 muddy children. However, E k (Ψ) is false because Ki1 . . . Kik (Ψ) is false when i1 is instantiated by any of m1, . . . mk. This is so because everyone can see k −1 muddy children, and only the n − k clean children can see the k muddy children. In Scenario B, the only knowledge available in the system is E k−1(Ψ), and this is not enough for the children with mud on their forehead to ever respond affirmatively to the father. In order that the children with mud be able to respond affirmatively, E k (Ψ) needs to be true in the system so that the children can use the knowledge progressively step-by-step and answer correctly in the k th round of questioning. How was this E k (Ψ) achievable in Scenario A? When the father announced 273

Ψ in everyone’s common presence, he provided the system C(Ψ) and hence E k (Ψ). Thus, every child knew Ψ, and every child knew that every child knew Ψ, and every child knew that every child knew that every child knew Ψ, and so on. With this E k (Ψ) being present in the system, the children could use it progressively round-by-round until in the k th round, they could answer the father’s question correctly.

8.2.3 Kripke Structures A popular approach to defining semantics is in terms of possible worlds. This approach is formalized using Kripke structures. Definition 20. (Kripke structure.) A Kripke structure M for n agents and a set of primitive propositions Φ is a tuple (S, π, K1 , . . . Kn ), where the components of this tuple are as follows. 1. S is the set of all consistent states (or possible worlds), with respect to an execution. 2. π is an interpretation that associates a truth assignment to each primitive proposition in Φ, for each state s ∈ S. Thus, ∀s ∈ S, π(s) : Φ → {0, 1}. 3. Ki is a binary relation on S giving all the pairs of states that are indistinguishable by Pi . A Kripke structure is conveniently viewed as a graph with labeled nodes connected by labeled edges. The set of nodes is the set of states S; the label of node s ∈ S also gives the primitive propositions that are true and false at s. In our simple example, we assume that Φ contains a single proposition. The logic can be extended to multiple propositions in a straightforward manner. The edge (s, t) is labeled by the identity of every process Pi such that (s, t) ∈ Ki , i.e., every process Pi that cannot distinguish between states s and t. We assume (for simplicity) that edges are bidirectional and that the K relations are reflexive, i.e., there is a self-loop at each node. In Section 8.2.4, the Muddy Children puzzle is used to illustrate the definitions and concepts of this section. The formal definition of knowledge is now given in Figure 8.1. Here, “|=” denotes the “satisfaction” operator. This definition of levels of knowledge has a very convenient and useful graph-theoretic representation, as we will illustrate for the Muddy Children puzzle. Definition 21. (Reachability of states.) 1. A state t is reachable from state s in k steps if there exist states s0 , s1 , . . . , sk such that s0 = s, sk = t, and for all j ∈ [0, k − 1], there exists some Pi such that (sj , sj+1) ∈ Ki . 2. A state t is reachable from state s if t is reachable from s in k steps, for some k > 1. Definition 21 defines state reachability in the Kripke structure. The following definitions of knowledge are expressed in terms of reachability of states within the Kripke structure, and can be readily seen to mirror the original definition of knowledge. 274

(M, s) |= φ if and only if φ is true in state s in Kripke structure M, i.e., π(s)(φ) = true. Analogously, we can define formulae using conjunctions and negations over primitive propositions. (M, s) |= Ki (φ) if and only if (M, t) |= φ, for all states t such that (s, t) ∈ Ki V (M, s) |= E 1 (φ) if and only if (M, s) |= i∈N Ki (φ) V (M, s) |= E k+1 (φ) for k ≥ 1 if and only if (M, s) |= i∈N Ki (E k (φ)), for k ≥ 1 V (M, s) |= C(φ) if and only if (M, s) |= k∈Z∗ E k (φ)

(Distributed Knowledge D.) (M, s) |= D(φ) if and only if (M, t) |= φ for each state t such that (s, t) ∈ ∩i Ki Figure 8.1: The definitions of regular knowledge.

Theorem 7. (Knowledge in terms of reachability of states.) 1. (M, s) |= E k (φ) if and only if (M, t) |= φ for each state t that is reachable from state s in k steps. 2. (M, s) |= C(φ) if and only if (M, t) |= φ for each state t that is reachable from state s.

8.2.4 Muddy Children Puzzle using Kripke Structures We now illustrate the Kripke structure for the Muddy Children puzzle. The definitions and concepts of the previous section will be clarified by the example. Let us assume there are n = 3 children; and k = 2 children have mud on their forehead. Each of the 8 states can be described by a boolean n-vector, where a clean child is denoted by a 0 and a child with mud on the forehead is denoted by a 1. Let us further assume that the actual state is (1, 1, 0). The Kripke structure M is illustrated in Figure 8.2(a). In the world (1, 1, 0), each child can see that there is at least one other child who has mud on the forehead, and hence (M, (1, 1, 0)) |= E(ψ). From Theorem 7, it follows that (M, (1, 1, 0)) |= ¬E 2 (Ψ) because the world (0, 0, 0) is 2-reachable from (1, 1, 0) and Ψ is not true in this world. Generalizing this observation in terms of Kripke structures assuming k muddy children, we have that E k−1 (Ψ) is true because each world reachable in k − 1 hops has at least one ′′ 1′′ , implying there is at least one child with a muddy forehead. However, E k (Ψ) is false because the world (0, . . . , 0) is reachable in k hops. Scenario A. Fact Ψ is already known to all children in the state (1, 1, 0). Still, when the father announces Ψ in Scenario A, the state of knowledge changes. Before the father’s announcement, Child 1 believes the state (1, 0, 0) possible, and in that state (1, 0, 0), child 2 considers the state (0, 0, 0) possible. After the father announces Ψ, it becomes common knowledge that one child has 275

2

(0,0,1)

3

2

(0,0,1)

3

2

(0,0,0)

(0,1,1) (0,0,0)

(0,1,0)

(1,0,1)

2

(1,0,0)

(a)

1

1

2 3

(0,1,0)

1

1

1

1

(0,1,1)

(0,0,1) (0,0,0)

(0,1,0)

1

1

(0,1,1)

3

2

(1,1,1)

3

3 (1,1,0)

(1,0,1)

2

(1,0,0)

(b)

2

(1,1,1) (1,0,1)

3 (1,1,0)

(1,0,0)

(1,1,1)

3 (1,1,0)

(c)

Figure 8.2: Kripke structure for n = 3 muddy children puzzle. Note that the actual state is (1,1,0). (a) The entire Kripke structure. (b) After the father announces Ψ. (c) After the first round of questioning and its answers. a muddy forehead - this change in the group’s state of knowledge can be graphically depicted by deleting all the edges connecting state (0, 0, 0). After the father has announced Ψ, even if one child has a muddy forehead, he will not consider the state (0, 0, 0) possible. When the father asks the question the first time and all children respond “No”, then all edges connecting to all possible worlds with a single “1” in the state tuple get deleted – this is because if there were only a single child with mud on his forehead, he would have answered “yes” in response to the first question. Thus, it is now common knowledge that there are at least two children with muddy foreheads. Generalizing this by induction, when the father askes the question the xth time and all children respond “No”, then all edges connecting to all possible worlds with x or fewer than x “1”s in the state tuple get deleted – this is because if there were only x children with mud on their forehead, they would all have answered “yes” in response to the xth question. It is now common knowledge that there are at least x + 1 children with mud on their forehead. If there are exactly x + 1 children with mud on their forehead, those children will all answer “Yes” the x + 1th time the question is asked because they can see exactly x other children with mud on their foreheads. They could not answer “Yes” earlier because they considered a world possible, in which they did not have mud on their own forehead. Graphically, the Kripke structure gets modified in each iteration as follows. If in the iteration, it becomes common knowledge that world t is impossible, then for each node s reachable from the actual state r, any edge (s, t) is deleted. The Kripke structure shown in Figure 8.2(a) gets modified to that shown in part (b) after the father’s announcement. That further gets modified as shown in part (c), after the first time the question is asked and all the children reply “No”. Scenario B. In Scenario B, the childrens’ state of knowledge never changes and hence the Kripke structure in Figure 8.2(a) never changes, no matter how often the father askes the question. When the father asks the question the first time, all children answer “No” because they each consider both worlds possible - one in which they have mud on their forehead, and one in which they do not. As it is common knowledge even before the father asks the question that the answer is going to be 276

“No”, no knowledge is added by the question or the response. Inductively, this argument holds for each round of questioning. Hence, the Kripke structure never changes.

8.2.5 Properties of Knowledge In a formal framework to reason about knowledge, the properties of knowledge must also be specified formally. Although these properties can be specified with different semantics, the most common semantics that are adequate for modeling real distributed systems are given by the axiom system that is historically termed as the S5 system. We first characterize the formulae that are always true. A formula ψ is valid in Kripke structure M, denoted M |= ψ, if (M, s) |= ψ, for all s ∈ S. A formula ψ is satisfiable in M if (M, s) |= ψ, for some s ∈ S. A formula is valid if it is valid in all structures, and it is satisfiable if it is satisfiable in some structure. The five axioms of modal logic S5, given in Figure 8.3, are satisfied for all formulas, all structures, and all processes.

• Distribution Axiom: Ki ψ ∧ Ki (ψ = :φ) = :Ki φ

Each process knows the logical consequences of its local knowledge. The knowledge operator gets distributed over the implication relation.

• Knowledge Axiom: Ki ψ = :ψ

If a process knows a fact, then the fact is necessarily true. If Ki ψ is true in a particular state, then ψ is true in all states that the process considers as possible.

• Positive Introspection Axiom: Ki ψ = :Ki Ki ψ A process knows what it knows.

• Negative Introspection Axiom: ¬Ki ψ = :Ki ¬Ki ψ A process knows what it does not know.

• Knowledge Generalization Rule: For a valid formula or fact ψ, Ki ψ

If ψ is true in all possible worlds, then ψ must be true in all the possible worlds with respect to any process and any given world. Hence, Ki ψ must be true in all possible worlds. Here, it is assumed that a process knows all valid formulas, which are necessarily true. Note that this rule is different from the rule “ψ = :Ki ψ”. Figure 8.3: The axioms of the S5 modal logic.

8.3 Knowledge in Synchronous Systems indexknowledge!synchronous system Classical problems such as the “muddy children” problem and the “cheating husbands” problem which are widely used to illustrate the theory of knowl277

edge have been explained in the synchronous system model. The definitions and the treatment of knowledge we have seen thus far was again for synchronous systems. Common knowledge captures the notion of agreement in distributed systems. What are the various ways by which common knowledge can be attained in a synchronous system? • By initializing all the processes with common knowledge of the fact. • By broadcasting the fact to every process in a round of communication, and having all the processes know that the fact is being broadcast. Each process can begin supporting common knowledge from the next round. This is the mechanism that was used by the father when he announced Ψ in the Muddy Children puzzle in Scenario A.

8.4 Knowledge in Asynchronous Systems Here, we adapt the definitions of knowledge given in Figure 8.1 to asynchronous systems.

8.4.1 Logic and Definitions In the system model, the possible worlds are the consistent cuts of the set of possible executions in an asynchronous system. Let (a, c) denote a cut c in asynchronous execution a. As each cut also identifies a global state, (a, c) is also used to denote the state of the system after (a, c). (a, c)i denotes the projection of c on process i, and is also used to denote the state of process i after (a, c). Two cuts c and c′ are indistinguishable by process i, denoted (a, c) ∼i (a′ , c′ ), if and only if (a, c)i = (a′ , c′ )i . The semantics of knowledge are based on asynchronous executions, instead of timed executions. The modal operator Ki (φ) means “φ is true in all possible consistent global states (cuts) that include process i’s local state”. Observe that Ki (φ) is implicitly quantified over all consistent states over all runs, that include i’s local state. Similar meanings hold for E(φ) and E k (φ), for k > 1. We also define E 0 (φ) to be φ for simplicity. Formal definitions of knowledge for asynchronous distributed systems are given in Figure 8.4. As knowledge can be known by a process only in a specific local state, we also say that “i knows φ in state sxi ”, denoted sxi |= φ, as a shorthand for (∀(a, c)) ((a, c)i = sxi = : (a, c) |= φ). Analogously, we define sxi |= Ki (φ) to be (∀(a, c)) ((a, c)i = sxi = : (a, c) |= Ki (φ)). Recall that φ can be of the form E k (ψ), for any fact ψ. Definition 23. (Learning.) Process i learns φ in state sxi of execution a if i knows φ in sxi and, for all states syi in execution a such that y < x, i does not know φ. We also say that a process attains φ (in some state) if the process learns φ in the present or an earlier state. A fact φ is attained in an execution a if ∃c, (a, c) |= φ. Observe that a process cannot attain a fact before the fact is attained in an execution. This corresponds to the intuition that even though a fact becomes true in an execution, information may need to be propagated for a process to learn the fact. 278

Definition 22. (Knowledge in asynchronous systems defined using consistent cuts) (a, c) |= φ if and only if φ is true in cut c of asynchronous execution a. (a, c) |= Ki (φ) if and only if ∀(a′ , c′ ), ((a′ , c′ ) ∼i (a, c) = :(a′ , c′ ) |= φ) (a, c) |= E 0 (φ) if and only if (a, c) |= φ V (a, c) |= E 1 (φ) if and only if (a, c) |= i∈N Ki (φ) (a, c) |= E k+1(φ) for k ≥ 1 if and only if (a, c) |=

V

i∈N

Ki (E k (φ)), for k ≥ 1

(a, c) |= C(φ) if and only if (a, c) |= the greatest fixed point knowledge X satisfying X = E(X ∧φ). C(φ) implies ∧k∈Z∗ E k (φ). Figure 8.4: The definition of knowledge for asynchronous distributed systems. Definition 24. (Local fact.) A fact φ is local to process i in system A if A |= (φ = :Ki φ) A fact that is not local is a global fact. The state of a process, the local clock value of a process, and the local component of the timestamp of an event at a process are examples of local facts. The global state of a system and the timestamp of a cut are examples of global facts.

8.4.2 Agreement in Asynchronous Systems We consider the following problem: “Two processes that communicate by asynchronous messagepassing need to agree on a binary value. Does there exist a protocol that they can follow to reach consensus?” Reaching consensus among a group of processes implies the attainment of common knowledge among that group of processes. We first consider a system where communication is not reliable, implying that messages may be lost in transit. Theorem 8. There does not exist any protocol for two processes to reach common knowledge about a binary value in an asynchronous message-passing system with unreliable communication. An informal argument is as follows. Without loss of generality, we assume that the fact is true at Pi , and the processes Pi and Pj follow a protocol in which they send messages to each other serially. Thus, Pi first sends a message M to Pj informing it of the fact, and because communication is not reliable, Pi needs an acknowledgement ACK1 back to know that Pj has received the message. But then, Pj does not know whether Pi has received the acknowledgement ACK1. Hence, it does not know whether or when Pi will begin supporting the common knowledge, and hence it itself cannot begin supporting the common knowledge. Therefore, Pi needs to send back an acknowledgement ACK2 to Pj to acknowledge the receipt of ACK1. However, Pi now needs an acknowledgement of the delivery of ACK2, similar to its need for an acknowledgement for M. This is a non-terminating argument, and hence this protocol will not work to achieve common knowledge. 279

More generally, let there exist a protocol with k messages being sent between Pi and Pj , and let this be the minimal protocol in the sense of using the minimum number of messages. Then, the sender of the last message asserts common knowledge of the fact even if it does not know whether the message was delivered. Hence, the k th message is redundant, which implies there is a protocol with k−1 messages to attain common knowledge. This contradicts the assumption that the minimal protocol requires k messages. Hence, such a protocol does not exist. Using similar reasoning, we also have the following similar impossibility result for reliable asynchronous systems. Theorem 9. There does not exist any protocol for two processes to reach common knowledge about a binary value in a reliable asynchronous message-passing system without an upper bound on message transmission times. Even though the upper bound on message transmission time is guaranteed, a process does not know when to support the common knowledge and hence requires an acknowledgement, and the sender of that acknowledgement will itself require an acknowledgement, and so on.

8.4.3 Variants of Common Knowledge Common knowledge captures the notion of agreement among the processes, and hence attaining common knowledge is a fundamental problem in distributed systems. Common knowledge requires the notion of simultaneity of action across the processes. The instantaneous simultaneity attained by tightly synchronized clocks has some margin of error. Given the impossibility of achieving simultaneity, and hence of attaining common knowledge in reliable asynchronous systems, what hope is there? Fortunately, there are weaker versions of common knowledge that can be substituted for regular common knowledge. Epsilon common knowledge. This form of common knowledge corresponds to the processes reaching agreement within ǫ time units. This definition implicitly assumes timed runs as it is not possible to exactly define time units in an asynchronous system. This common knowledge is defined using E ǫ which denotes “everyone knows within a time duration of ǫ units”. Epsilon common knowledge C ǫ (φ) is the greatest fixed point of X = E ǫ (φ ∧ X), where X is the free variable in the greatest fixed-point operator. Eventual common knowledge. This form of common knowledge corresponds to the processes reaching agreement at some (not necessarily consistent) global state in the execution. E ⋄ denotes “everyone will eventually know (at some point in their execution)”. Eventual common knowledge C ⋄ (φ) is the greatest fixed point of X = E ⋄ (φ ∧ X). Timestamped common knowledge. This form of common knowledge corresponds to the processes reaching agreement at local states having the same local clock value. It is applicable to asynchronous systems. Let KiT (φ) denote the fact that process i knows φ at local clock value T . Then E T (φ) = ∧i KiT (φ) and timestamped common knowledge C T (φ) is the greatest fixed point of X = E T (φ ∧ X). If it is common knowledge that all clocks are always 280

perfectly synchronized, then timestamped common knowledge is equivalent to regular common knowledge. Concurrent common knowledge. This form of common knowledge corresponds to the processes reaching agreement at local states that belong to a consistent cut. When a process Pi attains concurrent common knowledge of a fact φ, it also knows that each other process Pj has also attained the same concurrent common knowledge in its local state which is consistent with Pi ’s local state. This form of knowledge is applicable to asynchronous systems and is the most popular form of common knowledge in real systems. Hence, we define it in detail below, and give two protocols for attaining such common knowledge. Here, we note that this variant of common knowledge is incomparable with C ǫ , C ⋄ , and C T .

8.4.4 Concurrent Common Knowledge Concurrent common knowledge is based on the notion of the various processes attaining the common knowledge on a consistent cut. The possibly operator1 Pi in conjunction with the Ki operator is used to formally define such knowlege. Pi (φ) means “φ is true in some consistent state in the V same asynchronous run, that includes process i’s local state”. E C (φ) is defined as i∈N Ki (Pi (φ)). E C (φ) means that every process at the (given) cut knows only that φ is true in some cut that is consistent with its own local state. By induction, similar meanings can be assigned for higher levels of knowledge. The formal definition of levels of concurrent knowledge E C is as shown in Figure 8.5. (a, c) |= φ if and only if φ is true in cut c of execution a. (a, c) |= Ki (φ) if and only if ∀(a′ , c′ ), ((a′ , c′ ) ∼i (a, c) = :(a′ , c′ ) |= φ) (a, c) |= Pi (φ) if and only if ∃(a, c′ ), ((a, c′ ) ∼i (a, c) ∧ (a, c′ ) |= φ) 0

(a, c) |= E C (φ) if and only if (a, c) |= φ V 1 (a, c) |= E C (φ) if and only if (a, c) |= i∈N Ki Pi (φ) V k+1 k (a, c) |= E C (φ) for k ≥ 1 if and only if (a, c) |= i∈N Ki Pi (E C (φ)), for k ≥ 1

(a, c) |= C C (φ) if and only if (a, c) |= the greatest fixed point knowledge X satisfying X = E C (X ∧ φ). C C (φ) implies ∧k∈Z∗ (E C )k (φ). Figure 8.5: The definition of concurrent knowledge for asynchronous distributed systems. 1

The notation Pi for this operator is not to be confused with Pi used to denote process i. Also, the semantics of this operator is different from the P ossibly modality defined on global predicates.

281

The concurrent knowledge definitions are weaker than the corresponding knowledge definitions in Definition 22 (Figure 8.4). But for a local, stable fact, and assuming other processes learn the fact via message chains, it can be seen that the two definitions become equivalent. If concurrent common knowledge C C (φ) is attained at a consistent cut, then (informally speaking) each process at its local cut state knows that “in some state consistent with its own local cut state, φ is true and that all other process know all this same knowledge (described within quotes)”. Concurrent common knowledge is a necessary and sufficient condition for performing concurrent actions in asynchronous distributed systems, analogous to simultaneous actions and common knowledge in synchronous systems. The form of knowledge underlying many existing protocols involves processes reaching agreement about some property of a consistent global state, defined using logical time and causality, and can be easily understood in terms of concurrent common knowledge. Global snapshot algorithms can be run concurrently with the underlying computation and can be used to achieve concurrent common knowledge. Snapshot algorithms typically require |L| messages and d time steps, where d is the diameter of the network. More message-efficient snapshot algorithms that need only O(|N|) messages use certain forms of computation inhibition – local or global inhibition, and send inhibition and/or receive inhibition based on network characteristics such as availability of FIFO channels, to reduce the number of messages to take a snapshot. Nevertheless, each snapshot requires at least O(|N|) messages and possibly inhibitory delay as overhead. Specifically, concurrent common knowledge can be attained in an asynchronous system, as shown by the protocols in Figure 8.6– 8.9. In these protocols, each process Pi must attain C C (φ) on a consistent cut, (i) by learning φ, and (ii) by learning that each other process Pj will also attain that exact state of knowledge in a local state that is consistent with Pi ’s local state in which Pi attains C C (φ). Snapshot-based algorithm. Protocol 1 (Figure 8.6) is a form of a global snapshot algorithm, where each process knows that all processes are participating in the same algorithm. It can also be viewed as a variant of the distributed asynchronous breadth-first search algorithm (seen in Module 2). Observe that the set of states denoted as cut state at each process, and at which the processes begin supporting C C (φ), indeed form a consistent set of states. Complexity: Protocol 1 uses 2l messages and a time complexity equal to the diameter of the network. Three-phase send-inhibitory algorithm. Protocol 2 (Figure 8.7) has three phases and uses sendinhibition. It also assumes that the predicate φ that becomes true when the protocol is initiated remains true for the duration of the protocol. Observe that send inhibition is necessary to ensure that the set of cut states at which the processes begin supporting C C (φ) are consistent. A process Pi does not send any message between receiving the PREPARE and sending the CUT (when it reaches its cut state), and receiving the RESUME control messages, and any message sent by Pi after receiving the RESUME message will necessarily be received by 282

any other process Pj after Pj has reached its cut state. Hence the cut states are guaranteed to be consistent with each other. Complexity: Protocol 2 uses 3(n − 1) messages and a time complexity of three message hops. However, it is send-inhibitory and requires FIFO channels. The three-phase send-inhibitory tree algorithm. Protocol 3 (Figure 8.8) is a variant of Protocol 2. It protocol uses a (Broadcast - Convergecast - Broadcast) sequence on a spanning tree (ST) on the network topology to record the global state along a consistent cut. Complexity: This message-send inhibitory algorithm requires a total of 3(n − 1) messages and works in a system with non-FIFO channels. Inhibitory Ring algorithm: Protocol 4 (Figure 8.9) assumes that a logical ring is superimposed on the network topology. Complexity. This message-send inhibitory algorithm requires 2n messages, and works in a system with FIFO channels. The time complexity is 2n hops. Therefore the process can infer (E C )i (φ) (for any i) is attained by processes along a consistent cut including the current local state. Protocol 1 (Snapshot-based algorithm). 1. At some time when the initiator I knows φ: • it sends a marker MARKER(I, φ, CCK) to each neighbour Pj , and atomically reaches its cut state. 2. When a process Pi receives for the first time, a message MARKER(I, φ, CCK) from a process Pj : • process Pi forwards the message to all of its neighbours except Pj , and atomically reaches its cut state. Figure 8.6: Snapshot-based protocol to attain concurrent common knowledge. A process attains C C (φ) when it reaches its cut state. Algorithms such as the above variants of the classical snapshot algorithm require at least O(l) messages, or O(n) messages and varying degrees of message inhibition each time there is a need to achieve concurrent knowledge of some fact.

8.5 Knowledge Transfer Formalizing how processes learn facts is done by relating knowledge gain to message chains in the execution. 283

Protocol 2 (Three-phase send-inhibitory algorithm). 1. At some time when the initiator I knows φ: • it sends a marker P REP ARE(I, φ, CCK) to each process Pj . 2. When a (non-initiator) process receives a marker P REP ARE(I, φ, CCK): • it begins send-inhibition for non-protocol events.

• sends a marker CUT (I, φ, CCK) to the initiator I. • it reaches its cut state at which it attains C C (φ).

3. When the initiator I receives a marker CUT (I, φ, CCK) from each other process: • the initiator reaches its cut state

• sends a marker RESUME(I, φ, CCK) to all other processes.

4. When a (non-initiator) process receives a marker RESUME(I, φ, CCK): • it resumes sending its nonprotocol messages which had been inhibited in step 2. Figure 8.7: Three-phase send-inhibitory protocol to attain concurrent common knowledge. A process attains C C (φ) when it reaches its cut state.

Protocol 3 (Three-phase send-inhibitory tree algorithm). Phase I (broadcast): The root initiates PREPARE control messages down the ST; when a process receives such a message, it inhibits computation message sends and propagates the received control message down the ST. Phase II (convergecast): A leaf node initiates this phase after it receives the PREPARE control message broadcast in phase I. The leaf reaches and records its cut state, and sends a CUT control message up the ST. An intermediate (and the root) node reaches and records its cut state when it receives such a CUT control message from each of its children, and then propagates the control message up the ST. Phase III (broadcast): The root initiates a broadcast of a RESUME control message down the ST after Phase II terminates. On receiving such a RESUME message, a process resumes inhibited computation message send activity and propagates the control message down the ST. Figure 8.8: Three-phase send-inhibitory tree protocol to attain concurrent common knowledge. A process attains C C (φ) when it reaches its cut state.

284

Protocol 4 (Send-inhibitory ring algorithm). 1. Once a fact φ about the system state is known to some process, the process atomically reaches its cut state and begins supporting C(φ), begins send inhibition, and sends a control message CUT (φ) along the ring. 2. This CUT (φ) message announces φ. When a process receives the CUT (φ) message, it reaches its cut state and begins supporting C(φ), begins send inhibition, and forwards the message along the ring. 3. When the initiator gets back CUT (φ), it stops send inhibition, and forwards a RESUME message along the ring. 4. When a process receives the RESUME message, it stops send-inhibition, and forwards the RESUME message along the ring. The protocol terminates when the initiator gets back the RESUME it initiated. Figure 8.9: Send-inhibitory ring protocol to attain concurrent common knowledge. A process attains C C (φ) when it reaches its cut state. Definition 25. (Message chain.) A message chain in an execution is a sequence of messages hmik , mik−1 , mik−2 , . . ., mi1 i such that for all 0 < j ≤ k, mij is sent by process ij to process ij−1 and receive(mij ) ≺ send(mij−1 ). A message chain identifies the corresponding process chain hi0 , i1 , . . . , ik−2, ik−1 , ik i. The above definition adopts the convention that a process chain lists the processes in an order which is the reverse of the order in which they send the messages in the corresponding message chain. Furthermore, a process chain includes the recipient of the last message sent in the corresponding message chain, and this is the first process in the process chain. A message chain with k messages thus identifies a process chain with k + 1 processes. Knowledge can be transferred among processes only if a process chain exists among those processes. If φ is false in an execution and later P1 knows that P2 knows that . . . Pk knows φ, then there must exist a process chain hi1 , i2 , . . . ik i. In the system model used thus far, (a, c)i denotes the projection of c on process i, and is also used to denote the state of process i after (a, c). Two cuts c and c′ are indistinguishable by process i, denoted (a, c) ∼i (a′ , c′ ), if and only if (a, c)i = (a′ , c′ )i . In the interleaving model of the distributed system execution, wherein all the events at the different processes are interleaved to form a global total order, the indistinguishability of different views can be expressed using isomorphism of executions. In the following explanation, we assume x, y, z denote executions or execution prefixes in the interleaving model. We let xp denote the projection of execution x on process p. indexexecution isomorphism Definition 26. (Isomorphism of executions.) 285

1. For all executions x and y, relation x[p]y is defined to be true if and only if xp = yp . 2. For all executions x and y and a process group G, relation x[G]y is defined to be true if and only if, for all p ∈ G, xp = yp . 3. Let Gi be process group i and let k > 1. Then, x[G0 , G1 , . . . , Gk ]z if and only if x[G0 , G1 , . . . , Gk−1]y and y[Gk ]z. Two executions are isomorphic with respect to a group of processes if and only if none of the processes in the group can distinguish between the two executions. For Definition 26.1 and 26.2, drawing an analogy with Kripke structures (Definition 21), the edge connecting two state nodes (which would correspond to the states after executions x and y) are labeled by all the processes that cannot distinguish between the two states. Thus, for all i such that (x, y) ∈ Ki , the edge connecting (x, y) is labeled with Pi . For Definition 26(3), analogously in Kripke structures (Definition 21), the set of states reachable from x in k steps, denoted z, can be expressed in terms of the set of states reachable from x in k − 1 steps, denoted y, and the set of states z reachable from states in y in one step. The definition of isomorphism of executions allows an alternate way of reasoning with local views of processes, tailored more for asynchronous distributed computing systems. When a message is received in an execution, the set of executions that are isomorphic can only decrease because now executions that do not contain the corresponding send event can be ruled out. The knowledge operator in the interleaving model is defined as follows. Definition 27. (Knowledge operator in the interleaving model.) p knows φ at execution x if and only if, for all executions y such that x[p]y, φ is true at y. The following theorem formally shows in the interleaving model that knowledge is gained sequentially. Theorem 10. (Knowledge transfer.) For process groups G1 , . . ., Gk , and executions x and y, (KG1 KG2 . . . KGk (φ) at x and x[G1 , . . . Gk ]y) = : KGk (φ) at y. The theorem can be shown to be true by induction on k, along the lines of the following argument. For k = 1, the result is straightforward. Assume the induction hypothesis for k−1. For k, we can infer there exists some z such that x[G1 , . . . Gk−1 ]z and z[Gk ]y. From KG1 KG2 . . . KGk−1 [KGk (φ)] at x, and from the induction hypothesis, it can be inferred that KGk−1 [KGk (φ)] at z. Hence, KGk (φ) at z. As z[Gk ]y, KGk (φ) at y. In terms of Kripke structures, Theorem 10 states that there is a path from state node x = s0 to state node y = sk , via state nodes s1 , s2 , . . ., sk−1 , such that the k edges (si , si+1 ), 0 ≤ i ≤ k − 1, on the path are labeled by Gi+1 . Theorem 11 formalizes the observation that there must exist a message chain hmik , mik−1 , mik−2 , . . ., mi1 i in order that a fact φ that becomes known to Pk after execution prefix x of y, leads to the state of knowledge K1 K2 . . . Kk (φ) after execution y. Theorem 11. (Knowledge gain theorem.) For processes P1 , . . ., Pk , and executions x and y, where x is a prefix of y, let 286

• ¬Kk (φ) at x and K1 K2 . . . Kk (φ) at y. Then there is a process chain hi1 , . . . ik−1 , ik i in (x, y).

8.6 Knowledge and Clocks We assume all facts are timestamped (physically or logically) by the of their becoming true and by the process at which they became true. A full-information protocol (FIP) is a protocol in which a process piggybacks all the knowledge it has on outgoing messages, and in which a process adds to its knowledge all the knowledge that is piggybacked on any message it receives. Thus, knowledge always increases when a message is received. The amount of knowledge would keep increasing as the execution proceeds, which may not make FIP protocols a practical way to distributed knowledge. Facts can always be appropriately encoded as integers. Monotonic facts are facts about a property that keep increasing monotonically (e.g., the latest time of taking a checkpoint at a process). By using a mapping between logical clocks and monotonic facts, information about the monotonic facts can be communicated between processes using logical clock values piggybacked on messages. Being monotonic, all earlier facts can be inferred from the fixed amount of information that is maintained and piggybacked on messages. As a specific example, the vector clock Clki [j] indicates the local time at each process Pj , and implicitly that all lower clock values at Pj have occurred. With appropriate encoding, facts about a monotonic property can be represented using vector clocks. Matrix clocks are an extension of the idea behind vector clocks and contain information about other processes’ views of the system execution. A matrix clock is an array of size n × n. Matrix clocks are used to design distributed database protocols, fault-tolerant protocols, and protocols to discard obsolete information in distributed databases. They are also used to solve the distributed dictionary and distributed log problems. The rules that process Pi executes atomically to maintain its matrix clock using the matrix clock protocol are given in Figure 8.10. Vector clocks can be thought of as imparting knowledge to a process: when Clk[i] = x at process h, process h knows that process i has executed at least x events. Matrix clocks impart one more level of knowledge: when Clk[i, j] = x at process h, process h knows that process i knows that process j has executed at least x events. 1. The j th row of the matrix clock at process Pi , indicated by Clki [j, ·], gives the latest vector clock value of Pj ’s clock, as known to Pi . 2. The j th column of the matrix clock at process Pi , indicated by Clki [·, j], gives the latest scalar clock values of process Pj , i.e., Clk[j, j], as known to each process in the system. For a vector clock Clki , the j th entry Clki [j] represents the knowledge Ki Kj (φj ), where φj is the local component of process Pj ’s clock. For a matrix clock Clki , the [j, k]th entry Clki [j, k]

287

(local variables) array of int Clki [1 . . . n, 1 . . . n] MC0. Clki [j, k] is initialized to 0 for all j and k MC1. Before process i executes an internal event, it does the following. Clki [i, i] = Clki [i, i] + 1 MC2. Before process i executes a send event, it does the following: Clki [i, i] = Clki [i, i] + 1 Send message timestamped by Clki . MC3. When process i receives a message with timestamp T from process j, it does the following. (k ∈ N) Clkj [i, k] = max(Clki [i, k], T [j, k]); (l ∈ N \ {i}) (k ∈ N), Clki [l, k] = max(Clki [l, k], T [l, k]); Clki [i, i] = Clki [i, i] + 1; deliver the message. Figure 8.10: Matrix clocks. represents the knowledge Ki Kj Kk (φk ), where φk is the local component Clkk [k, k] of process Pk ’s clock. Vector and matrix clocks are convenient because they are updated without sending any additional messages; knowledge is imparted via the inhibition-free ambient message-passing that (i) eliminates protocol messages by using piggybacking, and (ii) diffuses the latest knowledge using only messages, whenever sent, by the underlying execution. Observe that the vector clock at a process provides knowledge E 0 (φ), where φ is a property of the global state (namely, the local scalar clock value of each process). Analogously, observe that a matrix clock at a process Pj gives the knowledge Kj (E 1 (φ)) = Kj (∧i∈N Ki (φ)), where φ is a property of the global state, namely, the local scalar clock value of each process.

8.7 Chapter Summary Processes in a distributed system can reason only with the partial view they have of the computation. The knowledge at a process is based on the values of its variables and any messages received by the process. The chapter first discussed the role of knowledge by using the Muddy Children puzzle of Halpern and Moses. To formalize the role of knowledge, several knowledge operators – E (every process knows), K (the process knows), and C (common knowledge) were introduced, Kripke structures were introduced to formalize these semanticvs in terms of possible worlds. The Muddy Children puzzle was recast in terms of the more formal Kripke structures.

288

The definitions of knowledge in synchronous systems and in asynchronous systems were then studied. The findamemtal result that common knowledge cannot be attained in an error-free message-passing asynchronous system was then examined. FOur weaker versions of common knowledge – epsilion common knowledge, eventual common knowledge, timestamped common knowledge, and concurrent common knowledge – that are achievable in asynchronous were then examined. Concurrent common knowledge underlies most of the protocols in asynchronous systems. Several algorithms to achieve concurrent common knowledge were then studied – the snapshot based algorithm, a 3-phase send-inhibitory algorithm, an algorithm that use the tree overlay, and one algorithm that uses a logical ring. A section on how proesses learn new information (viz, gain new knowledge) considered knowledge transfer and knowledge gain in terms of process chains and isomorphism of execution views. Finally, the relationship between the level of knowledge in message-passsing asynchronous systems and size of matrix logical clocks was studied.

8.8 Bibliographic Notes The muddy children example is taken from Halpern and Moses [6] and Halpern and Fagin [5]. The discussions on Kripke structures, S5 modal logic, and the definitions of regular knowledge and (regular) common knowledge in synchronous systems (Figure 8.1) are based on an excellent text by Fagin, Halpern, Moses, and Vardi [4]. The discussion on local facts, learning, knowledge transfer, and isomorphisms is based on the work by Chandy and Misra [1]. The results (Theorems 8 and 9) on agreement in asynchronous message-passing systems appear to be folklore. The notion of inhibition was formalized by Critchlow and Taylor [3]. Concurrent common knowledge and protocols to attain it for asynchronous systems were formalized by Panangaden and Taylor [10]. The definitions of epsilon, eventual, and timestamped common knowledge for asynchronous systems are also based on [10]. The defintion of knowledge for asynchronous systems (Figure 8.4) is based on Kshemkalyani [8, 9]. Matrix clocks are first used by Krishnakumar and Bernstein [7], Wuu and Bernstein [13], and Sarin and Lynch [12], and also studied by Ruget [11]. The relationship between clocks of various dimensions and knowledge was formalized by Kshemkalyani [8, 9].

8.9 Exercise Problems 1. In the Muddy Children puzzle (Section 8.1), if Ψ = “At most k children have mud on the forehead,” will the muddy children be able to identify themselves? If yes, in how many rounds of questioning? If not, why not? Analyze this scenario in detail. 2. There are two black hats and two white hats. One of these hats is hidden away and the color of this hat is not known to anybody. The remaining three hats are placed on the heads of three persons A, B, and C in such a way that none of the persons knows the color of the hat placed on his/her head. Draw a Kripke structure that describes this situation.

289

3. In a failure-free asynchronous message-passing system of n processes, process Pi learns a fact φ. (a) Devise simple noninhibitory protocols using a logical ring along which to pass control messages to achieve the following, and justify your answers. Use timing diagrams to illustrate your answers. i. A protocol to attain E 2 (φ) in the system. ii. A protocol so that each process knows E 2 (φ). (b) What is the earliest global time at which all processes know that everyone knows E 2 (φ)? How can all the processes know about this time? 4. In Theorem 9, assume that there exists an upper bound on message transmissiontimes. Which (if any) variant of concurrent common knowledge can hold in the system? please state your assumptions clearly to justify your reasoning used in your answer. 5. Consider the matrix clocks given in Figure 8.10. At any point in time after the execution of atomic steps MC0, MC1, MC2, or MC3, what is the minimum number of entries among the n2 entries of Clki that are guaranteed to be replicas of other entries in Clki ? Identify the exact set(s) of elements of the array Clki that will necessarily be identical. 6. Prove the following. For the equalities, you need to prove the implication in both directions. For each part, first prove the results using the interleaving model, and then prove the results using the partial order model. (a) Ki ¬φ implies that ¬Ki φ (b) Ki φ ∨ ¬Ki φ (c) Ki φ ∨ Ki ¬φ, if φ is a constant. (d) Ki φ ∧ Ki ψ = Ki (φ ∧ ψ) (e) Ki φ ∨ Ki ψ = Ki (φ ∨ ψ) (f) Ki (¬Ki φ) = ¬Ki φ

290

Bibliography [1] K. M. Chandy, J. Misra, How processes learn, Distributed Computing, 1: 40-52, 1986. [2] K. M. Chandy, L. Lamport, Distributed snapshots: Determining global states of distributed systems, ACM Transactions on Computer Systems, 3(1): 63-75, 1985. [3] C. Critchlow, K. Taylor, The inhibition spectrum and the achievement of causal consistency, Distributed Computing, 10(1): 11-27, 1996. [4] R. Fagin, J. Halpern, Y. Moses, M. Vardi, Reasoning about Knowledge, MIT Press, 1995. [5] J. Halpern, R. Fagin, Modeling knowledge and action in distributed systems, Distributed Computing, 3(4): 139-179, 1989. [6] J. Halpern, Y. Moses, Knowledge and common knowledge in a distributed environment, Journal of the ACM, 37(3): 549-587, 1990. [7] A. Krishnakumar, A. Bernstein, Bounded ignorance: A technique for increasing concurrency in a replicated system, ACM Transactions on Database Systems, 19(4): 586-625, Dec. 1994. [8] A. Kshemkalyani, The power of logical clock abstractions, Distributed Computing, 17(2): 131-151, August 2004. [9] A. Kshemkalyani, Concurrent knowledge and logical clock abstractions, Proc. 20th Conference on Foundations of Software Technology and Theoretical Computer Science, Lecture Notes in Computer Science 1974, Editors: S. Kapoor, S. Prasad, Springer-Verlag, 489-502, 2000. [10] P. Panangaden, K. Taylor, Concurrent common knowledge: Defining agreement for asynchronous systems, Distributed Computing, 6(2): 73-94, Sept. 1992. [11] F. Ruget, Cheaper matrix clocks, Proc. 8th Workshop on Distributed Algorithms, Editors: G. Tel, P. Vitanyi, Lecture Notes in Computer Science, Springer-Verlag, 355-369, 1994. [12] S. Sarin, N. Lynch, Discarding obsolete information in a distributed database system, IEEE Transactions on Software Engineering, 13(1): 39-46, 1987.

291

[13] G. Wuu, A. Bernstein, Efficient solutions to the replicated log and dictionary problems, Proc. 3rd ACM Symposium on Principles of Distributed Computing, 232-242, 1984.

292

Chapter 9 Distributed Mutual Exclusion Algorithms 9.1 Introduction Mutual exclusion is a fundamental problem in distributed computing systems. Mutual exclusion ensures that concurrent access of processes to a shared resource or data is serialized, that is, executed in mutually exclusive manner. Mutual exclusion in a distributed system states that only one process is allowed to execute the critical section (CS) at any given time. In a distributed system, shared variables (semaphores) or a local kernel cannot be used to implement mutual exclusion. Message passing is the sole means for implementing distributed mutual exclusion. The decision as to which process is allowed access to the CS next is arrived at by message passing, in which each process learns about the state of all other processes in some consistent way. The design of distributed mutual exclusion algorithms is complex because these algorithms have to deal with unpredictable message delays and incomplete knowledge of the system state. There are three basic approaches for implementing distributed mutual exclusion: 1. Token based approach 2. Non-token based approach 3. Quorum based approach In the token-based approach, a unique token (also known as the PRIVILEGE message) is shared among the sites. A site is allowed to enter its CS if it possesses the token and it continues to hold the token until the execution of the CS is over. Mutual exclusion is ensured because the token is unique. The algorithms based on this approach essentially differ in the way a site carries out the search for the token. In the non-token based approach, two or more successive rounds of messages are exchanged among the sites to determine which site will enter the CS next. A site enters the critical section (CS) when an assertion, defined on its local variables, becomes true. Mutual exclusion is enforced because the assertion becomes true only at one site at any given time. In the quorum based approach, each site requests permission to execute the CS from a subset of sites (called a quorum). The quorums are formed in such a way that when two sites concurrently 293

request access to the CS, one site receives both the requests and which is responsible to make sure that only one request executes the CS at any time. In this chapter, we describe several distributed mutual exclusion algorithms and compare their features and performance. We discuss relationship among various mutual exclusion algorithms and examine trade-offs among them.

9.2 Preliminaries In this section, we describe the underlying system model, discuss the requirements that mutual exclusion algorithms should satisfy, and discuss what metrics we use to measure the performance of mutual exclusion algorithms.

9.2.1 System Model The system consists of N sites, S1 , S2 , ..., SN . Without loss of generality, we assume that a single process is running on each site. The process at site Si is denoted by pi . All these processes communicate asynchronously over an underlying communication network. A process wishing to enter the CS, requests all other or a subset of processes by sending REQUEST messages, and waits for appropriate replies before entering the CS. While waiting the process is not allowed to make further requests to enter the CS. A site can be in one of the following three states: requesting the CS, executing the CS, or neither requesting nor executing the CS (i.e., idle). In the ‘requesting the CS’ state, the site is blocked and can not make further requests for the CS. In the ‘idle’ state, the site is executing outside the CS. In the token-based algorithms, a site can also be in a state where a site holding the token is executing outside the CS. Such state is refereed to as the idle token state. At any instant, a site may have several pending requests for CS. A site queues up these requests and serves them one at a time. We do not make any assumption regarding communication channels if they are FIFO or not. This is algorithm specific. We assume that channels reliably deliver all messages, sites do not crash, and the network does not get partitioned. Some mutual exclusion algorithms are designed to handle such situations. Many algorithms use Lamport-style logical clocks to assign a timestamp to critical section requests. Timestamps are used to decide the priority of requests in case the of a conflict. A general rule followed is that the smaller the timestamp of a request,the higher its priority to execute the CS. We use the following notations: N denotes the number of processes or sites involved in invoking the critical section, T denotes the average message delay, and E denotes the average critical section execution time.

9.2.2 Requirements of Mutual Exclusion Algorithms A mutual exclusion algorithm should satisfy the following properties:

294

Last site exits the CS

Next site enters the CS

time Synchronization delay

Figure 9.1: Synchronization Delay

1. Safety Property: The safety property states that at any instant, only one process can execute the critical section. This is an essential property of a mutual exclusion algorithm. 2. Liveness Property: This property states the absence of deadlock and starvation. Two or more sites should not endlessly wait for messages which will never arrive. In addition, a site must not wait indefinitely to execute the CS while other sites are repeatedly executing the CS. That is, every requesting site should get an opportunity to execute the CS in finite time. 3. Fairness: Fairness in the context of mutual exclusion means that each process gets a fair chance to execute the CS. In mutual exclusion algorithms, the fairness property generally means the CS execution requests are executed in the order of their arrival (time is determined by a logical clock) in the system. The first property is absolutely necessary and the other two properties are considered important in mutual exclusion algorithms.

9.2.3 Performance Metrics The performance of mutual exclusion algorithms is generally measured by the following four metrics: • Message complexity: It the number of messages that are required per CS execution by a site. • Synchronization delay: After a site leaves the CS, it is the time required and before the next site enters the CS (see Figure 9.1). Note that normally one or more sequential message exchanges may be required after a site exits the CS and before next site can enter the CS. • Response time: It is the time interval a request waits for its CS execution to be over after its request messages have been sent out (see Figure 9.2). Thus, response time does not include the time a request waits at a site before its request messages have been sent out. 295

CS Request arrives Its request messages sent out

The site enters the CS

CS execution time

The site exits the CS

time

Response Time

Figure 9.2: Response Time

• System throughput: It is the rate at which the system executes requests for the CS. If SD is the synchronization delay and E is the average critical section execution time, then the throughput is given by the following equation: system throughput=1/(SD+E) Generally, the value of a performance metric fluctuates statistically from request to request and we generally consider the average value of such a metric. Low and High Load Performance: The load is determined by the arrival rate of CS execution requests. Performance of a mutual exclusion algorithm depends upon the load and we often study the performance of mutual exclusion algorithms under two special loading conditions, viz., “low load" and “high load". Under low load conditions, there is seldom more than one request for the critical section present in the system simultaneously. Under heavy load conditions, there is always a pending request for critical section at a site. Thus, in heavy load conditions, after having executed a request, a site immediately initiates activities to execute its next CS request. A site is seldom in the idle state in heavy load conditions. For many mutual exclusion algorithms, the performance metrics can be computed easily under low and heavy loads through a simple mathematical reasoning. Best and Worst Case Performance: Generally, mutual exclusion algorithms have best and worst cases for the performance metrics. In the best case, prevailing conditions are such that a performance metric attains the best possible value. For example, in most mutual exclusion algorithms the best value of the response time is a round-trip message delay plus the CS execution time, 2T + E. Often for mutual exclusion algorithms, the best and worst cases coincide with low and high loads, respectively. For examples, the best and worst values of the response time are achieved when load is, respectively, low and high; in some mutual exclusion algorithms the best and the worse message traffic is generated at low and heavy load conditions, respectively. 296

9.3 Lamport’s Algorithm Lamport developed a distributed mutual exclusion algorithm as an illustration of his clock synchronization scheme [12]. The algorithm is fair in the sense that a request for CS are executed in the order of their timestamps and time is determined by logical clocks. When a site processes a request for the CS, it updates its local clock and assigns the request a timestamp. The algorithm executes CS requests in the increasing order of timestamps. Every site Si keeps a queue, request_queuei, which contains mutual exclusion requests ordered by their timestamps. (Note that this queue is different from the queue that contains local requests for CS execution awaiting their turn.) This algorithm requires communication channels to deliver messages the FIFO order.

The Algorithm Requesting the critical section: • When a site Si wants to enter the CS, it broadcasts a REQUEST(tsi , i) message to all other sites and places the request on request_queuei. ((tsi , i) denotes the timestamp of the request.) • When a site Sj receives the REQUEST(tsi , i) message from site Si ,places site Si ’s request on request_queuej and it returns a timestamped REPLY message to Si . Executing the critical section: Site Si enters the CS when the following two conditions hold: L1: Si has received a message with timestamp larger than (tsi , i) from all other sites. L2: Si ’s request is at the top of request_queuei. Releasing the critical section: • Site Si , upon exiting the CS, removes its request from the top of its request queue and broadcasts a timestamped RELEASE message to all other sites. • When a site Sj receives a RELEASE message from site Si , it removes Si ’s request from its request queue. When a site removes a request from its request queue, its own request may come at the top of the queue, enabling it to enter the CS. Clearly, when a site receives a REQUEST, REPLY or RELEASE message, it updates its clock using the timestamp in the message.

Correctness Theorem 1: Lamport’s algorithm achieves mutual exclusion. Proof: Proof is by contradiction. Suppose two sites Si and Sj are executing the CS concurrently. For this to happen conditions L1 and L2 must hold at both the sites concurrently. This 297

(1,1)

S

1 (1,2)

S

2

S

3

Figure 9.3: Sites S1 and S2 are Making Requests for the CS.

implies that at some instant in time, say t, both Si and Sj have their own requests at the top of their request_queues and condition L1 holds at them. Without loss of generality, assume that Si ’s request has smaller timestamp than the request of Sj . From condition L1 and FIFO property of the communication channels, it is clear that at instant t the request of Si must be present in request_queuej when Sj was executing its CS. This implies that Sj ’s own request is at the top of its own request_queue when a smaller timestamp request, Si ’s request, is present in the request_queuej – a contradiction!! Hence, Lamport’s algorithm achieves mutual exclusion. Theorem 2: Lamport’s algorithm is fair. Proof: A distributed mutual exclusion algorithm is fair if the requests for CS are executed in the order of their timestamps. The proof is by contradiction. Suppose a site Si ’s request has a smaller timestamp than the request of another site Sj and Sj is able to execute the CS before Si . For Sj to execute the CS, it has to satisfy the conditions L1 and L2. This implies that at some instant in time say t, Sj has its own request at the top of its queue and it has also received a message with timestamp larger than the timestamp of its request from all other sites. But request_queue at a site is ordered by timestamp, and according to our assumption Si has lower timestamp. So Si ’s request must be placed ahead of the Sj ’s request in the request_queuej . This is a contradiction. Hence Lamport’s algorithm is a fair mutual exclusion algorithm.

An Example In Figures 9.3 to 9.6, we illustrate the operation of Lamport’s algorithm. In Figure 9.3, sites S1 and S2 are making requests for the CS and send out REQUEST messages to other sites. The timestamps of the requests are (1, 1) and (1, 2), respectively. In Figure 9.4, both the sites S1 and S2 have received REPLY messages from all other sites. S1 has its request at the top of its request_queue but site S2 does not have its request at the top of its request_queue. Consequently, site S1 enters the CS. In Figure 9.5, S1 exits and sends RELEASE mesages to all other sites. In Figure 9.6, site S2 has received REPLY from all other sites and also received a RELEASE message from site S1 . Site S2 updates its request_queue and its request is now at the top of its request_queue. Consequently, it enters the CS next.

298

Site S1 enters the CS (1,1)

S

1 (1,2)

S

2

S

3

Figure 9.4: Site S1 enters the CS.

Site S1 enters the CS

(1,1)

Site S1 exits the CS

(1,1), (1,2)

S

1 (1,2)

(1,2)

S

2

(1,1), (1,2)

S

3

Figure 9.5: Site S1 exits the CS and sends RELEASE messages.

Site S1 enters the CS

(1,1)

Site S1 exits the CS

(1,1), (1,2)

S

1 (1,2)

S

2

Site S2 enters the CS

(1,1), (1,2)

S

3

Figure 9.6: Site S2 enters the CS

299

Performance For each CS execution, Lamport’s algorithm requires (N − 1) REQUEST messages, (N − 1) REPLY messages, and (N −1) RELEASE messages. Thus, Lamport’s algorithm requires 3(N −1) messages per CS invocation. Synchronization delay in the algorithm is T .

An Optimization In Lamport’s algorithm, REPLY messages can be omitted in certain situations. For example, if site Sj receives a REQUEST message from site Si after it has sent its own REQUEST message with timestamp higher than the timestamp of site Si ’s request, then site Sj need not send a REPLY message to site Si . This is because when site Si receives site Sj ’s request with timestamp higher than its own, it can conclude that site Sj does not have any smaller timestamp request which is still pending (because communication channels preserves FIFO ordering). With this optimization, Lamport’s algorithm requires between 3(N −1) and 2(N −1) messages per CS execution.

9.4 Ricart-Agrawala Algorithm The Ricart-Agrawala [21] algorithm assumes the communication channels are FIFO. The algorithm uses two types of messages: REQUEST and REPLY. A process sends a REQUEST message to all other processes to request their permission to enter the critical section. A process sends a REPLY message to a process to give its permission to that process. Processes use Lamport-style logical clocks to assign a timestamp to critical section requests. Timestamps are used to decide the priority of requests in case of conflict – if a process pi that is waiting to execute the critical section, receives a REQUEST message from process pj , then if the priority of pj ’s request is lower, pi defers the REPLY to pj and sends a REPLY message to pj only after executing the CS for its pending request. Otherwise, pi sends a REPLY message to pj immediately, provided it is currently not executing the CS. Thus, if several processes are requesting execution of the CS, the highest priority request succeeds in collecting all the needed REPLY messages and gets to execute the CS. Each process pi maintains the Request-Deferred array, RDi , the size of which is the same as the number of processes in the system. Initially, ∀i ∀j: RDi [j]=0. Whenever pi defer the request sent by pj , it sets RDi [j]=1 and after it has sent a REPLY message to pj , it sets RDi [j]=0.

9.4.1 Description of the Algorithm Requesting the critical section: (a) When a site Si wants to enter the CS, it broadcasts a timestamped REQUEST message to all other sites.

300

(b) When site Sj receives a REQUEST message from site Si , it sends a REPLY message to site Si if site Sj is neither requesting nor executing the CS, or if the site Sj is requesting and Si ’s request’s timestamp is smaller than site Sj ’s own request’s timestamp. Otherwise, the reply is deferred and Sj sets RDj [i]=1 Executing the critical section: (c) Site Si enters the CS after it has received a REPLY message from every site it sent a REQUEST message to. Releasing the critical section: (d) When site Si exits the CS, it sends all the deferred REPLY messages: ∀j if RDi [j]=1, then send a REPLY message to Sj and set RDi [j]=0. When a site receives a message, it updates its clock using the timestamp in the message. Also, when a site takes up a request for the CS for processing, it updates its local clock and assigns a timestamp to the request. In this algorithm, a site’s REPLY messages are blocked only by sites which are requesting the CS with higher priority (i.e., smaller timestamp).Thus, when a site sends out deffered REPLY messages, site with the next highest priority request receives the last needed REPLY message and enters the CS. Execution of the CS requests in this algorithm is always in the order of their timestamps.

Correctness Theorem 3: Ricart-Agrawala algorithm achieves mutual exclusion. Proof: Proof is by contradiction. Suppose two sites Si and Sj are executing the CS concurrently and Si ’s request has higher priority (i.e., smaller timestamp) than the request of Sj . Clearly, Si received Sj ’s request after it has made its own request. (Otherwise, Si ’s request will have lower priority.) Thus, Sj can concurrently execute the CS with Si only if Si returns a REPLY to Sj (in response to Sj ’s request) before Si exits the CS. However, this is impossible because Sj ’s request has lower priority.Therefore, Ricart-Agrawala algorithm achieves mutual exclusion. ✷ In Ricart-Agrawala algorithm, for every requesting pair of sites, the site with higher priority request will always defer the request of the lower priority site. At any time only the highest priority request succeeds in getting all the needed REPLY messages.

An Example Figures 9.7 to 9.10 illustrate the operation of Ricart-Agrawala algorithm. In Figure 9.7, sites S1 and S2 are making requests for the CS and send out REQUEST messages to other sites. The timestamps of the requests are (2, 1) and (1, 2), respectively. In Figure 9.8, S2 has received REPLY messages from all other sites and consequently, it enters the CS. In Figure 9.9, S2 exits the CS and sends a REPLY mesage to site S1 . In Figure 9.10, site S1 has received REPLY from all other sites and enters the CS next. 301

(1,1)

S1 (1,2)

S2

S3 Figure 9.7: Sites S1 and S2 are making request for the CS

Request is deferred

Site S 1 enters the CS

(1,1)

S1 (1,2)

S2

S3 Figure 9.8: Site S1 enters the CS

Request is deferred (1,1)

Site S 1enters the CS

Site S 1 exits the CS

S1 (1,2)

S2

S3 Figure 9.9: Site S1 exits the CS and sends a REPLY message to S2 ’s deferred request

302

Request is deferred (1,1)

Site S 1enters the CS

Site S 1 exits the CS

S1 Site S 2 enters the CS

(1,2)

S2

S3 Figure 9.10: Site S2 enters the CS

Performance For each CS execution, Ricart-Agrawala algorithm requires (N − 1) REQUEST messages and (N −1) REPLY messages. Thus, it requires 2(N −1) messages per CS execution. Synchronization delay in the algorithm is T .

9.5 Singhal’s Dynamic Information-Structure Algorithm Most mutual exclusion algorithms use a static approach to invoke mutual exclusion; i.e., they always take the same course of actions to invoke mutual exclusion no matter what is the state of the system. A problem with these algorithms is the lack of efficiency because these algorithms fail to exploit the changing conditions in the system. Note that an algorithm can exploit dynamic conditions of the system to optimize the performance. For example, if few sites are invoking mutual exclusion very frequently and other sites invoke mutual exclusion much less frequently, then a frequently invoking site need not ask for the permission of less frequently invoking site every time it requests an access to the CS. It only needs to take permission from all other frequently invoking sites. Singhal [29] developed an adaptive mutual exclusion algorithm based on this observation. The information-structure of the algorithm evolves with time as sites learn about the state of the system through messages. Dynamic informationstructure mutual exclusion algorithms are attractive because they can adapt to fluctuating system conditions to optimize the performance. The design of such adaptive mutual exclusion algorithms is challenging and we list some of the design challenges next: • How does a site efficiently know what sites are currently actively invoking mutual exclusion? • When a less frequently invoking site needs to invoke mutual exclusion, how does it do it?

303

• How does a less frequently invoking site makes a transition to more frequently invoking site and vice-versa. • How to insure that mutual exclusion is guaranteed when a site does not take the permission of every other site. • How to insure that a dynamic mutual exclusion algorithm does not waste resources and time in collecting systems state, offsetting any gain.

System Model We consider a distributed system consisting of n autonomous sites, say S1 , S2 , ..., Sn , which are connected by a communication network. We assume that the sites communicate completely by message passing. Message propagation delay is finite but unpredictable and between any pair of sites, messages are delivered in the order they are sent. For the ease of presentation, we assume that the underlying communication network is reliable and sites do not crash. However, methods have been proposed for recovery from message losses and site failures.

Data Structures Information-structure at a site Si consists of two sets. The first set Ri , called request set, contains the sites from which Si must acquire permission before executing CS. The second set Ii , called inform set, contains the sites to which Si must send its permission to execute CS after executing its CS. Every site Si maintains a logical clock Ci , which is updated according to Lamport’s rules. Every request for CS execution is assigned a timestamp which is used to determine its priority. The smaller the timestamp of a request, the higher its priority. Every site maintains three boolean variables to denote the state of the site: Requesting, Executing, and My_priority. Requesting and executing are true if and only if the site is requesting or executing CS, respectively. My_priority is true if pending request of Si has priority over the current incoming request.

Initialization The system starts in the following initial state: For a site Si (i = 1 to n), Ri := {S1 , S2 ,..., Si − 1, Si } Ii := Si Ci := 0 Requesting = Executing := False

304

Thus, initially site Si , 1≤i≤n, sends request messages only to sites Si , Si − 1, ..., S1 . If we stagger sites Sn to S1 from left to right, then the initial system state has the following two properties: 1. Each site requests permission from all the sites to its right and from no site to its left. Conversely, for a site, all the sites to its left asks for its permission and no site to its right asks for its permission. Or putting together, for a site, only all the sites to its left will ask for its permission and it will ask for the permission of only all the sites to its right. Therefore, every site Si divides all the sites into two disjoint groups; all the sites in the first group request permission from Si and Si requests permission from all the sites in the second group. This property is important for enforcing mutual exclusion. 2. The cardinality of Ri decreases in stepwise manner from left to right. Due to this reason, this configuration has been called "staircase pattern" in topological sense [27].

9.5.1 The Algorithm Site Si executes the following three steps to invoke mutual exclusion: Step 1: (Request Critical Section) Requesting = true; Ci = Ci + 1; Send REQUEST(Ci , i) message to all sites in Ri ; Wait until Ri = ∅; /* Wait until all sites in Ri have sent a reply to Si */ Requesting = false; Step 2: (Execute Critical Section) Executing = true; Execute CS; Executing = false; Step 3: (Release Critical Section) For every site Sk in Ii (except Si ) do Begin Ii = Ii – {Sk }; Send REPLY(Ci, i) message to Sk ; Ri = Ri + {Sk } End

305

REQUEST message handler REQUEST message handler at a site processes incoming REQUEST messages. It takes actions such as updating information-structure and sending REQUEST/REPLY messages to other sites. REQUEST message handler at site Si is given below: /* Site Si is handling message REQUEST(c, j) */ Ci := max{Ci, c}; Case Requesting = true: Begin if My_priority then Ii := Ii + {j } /*My_Priority is true if the pending request of Si has priority over the incoming request */ Else Begin Send REPLY(Ci , i) message to Sj ; If not (Sj ∈ Ri ) then Begin Ri = Ri + {Sj }; Send REQUEST(Ci , i) message to site Sj ; End; End; End; Executing = true: Ii = Ii + {Sj }; Executing = false ∧ Requesting = false: Begin Ri = Ri + {Sj }; Send REPLY(Ci , i) message to Sj ; End; REPLY message handler The REPLY message handler at a site processes incoming REPLY messages. It updates the information-structure. REPLY message handler at site Si is given below: /* Site Si is handling a message REPLY(c, j) */ Begin Ci := max{Ci , c}; Ri = Ri – {Sj }; End; Note that REQUEST and REPLY message handlers and the steps of the algorithm access shared data structures, viz., Ci , Ri , and Ii . To guarantee the correctness, it’s important that execution of REQUEST and REPLY message handlers and all three steps of the algorithm (except "wait for Ri 306

= ∅ to hold" in Step 1) mutually exclude each other.

An Explanation of the Algorithm At high level, Si acquires permission to execute the CS from all sites in its request set Ri and it releases the CS by sending a REPLY message to all sites in its inform set Ii . If site Si which itself is requesting the CS, receives a higher priority REQUEST message from a site Sj , then Si takes the following actions: (i) Si immediately sends a REPLY message to Sj , (ii) if Sj is not in Ri , then1 Si also sends a REQUEST message to Sj , and (iii) Si places an entry for Sj in Ri . Otherwise, (i.e., if the request of Si has priority over the request of Sj ), Si places an entry for Sj into Ii so that Sj can be sent a REPLY message when Si finishes with the execution of the CS. If Si receives a REQUEST message from Sj when it is executing the CS, then it simply puts Sj in Ii so that Sj can be sent a REPLY message when Si finishes with the execution of the CS. If Si receives a REQUEST message from Sj when it is neither requesting nor executing the CS, then it places an entry for Sj in Ri and sends Sj a REPLY message. Rules for information exchange and updating request and inform sets are such that the staircase pattern is preserved in the system even after the sites have executed the CS any number of times. However, the positions of sites in the staircase pattern change as the system evolves. (For a proof of this, see [28]). The site to execute CS last positions itself at the right end of the staircase pattern.

9.5.2 Correctness We informally discuss why the algorithm achieves mutual exclusion and why it is free from deadlocks. For a formal proof, the readers are referred to [28]. Achieving Mutual Exclusion: Note that the initial state of the information-structure satisfies the following condition: for every Si and Sj , either Sj ∈ Ri or Si ∈ Rj . Therefore, if two sites request CS, one of them will always ask for the permission of the another. However, this is not sufficient for mutual exclusion [18]; However, whenever there is a conflict between two sites (i.e., they concurrently invoke mutual exclusion), the sites dynamically adjust their request sets such that both request permission of each other satisfying the condition for mutual exclusion. This is a nice feature of the algorithm because if the information-structures of sites satisfy the condition for mutual exclusion all the time, sites will exchange more messages. Instead, it is more desirable to dynamically adjust the request set of the sites as and when needed to insure mutual exclusion because it optimizes the number of messages exchanged. 1

Absence of Sj from Ri implies that Si has not previously sent a REQUEST message to Sj . This is the reason why Si also sends a REQUEST message to Sj when it receives a REQUEST message from Sj . This step is also required to preserve the staircase pattern of the information-structure of the system.

307

Freedom from Deadlocks: In the algorithm, each request is assigned a globally unique timestamp which determines its priority. The algorithm is free from deadlocks because sites use timestamp ordering (which is unique system wide) to decide request priority and a request is blocked by only higher priority requests.

An Example Consider a system with five sites S1 , . . . , S5 . Suppose S2 and S3 want to enter the CS concurrently, and they both send appropriate request messages. S3 sends a request message to sites in its Request set - {S1 , S2 } and S2 sends a request message to the only site in its Request set – {S1 }. There are three possible scenarios: 1. If timestamp of S3 ’s request is smaller, then on receiving S3 ’s request, S2 sends a REPLY message to S3 . S2 also adds S3 to its Request set and sends S3 a REQUEST message. On receiving a REPLY message from S2 , S3 removes S2 from its Request set. S1 sends a REPLY to both S2 and S3 because its neither requesting to enter the CS nor executing the CS. S1 adds S2 and S3 to its Request set because any one of these sites could possibly be in the CS when S1 requests for an entry into CS in the future. On receiving S1 ’s REPLY message, S3 removes S1 from its Request set and since it has got REPLY messages from all sites in its (initial) Request Set, it enters the CS. 2. If timestamp of S3 is larger, then on receiving S3 ’s request, S2 adds S3 to its Inform set. When S2 gets a REPLY from S1 , it enters the CS. When S2 relinquishes the CS, it informs S3 (id of S3 is present in S2 ’s Inform set) about its consent to enter the CS. Then, S2 removes S3 from its Inform set and add S3 to its Request set. This is logical because S3 could be executing in CS when S2 requests an ‘CS entry’ permission in the future. 3. If S2 receives a REPLY from S1 and starts executing CS before S3 ’s REQUEST reaches S2 , S2 simply adds S3 to its Inform set, and sends S3 a REPLY after exiting the CS.

9.5.3 Performance Analysis The synchronization delay in the algorithm is T. Below, we compute the message complexity in low and heavy loads. Low load condition: In case of low traffic of CS requests, most of the time only one or no request for the CS will be present in the system. Consequently, the staircase pattern will reestablish between two sucssive requests for CS and there will seldom be an interference among the CS requests from different sites. In the staircase configuration, cardinality of the request sets of the sites is 1, 2, ..., (n-1), n, respectively, from right to left. Therefore, when traffic of requests for CS is low, sites will send 0, 1, 2, ..., (n-1) number of REQUEST messages with equal likelihood (assuming uniform traffic of CS requests at sites). Therefore, the mean number of REQUEST messages sent 308

per CS execution for this case is =(0+1+2+ ........+(n-1))/n = (n-1)/2. Since a REPLY message is returned for every REQUEST message, the average number of messages exchanged per CS execution is 2*(n-1)/2 = (n-1). Heavy load condition: When the rate of CS requests is high, all the sites always have a pending request for CS execution. In this case, a site on the average receives (n-1)/2 REQUEST messages from other sites while waiting for its REPLY messages. Since a site sends REQUEST messages only in response to REQUEST messages of higher priority, on the average it will send (n-1)/4 REQUEST messages while waiting for REPLY messages. Therefore, the average number of messages exchanged per CS execution in high demand is 2*[(n-1)/2+(n-1)/4] = 3*(n-1)/2.

9.5.4 Adaptivity in Heterogeneous Traffic Patterns An interesting feature of the algorithm is that its information-structure adapts itself to the environments of heterogeneous traffic of CS requests and to statistical fluctuations in traffic of CS requests to optimize the performance (the number of messages exchanged per CS execution). In non-uniform traffic environments, sites with higher traffic of CS requests will position themselves towards the right end of the staircase pattern. That is, sites with higher traffic of CS requests will tend to have lower cardinality of their request sets. Also, at a high traffic site Si , if Sj ∈ Ri , then Sj is also a high traffic site (this comes intuitively because all high traffic sites will cluster towards the right end of the staircase). Consequently, high traffic sites will mostly send REQUEST messages only to other high traffic sites and will seldom send REQUEST messages to sites with low traffic. This adaptivity results in a reduction in the number of messages as well as in delay in granting CS in environments of heterogeneous traffic.

9.6 Lodha and Kshemkalyani’s Fair Mutual Exclusion Algorithm Lodha and Kshemakalyani’s algorithm [13] decreases the message complexity of Ricart-Agrawala algorithm by using the following interesting observation: When a site is waiting to execute the CS, it need not receive REPLY messages from every other site. To enter the CS, a site only needs to receive a REPLY message from the site whose request just precedes its request in priority. For example, if sites Si1 ,Si2 , ..Sin have a pending request for CS and the request of Si1 has the highest priority and that of Sin has the lowest priority and the priority of requests decreases from Si1 to Sin , then a site Sik only needs a REPLY message from site Sik−1 , 1 < k ≤ n to enter the CS.

9.6.1 System Model Each request is assigned a priority ReqID and requests for the CS access are granted in the order of decreasing priority. We will defer the details of what ReqID is composed of to later sections.

309

The underlying communication network is assumed to be error free. Definition 1: Ri and Rj are concurrent iff Pi ’s REQUEST message is received by Pj after Pj has made its request and Pj ’s REQUEST message is received by Pi after Pi has made its request. Definition 2: Given Ri , we define the concurrency set of Ri as follows: S CSeti = {Rj | Ri is concurrent with Rj } {Ri }.

9.6.2 The Algorithm

The algorithm uses three types of messages: REQUEST, REPLY and FLUSH and obtains savings on the number of messages exchanged per CS access by assigning multiple purposes to each. For the purpose of blocking a mutual exclusion request, every site Si has a data structure called local_request_queue (denoted as LRQi ) which contains all concurrent requests made with respect to Si ’s request and these requests are ordered with respect to the priority.

All requests are totally ordered by their priorities and the priority is determined by the timestamp of the request. Hence, when a process receives a REQUEST message from some other process, it can immediately determine if it is allowed the CS access before the requesting process or after it. In this algorithm, messages play multiple roles and that is discussed next. • Multiple uses of a REPLY message 1. A REPLY message acts as a reply from a process that is not requesting. 2. A REPLY message acts as a collective reply from processes that have higher priority requests. A REPLY(Rj ) from a process Pj indicates that Rj is the request made by Pj for which it has executed the CS. It also indicates that all the requests with priority ≥ priority of Rj have finished executing CS and are no longer in contention. Thus, in such situations, a REPLY message is a logical reply and denotes a collective reply from all processes that had made higher priority requests. • Uses of a FLUSH message Similar to a REPLY message, a FLUSH message is a logical reply and denotes a collective reply from all processes that had made higher priority requests. After a process has exited the CS, it sends a FLUSH message to a process requesting with the next highest priority, which is determined by looking up the process’s local request queue. When a process Pi finishes executing the CS, it may find a process Pj in one of the following states: 310

1. Rj is in the local queue of Pi and located in some position after Ri , which implies that Rj is concurrent with Ri . 2. Pj had replied to Ri and Pj is now requesting with a lower priority. (Note that in this case Ri and Rj are not concurrent). 3. Pj ’s requst had higher priority than Pi ’s (implying that it had finished the execution of the CS) and is now requesting with a lower priority. (Note that in this case Ri and Rj are not concurrent). A process Pi after executing the CS, sends a FLUSH message to a process identified in the Case 1 above, which has the next highest priority, whereas it sends REPLY messages to the processes identified in Cases 2 and 3 as their requests are not concurrent with Ri (the resuests of processes in Cases 2 and 3 were deferred by Pi till it exits the CS). Now it is up to the process receiving the FLUSH message and the processes recieving REPLY messages in Cases 2 and 3 to determine who is allowed to enter the CS next. Consider a scenario where we have a set of requests R3 R0 R2 R4 R1 ordered in decreasing priority where R0 R2 R4 are concurrent with one another, then P0 maintains a local queue of [ R0 , R2 , R4 ] and when it exits the CS, it sends a FLUSH (only) to P2 . • Multiple uses of a REQUEST message Considering two processes Pi and Pj , there can be two cases: Case 1: Pi and Pj are not concurrently requesting. In this case, the process which requests first will get a REPLY message from the other process. Case 2: Pi and Pj are concurrently requesting. In this case, there can be two subcases: 1. Pi is requesting with a higher priority than Pj . In this case, Pj ’s REQUEST message serves as an implicit REPLY message to Pi ’s request. Also, Pj should wait for REPLY/FLUSH message from some process to enter the CS. 2. Pi is requesting with a lower priority than Pj . In this case, Pi ’s REQUEST message serves as an implicit REPLY message to Pj ’s request. Also, Pi should wait for REPLY/FLUSH message from some process to enter the CS.

The Algorithm • Initial local state for process Pi - int My_Sequence_Numberi =0 - array of boolean RVi [j]=0, ∀j ∈ {1...N} - queue of ReqID LRQi is NULL - int Highest_Sequence_Number_Seeni=0 311

• InvMutEx: Process Pi executes the following to invoke mutual exclusion: 1. My_Sequence_Numberi = Highest_Sequence_Number_Seeni + 1 2. LRQi = NULL 3. Make REQUEST(Ri ) message, where Ri = (My_Sequence_Numberi , i). 4. Insert this REQUEST in the LRQi in sorted order. 5. Send this REQUEST message to all other processes. 6. RVi [k]=0 ∀k ∈ {1...N}-{i}. RVi [i]=1. • RcvReq: Process Pi receives REQUEST(Rj ), where Rj =(SN, j), from process Pj : 1. Highest_Sequence_Number_Seeni = max(Highest_Sequence_Number_Seeni, SN). 2. if Pi is requesting: (a) if RV i [j] = 0, then insert this request in the LRQi (in sorted order) and mark RV i [j] = 1. If (CheckExecuteCS), then execute CS. (b) if RV i [j] = 1, then defer the processing of this request, which will be processed after Pi executes CS. 3. If Pi is not requesting, then send a REPLY(Ri ) message to Pj . Ri denotes the ReqID of the last request made by Pi that was satisfied. • RcvReply: Process Pi receives REPLY(Rj ) message from process Pj : Rj denotes the ReqID of the last request made by Pj that was satisfied. 1. RVi [j]=1 2. Remove all requests from LRQi that have a priority ≥ the priority of Rj 3. If(CheckExecuteCS), then execute CS. • FinCS: Process Pi finishes executing CS. 1. Send FLUSH(Ri ) message to the next candidate in the LRQi . Ri denotes the ReqID that was satisfied. 2. Send REPLY(Ri ) to the deferred requests. Ri is the ReqID corresponding to which Pi just executed the CS. • RcvFlush: Process Pi receives a FLUSH(Rj ) message from a process Pj : 1. RVi [j] = 1 2. Remove all requests in LRQi that have the priority ≥ the priority of Rj . 3. If (CheckExecuteCS) then execute CS.

• CheckExecuteCS: if (RVi [k]=1, ∀k ∈ {1..N}) and Pi ’s request is at the head of LRQi , then return true, else return false. 312

P1

P2

P3

Figure 9.11: Processes P1 and P2 send out REQUESTs

9.6.3 Safety, Fairness and Liveness Prrofs for safety, fairness and liveness are quite involved and interested readers are referred to the original paper for detailed proofs.

9.6.4 An Example Figure 9.11: Processes P1 and P2 are concurrent and they send out REQUESTs to all other processes. The REQUEST sent by P1 to P3 is delayed and hence is not shown until in Figure 9.13. Figure 9.12: When P3 receives the REQUEST from P2 , it sends REPLY to P2 . Figure 9.13: The delayed REQUEST of P1 arrives at P3 and at the same time, P3 sends out its REQUEST for CS, which makes it concurrent with the request of P1 . Figure 9.14: P1 exits the CS and sends out a FLUSH message to P2 . Figure 9.15: Since the requests of P2 and P3 are not concurrent, P 2 sends a FLUSH message to P3 . P3 removes (1,1) from its local queue and enters the CS. The data structures LRQ and RV are updated in each step as discussed previously.

9.6.5 Message Complexity To execute the CS, a process Pi sends N-1 REQUEST messages. It receives (N-|CSeti |) REPLY messages. There are two cases to consider: 1. |CSeti |≥ 2. There are two sub cases here. a. There is at least one request in CSeti whose priority is smaller than that of Ri . So Pi will send one FLUSH message. In this case the total number of messages for CS access is 2N-|CSeti |. When all the requests are concurrent, this reduces to N messages. 313

P1

P2

P3

Figure 9.12: P3 sends out REPLY to just P2

P

1

enters the CS

P1

P2

P3

Figure 9.13: P3 sends out REQUEST

P

1

enters the CS

P1 P1 sends a FLUSH message to P 2 P2

P3

Figure 9.14: P1 exits the CS and sends out a FLUSH message to P2

314

P

1

enters the CS

P1 P 1sends a FLUSH message to P

2

P2 P 2 sends a FLUSH message to P 3

P3

P 3 enters the CS

REQUEST from P1 REQUEST from P2 REQUEST from P 3 REPLY from P 3 FLUSH message

Figure 9.15: P3 enters the CS

315

b. There is no request in CSeti , whose priority is less than the priority of Ri . Pi will not send a FLUSH message. In this case, the total number of messages for CS access is 2N-1-|CSeti |. When all the requests are concurrent, this reduces to N-1 messages. 2. |CSeti | = 1. This is the worst case, implying that all requests are satisfied serially. Pi will not send a FLUSH message. In this case, the total number of messages for CS access is 2(N-1) messages.

9.7 Quorum-Based Mutual Exclusion Algorithms Quorum-based mutual exclusion algorithms respresented a departure from the trend in the following two ways: 1. A site does not request permission from all other sites, but only from a subset of the sites. This is a radically different approach as compared to Lamport and Ricart-Agrawala algorithms where all sites participate in conflict resolution of all other sites. In quorum-based mutual exclusion algorithm, the request set of sites are chosen such that ∀i ∀j : 1 ≤ i, j ≤ N :: Ri ∩ Rj 6= Φ. Consequently, every pair of sites has a site which mediates conflicts between that pair. 2. In quorum-based mutual exclusion algorithm, a site can send out only one REPLY message at any time. A site can send a REPLY message only after it has received a RELEASE message for the previous REPLY message. Therefore, a site Si locks all the sites in Ri in exclusive mode before executing its CS. Quorum-based mutual exclusion algorithms significantly reduce the message complexity of invoking mutual exclusion by having sites ask permission from only a subset of sites. Since these algorithms are based on the notion of ‘Coteries’ and ‘Quorums’, we first describe the idea of coteries and quorums. A coterie C is defined as a set of sets, where each set g ∈ C is called a quorum. The following properties hold for quorums in a coterie: • Intersection property: For every quorum g, h ∈ C, g ∩ h6= ∅. For example, sets {1,2,3}, {2,5,7} and {5,7,9} cannot be quorums in a coterie because the first and third sets do not have a common element. • Minimality property: There should be no quorums g, h in coterie C such that g ⊇ h. For example, sets {1,2,3} and {1,3} cannot be quorums in a coterie because the first set is a superset of the second. Coteries and quorums can be used to develop algorithms to ensure mutual exclusion in a distributed environment. A simple protocol works as follows: Let ‘a’ is a site in quorum ‘A’. If ‘a’ wants to invoke mutual exclusion, it requests permission from all sites in its quorum ‘A’. Every

316

site does the same to invoke mutual exclusion. Due to the Intersection Property, quorum ‘A’ contains at least one site that is common to the quorum of every other site. These common sites send permission to only one site at any time. Thus, mutual exclusion is guaranteed. Note that the Minimality property ensures efficiency rather than correctness. In the simplest form, quorums are formed as sets that contain a majority of sites. There exists a variety of quorums and a variety of ways to construct √ quorums. For example, Maekawa used the theory of projective planes to develop quorums of size N.

9.8 Maekawa’s Algorithm Maekawa’s algorithm [14] was the first quorum-based mutual exclusion algorithm. The request sets for sites (i.e., quorums) in Maekawa’s algorithm are constructed to satisfy the following conditions: M1: (∀i ∀j : i 6= j, 1 ≤ i, j ≤ N :: Ri ∩ Rj 6= φ) M2: (∀i : 1 ≤ i ≤ N :: Si ∈ Ri ) M3: (∀i : 1 ≤ i ≤ N :: |Ri | = K) M4: Any site Sj is contained in K number of Ri s, 1 ≤ i, j ≤ N. Maekawa used the √ theory of projective planes and showed that N = K(K − 1) + 1. This relation gives |Ri | = N. Since there is at least one common site between the request sets of any two sites (condition M1), every pair of sites has a common site which mediates conflicts between the pair. A site can have only one outstanding REPLY message at any time; that is, it grants permission to an incoming request if it has not granted permission to some other site. Therefore, mutual exclusion is guaranteed. This algorithm requires delivery of messages to be in the order they are sent between every pair of sites. Conditions M1 and M2 are necessary for correctness; whereas conditions M3 and M4 provide other desirable features to the algorithm. Condition M3 states that the size of the requests sets of all sites must be equal implying that all sites should have to do equal amount of work to invoke mutual exclusion.Condition M4 enforces that exactly the same number of sites should request permission from any site implying that all sites have “equal responsibility” in granting permission to other sites.

9.8.1 The Algorithm In Maekawa’s algorithm, a site Si executes the following steps to execute the CS. Requesting the critical section

317

(a) A site Si requests access to the CS by sending REQUEST(i) messages to all sites in its request set Ri . (b) When a site Sj receives the REQUEST(i) message, it sends a REPLY(j) message to Si provided it hasn’t sent a REPLY message to a site since its receipt of the last RELEASE message. Otherwise, it queues up the REQUEST(i) for later consideration. Executing the critical section (c) Site Si executes the CS only after it has received a REPLY message from every site in Ri . Releasing the critical section (d) After the execution of the CS is over, site Si sends a RELEASE(i) message to every site in Ri . (e) When a site Sj receives a RELEASE(i) message from site Si , it sends a REPLY message to the next site waiting in the queue and deletes that entry from the queue. If the queue is empty, then the site updates its state to reflect that it has not sent out any REPLY message since the receipt of the last RELEASE message.

Correctness Theorem 3: Maekawa’s algorithm achieves mutual exclusion. Proof: Proof is by contradiction. Suppose two sites Si and Sj are concurrently executing the CS. This means site Si received a REPLY message from all sites in Ri and concurrently site Sj was able to receive a REPLY message from all sites in Rj . If Ri ∩ Rj = {Sk }, then site Sk must have sent REPLY messages to both Si and Sj concurrently, which is a contradiction. ✷

Performance √ √ N. Therefore, an execution of the CS requires N RENote that√the size of a request set is √ √ QUEST, N REPLY, and N RELEASE messages, resulting in 3 N messages per CS execution. Synchronization delay in this algorithm is 2T . This is because after a site Si exits the CS, it first releases all the sites in Ri and then one of those sites sends a REPLY message to the next site that executes the CS. Thus, two sequential message transfers are required between two successive CS executions. As discussed next, Maekawa’s algorithm is deadlock-prone. Measures to handle deadlocks require additional messages.

9.8.2 Problem of Deadlocks Maekawa’s algorithm can deadlock because a site is exclusively locked by other sites and requests are not prioritized by their timestamps [14, 22]. Thus, a site may send a REPLY message to a site and later force a higher priority request from another site to wait. 318

Without the loss of generality, assume three sites Si , Sj , and Sk simultaneously invoke mutual exclusion. Suppose Ri ∩ Rj = {Sij }, Rj ∩ Rk = {Sjk }, and Rk ∩ Ri = {Ski }. Since sites do not send REQUEST messages to the sites in their request sets in any particular order and message delays are arbitrary, the following scenario is possible: Sij has been locked by Si (forcing Sj to wait at Sij ), Sjk has been locked by Sj (forcing Sk to wait at Sjk ), and Ski has been locked by Sk (forcing Si to wait at Ski ). This state represents a deadlock involving sites Si , Sj , and Sk .

Handling Deadlocks Maekawa’s algorithm handles deadlocks by requiring a site to yield a lock if the timestamp of its request is larger than the timestamp of some other request waiting for the same lock (unless the former has succeeded in acquiring locks on all the needed sites) [14, 22]. A site suspects a deadlock (and initiates message exchanges to resolve it) whenever a higher priority request arrives and waits at a site because the site has sent a REPLY message to a lower priority request. Deadlock handling requires the following three types of messages: FAILED: A FAILED message from site Si to site Sj indicates that Si can not grant Sj ’s request because it has currently granted permission to a site with a higher priority request. INQUIRE: An INQUIRE message from Si to Sj indicates that Si would like to find out from Sj if it has succeeded in locking all the sites in its request set. YIELD: A YIELD message from site Si to Sj indicates that Si is returning the permission to Sj (to yield to a higher priority request at Sj ). Details of how Maekawa’s algorithm handles deadlocks are as follows: • When a REQUEST(ts, i) from site Si blocks at site Sj because Sj has currently granted permission to site Sk , then Sj sends a FAILED(j) message to Si if Si ’s request has lower priority. Otherwise, Sj sends an INQUIRE(j) message to site Sk . • In response to an INQUIRE(j) message from site Sj , site Sk sends a YIELD(k) message to Sj provided Sk has received a FAILED message from a site in its request set or if it sent a YIELD to any of these sites, but has not received a new GRANT from it. • In response to a YIELD(k) message from site Sk , site Sj assumes as if it has been released by Sk , places the request of Sk at appropriate location in the request queue, and sends a GRANT(j) to the top request’s site in the queue. Thus, Maekawa-type algorithms require extra messages to handle deadlocks and may exchange these messages even though √ there is no deadlock. Maximum number of messages required per CS execution in this case is 5 N .

319

9.9 Agarwal-El Abbadi Quorum-Based Algorithm Agarwal and El Abbadi [1] developed a simple and efficient mutual exclusion algorithm by introducing tree quorums. They gave a novel algorithm for constructing tree-structured quorums in the sense that it uses hierarchical structure of a network. The mutual exclusion algorithm is independent of the underlying topology of the network and there is no need for a multicast facility in the network. However, such facility will improve the performance of the algorithm. The mutual exclusion algorithm assumes that sites in the distributed system can be organized into a structure such as tree, grid, binary tree, etc. and there exists a routing mechanism to exchange messages between different sites in the system. Agarwal-El Abbadi quorum-based algorithm, however, constructs quorums from trees. Such quorums are called ‘tree-structured quorums’. The following sections describe an algorithm for constructing tree-structured quorums and present an analysis of the algorithm and a protocol for mutual exclusion in distributed systems using tree-structured quorums.

9.9.1 Constructing a tree-structured quorum All the sites in the system are logically organized into a complete binary tree. To build such a tree, any site could be chosen as the root, any other two sites may be chosen as its children and so on. For a complete binary tree with level ‘k’, we have 2k+1 – 1 sites with its root at level k and leaves at level 0. The number of sites in a path from the root to a leaf is equal to the level of the tree k+1 which is equal to O(log n). There will be 2k leaves in the tree. A path in a binary tree is the sequence a1 , a2 . . . ai , ai+1 . . . . ak such that ai is the parent of ai+1 . The algorithm for constructing structured quorums from the tree is given in Figure 9.16. For the purpose of presentation, we assume that the tree is complete, however, the algorithm works for any arbitrary binary tree.

The algorithm for constructing tree-structured quorums uses two functions called GetQuorum(tree) and GrantsPermission(site) and assumes that there is a well-defined root for the tree. GetQuorum is a recursive function that takes a tree node ‘x’ as the parameter and calls GetQuorum for its child node provided that the GrantsPermission(x) is true. The GrantsPermission(x) is true only when the node ‘x’ agrees to be in the quorum. If the node ‘x’ is down due to a failure, then it may not agree to be in the quorum and the value of GrantsPermission(x) will be false. The algorithm tries to construct quorums in a way that each quorum represents any path from the root to a leaf, i.e., in this case (no failures) quorum is any set a1 , a2 . . . ai , ai+1 . . . .. ak where a1 is the root and ak is a leaf, and for all i 0, then the quorum size increases to (k-i) +2i. The penalty is severe when the failed node is near the root. Thus, the tree quorum algorithm may still allow quorums to be formed even after the failures of n - |log n |sites.

9.9.5 The Algorithm for Distributed Mutual Exclusion We now describe the algorithm for achieving distributed mutual exclusion using tree-structured quorums. Suppose a site s wants to enter the critical section (CS). The following events should occur in the given order: 1. Site s sends a ‘Request’ message to all other sites in the structured quorum it belongs to. 2. Each site in the quorum stores incoming requests in a request queue, ordere by their timestamps. 3. A site sends a ‘Reply’ message, indicating its consent to enter CS, only to the request at the head of its request queue, having the lowest timestamp. 4. If the site s gets a ‘Reply’ message from all sites in the structured quorum it belongs to, it enters the CS. 5. After exiting the CS, s sends a ‘Relinquish’ message to all sites in the structured quorum. On the receipt of the ‘Relinquish’ message, each site removes s’s request from the head of its request queue. 323

6. If a new request arrives with a timestamp smaller than the request at the head of the queue, an ‘Inquire’ message is sent to the process whose request is at the head of the queue and waits for a ‘Yield’ or ‘Relinquish’ message. 7. When a site s receives an ‘Inquire’ message, it acts as follows: If s has acquired all of its necessary replies to access the CS, then it simply ignores the ‘Inquire’ message and proceeds normally and sends a ‘Relinquish’ message after exiting the CS. If s has not yet collected enough replies from its quorum, then it sends a ‘Yield’ message to the inquiring site. 8. When a site gets the ‘Yield’ message, it puts the pending request (on behalf of which the ‘Inquire’ message was sent) at the head of the queue and sends a ‘Reply’ message to the requestor.

9.9.6 Correctness proof Mutual exclusion is guaranteed because the set of quorums satisfy the Intersection property. Proof for freedom from deadlock is similar to that of Maekawa’s algorithm. The readers are referred to the original source.

9.9.7 An Example Consider a coterie C which consists of quorums {1,2,3}, {2,4,5} and {4,1,6}. Suppose nodes 3, 5 and 6 want to enter CS, and they send requests to sites (1, 2), (2, 4) and (1, 4), respectively. Suppose site 3’s request arrives at site 2 before site 5’s request. In this case, site 2 will grant permission to site 3’s request and reject site 5’s request. Similarly, suppose site 3’s request arrives at site 1 before site 6’s request. So site 1 will grant permission to site 3’s request and reject site 6’s request. Since sites 5 and 6 did not get consent from all sites in their quorums, they do not enter the CS. Since site 3 alone gets consent from all sites in its quorum, it enters the CS and mutual exclusion is achieved.

9.10 Token-Based Algorithms In token-based algorithms, a unique token is shared among the sites. A site is allowed to enter its CS if it possesses the token. A site holding the token can enter its CS repeatedly until it sends the token to some other site. Depending upon the way a site carries out the search for the token, there are numerous token-based algorithms. Next, we discuss two token-based mutual exclusion algorithms. Before we start with the discussion of token-based algorithms, two comments are in order: First, token-based algorithms use sequence numbers instead of timestamps. Every request for the token contains a sequence number and the sequence numbers of sites advance independently. A site 324

increments its sequence number counter every time it makes a request for the token. (A primary function of the sequence numbers is to distinguish between old and current requests.) Second, correctness proof of token-based algorithms, that they enforce mutual exclusion, is trivial because an algorithm guarantees mutual exclusion so long as a site holds the token during the execution of the CS. Instead, the issues of freedom from starvation, freedom from deadlock, and detection of the token loss and its regeneration become more prominent.

9.11 Suzuki-Kasami’s Broadcast Algorithm In Suzuki-Kasami’s algorithm [30], if a site that wants to enter the CS, does not have the token, it broadcasts a REQUEST message for the token to all other sites. A site which possesses the token sends it to the requesting site upon the receipt of its REQUEST message. If a site receives a REQUEST message when it is executing the CS, it sends the token only after it has completed the execution of the CS. The basic idea underlying this algorithm may sound rather simple, however, there are the following two design issues must be efficiently addressed: 1. How to distinguishing an outdated REQUEST message from a current REQUEST message: Due to variable message delays, a site may receive a token request message after the corresponding request has been satisfied. If a site can not determined if the request corresponding to a token request has been satisfied, it may dispatch the token to a site that does not need it. This will not violate the correctness, however, this may seriously degrade the performance by wasting messages and increasing the delay at sites that are genuinely requesting the token. Therefore, appropriate mechanisms should implemented to determine if a token request message is outdateded. 2. How to determine which site has an outstanding request for the CS: After a site has finished the execution of the CS, it must determine what sites have an outstanding request for the CS so that the token can be dispatched to one of them. The problem is complicated because when a site Si receives a token request message from a site Sj , site Sj may have an outstanding request for the CS. However, after the corresponding request for the CS has been satisfied at Sj , an issue is how to inform site Si (and all other sites) efficiently about it. Outdated REQUEST messages are distinguished from current REQUEST messages in the following manner: A REQUEST message of site Sj has the form REQUEST(j, n) where n (n=1, 2, ...) is a sequence number which indicates that site Sj is requesting its nth CS execution. A site Si keeps an array of integers RNi [1..N] where RNi [j] denotes the largest sequence number received in a REQUEST message so far from site Sj . When site Si receives a REQUEST(j, n) message, it sets RNi [j]:= max(RNi [j], n). Thus, when a site Si receives a REQUEST(j, n) message, the request is outdated if RNi [j]>n. Sites with outstanding requests for the CS are determined in the following manner: The token consists of a queue of requesting sites, Q, and an array of integers LN[1..N], where LN[j] is the 325

sequence number of the request which site Sj executed most recently. After executing its CS, a site Si updates LN[i]:=RNi [i] to indicate that its request corresponding to sequence number RNi [i] has been executed. Token array LN[1..N] permits a site to determine if a site has an outstanding request for the CS. Note that at site Si if RNi [j]=LN[j]+1, then site Sj is currently requesting token. After executing the CS, a site checks this condition for all the j’s to determine all the sites which are requesting the token and places their id’s in queue Q if these id’s are not already present in the Q. Finally, the site sends the token to the site whose id is at the head of the Q.

The Algorithm Requesting the critical section (a) If requesting site Si does not have the token, then it increments its sequence number, RNi [i], and sends a REQUEST(i, sn) message to all other sites. (‘sn’ is the updated value of RNi [i].) (b) When a site Sj receives this message, it sets RNj [i] to max(RNj [i], sn). If Sj has the idle token, then it sends the token to Si if RNj [i]=LN[i]+1. Executing the critical section (c) Site Si executes the CS after it has received the token. Releasing the critical section Having finished the execution of the CS, site Si takes the following actions: (d) It sets LN[i] element of the token array equal to RNi [i]. (e) For every site Sj whose id is not in the token queue, it appends its id to the token queue if RNi [j]=LN[j]+1. (f) If the token queue is nonempty after the above update, Si deletes the top site id from the token queue and sends the token to the site indicated by the id. Thus, after executing the CS, a site gives priority to other sites with outstanding requests for the CS (over its pending requests for the CS). Note that Suzuki-Kasami’s algorithm is not symmetric because a site retains the token even if it does not have a request for the CS, which is contrary to the spirit of Ricart and Agrawala’s definition of symmetric algorithm: “no site possesses the right to access its CS when it has not been requested”.

326

Correctness Mutual exclusion is guaranteed because there is only one token in the system and a site holds the token during the CS execution. Theorem: A requesting site enters the CS in finite time. Proof: Token request messages of a site Si reach other sites in finite time. Since one of these sites will have token in finite time, site Si ’s request will be placed in the token queue in finite time. Since there can be at most N − 1 requests in front of this request in the token queue, site Si will get the token and execute the CS in finite time. ✷

Performance Beauty of Suzuki-Kasami algorithm lies in its simplicity and efficiency. No message is needed and the synchronization delay is zero if a site holds the idle token at the time of its request. If a site does not hold the token when it makes a request, the algorithm requires N messages to obtain the token. Synchronization delay in this algorithm is 0 or T .

9.12 Raymond’s Tree-Based Algorithm Raymond’s Tree-Based mutual exclusion algorithm [19] uses a spanning tree of the computer network to reduce the number of messages exchanged per critical section execution. The algorithm exchanges only O(log N) messages under light load, and approximately four messages under heavy load to execute the CS, where N is the number of nodes in the network. The algorithm assumes that the underlying network guarantees message delivery. The time or order of message arrival cannot be predicted. All nodes of the network are ’completely reliable. (Only for the initial part of the discussion, i.e., until node failure is discussed.). If the network is viewed as a graph, where the nodes in the network are the vertices of the graph, and the links between nodes are the edges of the graph, a spanning tree of a network of N nodes will be a tree that contains all the N nodes. A minimal spanning tree is one such tree with minimum cost. Typically, this cost function is based on the network link characteristics. The algorithm operates on a minimal spanning tree of the network topology or a logical structure imposed on the network. The algorithm considers the network nodes to be arranged in an unrooted tree structure as shown in Figure 9.18. Messages between nodes traverse along the undirected edges of the tree in the Figure 9.18. The tree is also a spanning tree of the seven nodes A, B, C, D, E, F, and G. It also turns out to be a minimal spanning tree because it is the only spanning tree of these seven nodes. A node needs to hold information about and communicate only to its immediate-neighboring nodes. In Figure 9.18, for example, node C holds information about and communicates only to nodes B, D, and G; it does not need to know about the other nodes A, E, and F for the operation of the algorithm.

327

A

B

E

F

C

D

G

Figure 9.18: Nodes with an unrooted tree structure.

Similar to the concept of tokens used in token-based algorithms, this algorithm uses a concept of privilege to signify which node has the privilege to enter the critical section. Only one node can be in possession of the privilege (called the privileged node) at any time, except when the privilege is in transit from one node to another in the form of a PRIVILEGE message. When there are no nodes requesting for the privilege, it remains in possession of the node that last used it.

9.12.1 The HOLDER Variables Each node maintains a HOLDER variable that provides information about the placement of the privilege in relation to the node itself. A node stores in its HOLDER variable the identity of a node that it thinks has the privilege or leads to the node having the privilege. The HOLDER variables of all the nodes maintain directed paths from each node to the node in the possession of the privilege. For two nodes X and Y, if HOLDERX = Y, we could redraw the undirected edge between the nodes X and Y as a directed edge from X to Y. Thus, for instance, if node G holds the privilege, Figure 9.18 can be redrawn with logically directed edges as shown in the Figure. The shaded node above represents the privileged node. The following will be the values of the HOLDER variables of various nodes: HOLDERA = B (Since the privilege is located in a sub-tree of A denoted by B.) Proceeding with similar reasoning, we have HOLDERB = C HOLDERC = G HOLDERD = C HOLDERE = A HOLDERF = B HOLDERG = self Now suppose node B that does not hold the privilege wants to execute the critical section. Then B sends a REQUEST message to HOLDERB , i.e., C, which in turn forwards the REQUEST message to HOLDERC , i.e., G. So a series of REQUEST messages flow between the node making the request for the privilege and the node having the privilege. The privileged node G, if it no longer needs the privilege, sends the PRIVILEGE message to its 328

A

B

E

F

C

D

G

Figure 9.19: Tree with logically directed edges, all pointing in a direction towards node G - the privileged node.

A

B

C

E

F

G

D

Figure 9.20: Tree with logically directed edges, all pointing in a direction towards node G - the privileged node.

neighbor C, which made a request for the privilege, and resets HOLDERG to C. Node C, in turn, forwards the PRIVILEGE to node B, since it had requested the privilege on behalf of B. Node C also resets HOLDERC to B. The tree in Figure 9.19 will now look as in Figure 9.20 Thus, at any stage, except when the PRIVILEGE message is in transit, the HOLDER variables collectively make sure that directed paths are maintained from each of the N - 1 nodes to the privileged node in the network.

9.12.2 The Operation of the Algorithm Data Structures The algorithm requires each node to maintain the following variables:

Variable Name

Possible Values

HOLDER

Indicates the location of the “self” or the identity of one of privileged node in relation to the immediate neighbours. the current node. 329

Comments

Indicates if the current node is executing the critical section.

USING

True or false.

REQUEST_Q

The REQUEST_Q of a node A FIFO queue that could con- consists of the identities of tain “self” or the identities of those immediate neighbors immediate neighbors as ele- that have requested for privilege but have not yet been sent ments. the privilege .

ASKED

True or false.

Indicates if node has sent a request for the privilege.

The value “self” is placed in REQUEST_Q if the node makes a request for the privilege for its own use. The maximum size of REQUEST_Q of a node is the number of immediate neighbors + 1 (for “self”). ASKED prevents the sending of duplicate requests for privilege, and also makes sure that the REQUEST_Qs of the various nodes do not contain any duplicate elements.

The Algorithm The algorithm consists of the following parts: • ASSIGN_PRIVILEGE • MAKE_REQUEST • Events • Message Overtaking

ASSIGN_PRIVILEGE This is a routine to effect the sending of a PRIVILEGE message. A privileged node will send a PRIVILEGE message if • it holds the privilege but is not using it, • its REQUEST_Q is not empty, and • the element at the head of its REQUEST_Q is not “self.” That is, the oldest request for privilege must have come from another node. 330

A situation where “self” is at the head of REQUEST_Q may occur immediately after a node receives a PRIVILEGE message. The node will enter into the critical section after removing “self” from the head of REQUEST_Q. If the id of another node is at the head of REQUEST_Q, then it is removed from the queue and a PRIVILEGE message is sent to that node. Also, the variable ASKED is set to false since the currently privileged node will not have sent a request to the node (called HOLDER-to-be) which is about to receive the PRIVILEGE message.

MAKE_REQUEST This is a routine to effect the sending of a REQUEST message. An unprivileged node will send a REQUEST message if • it does not hold the privilege, • its REQUEST_Q is not empty, i.e., it requires the privilege for itself, or on behalf of one of its immediate neighboring nodes, and • it has not sent a REQUEST message already. The variable ASKED is set to true to reflect the sending of the REQUEST message. The MAKE_REQUEST routine makes no change to any other variables. The variable ASKED will be true at a node when it has sent REQUEST message to an immediate neighbor and has not received a response. The variable will be false otherwise. A node does not send any REQUEST messages, if ASKED is true at that node. Thus the variable ASKED makes sure that unnecessary REQUEST messages are not sent from the unprivileged node, and consequently ensures that the REQUEST_Q of an immediate neighbor does not contain duplicate entries of a neighbouring node. This makes the REQUEST_Q of any node bounded, even when operating under heavy load.

Events Below we show four events that constitute the algorithm.

Event

Algorithm Functionality

A node wishes to execute critical section.

Enqueue(REQUEST_Q, self); ASSIGN_PRIVILEGE; MAKE_REQUEST

A node receives a REQUEST message from Enqueue(REQUEST_Q, X); ASone of its immediate neighbors X. SIGN_PRIVILEGE; MAKE_REQUEST

331

A node receives a PRIVILEGE message.

HOLDER := self; ASSIGN_PRIVILEGE; MAKE_REQUEST

A node exits the critical section.

USING := false; ASSIGN_PRIVILEGE; MAKE_REQUEST

A node wishes critical section entry: If it is the privileged node, the node could enter the critical section using the ASSIGN_PRIVILEGE routine. If not, the node could send a REQUEST message using the MAKE_REQUEST routine in order to get the privilege. A node receives a REQUEST message from one of its immediate neighbors: If this node is the current HOLDER, it may send the PRIVILEGE to a requesting node using the ASSIGN_PRIVILEGE routine. If not, it could forward the request using the MAKE_REQUEST routine. A node receives a PRIVILEGE message: The ASSIGN_PRIVILEGE routine could result in the execution of the critical section at the node, or may forward the privilege to another node. After the privilege is forwarded, the MAKE_REQUEST routine could send a REQUEST message to reacquire the privilege, for a pending request at this node. A node exits the critical section: On exit from the critical section, this node may pass the privilege on to a requesting node using the ASSIGN_PRIVILEGE routine. It may then use the MAKE_REQUEST routine to get back the privilege, for a pending request at this node.

Message Overtaking This algorithm does away with the use of sequence numbers because of its inherent operation. The algorithm works such that message flow between any two neighboring nodes sticks to a logical pattern as shown in the Figure 9.21. If at all message overtaking occurs between the nodes A and B, it can occur when a PRIVILEGE message is sent from node A to node B, which is then very closely followed by a REQUEST message from node A to node B. In other words, node A sends the privilege and immediately wants it back. Such message overtaking as described above will not affect the operation of the algorithm. If node B receives the REQUEST message from node A before receiving the PRIVILEGE message from node A, A’s request will be queued in REQUEST_QB . Since B is not a privileged node, it will not be able to send a privilege to node A in reply. When node B receives the PRIVILEGE message from A after receiving the REQUEST message, it could enter the critical section or could send a PRIVILEGE message to an immediate neighbor at the head of REQUEST_QB , which need not be node A. So message overtaking does not affect the algorithm.

332

Node A Node A Node A Node A

−−−− REQUEST −−−−> 240KP a) ∧ (T emperatureb > 300◦ C) should the two reagents be mixed. As another example, consider a distributed execution where variables x, y, and z are local to processes Pi , Pj and Pk , respectively. An application might be interested in detecting the predicate ψ2 = xi + yj + zk < −125. In a nuclear power plant, sensors at various locations would monitor the relevant parameters such as the radioactivity level and temperature at multiple locations within the reactor. Observe that the “predicate detection” problem is inherently different from the global snapshot problem. A global snapshot gives one of the possible states that could have existed during the period of the snapshot execution. Thus, a snapshot algorithm can observe only one of the predicate values that could have existed during the algorithm execution. Predicates can be either stable or unstable. A stable predicate is a predicate that remains true φ, where “ ” is once it becomes true. In traditional systems, a predicate φ is stable if φ = : the “henceforth” operator from temporal logic. In distributed executions, a more precise definition is needed, due to the absence of global time. Formally, a predicate φ at a cut C is stable if the following holds. (C |= φ) = : (∀C ′ | C ⊆ C ′ , C ′ |= φ) Deadlock in a system is a stable property because the deadlocked processes continue to remain deadlocked (until deadlock resolution is performed). Termination of an execution is another stable property. Specific algorithms to detect termination of the execution, and to detect deadlock will be considered in separate chapters. Here, we look at a general technique to detect a stable predicate.

370

11.1.1 Stable Predicates Deadlock: A deadlock represents a system state where a subset of the processes are blocked on one another, waiting for a reply from the other processes in that subset. The waiting relationship is represented by a Wait-For Graph (WFG) where an edge from i to j indicates that process i is waiting for a reply from process j. Given a Wait-For Graph G = (V, E), a deadlock is a subgraph G′ = (V ′ , E ′ ) such that V ′ ⊆ V and E ′ ⊆ E and for each process i in V ′ , the process i remains blocked unless it receives a reply from some process(es) in V ′ . There are two conditions that characterize the deadlock state of the execution. • (local condition:) each deadlocked process is locally blocked, and • (global condition:) the deadlocked process will not receive a reply from some process(es) in V ′. Termination: Termination of an execution is another stable property, and is best understood by viewing a process as alternating between two states: active state and passive state. An active process spontaneously becomes passive when it has no further work to do; a passive process can become active only when it receives a message from some other process. If such a message arrives, then the process becomes active by doing CPU processing and maybe sending messages as a result of the processing. An execution is terminated if each process is passive, and will not become active unless it receives more messages. There are two conditions that characterize the termination state of the execution. • (local condition:) each process is in passive state, and • (global condition:) there is no message in transit between any pair of processes. Generalizing from the above two most frequently encountered stable properties, we assume that each stable property can be characterized by a local process state component, and a channel component or a global component. Recall from our discussion of global snapshots that any channel property can be observed by observing the local states at the two endpoints of the channel, in a consistent manner. Thus, any global condition can be observed by observing the local states of the processes. We now address the question: “What are the most effective techniques for detecting a stable property?” Clearly, repeatedly or periodically taking a global snapshot will work; if the property is true in some snapshot, then it can be claimed that the property is henceforth true. However, recording a snapshot is expensive; recall that it can require up to O(n2 ) control messages without inhibition, or O(n) messages with inhibition. The approach that has been widely adopted is the two-phase approach of observing potentially inconsistent global states. In each state observation, all the local variables necessary for defining the local conditions, as well as the global conditions, are observed. Two potentially inconsistent global states are recorded consecutively, such that the second recording is initiated after the first recording has completed. This is illustrated in Figure 11.1. The stable property can be declared to be true if the following holds. 371

P1 P 2 time

P n−1 Pn phase 1

phase 2

event at which local variables are sampled

Figure 11.1: Two-phase detection of a stable property. If the values of the relevant local variables that capture the property have not changed between the two phases, then the stable property is true. • The variables on which the local conditions as well as the global conditions are defined have not changed in the two observations, as well as between the two observations. If none of the variables changes between the two observations, it can be claimed that after the termination of the first observation and before the start of the second observation, there is an instant in physical time when the variables still have the same value. Even though the two observations are each inconsistent, if the global property is true at a common physical time, then the stable property will necessarily be true. The most common ways of taking a pair of consecutive, not necessarily consistent, snapshots using O(n) control messages are as follows. • Each process randomly records its state variables and sends them to a central process via control messages. When the central process receives this message from each other process, the central process informs each other process to send its (uncoordinated) local state again. • A token is passed around a ring, and each process appends its local state to the contents of the token. When the token reaches the initiator, it passes the token around for a second time. Each process again appends its local state to the contents of the token. • On a predefined spanning tree, the root (coordinator) sends a query message in the fan-out sweep of the tree broadcast. In the fan-in sweep of the ensuing tree convergecast, each node collects the local states of the nodes in its subtree rooted at itself and forwards it to its parent. When the root gets the local states from all the nodes in its tree, the first phase completes. The second phase, which contains another broadcast followed by a convergecast, is initiated.

11.1.2 Unstable Predicates An unstable predicate is a predicate that is not stable and hence may hold only intermittently. The following are some of the several challenges in detecting unstable predicates. 372

• Due to unpredictable message propagation times, and unpredictable scheduling of the various processes on the processors under various load conditions, even for deterministic executions, multiple executions of the same distributed program may pass through different global states. Further, the predicate may be true in some executions and false in others. • Due to the nonavailability of instantaneous time in a distributed system, – Even if a monitor finds the predicate to be true in a global state, it may not have actually held in the execution. – Even if a predicate is true for a transient period, it may not be detected by intermittent monitoring. Hence, periodic monitoring of the execution is not adequate. These challenges are faced by snapshot-based algorithms as well as by a central monitor that evaluates data collected from the monitored processes. To address these challenges, we can make two important observations. • It seems necessary to examine all the states that arise in the execution, so as not to miss the predicate being true. Hence, it seems useful to define predicates, not on individual states, but on the observation of the entire execution. • For the same distributed program, even given that it is deterministic, multiple observations may pass through different global states. Further, a predicate may be true in some of the program observations but not in others. Hence it is more useful to define the predicates on all the observations of the distributed program and not just on a single observation of it.

11.2 Modalities on Predicates To address the above complications, predicates are defined, not on global states or on an individual observation of an execution, but on all the possible observations of the distributed execution. The following two modalities on any predicate φ are defined. • P ossibly(φ): There exists a consistent observation of the execution such that predicate φ holds in a global state of the observation. • Def initely(φ): For every consistent observation of the execution, there exists a global state of it in which predicate φ holds. Consider the example in Figure 11.2(a). The execution is run at processes P1 and P2 . Event eki denotes the k th event at process Pi . Variable a is local to P1 and variable b is local to P2 . The state lattice for the execution is shown in Figure 11.2(b). Each state is labeled by a tuple (c1 , c2 ), where c1 and c2 are the event counts at P1 and P2 , respectively. The execution shown in part (a) goes through the following sequence of global states, and events causing the state transitions between 373

the global states. (0, 0), e12, (0, 1), e11 , (1, 1), e22, (1, 2), e21, (2, 2), e32, (2, 3), e42, (2, 4), e31 , (3, 4), e41, (4, 4), e52, (4, 5), e51, (5, 5), e61, (6, 5), e62 , (6, 6), e72, (6, 7) When the same distributed program is run again, observe that it may not pass through the same intermediate states as the state transitions from the initial state (0,0) to the final state (6,7). • Now observe that Def initely(a + b = 10) holds by the following reasoning. When b is assigned 7 at event e12 , process P1 ’s execution may be in any state from the initial state up to the state preceding event e31 , in which a = 3. However, before the value of b changes from 7 to 5 at event e42 , and in fact before P2 executes event e32 , P1 must have executed event e11 at which time a = 3. This is true for all equivalent executions. Hence, Def initely(a + b = 10) holds. With respect to the state lattice in Figure 11.2(b), the states in which a + b = 10 is true are marked therein. From the state lattice, it can be seen that in every execution, the state (2,2) must occur, and in this state a + b = 10. • Observe that P ossibly(a + b = 5) holds by the following reasoning. The predicate a + b = 5 can be true only if: (i) a = 3 ∧ b = 2, which is true in states (2,5) and (3,5), or (ii) a = 0 ∧ b = 5, which is true in state (6,4), or (iii) a = 8 ∧ b = −3 which is true in state (5,7), in some equivalent execution. State (i) is possible in physical time after the occurrence of event e52 and before the occurrence of e41 . In the execution shown, e52 occurs after e41 . However, in an equivalent execution, event e41 may be delayed to occur after event e52 , in which case b changes to a value other than 2 after a becomes 8. Hence, the predicate is true in this equivalent execution. It so happens that a similar argument also holds for (ii) and (iii). • Predicate Def initely(a + b = 5) is not true in the shown execution because there exists at least one path through the state lattice such that a + b = 5 is never true in any state along that path.

11.2.1 Complexity of Predicate Detection As we suspect from the examples in this section, the predicate detection problem is complex. For n processes and a maximum of m events per process, we need to examine up to an exponential number mn states. The global predicate detection problem can be readily shown to be NP-complete using a standard reduction from the satisfiability problem (see Exercise 4).

11.3 Centralized Algorithm for Relational Predicates To detect predicates, we first assume that the state lattice is available. A global state GS = {sk11 , sk22 , . . . , sknn } is abbreviated as GS k1 ,k2 ,...kn .

374

initial state

0,0

1,0 2,0

0,1 1,1

2,1 P

1

2

1

p

4

3

e1 e1

e1

e1

5

e1

2

a=3

a=8

4,3

5,2

time

5,3

6,2

b=5

b=2

a+b = 5

p

2

1 e2

2 e2

3 e2

4 e2

5 e2

6 e2

(a)

7 e2

4,4

6,4

b = −3

2,4 2,5

3,4

5,4

6,3

local var. b = 7

3,3

4,2

a=0

a+b = 10 2,3

3,2

6

1

local var.

1,2 2,2

P

e1

0,2

3,5 4,5

5,5 6,5

a+b = 10 5,6

6,6 final state (b)

a+b = 5

a+b = 5 5,7

6,7

Figure 11.2: Example to illustrate P ossibly(φ) and Def initely(φ). (a) The example execution. (b) The state lattice for the execution. Each label in the lattice gives the event count at P1 , P2 . • P ossibly(φ): To detect P ossibly(φ), an exhaustive search of the state lattice for any one state that satisfies φ needs to be done. The search can terminate as soon as such a state is found. Presumably, there is particular interest in finding the ‘earliest’ state that satisfies φ. P The level of a global state hski i (∀i)i is i=n i=1 ki . The algorithm in Figure 11.3 examines the state lattice level-by-level, beginning from the initial state at level 0 and going to the final state. Each level is examined to find a state in which φ is true. If such a state is found, the algorithm terminates. • Def initely(φ): For Def initely(φ) to be true, there should exist a set of states satisfying φ such that every path through the lattice goes through one of these states. It is sufficient but not necessary that all the states at any particular level in the lattice satisfy φ. To see this, consider the execution in Figure 11.4. Here, Def initely(φ) is true, yet the states satisfying φ are at different levels. As Def initely(φ) may be true but there may not exist any level in which all the states satisfy φ, the algorithm in Figure 11.3 to detect Def initely(φ) cannot use an approach similar to that used for P ossibly(φ). In particular, replacing the loop condition in line (1a) by the following will not work: “(some state in Reachφ satisfies ¬φ)”. The algorithm examines the state lattice level-by-level but differs in the following two respects. 1. Rather than track the states (at a level) in which φ is true, it tracks the states in which φ is not true.

375

(variables) set of global states Reachφ , Reach_Nextφ ←− {GC 0,0,...0 } int lvl ←− 0 (1) P ossibly(φ) (1a) while (no state in Reachφ satisfies φ) do (1b) if (Reachφ = {f inal state}) then return false; (1c) lvl ←− lvl + 1; (1d) Reachφ ←− {states at level lvl }; (1e) return true. (2) Def initely(φ) (2a) remove from Reachφ those states that satisfy φ (2b) lvl ←− lvl + 1; (2c) while (Reachφ 6= ∅) do (2d) Reach_Nextφ ←− {states of level lvl reachable from a state in Reachφ }; (2e) remove from Reach_Nextφ all the states satisfying φ; (2f) if Reach_Nextφ = {final state} then return false; (2g) lvl ←− lvl + 1; (2h) Reachφ ←− Reach_Nextφ ; (2i) return true. Figure 11.3: Detecting a relational predicate by examining the state lattice (on-line, centralized). 2. Additionally, the set of states tracked at a level have to be reachable from the set of those states at the previous level, that are known to satisfy (1) and recursively this same property (2). The variable Reach_Nextφ is used to track such states at level lvl, as constructed from the states at the previous level. Thus, Reach_Nextφ at level lvl contains the set of states at level lvl that are reachable from the initial state without passing through any state satisfying φ. The algorithm terminates successfully when Reach_Nextφ becomes the empty set; otherwise it terminates unsuccessfully after examining the final state. Example: Figure 11.4 shows an example execution and the corresponding state lattice. The states belonging to Reach_Nextφ (line (2d)) at any level are either marked by shaded circles or clear circles. The states belonging to Reach_Nextφ (line (2f)) at any level are marked by clear circles. In line (2b), when lvl = 11, Reachφ becomes ∅ and the algorithm exits from the loop. The centralized algorithms in Figure 11.3 assumed that the states at any level were readily available. But in an on-line algorithm, these global states need to be assembled from local states, on the fly. How can that be accomplished? Each process Pi can send a local trace of its local states ski i , with their vector timestamps, to the central process P0 . P0 maintains n queues, Q1 . . . Qn , for 376

state lattice labelled using event numbers 0,0 initial

1 0 01,1 1 0 1 02,2 1 1 0 0 2,3 0 1 1 03,2 1 0 1 04,2 1 0 1 0 1 3,4 0 1 05,2 1

P

e11 e12

e13

e14

e15

e 17 e18

e16

1

P2

e21

e22

e23

e24

e25

e26

e27

1 0 0 04,6 1 1 05,5 1 0 1 0 06,5 1 1 07,4 1 p2

p1

levels 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

1 0 0state in which predicate is true 1

state reachable without predicate being true (b)

(a)

Figure 11.4: Example to show that states in which Def initely(φ) is satisfied need not be at the same level in the state lattice. (a) Execution. (b) Corresponding state lattice.

P 1

X1

X2

Y1

Y2 Y3

X2 X1 Q 1 Q Y4 Y3 Y2 Y1 2

Y4

P 2 P n

Z1

Z1

Q

n

Figure 11.5: Queues Q1 to Qn for each of the n processes. the events of each of the processes, as shown in Figure 11.5. Each local state received from process Pi is enqueued in Qi . As global state GS = {sk11 , sk22 , . . . , sknn }, also abbreviated as GS k1 ,k2 ,...kn , is assembled from the corresponding local states, how long does a local state need to be kept in its queue? This is answered by the following two observations, based on the vector clocks. k1 ,k2 ,...kn containing ski i is identified as follows. The j th compo• The earliest global state GSmin k nent of V C(ski i ) is the local value of Pj in its local snapshot state sj j . This is expressed as Equation 11.1. k (∀j) V C(sj j )[j] = V C(ski i )[j] (11.1)

It now follows that the lowest level of the state lattice, in which local state ski i (k th local state of Pi ) participates, is the sum of the components of V C(ski i ) – this assumes that in the vector clock operation, the local component is incremented by one for each local event. 377

k1 ,k2 ,...kn containing ski i is identified as follows. The ith component • The latest global state GSmax k of V C(sj j ) should be the largest possible value but cannot exceed or equal V C(ski i )[i] for k consistency of the two states ski i and sj j . V C(ski i ) is identified as per Equation 11.2; but note k +1 k that the condition on V C(sj j )[i] is applicable if sj j is the last state at Pj . k

k +1

(∀j) V C(sj j )[i] < V C(ski i )[i] ≤ V C(sj j

)[i]

(11.2)

P k Hence, the highest level of the state lattice, in which local state ski i participates, is nj=1 V C(sj j [j]) subject to the above equation. P P k From Expressions 11.1 and 11.2, we have that nj=1 V C(ski i )[j] is the lowest level, and nj=1 V C(sj j [j]), k

where sj j ∈ GSmax , is the highest level, between which local state ski i is useful in constructing a global state. Given the states of level lvl, the set of states at level lvl + 1 can be constructed as follows. For each state GS k1 ,k2 ,...,kn , construct the n global states GS k1 +1,k2 ,...,kn , GS k1 ,k2 +1,...,kn . . . GS k1 ,k2 ,...,kn +1 . Deterministic versus nondeterministic programs: We need to remember that the entire analysis of predicates, their modalities, and detection algorithms, applies only to deterministic programs. For non-deterministic programs, different executions may have different partial orders.

11.4 Conjunctive Predicates The predicates considered so far are termed relational predicates because the predicate can be an arbitrary relation on the variables in the system. A predicate φ is a conjunctive predicate if and only if φ can be expressed as the conjunction ∧i∈N φi , where φi is a predicate local to process i. For a wide range of applications, the predicate of interest can be modeled as a conjunctive predicate. Conjunctive predicates have the following property. • If φ is false in any cut C, then there is at least one process i such that the local state of i in cut C will never form part of any other cut C ′ such that φ is true in C ′ . More formally, a conjunctive predicate φ is defined as the following. C 6|= φ = :∃i ∈ N, ∀C ′ ∈ Cuts, C ′ 6|= φ, where C ′ [i] = C[i] Here, the state C[i] is a forbidden state because it will never form part of any cut that satisfies the predicate. Given a conjunctive predicate, if it is evaluated as false in some cut C, we can advance the local state of at least one process to the next event, and then evaluate the predicate in the resulting cut. This gives a O(mn) time algorithm, where m is the number of events at any process, to check for a linear predicate, as opposed to an exponential algorithm. Consider the following example on modalities on conjunctive predicates, shown for the same execution considered earlier in Figure 11.2. 378

P 0 P 1 P 2 Figure 11.6: For a conjunctive predicate, the shaded durations indicate the periods when the local predicates are true. • The predicate P ossibly(a = 3 ∧ b = 2) holds by the following reasoning. The predicate can be true only if a = 3 ∧ b = 2 simultaneously in the execution. This is possible in physical time after the occurrence of event e52 and before the occurrence of e41 . In the execution shown, e41 occurs before e52 . However, in an equivalent execution, event e41 may be delayed to occur after event e52 , in which case, a changes to a value other than 3 after b becomes 2. Hence, P ossibly(a = 3 ∧ b = 2) is true. Note that in Figure 11.2, a + b = 5 was true in states (2, 5), (3, 5), (6, 4), and (5, 7). Among these, a = 3 ∧ b = 2 is true only in (2, 5) and (3, 5). • Def initely(a = 3 ∧ b = 7) holds by the following reasoning. When a is assigned 3 at event e11 , process P2 ’s execution may be at any event from e02 up to but not including e32 . However, before the value of a changes from 3 to 8 at event e41 , P2 must have executed event e22 at which time b = 7. This is true for all equivalent executions. Note that in Figure 11.2, a + b = 10 was true in states (1, 1), (2, 1), (1, 2), (2, 2), (3, 2), (2, 3), (3, 3), (4, 5), (5, 5), and (5, 6). Among these, a = 3 ∧ b = 7 is true only in all except (4, 5), (5, 5), and (5, 6).

11.4.1 Interval-based Centralized Algorithm for Conjunctive Predicates Conjunctive predicates are a popular class of predicates. Conjunctive predicates have the advantage that each process can locally determine whether the local component φi is satisfied; if not, the local state cannot be part of any global state satisfying φ! This has the following implication: starting with the initial state, we examine global states. If φ is not satisfied, then the local state of at least one process can be advanced and the next global state is examined. Either φ is satisfied, or we repeat the step. Within mn steps, we will have examined all necessary global states, giving a O(mn) upper bound on the time complexity. There are two broad approaches to detecting conjunctive predicates: the global state based approach, and the interval based approach. The global state based approach involves examining the global states, as suggested above and also seen in the previous Section 11.3. In the interval-based approach, each process identifies alternating time durations when the local predicate alternates between true and false. This is illustrated in Figure 11.6. Let us consider any two processes Pi and Pj , and let the intervals at these processes when the local predicates φi and φj are true be denoted Xi and Yj , respectively. Let the start and end of an interval X be 379

min(X)

X

min(Y)

max(X)

Y

min(X)

max(Y)

X

max(X)

min(Y)

(a) Definitely(phi)

Y

max(Y)

(b) Possibly(phi)

Figure 11.7: Illustrating conditions for Def initely(φ) and ¬P ossibly(φ), for two processes. denoted as min(X) and max(X), respectively. Assume the global predicate is defined on these two processes. We can observe the following definitions of Def initely(φ) and P ossibly(φ) with the aid of Figure 11.7. ^ Def initely(φ) : min(X) ≺ max(Y ) min(Y ) ≺ max(X) _ P ossibly(φ) : max(X) ≺ min(Y ) max(Y ) ≺ min(X)

(11.3)

Def initely(φ) if and only if ∧i,j∈N Def initely(φi ∧ φj )

(11.5)

P ossibly(φ) if and only if ∧i,j∈N P ossibly(φi ∧ φj )

(11.6)

(11.4)

When the global predicate is defined on more than two processes, the following results for P ossibly and Def initely are expressible in terms of P ossibly and Def initely for pairs of processes. The results can be observed to be true with the help of Figure 11.6.

Figure 11.8 gives an algorithm that is run by a central server P0 to detect P ossibly(φ) or Def initely(φ) for a conjunctive predicate φ. Whenever an interval completes, a process could send the vector timestamp of the start and of the end events of that interval as a Log entry to the central server process. But observe that for any two local intervals Y and Y ′ , if there is no send or receive event between the start of the previous interval and the end of the latter interval, then Y and Y ′ have the exact same relation with respect to all other intervals at all other processes. Hence, an interval needs to be sent to P0 if there is a send or receive event since the start of the previous interval and the end of this interval. Each execution message thus causes at most four control messages to P0 – two at the sender and two at the receiver. The algorithm uses two queues, updatedQueues and newUpdatedQueues. The updatedQueues stores the indices of all the queues whose heads got updated. The latter is a temporary variable for updating updatedQueues. A queue gets updated when a new interval potentially becomes the head of the queue; such a new interval becomes a ‘candidate’ interval for the solution. A queue gets updated under two situations: (1) a new interval is enqueued on to an empty queue, or (2) the current head of a queue gets deleted because it is determined that it cannot possibly be a part of the solution. Each new candidate interval (i.e., head of some queue) is examined with respect to the 380

heads of all other queues, in accordance with Equations 11.3 and 11.4, to determine if the desired modality is satisfied. In each comparison, if the desired modality is not satisfied, one of the two intervals examined is marked for deletion (and the corresponding queue is said to be updated). • Specifically, statements (12)-(15) can be used to check for Def initely(φ) in accordance with Equation 11.3. • Statements (12’)-(15’) can be used to check for P ossibly(φ) in accordance with Equation 11.4. The set updatedQueues stores the indices of all the queues whose heads get updated. In each iteration of the while loop, the index of each queue whose head is updated is stored in set newUpdatedQueues (lines (12)-(15) or (12’)-(15’)). In lines (16) and (17), the heads of all these queues are deleted and indices of the updated queues are stored in the set updatedQueues. Thus, an interval gets deleted only if it cannot be part of the solution. Now observe that each interval gets processed unless a solution is found using an interval from each process. From Equations 11.5 and 11.6, if every queue is non-empty and their heads cannot be pruned, then a solution exists and the set of intervals at the head of each queue forms a solution. Termination: If a solution exists, it is eventually detected by lines (18)-(19). Otherwise, P0 waits to receive an interval from some process. The code can be modified to detect the end of the execution at a process, and to notify P0 about it. Complexity: Let p be the number of intervals per process, and M be the number of messages sent in the execution. Message complexity: The number of control messages sent by the n processes to P0 is min(pn, 4M). The first term denotes a message being sent for each interval completed. The second term denotes that at most 4 control messages get sent for each execution message, in accordance with the observation made earlier. Each control message contains the vector timestamp, which has size n integers. Space complexity: The space complexity at P0 is min(pn, 4M) · 2n because all the intervals may have to be queued up among the queues Q1 , . . . Qn . Time complexity: When an interval is compared with others (loop in lines (9)-(15)), there are O(n) steps in one such comparison. Any interval participates in at most n such comparisons before at least one interval gets deleted (otherwise a solution is found). As there are min(pn, 4M) · 2n intervals, the time complexity is O(n · n · (min(pn, 4M))).

11.4.2 Global State based Centralized Algorithm for P ossibly(φ), where φ is conjunctive A more efficient algorithm to detect P ossibly(φ) than the generic algorithm in Figure 11.8 can be devised by tailoring an algorithm to this specific modality. 381

queue of Log: Q1 , Q2 , . . . Qn ⇐=⊥ set of int: updatedQueues, newU pdatedQueues ⇐= {} On receiving interval from process Pz at P0 : (1) Enqueue the interval onto queue Qz (2) if (number of intervals on Qz is 1) then (3) updatedQueues ←− {z} (4) while (updatedQueues is not empty) (5) newU pdatedQueues ←− {} (6) for each i ∈ updatedQueues (7) if (Qi is non-empty) then (8) X ←− head of Qi (9) for j = 1 to n (10) if (Qj is non-empty) then (11) Y ←− head of Qj (12) if (min(X) 6≺ max(Y )) then // Def initely (13) newU pdatedQueues ←− {j} ∪ newU pdatedQueues (14) if (min(Y ) 6≺ max(X)) then // Def initely (15) newU pdatedQueues ←− {i} ∪ newU pdatedQueues (12’) if (max(X) ≺ min(Y )) then // P ossibly (13’) newU pdatedQueues ←− {i} ∪ newU pdatedQueues (14’) if (max(Y ) ≺ min(X)) then // P ossibly (15’) newU pdatedQueues ←− {j} ∪ newU pdatedQueues (16) Delete heads of all Qk where k ∈ newU pdatedQueues (17) updatedQueues ←− newU pdatedQueues (18) if (all queues are non-empty) then (19) solution found. Heads of queues identify intervals that form the solution.

Figure 11.8: Detecting a conjunctive predicate (centralized, on-line) for P ossibly or Def initely modality. For Def initely(φ), lines (12)-(15) are executed. For P ossibly(φ), lines (12’)-(15’) are executed. To detect both, disjoint data structures are required. Observe that P ossibly(φ) holds if and only if there is a consistent global state in the execution in which φ holds. Thus, detecting P ossibly(φ) is equivalent to identifying a consistent global state in which the local state at each process Pi satisfies φi . In this consistent global state, for any two local states si and sj at Pi and Pj , respectively, the following must hold. (mutually concurrent) ∀i, ∀j, si 6≺ sj ∧ sj 6≺ si

(11.7)

Each process Pi sends the vector timestamp of the local state when φi becomes true, to the server process P0 . In fact, such a message needs to be sent only each time that the local predicate becomes true for the first time since the previous communication event. This is because internal events that are not separated by communication events are equivalent in terms of consistent global states. The algorithm in Figure 11.9 tracks the most recent global state that can potentially satisfy P ossibly(φ) using a two-dimensional array GS[1 . . . n, 1 . . . n], where row GS[i] stores the vector timestamp of 382

type Log start: array[1 . . . .n] of integer; end: array[1 . . . .n] of integer; array of integer: GS[1 . . . n, 1 . . . n]; //ith row tracks vector time of Pi array of boolean: V alid[1 . . . n]; //V alid[j] = 0 implies Pj state GS[j, ·] needs to be advanced queue of Log: Q1 , Q2 , . . . Qn ←−⊥; //Qi stores timestamp info from Pi (1) while (∃j | V alid[j] = 0) do //Pj ’s state GS[j, ·] is not consistent with others (2) if (Qj =⊥ and Pj has terminated) then (3) return(0); (4) else (5) await Qj becomes non-empty; (6) GS[j, 1 . . . n] ←− head(Qj ); //Consider next state of Pj for consistency (7) dequeue(head(Qj )); (8) V alid[j] ←− 1; (9) for k = 1 to n do //Check Pj ’s state w.r.t. Pk ’s state (for every Pk ) (10) if k 6= j and V alid[k] = 1 then (11) if GS[j, j] ≤ GS[k, j] then //Pj ’s state is inconsistent with Pk ’s state (12) V alid[j] ←− 0; //next state of Pj needs to be considered (13) else if GS[k, k] ≤ GS[j, k] then //Pk ’s state is inconsistent with Pj ’s state (14) V alid[k] ←− 0; //next state of Pk needs to be considered (15) return(1).

Figure 11.9: Global state based detection of a conjunctive predicate (centralized, on-line, P ossibly). the local state of process Pi . At P0 , the queuing of the vector timestamps received from Pi into Qi is not shown explicitly. The algorithm run by P0 picks any process Pj such that V alid[j] = 0 and dequeues the head of Qj for consideration of consistency with respect to the current states of all processes (lines (6-8)). The main check is in lines (9)-(14) where Pj ’s state is checked for mutual consistency with Pk ’s state, for all k. • If Pj ’s state is old and hence causes inconsistency, it is marked as invalid (lines (11-12)). See Fig 11.10(a). • If Pk ’s state is old and hence causes inconsistency, it is marked as invalid (lines (13-14)). See Fig 11.10(b). After this main check, the algorithm continues in the main while loop and picks another process Pj such that V alid[j] = 0. A consistent state is detected when V alid[j] = 1 for all j. Termination: The algorithm terminates successfully (line (15)) if V alid[j] = 1 for all j, indicating a solution is found. It terminates unsuccessfully (line (3)) if some process terminates and its queue is empty.

383

GS[j,j]

GS[j,k]

j k GS[k,j]

GS[k,k]

(a)

(b)

Figure 11.10: In the algorithm of Figure 11.9, P0 tests whether Pj ’s and Pk ’s candidate local states are consistent with each other. (a) Pj ’s old state is invalid. (b) Pk ’s old state is invalid. Complexity: Let m be the number of local states at any process. Let M denote the total number of messages sent in the execution. Time complexity: As there are at most mn local states that are processed by P0 , and for each such local state, the for loop in line (9) is invoked once and requires 2n integer comparisons, the time complexity of the algorithm is O(n2 m). Space complexity: The space complexity at P0 is O(n2 m) because there are at most mn states, each represented as a vector timestamp, that can be queued among the n queues Q1 to Qn . Message complexity: The number of control messages sent by the n processes to P0 is 2M, and each message contains the vector timestamp, which has size n integers.

11.5 Distributed Algorithms for Conjunctive Predicates 11.5.1 Distributed State-based Token Algorithm for P ossibly(φ), where φ is Conjunctive The algorithm in Figure 11.11 is a distributed version of the algorithm in Figure 11.9. Each queue Qi is maintained locally at Pi . The data structure GS no longer needs to be a n×n array. Instead, a unique token is passed among the processes serially. The token carries a vector GS corresponding to the vector timestamp of the earliest global state under consideration as a candidate solution. A process Pi receives a token only when T oken.V alid[i] = 0. All local states of Pi up to T oken.GS[i] will necessarily be not consistent with the earliest possible candidate local state of some other process. So Pi has to now consider from its local queue Qi , the first local state with timestamp greater than T oken.GS[i] (lines (3)-(6)). Based on such a state of Pi , now written to T oken.GS[i] in line (4), for each j, Pi now determines in line (8) whether Pj ’s candidate local state T oken.GS[j] is consistent with T oken.GS[i]. This test is illustrated in Figure 11.12. • If the condition in line (8) is true (Figure 11.12(a)), Pj ’s state is not consistent. T oken.V alid[j] is reset. This implies that the token must visit Pj before termination of the algorithm and Pj needs to find a local state that is mutually consistent with all the other states in T oken.GS. 384

struct token { array of integer: GS[1 . . . n]; //Earliest possible global state as a candidate solution array of boolean: V alid[1 . . . n]; }T oken; //V alid[j] = 0 indicates Pj ’s state GS[j] is invalid queue of Log: Qi ←−⊥ Initialization. T oken is at a randomly chosen process. On receiving T oken at Pi (1) while (T oken.V alid[i] = 0) do // T oken.GS[i] is the latest state of Pi known to be inconsistent (2) await (Qi to be nonempty); //with other candidate local state of Pj , for some j (3) if ((head(Qi ))[i] > T oken.GS[i]) then (4) T oken.GS[i] ←− (head(Qi ))[i]; // earliest possible state of Pi that can be part of solution (5) T oken.V alid[i] ←− 1; //is written to T oken and its validity is set. (6) else dequeue head(Qi ); (7) for j = 1 to n (j 6= i) do // for each other process Pj : based on Pi ’s local state, determine whether (8) if j 6= i and (head(Qi ))[j] ≥ T oken.GS[j] then // Pj ’s candidate local state (in T oken) (9) T oken.GS[j] ←− (head(Qi ))[j]; // is consistent. If not, Pj needs to consider a (10) T oken.V alid[j] ←− 0; // later candidate state with a timestamp > head((Qi )[j] (11) dequeue head(Qi ); (12) if for some k, T oken.V alid[k] = 0 then (13) send T oken to Pk ; (14) else return(1).

Figure 11.11: Global state based detection of a conjunctive predicate (distributed, on-line, P ossibly). Code shown is for Pi , 1 ≤ i ≤ n. • If the condition in line (8) is false (Figure 11.12(b)), Pj ’s state is consistent. Termination: The algorithm finds a solution when T oken.V alid[j] is 1, for all j (line (14)). If a solution is not found, the code hangs in line (2). The code can be modified to terminate unsuccessfully in line (2) by modeling an explicit ‘process terminated’ state in this case. Complexity: Time complexity: Each time a token is received by Pi , at least one local state is examined and deleted. This involves O(n) comparisons in the main loop (lines (7)-(10)). Assuming a total of m states at a process, the time overhead at a process is O(mn). The time overhead across processes is cumulative as the token travels serially. Hence, total time complexity is O(mn2 ). Space complexity: In the worst case, all the local states may get queued in Qi , leading to a space requirement of O(mn). Across all processes the space requirement becomes O(mn2 ). Message complexity: The token makes O(mn) hops, and the size of the token is 2n integers.

385

head(Q_i)

head(Q_i)

i j

Token.GS[j] head(Q_i)[j]

head(Q_i)[j]

(a)

Token.GS[j]

(b)

Figure 11.12: In the algorithm of Figure 11.11, Pi tests whether Pj ’s candidate local state T oken.GS[j] is consistent with head(Qi )[i], which is assigned to T oken.GS[i]. The two possibilities are illustrated. (a) Not consistent. (b) Consistent.

11.5.2 Distributed Interval-based Token Algorithm for Def initely(φ), where φ is Conjunctive We now study an interval-based distributed token-based algorithm to detect Def initely(φ) based on the tests in Equations 11.3 and 11.5. Define Ii ֒→ Ij as: min(Ii ) ≺ max(Ij ). Problem Statement. In a distributed execution, identify a set of intervals I containing one interval from each process, such that (i) the local predicate φi is true in Ii ∈ I, and (ii) for each pair of processes Pi and Pj , Def initely(φi,j ) holds, i.e., Ii ֒→ Ij and Ij ֒→ Ii . The algorithm is given in Figure 11.14. The vector timestamps of the start of and of the end of an interval form a data type Log, as shown in Figure 11.13. When an interval completes at process Pi , the interval’s Log is added to a local queue Qi selectively, as shown in Figure 11.13. An interval Y at Pj is deleted if on comparison with some interval X on Pi , X 6֒→ Y , i.e., Vi (min(X))[i] 6≤ Vj (max(Y ))[i]. Thus the interval (Y ) being deleted or retained depends on its value of Vj (max(Y ))[i]. The value Vj (max(Y ))[i] changes only when a message is received. Hence an interval needs to be stored only if a receive has occurred since the last time a Log of a local interval was queued. The token-based algorithm uses three types of messages (see Figure 11.14) that are sent among the processes. Request messages of type REQUEST , reply messages of type REP LY , and token messages of type T OKEN, are denoted REQ, REP , and T , respectively. Only the token-holder process can send REQs and receive REP s. The process (Pi ) having the token sends REQs to all other processes (line 3f). Logi .start[i] and Logi .end[j] for the interval at the head of the queue Qi are piggybacked on the request REQ sent to process Pj (lines 3c-3e). On receiving a REQ from Pi , process Pj compares the piggybacked interval X with the interval Y at the head of its queue Qj (line 4e). The comparisons between intervals on process Pi and Pj can result in these outcomes. (1) Def initely(φi,j ) is satisfied. (2) Def initely(φi,j ) is not satisfied and interval X can be removed from the queue Qi . The process index i is stored in REP.updated (line 4f). (3) Def initely(φi,j ) is not satisfied and interval Y can be removed from the queue Qj . The interval

386

type Log start: array[1 . . . .n] of integer; end: array[1 . . . .n] of integer; type Q: queue of Log; When an interval begins: Logi .start ←− Vi . When an interval ends: Logi .end ←− Vi if (a receive event has occurred since the last time a Log was queued on Qi ) then Enqueue Logi on to the local queue Qi . Figure 11.13: Maintaining intervals for detection of a conjunctive predicate (distributed, on-line, Def initely). at the head of Qj is dequeued and process index j is stored in REP.updated (lines 4g, 4h). Note that outcomes (2) and (3) may occur together. After the comparisons, Pj sends REP to Pi . Once the token-holder process Pi receives a REP from all other processes, it stores the indices of all the updated queues in the set T.updatedQueues (lines 3h, 3i). A solution, identified by the set I formed by the interval Ik at the head of each queue Qk , is detected if the set updatedQueues is empty. Otherwise, if index i is contained in T.updatedQueues, process Pi deletes the interval at the head of its queue Qi (lines 3m, 3n). As the set T.updatedQueues is non-empty, the token is sent to a process selected randomly from the set (line 3o). The crux of the correctness of this algorithm is based on Equations 11.3 and 11.5 for Def initely(φ). We can make the following observations from the algorithm. • If Def initely(φi,j ) is not true for a pair of intervals Xi and Yj , then either i or j is inserted into T.updatedQueues. • An interval is deleted from queue Qi at process Pi if and only if the index i is inserted into T.updatedQueues. • When a solution I is detected by the algorithm, the solution is correct, i.e., for each pair Pi , Pj ∈ N, the intervals Ii = head(Qi ) and Ij = head(Qj ) are such that Ii ֒→ Ij and Ij ֒→ Ii (and hence by Equations 11.3 and 11.5, Def initely(φ) must be true). • If a solution I exists, i.e., for each pair Pi , Pj ∈ N, the intervals Ii , Ij belonging to I are such that Ii ֒→ Ij and Ij ֒→ Ii (and hence Def initely(φ) must be true), then the solution is detected by the algorithm. Complexity: The complexity analysis can be done in terms of two parameters – the maximum number of messages sent per process (m) and the maximum number of intervals per process (p). 387

//used by Pi to send a request to each Pj //contains Logi .start[i] for the interval at the queue head of Pi //contains Logi .end[j] for the interval at the queue head of Pi , when sending to Pj

type REQUEST start : integer; end : integer; type REPLY updated: set of integer;

//used to send a response to a received request //contains the indices of the updated queues

type TOKEN updatedQueues: set of integer;

//used to transfer control between two processes //contains the index of all the updated queues

(1) Process Pi initializes local state (1a) Qi is empty. (2) Token initialization (2a) A randomly elected process Pi holds the token T . (2b) T.updatedQueues ←− {1, 2, . . . , n}. (3) RcvT oken : When Pi receives a token T (3a) Remove index i from T.updatedQueues (3b) wait until (Qi is nonempty) (3c) REQ.start ←− Logi .start[i], where Logi is the log at head of Qi (3d) for j = 1 to n (3e) REQ.end ←− Logi .end[j] (3f) Send the request REQ to process Pj (3g) wait until (REPj is received from each process Pj ) (3h) for j = 1 to n (3i) T.updatedQueues ←− T.updatedQueues ∪ REPj .updated (3j) if (T.updatedQueues is empty) then (3k) Solution detected. Heads of the queues identify intervals that form the solution. (3l) else (3m) if (i ∈ T.updatedQueues) then (3n) dequeue the head from Qi (3o) Send token to Pk where k is randomly selected from the set T.updatedQueues. (4) RcvReq : When a REQ from Pi is received by Pj (4a) wait until (Qj is nonempty) (4b) REP.updated ←− φ (4c) Y ←− head of local queue Qj (4d) Vi− (X)[i] ←− REQ.start and Vi+ (X)[j] ←− REQ.end (4e) Determine X ֒→ Y and Y ֒→ X (4f) if (Y 6֒→ X) then REP.updated ←− REP.updated ∪ {i} (4g) if (X 6֒→ Y ) then (4h) REP.updated ←− REP.updated ∪ {j} (4i) Dequeue Y from local queue Qj (4j) Send reply REP to Pi .

Figure 11.14: Def initely).

Interval based detection of a conjunctive predicate (distributed, on-line,

388

Space complexity: This is analyzed for each process, and for the entire system. • The worst-case space overhead across all the processes is 2mn2 . The worst-case space overhead at any process is O(mn2 ). • The total number of Logs stored at each process is p because in the worst case, the Log for each interval may need to be stored. As each Log has size 2n, the worst-case overhead is 2np integers over all Logs per process, and the worst-case space complexity across all processes is 2n2 p = O(n2p). As the total number of Logs stored on all the processes is min(np, mn), the worst-case space overhead across all the processes is min(2n2 p, 2n2 m). This is equivalent to min(2np, 2nm) per process if the mn message destinations are divided equally among the processes (implying that each process has up to min(p, m) Logs). The worst-case space overhead at a process is min(2np, 2n(n − 1)m). Time complexity: The two components contributing to time complexity are RcvReq and RcvT oken. RcvReq: In the worst case, the number of REQs received by a process is equal to the number of Logs on all other processes, because a REQ is sent only once for each Log. The total number of Logs over all the queues is min(np, mn), hence the number of interval pairs compared per process is min((n − 1)p, m(n − 1)). As it takes O(1) time to execute RcvReq, the worst-case time complexity per process for RcvReq is O(min(np, mn)). As the processes execute RcvReq in parallel, this is also the total time complexity for RcvReq. RcvT oken: The token makes at most min(np, mn) hops serially and each hop requires O(n) time complexity. Hence the worst-case time complexity for RcvT oken across all processes is O(min(pn2 , mn2 )). In the worst case, a process receives the token each time its queue head is deleted, and this can happen as many times as the number of Logs at the process. As the number of Logs at a process is min(p, m(n − 1)), the worst-case time complexity per process is O(min(pn, mn2 )). The worst-case time complexity across all the processes is O(min(pn2 , mn2 )). This is equivalent to O(min(pn, mn)) per process if the mn message destinations are divided equally among the processes (implying that each process has up to min(p, m) Logs). The worstcase time complexity at a process is O(min(pn, mn2 )). Message complexity: For each Log, either no messages are sent, or n − 1 REQs, n − 1 REP s and one token T are sent. • As the total number of Logs over all the queues is min(np, mn), hence the worst-case number of messages over all the processes is O(n min(np, mn)).

389

• The size of each T is equal to O(n), while the size of each REP and each REQ is O(1). Thus for each Log, the message space overhead is O(n) if any messages are sent for that Log. Hence the worst-case message space overhead over all the processes is equal to O(n min(np, mn)).

log entry

i j k Least_Sol

interval

(a)

Current_Conc_Ints

(b)

Figure 11.15: Illustrations of definitions used by the algorithm in Figure 11.16. (a) Definition of an interval. (b) Definitions of interval vectors Least_Sol, Current_Conc_Ints, and Log entries.

11.5.3 Distributed Interval-based Piggybacking Algorithm for P ossibly(φ), where φ is Conjunctive Unlike the previous algorithm which was a token-based algorithm to detect Def initely(φ), we now look at a distributed algorithm for detecting P ossibly(φ) without using any control messages. Instead, the algorithm piggybacks the necessary information on the application messages. The algorithm therefore illustrates a different design approach. In this algorithm, the semantics of an interval is that each interval at a process represents the duration between two consecutive communication events at the process. Intervals are sequentially numbered at any process Pi , as Ii0 , Ii1 , Ii2 , . . .. Two intervals at Pi and Pj are concurrent if P ossibly(φ) is true as determined by using Equation 11.4, and assuming φi is true in Ii and φj is true in Ij . The following variables are used at each process. • not_yet_logged[1 . . . n], a boolean, is used to determine whether in the current interval, the ‘sequence number’ of the interval is logged when the predicate first became true in this interval. This variable helps to minimize the number of intervals logged, by ensuring that in any interval, the interval is logged only once in the local log (see below) when the local predicate first becomes true in the interval (lines 1a-1b). Logging just once is important when the predicate may toggle its truth value multiple times within an interval.

390

• Current_Conc_Ints[1 . . . n], an array of integers, is used to keep track of the latest known concurrent set of intervals (as per Equations 11.4 and 11.6). However, it is not necessarily known whether the local predicates at the various processes are true in these intervals, because this array is updated at the start of an interval. • Least_Sol[1 . . . n], an array of integers, is used to track the least possible global state (i.e., set of intervals) that could possibly satisfy P ossibly(φ). In other words, no interval at any process, that precedes that process’s interval in Least_Sol, can ever be part of the solution global state. We have that (∀k) Current_Conc_Ints[k] ≥ Least_Sol[k]. • V alid[1 . . . n], a boolean array, tells whether the corresponding interval in Least_Sol is valid, i.e., whether the local predicate is ever satisfied in that interval. V alid[j] = 1 means φj is necessarily satisfied in Least_Sol[j]; if 0, it is not yet known whether φj is satisfied because the interval has not yet completed. It follows that P ossibly(φ) is true and φ is satisfied in the state identified by Least_Sol when all the entries in array V alid are true. • The queue Log at each process tracks the various values (vectors) of intervals (one interval per process) that are locally generated, one for each local communication event. In some sense, this tracks the intermediate states Current_Conc_Ints as they are generated, between global state Least_Sol and the ‘current’ global state Current_Conc_Ints. At the time the local predicate becomes true and not_yet_logged is false, (i) Current_Conc_Ints is enqueued locally, and (ii) if Current_Conc_Ints[i] = Least_Sol[i], then V alid[i] is set to true (lines 1c-1d). The array Current_Conc_Ints’s local component is always updated for each send and receive event. The array is also piggybacked on each message sent. The receiver takes the maxima of its local array and the sender’s array (line 3a). Thus, this global state is always kept up to date. The array Least_Sol plays ‘catch up’ with Current_Conc_Ints. At a send event at Pi , Least_Sol[i] is set to Current_Conc_Int[i] if the log Logi is empty (lines 2c-2d). The arrays Least_Sol and V alid are also piggybacked on the message sent (line 2e). At a receive event, the receiver Pi takes the more up to date information (line 3b) on Least_Sol and V alid, that it has and what it receives from Pj . (Assuming a solution is not found here, i.e., V alid is not all 1, further processing is necessary to advance Least_Sol.) In this step, the previous value of Least_Sol[i] may advance. As a result, entries of Current_Conc_Ints in the log Logi that are older than the new value of Least_Sol[i] are dequeued and deleted (lines 3e-3f ). If Logi becomes empty, Least_Sol catches up completely with Current_Conc_Ints and all the entries in the vector V alid are reset as we no longer know whether the local predicates were true in the corresponding intervals of Current_Conc_Ints (lines 3g-3h). If Logi is nonempty (line 3i), then the current head of the Log represents one of the earlier values of Current_Conc_Ints. The information of this queue head and associated validity vector of all 0s, is combined with the value of (Least_Soli , V alidi ) 391

array of integer: Least_Sol[1 . . . n]; array of boolean: V alid[1 . . . n]; array of integer: Current_Conc_Ints[1 . . . n]; queue of Current_Conc_Ints: Log ←−⊥ boolean: not_yet_loggedi ←− 1; (1) When local predicate φi becomes true at Pi : (1a) if not_yet_loggedi then (1b) enqueue(Logi , Current_Conc_Intsi ); not_yet_loggedi ←− f alse; (1c) if Least_Soli [i] = Current_Conc_Intsi [i] then (1d) V alidi [i] ←− true. (2) Pi sends a message, with hCurrent_Conc_Intsi , Least_Soli , V alidi i appended (2a) Current_Conc_Intsi [i] ←− Current_Conc_Intsi [i] + 1; (2b) not_yet_loggedi ←− true; (2c) if empty(Logi ) then (2d) Least_Soli [i] ←− Current_Conc_Intsi [i]; (2e) send the message with vectors hCurrent_Conc_Intsi , Least_Soli , V alidi i piggybacked. (3) When Pi receives a message from Pj with hCurrent_Conc_Intsj , Least_Solj , V alidj i piggybacked (3a) Current_Conc_Intsi ←− max(Current_Conc_Intsi , Current_Conc_Intsj ); (3b) (Least_Soli , V alidi ) ←− Combine_M axima((Least_Soli , V alidi ), (Least_Solj , V alidj )); (3c) Current_Conc_Intsi [i] ←− Current_Conc_Intsi [i] + 1; (3d) not_yet_loggedi ←− true; (3e) while ((not empty(Logi )) and ((head(Logi ))[i] < Least_Soli [i]) do (3f) dequeue(Logi ); (3g) if empty(Logi ) then (3h) Least_Soli ←− Current_Conc_Intsi ; V alidi ←− [0, 0, . . . , 0]; (3i) else (3j) (Least_Soli , V alidi ) ←− Combine_M axima((Least_Soli , V alidi ), (head(Logi ), [0, 0, . . . , 0])); (3k) V alidi [i] ←− 1; (3l) if V alidi ←− [1, 1, . . . , 1] then (3m) P ossibly(φ) is true in global state Least_Soli ; (3n) Deliver the message. (4) function Combine_M axima((C1, A1), (C2, A2)) array of integer: C[1 . . . n]; array of boolean: A[1 . . . n]; (4a) for x = 1 to n do (4b) case: (4c) C1[x] > C2[x] −→ (C[x] ←− C1[x]; A[x] ←− A1[x]); (4d) C1[x] < C2[x] −→ (C[x] ←− C2[x]; A[x] ←− A2[x]); (4e) C1[x] = C2[x] −→ (C[x] ←− C1[x]; A[x] ←− (A1[x] or A2[x])); (4f) return (C, A).

Figure 11.16: Interval based detection of a conjunctive predicate (distributed, on-line, P ossibly). (line 3j) and V alid[i] is set to 1 (line 3k) because the global state from head(Logi ) implies that φi was true in the local interval in that global state. At this stage, if V alidi [k] is true for all k, then a solution state is given by Least_Sol. 392

Termination: If V alid[k] = 1 for all k, the algorithm finds a set of intervals satisfying P ossibly(φ). Note that for this to happen, some process must have received information about all such intervals and that they were valid. It may happen that such a set of intervals indeed exists but no process is able to see all these intervals under two related conditions: (i) there is not enough communication on which such information can be piggybacked, and (ii) the underlying execution terminates shortly after such a set of intervals come into existence. Exercise 8 asks you to analyze this termination condition further. Complexity: Let Ms and Mc denote the number of messages sent by a process, and the number of communication events at a process, respectively. Time complexity: Each message send and message receive requires O(n) processing. The time complexity at a process is O(Mc n) and across all processes, this is O(Mc n2 ) = O(Ms n2 ). Space complexity: The Log at a process may have to hold up to Mc intervals, each of size O(n). The other data structures are integer or boolean arrays of size n and require O(n) space P locally. Hence, the system space complexity is O( ni=1 Mc n) = O(Mc n2 ) = O(Ms n2 ).

Message complexity: On each message sent by the application, O(3n) data is piggybacked. No control messages are used. If a process sends up to Ms messages, the total space overhead is O(Ms n2 ). Fault-tolerance: The algorithm is resilient to message losses because it uses piggybacking of control information (See Exercise 9).

11.6 Further Classification of Predicates We have thus far seen relational predicates, conjunctive predicates, local predicates (in an earlier chapter), and stable predicates. Here we formally define local predicates, and then consider two more types of predicates. Local predicate: A local predicate is a predicate whose value is fully controlled by a single process. Disjunctive predicates: If a predicate φ can be expressed as the disjunction ∨i∈N φi , where φi is a predicate local to process i, then φ is a disjunctive predicate. Disjunctive predicates are straightforward to detect; each process monitors the local disjunct, and when it becomes true, informs the other processes. If the disjunct at Pi becomes true after the xth local event, then in the state lattice diagram, φ will be true in all global states having x events at Pi . It is now easy to see that for a disjunctive predicate, P ossibly(φ) = Def initely(φ). Observer-independent predicates: Different observers may observe different cuts of the execution; an observer can only determine if the predicate φ became true in the cuts it can observe.

393

If φ is observer-independent, different observers will all agree on whether the predicate φ became true. We have seen that Def initely(φ) = : P ossibly(φ). If the predicate φ also satisfies the condition P ossibly(φ) = : Def initely(φ), and thus P ossibly(φ) = Def initely(φ), then it is an observer-independent predicate. The predicate will be seen to hold or to not hold independent of the observer. Stable predicates as well as disjunctive predicates are both observer-independent. The modalities Possibly and Definitely are coarse-grained. Predicates can also be detected under a rich, fine-grained suite of modalities based on the causality relation.

11.7 Chapter Summary Observing global states is a fundamental problem in asynchronous distributed systems, as studied in Chapter ??. A natural extension of this problem is to detect global states that satisfy a given predicate on the variables of the distributed program. The chapter first considered stable predicates, which are predicates that remain true once they become true. Deadlock detection and termination detection are based on stable predicate detection. Unstable predicates on the program variables are difficult to detect because the values of variables that make the predicate true can changes and falsify the predicate. Hence, unstable predicates are defined under modalities: Possibly and Definitely. Furthermore, a predicate can be broadly classified as conjunctive or relational. A relational predicate is a predicate using any relation of the distributed variables, whereas a conjuctive predicate is defined to be a conjunct of local predicates. The chapter studied a centralized algorithm for detecting relational predicates, having exponential complexity. This complexity seems to be inherent for relational predicates. The next centralized algorithms considered for conjunctive predicates were: (i) an interval-based algorithm for detecting both modalities Possibly and Definitely, and (ii) a global state based algorithm for detecting under Posssibly modality. The chapter then covered three distributed algorithms for conjunctive predicates, all having polynomial complexity. The first was a state-based token-based algorithm for the Possibly modality. The second was an interval-based token-based algorithm for the Definitely modality. The third was an interval-based piggybacking algorithm for the Possibly modality. These representative algorithms illustrate different techniques for conjunctive predicate detection. The chapter concluded by mentioning other more sophisticated predicate modalities.

11.8 Bibliographic Notes The discussion on stable and unstable predicates is based on Chandy and Lamport [6]. Pnueli first introduced a temporal logic for programs with the ‘henceforth’ operator [21]. The discussion on detecting deadlocks is based on Kshemkalyani and Singhal [16] and the discussion on termination 394

detection is based on Mattern [20]. The challenges in detecting unstable predicates, the P ossibly and Def initely modalities, and the notion of the state lattice were formulated by Cooper and Marzullo [8] and Marzullo and Neiger [18]. The centralized algorithms to detect P ossibly and Def initely for relational predicates are based on Cooper and Marzullo [8]. Various techniques to improve the efficiency are given by Alagar and Venkatesan [1]. Conjunctive predicates were discussed by Venkatesan and Dathan [22], Garg and Waldecker [11] and Kshemkalyani [14]. The discussion on the conditions to detect conjunctive predicates is based on Kshemkalyani [14] and Chandra and Kshemkalyani [4]. The centralized algorithm for P ossibly(φ) and Def initely(φ) where φ is conjunctive, in Figure 11.8, is adapted from Chandra and Kshemkalyani [2] and Garg and Waldecker [11, 12]. The centralized algorithm for P ossibly(φ) where φ is conjunctive, in Figure 11.9, is based on the test for consistent states using vector clocks of Mattern [19] and Fidge [9]. The distributed state-based algorithm for P ossibly(φ) where φ is conjunctive, in Figure 11.11, is based on Garg and Chase [10]. The distributed interval-based algorithm for Def initely(φ) where φ is conjunctive, in Figure 11.14, is based on Chandra and Kshemkalyani [3]. The distributed interval-based algorithm for P ossibly(φ) where φ is conjunctive, in Figure 11.16, is based on Hurfin, Mizuno, Raynal,and Singhal [13]. Observer-independent predicates were introduced by Charron-Bost, Delporte-Gallet, and Fauconnier [7]. A fine-grained set of modalities was introduced by [14]. Their mapping to the Possibly/Definitely modalities was proposed in [15]. Algorithms to detect predicates under these fine-grained modalities were given in [4, 2, 5].

11.9 Exercise Problems 1. State whether True or False for each of the following. Justify your answers. (a) P ossibly(φ) = :¬Def initely(φ) (b) P ossibly(φ) = :Def initely(φ) (c) P ossibly(φ) = :Def initely(¬φ) (d) P ossibly(φ) = :¬Def initely(¬φ) (e) Def initely(φ) = :P ossibly(φ) (f) Def initely(φ) = :P ossibly(¬φ) (g) Def initely(φ) = :¬P ossibly(φ) (h) Def initely(φ) = :¬P ossibly(¬φ) 2. A conjunctive predicate φ = ∧i∈N φi , where φi is a predicate defined on variables local to process Pi . In a distributed execution (E, ≺), let F irst_Cut(φ) be denote the earliest or smallest consistent cut in which the global conjunctive predicate φ becomes true.

395

Recall that in different equivalent executions, a different “path” may be traced through the state lattice. Therefore, for different re-executions of this (deterministic) distributed program, is the state F irst_Cut(φ) well-defined? i.e., is it uniquely identified? Worded equivalently, is the set of cuts C(φ) closed under intersection? 3. Somewhat similar to the earlier problem, we now need to show a stronger property for linear predicates. Using the standard notation, let Cuts(φ) denote the set of cuts satisfying φ. Prove the following. “Cuts(φ) is closed under intersection if and only if φ is linear.” 4. Prove that the predicate detection problem is NP-complete. (Hint: Show a reduction from the satisfiability (SAT) problem.) 5. If it is known that P ossibly(φ) is true and Def initely(φ) is false in an execution, then what can be said about P ossibly(φ) and about Def initely(φ) in terms of the paths of the state lattice of that execution? 6. For the algorithm in Figure 11.3, answer the following. (a) When can the algorithm begin constructing the global states of level lvl? (b) When are all the global states of level lvl constructed? 7. Can the algorithm for global state based detection of a conjunctive predicate (centralized, on-line, P ossibly) of Figure 11.9 be modified to detect Def initely(φ)? If yes, give the modified algorithm and show it is correct. 8. Determine whether the interval-based distributed algorithm (Figure 11.16) to detect P ossibly(φ) will always detect P ossibly(φ), even though the algorithm is correct in principle. If it will not, extend the algorithm to ensure that a solution is always detected if it exists. Hint: Consider the termination of the execution and the P ossibly modality holding just a little before the termination. 9. Analyze the degree to which the algorithm in Figure 11.16 is resilient to message losses. 10. Show the following relationships among the various classes of predicates. (a) The set of stable predicates is a proper subset of the set of observer-independent predicates. (b) The set of disjunctive predicates is a proper subset of the set of observer-independent predicates.

396

Bibliography [1] S. Alagar, S. Venkatesan, Techniques to tackle state explosion in global predicate detection, IEEE Trans. Software Engg., 27(8): 704-714, 2001. [2] P. Chandra, A.D. Kshemkalyani, Algorithms for detecting global predicates under fine-grained modalities, Proc. ASIAN 2003, LNCS, Springer, p. 91-109, December 2003. [3] P. Chandra, A.D. Kshemkalyani, Distributed algorithm to detect strong conjunctive predicates, Information Processing Letters, 87(5): 243-249, September 2003. [4] P. Chandra, A.D. Kshemkalyani, Detection of orthogonal interval relations, Proc. HighPerformance Computing Conference, 323-333, LNCS 2552, Springer, 2002. [5] P. Chandra, A. D. Kshemkalyani, Causality-based predicate Detection across space and time, IEEE Transactions on Computers, 54(11): 1438-1453, November 2005 [6] K. M. Chandy, L. Lamport: Distributed snapshots: Determining global states of distributed systems, ACM Transactions on Computer Systems, 3(1): 63-75, 1985. [7] B. Charron-Bost, C. Deloprte-Gallet, H. Fauconnier, Local and temporal predicates in distributed systems, ACM Trans. on Programming Languages and Systems, 17(1): 157-179, 1995. [8] R. Cooper, K. Marzullo, Consistent detection of global predicates, Proc. ACM/ONR Workshop on Parallel & Distributed Debugging, 163-173, May 1991. [9] C. J. Fidge, Timestamps in message-passing systems that preserve partial ordering, Australian Computer Science Communications, 10(1): 56-66, 1988. [10] V.K. Garg, C. Chase, Distributed algorithms for detecting conjunctive predicates, Proceedings 15th IEEE International Conference on Distributed Computing Systems, p. 423-430, 1995. [11] V.K. Garg, B. Waldecker, Detection of weak unstable predicates in distributed programs, IEEE Trans. Parallel & Distributed Systems, 5(3):299-307, Mar. 1994. [12] V.K. Garg, B. Waldecker, Detection of strong unstable predicates in distributed programs, IEEE Trans. Parallel & Distributed Systems, 7(12):1323-1333, Dec. 1996. 397

[13] M. Hurfin, M. Mizuno, M. Raynal, M. Singhal, Efficient distributed detection of conjunctions of local predicates, IEEE Trans. Software Engg., 24(8): 664-677, 1998. [14] A.D. Kshemkalyani, Temporal interactions of intervals in distributed systems, Journal of Computer and System Sciences, 52(2): 287-298, April 1996. [15] A.D. Kshemkalyani, A fine-grained modality classification for global predicates, IEEE Trans. Parallel & Distributed Systems, 14(8): 807-816, August 2003. [16] A.D. Kshemkalyani, M. Singhal, Correct two-phase and one-phase deadlock detection algorithms for distributed systems, Proceedings of 2nd IEEE Symposium on Parallel and Distributed Processing, p. 126-129, Dec. 1990. [17] L. Lamport, Time, clocks, and the ordering of events in a distributed system, Communications of the ACM, 21(7): 558-565, July 1978. [18] K. Marzullo, G. Neiger, Detection of global state predicates, Proc. 5th Workshop on Distributed Algorithms, LNCS 579, Springer-Verlag, 254-272, October 1991. [19] F. Mattern, Virtual time and global states of distributed systems, Proc. International Workshop on Parallel and Distributed Algorithms, North-Holland, pp. 215-226, October 1998. [20] F. Mattern, Algorithms for distributed termination detection, Distributed Computing, 2: 161175, 1987. [21] A. Pnueli, The temporal logic of programs, Proc. IEEE Symposium on Foundations of Computer Science, p. 46-57, 1977. [22] S. Venkatesan, B. Dathan, Testing and debugging distributed programs using global predicates, IEEE Trans. Software Engg., 21(2): 163-177, Feb. 1995.

398

Chapter 12 Distributed Shared Memory 12.1 Abstraction and Advantages Distributed shared memory (DSM) is an abstraction provided to the programmer of a distributed system. It gives the impression of a single monolithic memory, as in a traditional von Neumann architecture. The programmer accesses the data across the network using only read and write primitives, as he would in a uniprocessor system. The programmer does not have to deal with send and receive communication primitives and the ensuing complexity of dealing explicitly with synchronization and consistency in the message-passing model. The DSM abstraction is illustrated in Figure 12.1. A part of each computer’s memory is earmarked for shared space, and the remainder is private memory. To provide the programmers the illusion of a single shared address space, a memory mapping management layer is required to manage the shared virtual memory space.

CPU Memory

CPU Memory

CPU Memory

Memory manager

Memory manager

Memory manager

Shared Virtual Memory Figure 12.1: Abstract view of DSM The following are the advantages of DSM. 1. Communication across the network is achieved by the read/write abstraction that simplifies the task of the programmer.

399

process

process

process

response response invocation invocation

Memory manager

response invocation

Memory manager

Memory manager

Shared Virtual Memory Distributed shared memory

Figure 12.2: Detailed abstraction of DSM and interaction with application processes. 2. A single address space is provided, thereby providing the possibility of avoiding data movement across multiple address spaces by using passing-by-reference, instead of passing-byvalue. 3. If a block of data needs to be moved, the system can exploit locality of reference to reduce the communication overhead. 4. DSM is often cheaper than using dedicated multiprocessor systems, because it uses simpler software interfaces and off-the-shelf hardware. 5. There is no bottleneck presented by a single memory access bus. 6. DSM effectively provides a large (virtual) main memory. 7. DSM provides portability of programs written using DSM. This portability arises due to a common DSM programming interface, that is independent of the operating system and other low-level system characteristics. Although a familiar (i.e., read/write) interface is provided to the programmer, there is a catch to it. Under the covers, there is inherently a distributed system and a network, and the data needs to be shared in some fashion. There is no silver bullet. Moreover, with the possibility of data replication and/or the concurrent access to data, concurrency control needs to be enforced. Specifically, when multiple processors wish to access the same data object, a decision about how to handle concurrent accesses needs to be made. As in traditional databases, if a locking mechanism based on read and write locks for objects is used, concurrency is severely restrained, defeating one of the purposes of having the distributed system. On the other hand, if concurrent access is permitted by different processors to different replicas, the problem of replica consistency (which is a generalization of the problem of cache consistency in computer architecture studies) needs to be addressed. The main point of allowing concurrent access (by different processors) to the same data object is to increase throughput. But in the face of concurrent access, the semantics of what value a read operation 400

returns to the program needs to be specified. The programmer ultimately needs to understand this semantics, which may differ from the Von Neumann semantics, because the program logic depends greatly on this semantics. This compromises the assumption that the DSM is transparent to the programmer. Before examining the challenges in implementing replica coherency in DSM systems, we look at the disadvantages. 1. The programmer is not shielded from having to know about various replica consistency models and coding his distributed application according to the semantics of these models. 2. As DSM is implemented under the covers using asynchronous message-passing, the overheads incurred are at least as high as those of a message-passing implementation. As such, DSM implementations cannot be more efficient than asynchronous message-passing implementations. The generality of the DSM software may make it less efficient. 3. By yielding control to the DSM memory management layer, the programmer loses the ability to use his own message-passing solutions for accessing shared objects. It is likely that the standard vanilla implementations of DSM have a higher overhead than a programmer-written implementation tailored for a specific application and system. The main issues in designing a DSM system are the following. • Determining what semantics to allow for concurrent access to shared objects. The semantics needs to be clearly specified so that the programmer can code his program using an appropriate logic. • Determining the best way to implement the semantics of concurrent access to shared data. One possibility is to use replication. One decision to be made is the degree of replication – partial replication at some sites, or full replication at all the sites. A further decision then is to decide on whether to use read-replication (replication for the read operations) or writereplication (replication for the write operations) or both. • Selecting the locations for replication (if full replication is not used), to optimize efficiency from the system’s viewpoint. • Determining the location of remote data that the application needs to access, if full replication is not used. • Reducing communication delays and the number of messages that are involved under the covers while implementing the semantics of concurrent access to shared data. There is a wide range of choices on how these issues can be addressed. In part, the solution depends on the system architecture. Recall from Chapter 1 that DSM systems can range from tightly-coupled (hardware and software) multicomputers to wide-area distributed systems with heterogenous hardware and software. There are four broad dimensions along which DSM systems can be classified and implemented. 401

Type of DSM single-bus multiprocessor switched multiprocessor NUMA system Page-based DSM Shared variable DSM Shared object DSM

Examples Firefly, Sequent Alewife, Dash Butterfly, CM* Ivy, Mirage Midway, Munin Linda, Orca

Management by MMU by MMU by OS by OS by language runtime system by language runtime system

Caching hardware control hardware control software control software control software control

Remote access by hardware by hardware by hardware by software by software

software control

by software

Table 12.1: Comparison of DSM systems

• Whether data is replicated or cached • Whether remote access is by hardware or by software • Whether the caching/replication is controlled by hardware or software • Whether the DSM is controlled by the distributed memory managers, by the operating system, or by the language runtime system. The various options for each of these four dimensions, and their comparison, are shown in Figure 12.1.

12.2 Memory Consistency Models Memory coherence is the ability of the system to execute memory operations correctly. Assume n processes and si memory operations per process Pi . Also assume that all the operations issued by a process are executed sequentially (that is, pipelining is disallowed), as shown in Figure 12.3. Observe that there are a total of (s1 + s2 + . . . + sn )!/(s1 !s2 ! . . . sn !) possible permutations or interleavings of the operations issued by the processes. The problem of ensuring memory coherence then becomes the problem of identifying which of these interleavings are “correct”, which of course requires a clear definition of “correctness”. The memory consistency model defines the set of allowable memory access orderings. While a traditional definition of correctness says that a correct memory execution is one that returns to each Read operation, the value stored by the most recent Write operation, the very definition of “most recent” becomes ambigious in the presence of concurrent access and multiple replicas of the data item. Thus, a clear definition of correctness is required in such a system; the objective is to disallow the interleavings that make no semantic sense, while not being overly restrictive so as to permit a high degree of concurrency. 402

process

op1

op2

op3

invocation invocation invocation response response response

local memory manager

opk invocation response

Figure 12.3: Sequential invocations and responses in a DSM system, without any pipelining. The DSM system enforces a particular memory consistency model; the programmer writes his program keeping in mind the allowable interleavings permitted by that specific memory consistency model. A program written for one model may not work correctly on a DSM system that enforces a different model. The model can thus be viewed as a contract between the DSM system and the programmer using that system. We now consider six consistency models, that are related as shown in Figure 12.12. Notation: A write of value a to variable x is denoted as Write(x,a). A read of variable x that returns value a is denoted as Read(x,a). A subscript on these operations is sometimes used to denote the processor that issues these operations.

12.2.1 Strict consistency/Atomic consistency/Linearizability The strictest model, corresponding to the notion of correctness on the traditional Von Neumann architecture or the uniprocessor machine, requires that any Read to a location (variable) should return the value written by the most recent Write to that location (variable). Two salient features of such a system are the following. (i) A common global time axis is implicitly available in a uniprocessor system. (ii) Each write is immediately visible to all processes. Adapting this correctness model to a DSM system with operations that can be concurrently issued by the various processes gives the strict consistency model, also known as the atomic consistency model. The model is more formally specified as follows. 1. Any Read to a location (variable) is required to return the value written by the most recent Write to that location (variable) as per a global time reference. For operations that do not overlap as per the global time reference, the specification is clear. For operations that overlap as per the global time reference, the following further specifications are necessary. 2. All operations appear to be executed atomically and sequentially. 3. All processors see the same ordering of events, which is equivalent to the global-time occurrence of non-overlapping events. An alternate way of specifying this consistency model is in terms of the ‘invocation’ and ‘response’ to each Read and Write operation, as shown in Figure 12.3. Recall that each operation 403

takes a finite time interval and hence different operations by different processors can overlap in time. However, the invocation and the response to each invocation can both be separately viewed as being atomic events. An execution sequence in global time is viewed as a sequence Seq of such invocations and responses. Clearly, Seq must satisfy the conditions: • (Liveness:) Each invocation must have a corresponding response, and • (Correctness:) The projection of Seq on any processor i, denoted Seqi , must be a sequence of alternating invocations and responses if pipelining is disallowed. Despite the concurrent operations, a linearizable execution needs to generate an equivalent global order on the events, that is a permutation of Seq, satisfying the semantics of linearizability. More formally, a sequence Seq of invocations and responses is linearizable (LIN) if there is a permutation Seq ′ of adjacent pairs of corresponding hinvoc, respi events satisfying: 1. For every variable v, the projection of Seq ′ on v, denoted Seqv′ , is such that • every Read (adjacent hinvoc, respi event pair) returns the most recent Write (adjacent hinvoc, respi event pair) that immediately preceded it. 2. If the response op1(resp) of operation op1 occurred before the invocation op2(invoc) of operation op2 in Seq, then op1 (adjacent hinvoc, respi event pair) occurs before op2 (adjacent hinvoc, respi event pair) in Seq ′ . Condition 1 specifies that every processor sees a common order Seq ′ of events, and that in this order, the semantics is that each Read returns the most recent completed Write value. Condition 2 specifies that the common order Seq ′ must satisfy the global time order of events, viz., the order of non-overlapping operations in Seq must be preserved in Seq ′ . Examples: Figure 12.4 shows three executions. Figure 12.4(a): The execution is not linearizable because although the Read by P2 begins after Write(x, 4), the Read returns the value that existed before the Write. Hence, a permutation Seq ′ satisfying the above condition(2) on global time order does not exist. Figure 12.4(b): The execution is linearizable. The global order of operations (corresponding to hresponse, invocationi pairs in Seq ′ ), consistent with the real-time occurrence is: Write(y, 2), Write(x, 4), Read(x, 4), Read(y, 2). This permutation Seq ′ satisfies the conditions (1 and 2). Figure 12.4(c): The execution is not linearizable. The two dependencies: Read(x, 0) before Write(x, 4), and Read(y, 0) before Write(x, 2) cannot both be satisfied in a global order while satisfying the local order of operations at each processor. Hence, there does not exist any permutation Seq ′ satisfying conditions (1 and 2).

404

P 1 P 2

P 1 P 2

P 1 P 2

Write(x,4) Write(y,2)

Read(y,2) Read(x,0)

(a)Sequentially consistent but not linearizable Read(y,2)

Write(x,4) Write(y,2)

Read(x,4)

(b) Sequentially consistent and linearizable Write(x,4) Write(y,2)

Read(y,0) Read(x,0)

(c) Not sequentially consistent (and hence not linearizable)

Figure 12.4: Examples to illustrate definitions of linearizability and sequential consistency. The initial values of variables are zero. 12.2.1.1 Implementations Implementing linearizability is expensive because a global time scale needs to be simulated. As all processors need to agree on a common order, the implementation needs to use total order. For simplicity, we assume full replication of each data item at all the processors. Hence, total ordering needs to be combined with a broadcast. Figure 12.5 gives the implementation assuming the existence of a total order broadcast primitive that broadcasts to all processors including the sender. Hence, the Memory Manager software has to be placed between the application above it and the total order broadcast layer below it. Although the algorithm in Figure 12.5 appears simple, it is also subtle. The total order broadcast ensures that all processors see the same order. • For two nonoverlapping operations at different processors, by the very definition of nonoverlapping, the response to the former operation precedes the invocation of the latter in global time. • For two overlapping operations, the total order ensures a common view by all processors. For a Read operation, when the Memory Managers systemwide receive the total order broadcast, they do not perform any action. Why is the broadcast then necessary? The reason is this. If Read operations do not participate in the total order broadcasts, they do not get totally ordered with respect to the Write operations as well as with respect to the other Read operations. This can lead to a violation of linearizability, as shown in Figure 12.6. The Read by Pk returns the value written by Pi . The later Read by Pj returns the initial value of 0. As per the global time ordering requirement of linearizability, the Read by Pj that occurs after the Read by Pk must also return 405

(shared var) int: x; (1) When the Memory Manager receives a Read or Write from application: (1a) total_order_broadcast the Read or Write request to all processors; (1b) await own request that was broadcast; (1c) perform pending response to the application as follows (1d) case Read: return value from local replica; (1e) case Write: write to local replica and return ack to application. (2) When the Memory Manager receives a total_order_broadcast(Write, x, val) from network: (2a) write val to local replica of x. (3) When the Memory Manager receives a total_order_broadcast(Read, x) from network: (3a) no operation.

Figure 12.5: Implementing Linearizability (LIN) using total order broadcasts. Code shown is for Pi , 1 ≤ i ≤ n. Write(x,4) P_i P_j

total order broadcast

Read(x,0) P_k Read(x,4)

Figure 12.6: A violation of linearizability (LIN) if Read operations do not participate in the total order broadcast. the value 4. However, that is not the case in this example, wherein the Read operations do not participate in the total order broadcast.

12.2.2 Sequential Consistency Linearizability or strict/atomic consistency is difficult to implement because the absence of a global time reference in a distributed system necessitates that the time reference has to be simulated. This is very expensive. Programmers can deal with weaker models. The first weaker model, that of sequential consistency (SC) was proposed by Lamport and uses logical time reference instead of the global time reference. Sequential consistency is specified as follows. • The result of any execution is the same as if all operations of the processors were executed in some sequential order. 406

• The operations of each individual processor appear in this sequence in the local program order. Although any possible interleaving of the operations from the different processors is possible, all the processors must see the same interleaving. In this model, even if two operations from different processors (on the same or different variables) do not overlap in a global time scale, they may appear in reverse order in the common sequential order seen by all the processors. More formally, a sequence Seq of invocation and response events is sequentially consistent if there is a permutation Seq ′ of adjacent pairs of corresponding hinvoc, respi events satisfying: 1. For every variable v, the projection of Seq ′ on v, denoted Seqv′ , is such that: • every Read (adjacent hinvoc, respi event pair) returns the most recent Write (adjacent hinvoc, respi event pair) that immediately preceded it. 2. If the response op1(resp) of operation op1 at process Pi occurred before the invocation op2(invoc) of operation op2 by process Pi in Seq, then op1 (adjacent hinvoc, respi event pair) occurs before op2 (adjacent hinvoc, respi event pair) in Seq ′ . Condition (1) is the same as that for linearizability. Condition (2) differs from that for linearizability. It specifies that the common order Seq ′ must satisfy only the local order of events at each processor, instead of the global order of nonoverlapping events. Hence the order of non-overlapping operations issued by different processors in Seq need not be preserved in Seq ′ . Examples: Three examples are considered in Figure 12.4. Figure 12.4(a): The execution is sequentially consistent: the global order Seq ′ is: Write(y, 2), Read(x, 0), Write(x, 4), Read(y, 2). Figure 12.4(b): As the execution is linearizable (seen in Section 12.2.1), it is also sequentially consistent. The global order of operations (corresponding to hresponse, invocationi pairs in Seq ′ ), consistent with the real-time occurrence is: Write(y, 2), Write(x, 4), Read(x, 4), Read(y, 2). Figure 12.4(c): The execution is not sequentially consistent (and hence not linearizable). The two dependencies: Read(x, 0) before Write(x, 4), and Read(y, 0) before Write(x, 2) cannot both be satisfied in a global order while satisfying the local order of operations at each processor. Hence, there does not exist any permutation Seq ′ satisfying conditions (1 and 2). 12.2.2.1 Implementations As sequential consistency (SC) is less restrictive than linearizability (LIN), it should be easier to implement it. As all processors are required to see the same global order, but global time ordering need not be preserved across processes, it is sufficient to use total order broadcasts for the Write operations only. In the simplified algorithm, no total order broadcast is required for Read operations, because: 407

(shared var) int: x; (1) When the Memory Manager at Pi receives a Read or Write from application: (1a) case Read: return value from local replica; (1b) case Write(x,val): total_order_broadcasti (Write(x,val)) to all processors including itself. (2) When the Memory Manager at Pi receives a total_order_broadcastj (Write, x, val) from network: (2a) write val to local replica of x; (2b) if i = j then return acknowledgement to application.

Figure 12.7: Implementing Sequential Consistency (SC) using local Read operations. Code shown is for Pi , 1 ≤ i ≤ n. 1. all consecutive operations by the same processor are ordered in that same order because of not using pipelining, and 2. Read operations by different processors are independent of each other and need to be ordered only with respect to the Write operations in the execution. In Exercise 1, you will be asked to reason this more thoroughly. Two algorithms for SC are next given, that exhibit a trade-off of the inhibition of Read versus Write operations. Local-Read algorithm: The first algorithm for SC, given in Figure 12.7, is a direct simplification of the algorithm for linearizability, given in Figure 12.5. In the algorithm, a Read operation completes atomically, whereas a Write operation does not. Between the invocation of a Write by Pi (line 1e) and its acknowledgement (lines 2a,2b), there may be multiple Write operations initiated by other processors that take effect at Pi (line 2a). Thus, a Write issued locally has its completion locally delayed. Such an algorithm is acceptable for Read-intensive programs. Local-Write algorithm: The algorithm in Figure 12.8 does not delay acknowledgement of Writes. For Write-intensive programs, it is desirable that a locally issued Write gets acknowledged immediately (as in lines 2a-2c), even though the total order broadcast for the Write, and the actual update for the Write may not go into effect by updating the variable at the same time (line 3a). The algorithm achieves this at the cost of delaying a Read operation by a processor until all previously issued local Write operations by that same processor have locally gone into effect (i.e., previous Writes issued locally have updated their local variables being written to). The variable counter is used to track the number of Write operations that have been locally initiated but not completed at any time. A Read operation completes only if there are no prior locally initiated Write operations that have not written to their variables (line 1a), i.e., there are no pending locally initiated Write operations to any variable. Otherwise, a Read operation is delayed until after all previously initiated Write operations have written to 408

(shared var) int: x; (1) When the Memory Manager at Pi receives a Read(x) from application: (1a) if counter = 0 then (1b) return x (1c) else Keep the Read pending. (2) When the Memory Manager at Pi receives a Write(x,val) from application: (2a) counter ←− counter + 1; (2b) total_order_broadcasti the Write(x, val); (2c) return acknowledgement to the application. (3) When the Memory Manager at Pi receives a total_order_broadcastj (Write, x, val) from network: (3a) write val to local replica of x. (3b) if i = j then (3c) counter ←− counter − 1; (3d) if (counter = 0 and any Reads are pending) then (3e) perform pending responses for the Reads to the application.

Figure 12.8: Implementing Sequential Consistency (SC) using local Write operations. Code shown is for Pi , 1 ≤ i ≤ n. their local variables (lines 3b-3d), which happens after the total order broadcasts associated with the Write have delivered the broadcast message locally. This algorithm performs fast (local) Writes and slow Reads. The algorithm pipelines all Write updates issued by a processor. The Read operations have to wait for all Write updates issued earlier by that processor to complete (i.e., take effect) locally before the value to be read is returned to the application.

12.2.3 Causal Consistency For the sequential consistency model, it is required that Write operations issued by different processors must necessarily be seen in some common order by all processors. This requirement can be relaxed to require only that Writes that are causally related must be seen in that same order by all processors, whereas ‘concurrent’ Writes may be seen by different processors in different orders. The resulting consistency model is the causal consistency model. We have seen the definition of causal relationships among events in a message passing system. What does it mean for two Write operations to be causally related? The causality relation for shared memory systems is defined as follows. Local order: At a processor, the serial order of the events defines the local causal order

409

P1

W(x,2) W(x,4) R(x,4) W(x,7)

P2

R(x,2) R(x,7)

P3

R(x,4)

P

4

P1

R(x,7)

(a)Sequentially consistent and causally consistent W(x,2) W(x,4) W(x,7)

P2

R(x,7) R(x,2) P3

R(x,4)

P

4

P1 P2

(b) Causally consistent but not sequentially consistent W(x,2) W(x,4) R(x,4) W(x,7) R(x,2) R(x,7)

P3 P4

R(x,7)

R(x,7)

R(x,4)

(c) Not causally consistent but PRAM consistent

Figure 12.9: Examples to illustrate definitions of sequential consistency (SC), causal consistency (CC), and PRAM consistency. The initial values of variables are zero. Inter-process order: A Write operation causally precedes a Read operation issued by another processor if the Read returns a value written by the Write. Transitive closure: The transitive closure of the above two relations defines the (global) causal order. Examples: The examples in Figure 12.9 illustrate causal consistency. Figure 12.9(a): The execution is sequentially consistent (and hence causally consistent). Both P3 and P4 see the operations at P1 and P2 in sequential order and in causal order. Figure 12.9(b): The execution is not sequentially consistent but it is causally consistent. Both P3 and P4 see the operations at P1 and P2 in causal order because the lack of a causality relation between the Writes by P1 and by P2 allows the values written by the two processors to be seen in different orders in the system. The execution is not sequentially consistent because there is no global satisfying the contradictory ordering requirements set by the Reads by P3 410

and by P4 . What can be said if the two Read operations of P4 returned 7 first and then 4? (See Exercise 4.) Figure 12.9(c): The execution is not causally consistent because the second Read by P4 returns 4 after P4 has already returned 7 is an earlier Read. 12.2.3.1 Implementation We first examine the definition of sequential consistency. Even though all processors only need to see some total order of the Write operations, observe that if two Write operations are related by causality (i.e., the second Write begins causally after a Read that reads the value written by the first Write), then the order of the two Writes seen by all the processors also satisfies causal order! In the implementation, even though a total-order-broadcast primitive is used, observe that it implicitly provides causal ordering on all the Write operations. Thus, due to the nature of the definition of causal ordering in shared memory systems, a total-order-broadcast also provides causal order broadcast, unlike the case for message-passing systems. (Exactly why is it so?) In contrast to the SC requirement, causal consistency implicitly requires only that causal order be provided. Thus, a causal-order-broadcast can be used in the implementation. The details of the implementation are left as Exercise 5.

12.2.4 PRAM (Pipelined RAM) or Processor Consistency Causal consistency requires all causally-related Writes to be seen in the same order by all processors. This may be viewed as being too restrictive for some applications. A weaker form of consistency requires only that Write operations issued by the same (any one) processor are seen by all other processors in that same order in which they were issued, but Write operations issued by different processors nay be seen in differing orders by different processors. In relation to the ‘causality’ relation between operations, only the local causality relation, as defined by the local order of Write operations, needs to be seen by other processors. Hence, this form of consistency is termed processor consistency. An equivalent name for this consistency model is Pipelined RAM (PRAM), to capture the behavior that all operations issued by any processor appear to the other processors in a FIFO pipelined sequence. Examples: • In Figure 12.9(c), the execution is PRAM consistent (even though it is not causally consistent) because (trivially) both P3 and P4 see the updates made by P1 and P2 in FIFO order along the channels P1 to P3 and P2 to P3 , and along P1 to P4 and P2 to P4 , respectively. • While PRAM consistency is more permissive than causal consistency, this model must be used with care by the programmer because it can lead to rather unintuitive results. For example, examine the code in Figure 12.10, where x and y are shared variables. It is possible that on a PRAM system, both processes P1 and P2 get killed. This can happen as follows. (i) P1 writes 4 to x in line (1a) and P2 writes 6 to y in line (2a) at about the same time. (ii) Before 411

(shared variables) int: x, y; Process 1

Process 2

... (1a) x ←− 4; (1b) if y = 0 then kill(P2 ).

... (2a) y ←− 6; (2b) if x = 0 then kill(P1 ).

Figure 12.10: A counter-intuitive behaviour of a PRAM-consistent program. The initial values of variables are zero. these written values propagate to the other processor, P1 reads y (as being 0) in line (1b) and P2 reads x (as being 0) in line (2b). Here, a Read (e.g., in (1b) or (2b)) can effectively ‘overtake’ a preceding Write (of (2a) or (1a), resp.) if the two accesses by the same processor are to different locations. However, this would not be expected on a conventional machine, where at most one process may get killed, depending on the interleaving of the statements. • The execution in Figure 12.11(a) violates PRAM consistency. An explanation is given in Section 12.2.5.

12.2.4.1 Implementations PRAM consistency can be implemented using FIFO broadcast. The implementation details are left an Exercise 6.

12.2.5 Slow Memory The next weaker consistency model is that of slow memory. This model represents a locationrelative weakening of the PRAM model. In this model, only all Write operations issued by the same processor and to the same memory location must be observed in the same order by all the processors. Examples: The examples in Figure 12.11 illustrate slow memory consistency. Figure 12.11(a): The updates to reach of the variables are seen pipelined separately in a FIFO fashion. The ‘x’ pipeline from P1 to P2 is slower than the ‘y’ pipeline from P1 to P2 . Thus, the overtaking effect is allowed. However, PRAM consistency is violated because the FIFO property is violated over the single common ‘pipeline’ from P1 to P2 – the update to y is seen by P2 but the much older value of x = 0 is seen by P2 later. Figure 12.11(b): Slow memory consistency is violated because the FIFO property is violated for the pipeline for variable x. ‘x = 7’ is seen by P2 before it sees ‘x = 0’ and ‘x = 2’ although 7 was written to x after the values of 0 and 2. 412

P1

W(x,2) W(y,4)

W(x,7) R(y,4)

P2

P1

R(x,0) R(x,0) R(x,7)

(a) Slow memory but not PRAM consistent W(x,2) W(y,4)

W(x,7) R(y,4)

P2

R(x,7) R(x,0) R(x,2)

(b) Violation of slow memory consistency

Figure 12.11: Examples to illustrate definitions of PRAM consistency and slow memory. The initial values of variables are zero.

no consistency model pipelined RAM (PRAM) Sequential consistency Linearizability/ Atomic consistency/ Strict consistency Causal consistency Slow memory

Figure 12.12: A strict hierarchy of the memory consistency models. 12.2.5.1 Implementations Slow memory can be implemented using a broadcast primitive that is weaker than even the FIFO broadcast. What is required is a FIFO broadcast per variable in the system, i.e., the FIFO property should be satisfied only for updates to the same variable. The implementation details are left as Exercise 7.

12.2.6 Hierarchy of Consistency Models Based on the definitions of the memory consistency models seen so far, there exists a hierarchy among the models, as depicted in Figure 12.12.

413

12.2.7 Other Models based on Synchronization Instructions We have seen several popular consistency models. Based on the consistency model, the behaviour of the DSM differs, and the programmer’s logic therefore depends on the underlying consistency model. It is also possible that newer consistency models may arise in the future. The consistency models seen so far apply to all the instructions in the distributed program. We now briefly mention some other consistency models that are based on a different principle, namely that the consistency conditions apply only to a set of distinguished ‘synchronization’ or ‘coordination’ instructions. These synchronization instructions are typically from some run-time library. A common example of such a statement is the barrier synchronization. Only the synchronization statements across the various processors must satisfy the consistency conditions; other program statements between synchronization statements may be executed by the different processors without any conditions. Examples of consistency models based on this principle are: entry consistency, weak consistency, and release consistency. The synchronization statements are inserted in the program based on the semantics of the types of accesses. For example, accesses may be conflicting (to the same variable) or non-conflicting (to different variables), conflicting accesses may be competing (a Read and a Write, or two Writes) or non-conflicting (two Reads), and so on. We outline the definitions of these consistency models but skip further implementation details of such models. Weak Consistency: Some applications do not require even seeing all writes, let alone seeing them in some order. Consider the case of a process executing a CS, repeatedly reading and writing some variables in a loop. Other processes are not supposed to read or write these variables until the first process has exited its CS. However, if the memory has no way of knowing when a process is in a CS and when it is not, the DSM has to propagate all writes to all memories in the usual way. But by using synchronization variables, processes can deduce whether the CS is occupied. A synchronization variable in this model has the following semantics: it is used to propagate all writes to other processors, and to perform local updates with regard to changes to global data that occurred elsewhere in the distributed system. When synchronization occurs, all Writes are propagated to other processes, and all Writes done by others are brought locally. In an implementation specifically for the CS problem, updates can be propagated in the system only when the synchronization variable is accessed (indicating an entry or exit into the CS). Weak consistency has the following three properties which guarantee that memory is consistent at the synchronization points. 1. Accesses to synchronization variables are sequentially consistent. 2. No access to a synchronization variable is allowed to be performed until all previous writes have completed everywhere. 3. No data access (either Read or Write) is allowed to be performed until all previous accesses to synchronization variables have been performed.

414

An access to the synchronization variable forces Write operations to complete, and effectively flushes the pipelines. Before reading shared data, a process can perform synchronization to ensure it accesses the most recent data. Release Consistency: The drawback of weak consistency is that when a synchronization variable is accessed, the memory does not know whether this is being done because the process is finished writing the shared variables (exiting the CS) or about to begin reading them (entering the CS). Hence, it must take the actions required in both cases. 1. Ensuring that all locally initiated Writes have been completed, i.e., propagated to all other processes. 2. Ensuring that all Writes from other machines have been locally reflected. If the memory could differentiate between entering the CS and leaving the CS, a more efficient implementation is possible. To provide this information, two kinds of synchronization variables or operations are needed instead of one. Release consistency provides these two kinds. Acquire accesses are used to tell the memory system that a critical region is about to be entered. Hence, the actions for Case (2). above need to be performed to ensure that local replicas of variables are made consistent with remote ones. Release accesses say that a critical region has just been exited. Hence, the actions for Case (1). above need to be performed to ensure that remote replicas of variables are made consistent with the local ones that have been updated. The Acquire and Release operations can be defined to apply to a subset of the variables. The accesses themselves can be implemented either as ordinary operations on special variables or as special operations. If the semantics of a CS is not associated with the Acquire and Release operations, then the operations effectively provide for barrier synchronization. Until all processes complete the previous phase, none can enter the next phase. The following rules are followed by the protected variables in the general case. • All previously initiated Acquire operations must complete successfully before a process can access a protected shared variable. • All accesses to a protected shared variable must complete before a Release operation can be performed. • The Acquire and Release operations effectively follow the PRAM consistency model. A relaxation of the release consistency model is called the lazy release consistency model. Rather than propagating the updated values throughout the system as soon as a process leaves a critical region (or enters the next phase in the case of barrier synchronization), the updated values

415

are propagated to the rest of the system only on demand, i.e., only when they are needed. Changes to shared data are only communicated when an Acquire access is performed by another process. Entry Consistency: Entry consistency requires the programmer to use Acquire and Release at the start and at the end of each CS, respectively. But unlike release consistency, entry consistency requires each ordinary shared variable to be associated with some synchronization variable such as a lock or barrier. When an Acquire is performed on a synchronization variable, only access to those ordinary shared variables that are guarded by that synchronization variable is regulated.

12.3 Shared Memory Mutual Exclusion Operating systems have traditionally dealt with multi-process synchronization using algorithms based on first principles (e.g., the well-known bakery algorithm), high-level constructs such as semaphores and monitors, and special ‘atomically executed’ instructions supported by specialpurpose hardware (e.g., Test-&-Set, Swap, and Compare-&-Swap). These algorithms are applicable to all shared memory systems. In this section, we will review the bakery algorithm which requires O(n) accesses in the entry section, irrespective of the level of contention. We will then study fast mutual exclusion which requires O(1) accesses in the entry section in the absence of contention. This algorithm also illustrates an interesting technique in resolving concurrency. As hardware primitives have the in-built atomicity that helps to easily solve the mutual exclusion problem, we will then examine mutual exclusion based on these primitives.

12.3.1 Lamport’s Bakery Algorithm Lamport proposed the classical bakery algorithm for n-process mutual exclusion in shared memory systems. The algorithm is so called because it mimics the actions that customers follow in a bakery store. A process wanting to enter the critical section picks a token number that is one greater than the elements in the array choosing[1 . . . n]. Processes enter the critical section in the increasing order of the token numbers. In case of concurrent accesses to choosing by multiple processes, the processes may have the same token number. In this case, a unique lexicographic order is defined on the tuple htoken, pidi, and this dictates the order in which processes enter the critical section. The algorithm for process i is given in Figure 12.13. The algorithm can be shown to satisfy the three requirements of the critical section problem: (i) mutual exclusion, (ii) bounded waiting, and (iii) progress. In the entry section, a process chooses a timestamp for itself, and resets it to 0 in the exit section. In steps (1a)-(1c), each process chooses a timestamp for itself, as the max of the latest timestamps of all processes, plus one. These steps are non-atomic; thus multiple processes could be choosing timestamps in overlapping durations. When process i reaches (1d), it has to check the status of each other process j, to deal with the effects of any race conditions in selecting timestamps. In (1d)-(1f), process i serially checks the status of each other process j. If j is selecting a timestamp 416

(shared vars) array of boolean: choosing[1 . . . n]; array of integer: timestamp[1 . . . n]; repeat (1) Pi executes the following for the entry section: (1a) choosing[i] ←− 1; (1b) timestamp[i] ←− maxk∈[1...n] (timestamp[k]) + 1; (1c) choosing[i] ←− 0; (1d) for count = 1 to n do (1e) while choosing[count] do no-op; (1f) while timestamp[count] 6= 0 and (timestamp[count], count) < (timestamp[i], i) do (1g) no-op. (2) Pi executes the critical section (CS) after the entry section (3) Pi executes the following exit section after the CS: (3a) timestamp[i] ←− 0. (4) Pi executes the remainder section after the exit section until false;

Figure 12.13: Lamport’s n-process bakery algorithm for shared memory mutual exclusion. Code shown is for process P i, 1 ≤ i ≤ n. for itself, j’s selection interval may have overlapped with that of i, leading to an unknown order of timestamp values. Process i needs to make sure that any other process j (j < i) that had begin to execute (1b) concurrently with itself and may still be executing (1b) does not assign itself the same timestamp. Otherwise mutual exclusion could be violated as i would enter the CS, and subsequently, j, having a lower process identifier and hence a lexicographically lower timestamp, would also enter the CS. Hence, i waits for j’s timestamp to stabilize, i.e., choosing[j] to be set to false. Once j’s timestamp is stabilized, i moves from (1e) to (1f). Either j is not requesting (in which case j’s timestamp is 0) or j is requesting. Step (1f) determines the relative priority between i and j. The process with a lexicographically lower timestamp has higher priority and enters the CS; the other process has to wait (step (1g)). Hence, mutual exclusion is satisfied. Bounded waiting is satisfied because each other process j can “overtake” process i at most once after i has completed choosing its timestamp. The second time j chooses a timestamp, the value will necessarily be larger than i’s timestamp if i has not yet entered its CS. Progress is guaranteed because the lexicographic order is a total order and the process with the lowest timestamp at any time in the loop (1d)-(1g) is guaranteed to enter the CS. Attempts to improve the bakery algorithm have lead to several important results. • Space complexity: A lower bound of n registers, specifically, the timestamp array, has been shown for the shared memory critical section problem. Thus, one cannot hope to have a more space-efficient algorithm for distributed shared memory mutual exclusion. • Time complexity: In many environments, the level of contention may be low. The O(n) 417

overhead of the entry section does not scale well for such environments. This concern is addressed by the field of fast mutual exclusion that aims to have O(1) time overhead for the entry and exit sections of the algorithm, in the absence of contention. Although this algorithm guarantees mutual exclusion and progress, unfortunately, this fast algorithm has a price – in the worst case, it does not guarantee bounded delay. Next, we will study Lamport’s algorithm for fast mutual exclusion in asynchronous shared memory systems. This algorithm is notable in that it is the first algorithm for fast mutual exclusion, and uses the asynchronous shared memory model. Further, it illustrates an important technique for resolving contention. The worst-case unbounded delay in the presence of persisting contention has been addressed subsequently, by using a timed model of execution, wherein there is an upper bound on the time it takes to execute any step. We will not discuss mutual exclusion under the timed model of execution.

12.3.2 Lamport’s WRWR Mechanism and Fast Mutual Exclusion Lamport’s fast mutual exclusion algorithm is given in Figure 12.14. The algorithm illustrates an important technique – the hW − R − W − Ri sequence that is a necessary and sufficient sequence of operations to check for contention and to ensure safety in the entry section, using only two registers. Steps (1b), (1c), (1g) , and (1h) represent a basic hW (x)−R(y)−W (y)−R(x)i sequence whose necessity in identifying a minimal sequence of operations for fast mutual exclusion is justified as follows. 1. The first operation needs to be a Write, say to variable x. If it were a Read, then all contending processes could find the value of the variable even outside the entry section. 2. The second operation cannot be a Write to another variable, for that could equally be combined with the first Write to a larger variable. The second operation should not be a Read of x because it follows Write of x and if there is no interleaved operation from another process, the Read does not provide any new information. So the second operation must be a Read of another variable, say y. 3. The sequence must also contain Read(x) and Write(y) because there is no point in reading a variable that is not written to, a writing a variable that is never read. 4. The last operation in the minimal sequence of the entry section must be a Read, as it will help determine whether the process can enter CS. So the last operation should be Read(x), and the second-last operation should be the Write(y). In the absence of contention, each process writes its own id to x and then reads y. Then finding that y has its initial value, the process writes its own id to y and then reads x. Finding x to still be its own id, it enters CS. Correctness needs to be shown in the presence of contention – let us discuss this after considering the structure of the remaining entry and exit section code. 418

(shared variables among the processes) integer: x, y; array of boolean b[1 . . . n];

// shared register initialized // flags to indicate interest in critical section

repeat (1) Pi (1 ≤ i ≤ n) executes entry section: (1a) b[i] ←− true; (1b) x ←− i; (1c) if y 6= 0 then (1d) b[i] ←− f alse; (1e) await y = 0; (1f) goto (1a); (1g) y ←− i; (1h) if x 6= i then (1i) b[i] ←− f alse; (1j) for j = 1 to N do (1k) await ¬b[j]; (1l) if y 6= i then (1m) await y = 0; (1n) goto (1a); (2) Pi (1 ≤ i ≤ n) executes critical section: (3) Pi (1 ≤ i ≤ n) executes exit section: (3a) y ←− 0; (3b) b[i] ←− f alse; forever.

Figure 12.14: Lamport’s deadlock-free fast mutual exclusion solution, using Ω(n) registers. Code is for process Pi , where 1 ≤ i ≤ n. In the exit section, the process must do a Write to indicate its completion of the CS. The Write cannot be to x which is also the first variable written in the entry section. So the operation must be Write(y). Now consider the sequence of interleaved operations by processes i, j, and k in the entry section, as shown in Figure 12.15. Process i enters its critical section, but there is no record of its identity or that it had written any variables at all, because the variables it wrote (shown boldfaced above) have been overwritten. In order that other processes can discover when (and who) leaves the CS, there needs to be another variable that is set before the CS and reset after the CS. This is the boolean, f lag[i]. Additionally, y needs to be reset on exiting the CS. The code in lines (1c)-(1f) has the following use. If a process p finds y 6= 0, then another process has executed at least line (1g) and not yet executed line (3a). So process p resets its own flag, and before retrying again, it awaits for y = 0. If process p finds y = 0 in line (1c), it sets y = p in line (1g) and checks if x = p. • If x = p, then no other process has executed line (1b), and any later process would be blocked in the line (1c)-(1f) loop now because y = p. Thus, if x = p, process p can safely 419

Process Pi

Process Pj Wj (x)

Process Pk

Wi (x) Ri (y) Rj (y) Wi (y) Wj (y) Ri (x) Wk (x) Rj (x)

variables hx = j, y = 0i hx = i, y = 0i hx = i, y = 0i hx = i, y = 0i hx = i, y = ii hx = i, y = ji hx = i, y = ji hx = k, y = ji hx = k, y = ji

Figure 12.15: An example showing the need for a boolean vector for fast mutual exclusion. enter the CS. • If x 6= p, then another process, say q, has overwritten x in line (1b) and there is a potential race. Two broad cases are possible. – Process q finds y 6= 0 in line (1c). It resets its flag, and stays in the (1d)-(1f) section at least until p has exited the CS. Process p on the other hand resets its own flag (line (1i)) and waits for all other processes such as q to reset their own flags. As process q is trapped in lines (1d)-(1f), process p will find y = i in line (1l) and enter the CS. – Process q finds y = 0 in line (1c). It sets y to q in line (1g), and enters the race, even closer to process p which is at line (1h). Of the processes such as p and q that contend at line (1h), there will be a unique winner. ∗ If no other process r has since written to x in line (1b), the winner is the process among p and q that executed line (1b) last, i.e., wrote its own id to x. That winner will enter the CS directly from line (1h), whereas the losers will reset their own flags, await the winner to exit and reset its flag, and also await other contenders at line (1h) and newer contenders to reset their own flags. The losers will compete again from line (1a) after the winner has reset y. ∗ If some other process r has since written its id to x in line (1b), both p and q will enter code in lines (1i)-(1n). Both p and q reset their flags, await for r which will be trapped in lines (1d)-(1f) to reset its flag, and then both p and q check the value of y. Between p and q, the process that last wrote to y in line (1g) will become the unique winner and enter the CS directly. The loser will then await for the winner to reset y, and then compete again from line (1a). Thus, mutual exclusion is guaranteed, and progress is also guaranteed. However, a process may be starved, although with decreasing probability, as its number of attempts increases.

420

(shared variables among the processes accessing each of the different object types) register: Reg ←− initial value; (local variables) integer: old ←− initial value;

// shared register initialized // value to be returned

(1) T est&Set(Reg) returns value: (1a) old ←− Reg; (1b) Reg ←− 1; (1c) return(old). (2) Swap(Reg, new) returns value: (2a) old ←− Reg; (2b) Reg ←− new; (2c) return(old).

Figure 12.16: Definitions of synchronization operations T est&Set and Swap. (shared variables) register: Reg ←− f alse; (local variables) integer: blocked ←− 0;

// shared register initialized // variable to be checked before entering CS

repeat (1) Pi executes the following for the entry section: (1a) blocked ←− true; (1b) repeat (1c) Swap(Reg, blocked); (1d) until blocked = f alse; (2) Pi executes the critical section (CS) after the entry section (3) Pi executes the following exit section after the CS: (3a) Reg ←− f alse; (4) Pi executes the remainder section after the exit section until false;

Figure 12.17: Mutual exclusion using Swap. Code shown is for process P i, 1 ≤ i ≤ n.

12.3.3 Hardware support for mutual exclusion Hardware support can allow for special instructions that perform two or more operations atomically. Two such instructions, T est&Set and Swap, are defined and implemented as shown in Figure 14.22. The atomic execution of two actions (a Read and a W rite operation) can greatly simplify a mutual excluison algorithm, as seen from the mutual exclusion code in Figure 12.17 and Figure 12.18, respectively. The algorithm in Figure 12.17 can lead to starvation. The algorithm in Figure 12.18 is enhanced to guarantee bounded waiting by using a “round-robin” policy to selectively grant permission when releasing the critical section.

421

(shared variables) register: Reg ←− f alse; array of boolean: waiting[1 . . . n]; (local variables) integer: blocked ←− initial value;

// shared register initialized

// value to be checked before entering CS

repeat (1) Pi executes the following for the entry section: (1a) waiting[i] ←− true; (1b) blocked ←− true; (1c) while waiting[i] and blocked do (1d) blocked ←− T est&Set(Reg); (1e) waiting[i] ←− f alse; (2) Pi executes the critical section (CS) after the entry section (3) Pi executes the following exit section after the CS: (3a) next ←− (i + 1)mod n; (3b) while next 6= i and waiting[next] = f alse do (3c) next ←− (next + 1)mod n; (3d) if next = i then (3e) Reg ←− f alse; (3f) else waiting[j] ←− f alse; (4) Pi executes the remainder section after the exit section until false;

Figure 12.18: Mutual exclusion with bounded waiting, using T est&Set. Code shown is for process P i, 1 ≤ i ≤ n.

12.4 Wait-freedom Processes that interact with each other, whether by message passing or by shared memory, need to synchronize their interactions. Traditional solutions to synchronize asynchronous processes via shared memory objects (also called concurrent objects) use solutions based on locking, busy waiting, critical sections, semaphores, or conditional waiting. An arbitrary delay of a process or its crash failure can prevent other processes from completing their operations. This is undesirable. Wait-freedom is a property that guarantees that any process can complete any synchronization operation in a finite number of lower-level steps, irrespective of the execution speed of other processes. More precisely, a wait-free implementation of a concurrent object guarantees that any process can complete an operation on it in a finite number of steps, irrespective of whether other processes crash or encounter unexpected delays. Thus, processes that crash, or encounter unexpected delays (such as delays due to high processor load, swapping out of memory, or CPU schedulng policies) should not delay other processes in a wait-free implementation of a concurrent object. Not all synchronizations have wait-free solutions. As a trivial example, a producer-consumer synchronization between two processes cannot be implemented in a wait-free manner if the pro422

ducer process crashes before posting its value – the consumer is necessarily blocked. Nevertheless, the notion of wait-freedom is an important concept in designing fault-tolerant systems and algorithms whenever possible. An alternate view of wait-freedom in terms of fault-tolerance is as follows. • An f -resilient system is a system in which up to f of the n processes can fail, and the other n − f processes can complete all their operations in a finite number of steps, independent of the states of the f processes that may fail. • When f = n − 1, any process is guaranteed to be able to complete its operations in a finite number of steps, independent of all other processes. A process does not depend on other processes, and its execution is therefore said to be wait-free. Wait-freedom provides independence from the behavior of other processes, and is therefore a very desirable property. In the remainder of this chapter which deals with shared register accesses, only wait-free solutions are considered.

12.5 Register Hierarchy and Wait-free Simulations Observe from our analysis of DSM consistency models that an underlying assumption was that any memory access takes a finite time interval, and the operation, whether a Read or Write, takes effect at some point during this time duration. In the face of concurrent accesses to a memory location, we cannot predict the outcome. In particular, in the face of a concurrent Read and Write operation, the value returned by the Read is unpredictable. This observation is true even for a simpler multiprocessor memory, without the context of a DSM. This observation led to the research area that tried to define the properties of access orderings for the most elementary memory unit, hereafter called a register. The access orderings depend on the properties of the register. An implicit assumption is that of the availability of global time. This is a reasonable assumption because we are studying access to a single register. Whether that register value is replicated in the system or not is a lower detail that is not relevant to the level of abstraction of this analysis. In keeping with the semantics of the Read and Write operations, the following register types have been identified to specify the value returned to a Read in the face of a concurrent Write operation. For the time being, we assume that there is a single reader process and a single writer process. Safe register: A Read operation that does not overlap with a Write operation returns the most recent value written to that register. A Read operation that does overlaps with a Write operation returns any one of the values that the register could possibly contain at any time. Consider the example of Figure 12.19 which shows several operations on an integer-valued register. We consider two cases, without and with the Write by P3 .

423

Write1 (x,4) P1

Write21 (x,6)

1

Read12 (x,?) Read22 (x,?)

P2

Read32 (x,?)

Write13 (x,−6)

P3

Figure 12.19: Examples to illustrate definitions of safe, regular, and atomic registers. The regular lines assume a SRSW register. If the dashed line is also used, the register is assumed to be SRMW. No Write by P3 : If the register is safe, Read12 must return the value 4, whereas Read22 and Read32 can return any possible integer (up to MAXINT) because these operations overlap with a Write, and the value returned is therefore ambiguous. Write by P3 : Same as for the “no Write” case. If multiple writers are allowed, or if Write operations are allowed to be pipelined, then what defines the most recent value of the register in the face of concurrent Write operations becomes complicated. We explicitly disallow pipelining in this model and analysis. In the face of Write operations from different processors that overlap in time, the notion of a serialization point is defined. Observe that each Write or Read operation has a finite duration between its invocation and its response. In this duration, there is effectively a single time instant at which the operation takes effect. For a Read operation, this instant is the one at which the instantaneous value is selected to be returned. For a Write operation, this instant is the one at which the value written is first ‘reflected’ in the register. Using this notion of the serialization point, the ‘most recent’ operation is unambiguously defined. Regular register: In addition to being a safe register, a Read that is concurrent with a Write operation returns either the value before the Write operation, or the value written by the Write operation. In the example of Figure 12.19, we consider the two cases, with and without the Write by P3 . No Write by P3 : Read12 must return 4, whereas Read22 can return either 4 or 6, and Read32 can also return either 4 or 6. Write by P3 : Read12 must return 4, whereas Read22 can return either 4 or -6 or 6, and Read32 can also return either 4 or -6 or 6. Atomic register: In addition to being a regular register, the register is linearizable (defined in Section 12.2.1) to a sequential register. In the example of Figure 12.19, we consider the two cases, with and without the Write by P3 . 424

Type safe regular atomic

Value binary integer

Writing Single-Writer Multi-Writer

Reading Single-Reader Multi-Reader

Table 12.2: Classification of registers by Type, Value, Writing Access, and Reading Access. The strength of the register increases down each column.

No Write by P3 : Read12 must return 4, whereas Read22 can return either 4 or 6. If Read22 returns 4, then Read32 can return either 4 or 6, but if Read22 returns 6, then Read32 must also return 6. Write by P3 : Read12 must return 4, whereas Read22 can return either 4 or -6 or 6, depending on the serialization points of the operations. 1. If Read22 returns 6 and the serialization point of Write13 precedes the serialization point of Write21, then Read32 must return 6. 2. If Read22 returns 6 and the serialization point of Write21 precedes the serialization point of Write13, then Read32 can return +6 or -6. 3. Cases (3) and (4) where Read22 returns -6 are similar to cases (1) and (2). The following properties, summarized in Table 12.2, characterize registers. • whether the register is single-valued (boolean) or multi-valued • whether the register is a single-reader (SR) or multi-reader (MR) register • whether the register is a single-writer (SW) or multi-writer (MW) register • whether the register is safe, regular, or atomic The above characteristics lead to a hierarchy of 24 register types, with the most elementary being the boolean SRSW safe register and the most complex being the multi-valued MRMW atomic register. A study of register construction deals with designing the more complex registers using simpler registers. Such constructions allow us to construct any register type from the most elementary register – the boolean SRSW safe register. We will study such constructions by assuming the following convention. R1 . . . Rq are q registers that are used to construct a stronger register R, as shown in Figure 12.20. We assume n processes exist; note that for various constructions, q may be different from n. Although the traditional memory architecture, based on serialized access via memory ports to a memory location, does not require such an elaborate classification, the bigger picture needs to be kept in mind. In addition to illustrating algorithmic design techniques, this study paves the way for accommodating newer technologies such as – quantum computing and DNA computing – for constructing system memory. 425

Write to

R

R

Writes to individual Ri

R1

Rq

Reads from individual Ri

Read from

R

Figure 12.20: Register simulations. (shared variables) SRSW safe registers R1 . . . Rn ←− 0;

// Ri is readable by Pi , writable by P0

(1) Write(R, val) executed by single writer P0 (1a) for all i ∈ {1 . . . n} do (1b) Ri ←− val. (2) Readi (R, val) executed by reader Pi , 1 ≤ i ≤ n (2a) val ←− Ri (2b) return(val).

Figure 12.21: Construction 1: SRSW Safe register to MRSW Safe register R. This construction can also be used for SRSW Regular register to MRSW Regular register R.

12.5.1 Construction 1: SRSW Safe to MRSW Safe Figure 12.21 gives the construction of a MRSW safe register R using only SRSW safe registers. Assume the single writer is process P0 and the n reader processes are P1 to Pn . Each of the n processes Pi can read only SRSW register Ri . As multiple readers are not allowed to access the same register, in essence, the data needs to be replicated. So in the construction, the writer P0 writes the same value to the n registers. Register Ri is read by Pi . In Figure 12.20, the value of q would hence be n. When a Read by Pi and a Write by P0 do not overlap their access to Ri , the Read obtains the correct value. When a Read by Pi and a Write by P0 overlap their access to Ri , as Ri is a safe register, Pi reads a legitimate value from Ri . Complexity: This construction has a space complexity of n times the size of a single register, which may be either binary or integer-valued. The time complexity is n steps.

426

(shared variables) boolean MRSW safe registers R1 . . . Rlog(m) ←− 0;

// Ri readable by all, writable by P0 .

(local variable) array of boolean: V al[1 . . . log(m)]; (1) Write(R, V al[1 . . . log m]) executed by single writer P0 (1a) for all i ∈ {1 . . . log(m)} do (1b) Ri ←− V al[i]. (2) Readi (R, V al[1 . . . log(m)]) executed by reader Pi , 1 ≤ i ≤ n (2a) for all j ∈ {1 . . . log m} do V al[j] ←− Rj (2b) return(V al[1 . . . log(m)]).

Figure 12.22: Construction 3: boolean MRSW Safe register to integer-valued MRSW Safe register R.

12.5.2 Construction 2: SRSW Regular to MRSW Regular This construction is identical to Construction 1 (Figure 12.21) except that regular registers are used instead of safe registers. When a Read by Pi and a Write by P0 do not overlap their access to Ri , the Read obtains the correct value. When a Read by Pi and a Write by P0 overlap their access to Ri , as Ri is a regular register, Pi reads from Ri either the earlier value or the value being written. Complexity: This construction has a space complexity of n times the size of a single register, which may be either binary or integer-valued. The time complexity is n steps.

12.5.3 Construction 3: Boolean MRSW Safe to integer-valued MRSW Safe Figure 12.22 gives the construction of an integer-valued MRSW safe register R. Assume the single writer is process P0 and the n reader processes are P1 to Pn . The construction can use only boolean MRSW registers – to construct an integer register of size m, at least log(m) boolean registers are necessary. So in the construction, the writer P0 writes the value in its binary notation to the log(m) registers R1 to Rlog(m) . Similarly, any reader reads registers Ri to Rlog(m) . When a Read by Pi and a Write by P0 do not overlap, the Read obtains the correct value. When a Read by Pi and a Write by P0 overlap their access to the registers, as the Ri (i = 1 to log(m)) registers are safe, Pi reads a legitimate value. Complexity: This construction has a space complexity of log(m) times the size of an integer m. The time complexity is O(log(m)) steps.

12.5.4 Construction 4: Boolean MRSW Safe to boolean MRSW Regular Figure 12.23 gives the construction of a boolean MRSW regular register R from a MRSW safe register. Assume the single writer is process P0 and the reader processes are Pi (1 ≤ i ≤ n). With 427

(shared variables) boolean MRSW safe register: R′ ←− 0;

// R′ is readable by all, writable by P0 .

(local variables) boolean local to writer P0 : previous ←− 0; (1) Write(R, val) executed by single writer P0 (1a) if previous 6= val then (1b) R′ ←− val; (1c) previous ←− val. (2) Read(R, val) process Pi , 1 ≤ i ≤ n (2a) val ←− R′ ; (2b) return(val).

Figure 12.23: Construction 4: boolean MRSW Safe register to boolean MRSW Regular register R. respect to Figure 12.20, q has the value 1. P0 writes R1 and all the n processes read R1 . When a Read by Pi and a Write by P0 do not overlap, the Read obtains the correct value. When a Read by Pi and a Write by P0 overlap, the safe register may not necessarily return the overlapping or the previous value (as required by a regular register), but may return a value written much earlier. If the value written before the Read begins is α, and the value being written by the concurrent Write is also α, the Read could return α or (1 − α) from the safe register, which is a problem for the regular register. The solution bypasses this problem by having the Write use a local variable previous to track the previous value of val. If the previous value that was written (line (1b)) and stored in previous (line (1c)) is the same as the new value to be written, then the new value is simply not written. This avoids any concurrent access to R. Complexity: This construction uses O(1) space and time. Can the above construction also construct a binary SRSW atomic register from a safe register? No. Consider P1 issues a Write11(α) that completes; then Write21(1 − α) begins and overlaps with Read12 and Read22 of P2 . With the above construction, Read12 could return 1 − α whereas the later Read22 could return α, thus violating the property of an atomic register.

12.5.5 Construction 5: Boolean MRSW Regular to integer-valued MRSW Regular Figure 12.24 gives the construction of an integer-valued MRSW regular register R using boolean MRSW regular registers. Assume the single writer is process P0 and the n reader processes are P1 to Pn . The construction can use only boolean MRSW registers – to construct an integer register of size m, unary notation is used, so m boolean registers are necessary. In Figure 12.20, q = m, and all the n processes can read all the q registers. 428

(shared variables) boolean MRSW regular registers R1 . . . Rm−1 ←− 0; Rm ←− 1;

// Ri readable by all, writable by P0 .

(local variables) integer: count; (1) Write(R, val) executed by writer P0 (1a) Rval ←− 1; (1b) for count = val − 1 down to 1 do (1c) Rcount ←− 0. (2) Readi (R, val) executed by Pi , 1 ≤ i ≤ n (2a) count = 1; (2b) while Rcount = 0 do (2c) count ←− count + 1; (2d) val ←− count; (2e) return(val).

Figure 12.24: Construction 5: boolean MRSW Regular register to integer-valued MRSW Regular register R. When a Read by Pi and a Write by P0 do not overlap, the Read obtains the correct value. To deal with a Read by Pi and a Write(s) by P0 overlapping their access to the registers, the following approach is used. A reader Pi scans left-to-right looking for a ‘1’ whereas the P0 writer process writes ‘1’ to the Rval location and then zeros out entries right-to-left. The Read is guaranteed to see a ‘1’ written by one of the Write operations it overlaps with, or the ‘1’ written by the Write that completed just before the Read began. As each of the bits are regular, its current or previous value is read; if the value is ‘0’, it is guaranteed that a ‘1’ has been written to the right. An implicit assumption here is the integer size, bounded by the number of bits in use. The register is initialized by this largest value. The construction is illustrated in Figure 12.25. In the figure, the reader scans from left to right as marked. Complexity: This construction uses m binary registers, where m is the largest integer that can be written by the application.

12.5.6 Construction 6: Boolean MRSW Regular to integer-valued MRSW Atomic Can the above construction (Figure 12.24) also construct an integer-valued MRSW atomic register from boolean MRSW regular registers? No. The problem is that when two successive Read operations overlap Write operations, ‘inversion’ of values returned by the Read operations can occur. Consider the following sequence of operations, depicted in Figure 12.26.

429

Write val to R

R Zero out entries

R1

Write 1

R val

R2 R3

Rm

Scan for "1"; return index. (bool MRSW reg to int MRSW reg) Scan for first "1"; then scan backwards and update pointer to lowest−ranked register containing a "1" (bool MRSW atomic to int MRSW atomic)

Read( R ) Figure 12.25: Illustrating Constructions 5 and 6.

Write2 a(R,3)

Write1 a(R,2) Write(R2 ,1)

Write(R1 ,0) Write(R3 ,1) Write(R2 ,0) Write(R ,0) 1

111111111111111111111111111111 Pa 000000000000000000000000000000

Read(R 1,0) Read(R 2,0) Read(R 3,1) Read(R 1,0) Read(R 2,1) 111111111111111111111111111111 000000000000000000000000000000

Pb

Read1 b(R,?) returns 3

Read2 b(R,?) returns 2

Figure 12.26: Example to illustrate inversion of values read by Pa and Pb . 1. Write1a (R, 2): The low-level operation W rite(R2 , 1) begins, i.e., R2 ←− 1 begins. 2. Read1b (R, ?): The following low-level operations get executed. count ←− 1; Read(Rcount , 0); count ←− 2; Read(Rcount , 0); count ←− 3; 3. Write1a (R, 2): The low-level operation W rite(R2 , 1) from step 1 completes, i.e., the value ‘1’ gets written to R2 ; then the left scan to zero out R1 proceeds by executing Write(R1 , 0). 4. Write2a (R, 3): The low-level operation Write(R3 , 1) executes, i.e., R3 ←− 1 begins and ends. 5. Read1b (R, ?): The low-level operation Read(Rcount=3 , ?) that was to begin after step 2 returns 1; the high-level Read completes and returns a value of 3. 430

(shared variables) boolean MRSW regular registers R1 . . . Rm−1 ←− 0; Rm ←− 1.

// Ri readable by all; writable by P0 .

(local variables) integer: count, temp; (1) Write(R, val) executed by P0 (1a) Rval ←− 1; (1b) for count = val − 1 down to 1 do (1c) Rcount ←− 0. (2) Readi (R, val) executed by Pi , 1 ≤ i ≤ n (2a) count ←− 1; (2b) while Rcount = 0 do (2c) count ←− count + 1; (2d) val ←− count; (2e) for temp = count down to 1 do (2f) if Rtemp = 1 then (2g) val ←− temp; (2h) return(val).

Figure 12.27: Construction 6: boolean MRSW Regular register to integer-valued MRSW Atomic register R. 6. Read2b (R, ?): This operation’s left-to right scan for a ‘1’ finds R2 = 1 and returns 2. This is because the low-level operation Write2(R2 , 0) belonging to the high-level operation Write2a (R, 3) has not yet zeroed out R2 . Here, Read2b (R, 2) returns the value written by Write1a (R, 2); whereas the earlier Read1b (R, 3) returns the value written by the later Write2a(R, 3). Hence, this execution is not linearizable. Figure 12.27 gives the construction of a integer-valued MRSW atomic register R by modifying the above solution as follows. The reader makes a right-to-left scan for a ‘1’ after its left-to-right scan completes. If it finds a ‘1’ in a lower index, it updates the value to be returned to this index. The purpose is to make sure that the lowest index (say α) in which a ‘1’ is found in this second ‘right-to-left’ scan is returned by the Read. As the writer also zeros out entries ‘right-to-left’, it is not possible that a later Read will find a ‘1’ written earlier in a position lower than α, by a Write that occurred earlier than the Write which wrote α. This allows a linearizable execution. With respect to Figure 12.20, q = m, and all the n processes can read all the q registers. This construction is also illustrated in Figure 12.25, as marked therein. A formal argument that this construction is correct needs to show that any execution is linearizable. To do so, it would define the linearization point of a Read and Write operation to capture the notion of the exact instant at which that operation effectively appears to take effect. • The value of the MRSW register at any moment is x, where Rx = 1 and ∀y < x, Ry = 0. 431

• The linearization point of a Write(R, x) operation is the first instant (line (1a) or (1c)) when Rx = 1 and ∀y < x, Ry = 0. • The linearization point of a Read(R, val) that returns (x) is the first instant (line (2d) or (2g)) when val gets assigned x in the low-level operations. The following observation can now be made from the construction and the definition of the linearization point of a Write. • The value of the MRSW register remains unchanged between the linearization points of any two consecutive Write operations. The Write operations are naturally ordered in the linearization sequence. In order to determine a complete linearization of the Read operations in addition to the Write operations, observe the following. • A Read operation returns the value written by that Write operation which has the latest linearization point that precedes the Read operation’s linearization point. It naturally follows that a later Read will never return the value written by a earlier Write, and hence the construction is linearizable. Complexity: This construction uses m binary registers, where m is the largest integer that is written by the application program. The time complexity is O(m).

12.5.7 Construction 7: Integer MRSW Atomic to integer MRMW Atomic We are given MRSW atomic registers, i.e., each register has only a single writer. To simulate a MRMW atomic register R, the variable has multiple copies, R1 . . . Rn , one per writer process. Writer Pi can only write to its copy Ri . Reader Pi can read all the registers R1 . . . Rn . When concurrent updates occur, a global linearization order must be created somehow. The Read operations must be able to recognize such a global order, and then return the appropriate version as per the semantics of the atomic register. That is the challenge. The construction is shown in Figure 12.28. With respect to Figure 12.20, q = n, and all the n processes can read all the q MRSW registers but only Pi can write to Ri . The idea used is similar to that used by the Bakery algorithm for mutual exclusion (Section 12.3.1), wherein each process competing for the critical section first sets its flag (behaving as the writer process) signalling its intention. The competing processes that make concurrent accesses (behaving as the reader processes) then read all the flags and deduce a global order that resolves the contention. Each register Ri has two fields: Ri .data and Ri .tag, where tag = hseq_no, pidi. A lexicographic order is defined on the tags, using seq_no as the primary key, and then pid as the secondary key. A common procedure invoked by the readers and writers is the Collect which reads all the registers, in no particular order. The reader returns the data corresponding to the (lexicographically) most recent Write. A writer chooses a tag greater than the (lexicographically) greatest tag returned by the Collect, when it writes its new value. 432

(shared variables) MRSW atomic registers of type hdata, tagi, where tag = hseq_no, pidi: R1 . . . Rn ; (local variables) array of MRSW atomic registers of type hdata, tagi, where tag = hseq_no, pidi: Reg_Array[1 . . . n]; integer: seq_no, j, k; (1) Writei (R, val) executed by Pi , 1 ≤ i ≤ n (1a) Reg_Array ←− Collect(R1 , . . . , Rn ); (1b) seq_no ←− max(Reg_Array[1].tag.seq_no, . . . Reg_Array[n].tag.seq_no) + 1; (1c) Ri ←− (val, hseq_no, ii). (2) Readi (R, val) executed by Pi , 1 ≤ i ≤ n (2a) Reg_Array ←− Collect(R1 , . . . , Rn ); (2b) identify j such that for all k 6= j, Reg_Array[j].tag > Reg_Array[k].tag; (2c) val ←− Reg_Array[j].data; (2d) return(val). (3) Collect(R1 , . . . , Rn ) invoked by Read and Write routines (3a) for j = 1 to n do (3b) Reg_Array[j] ←− Rj ; (3c) return(Reg_Array).

Figure 12.28: Construction 7: integer MRSW Atomic register to integer MRMW Atomic register R. All the Write operations are lexicographically totally ordered. Each Read is ordered so that it immediately follows that Write with the matching tag. Thus, this execution is linearizable. Complexity: This construction uses m binary registers, where m is the largest integer written by the application. The time complexity is O(m).

12.5.8 Construction 8: Integer SRSW Atomic to integer MRSW Atomic We are given SRSW atomic registers. To simulate a MRSW atomic register R, the variable has multiple copies, R1 , . . . Rn , one per reader process. The single writer can write to all of these registers. A first attempt at this construction would have the writer write to all the registers R1 . . . Rn , whereas reader Pi reads Ri . In Figure 12.20, q = n, and each Ri is read by Pi and written to by the single writer P0 . However, such a construction does not give a linearizable execution. Consider two reads Read1i and Read2j that both overlap a Write and Read2 begins after Read1 terminates. It is possible that: 1. Read1i reads Ri after the Write has written to Ri 2. but Read2j reads Rj before the writer has had a chance to update Rj . 433

1,1

1,2

1,n

R1

R2

Rn

SRSW atomic registers, one per process

2,1

2,2

2,n

mailboxes Last_Read_Values[1..n,1..n] (SRSW atomic registers)

n,1

n,2

n,n

R

Figure 12.29: Illustrating the data structures for Construction 8. This results in a non-linearizable execution. The problem above arose because a reader did not have access to what other readers read; in particular, a reader Pi cannot tell if another Read by Pj that completed before this Read began got a value that is newer than the value that the writer has written to Ri . In fact, performing multiple reads by the Pi processes, and/or more writes by P0 , and/or using more registers cannot solve this problem. To fix this problem, a reader process Pi must choose the latest of the values that other reader processes have last read, and the value in Ri . As only SRSW registers are available, unfortunately, this requires communication between each pair of reader processes, leading to O(n2 ) variables. Thus, a reader process must also write! An array Last_Read_V alues[1 . . . n, 1 . . . n] is used for this purpose. Last_Read_V alues[i, j] is the value that Pi ’s last Read returned, which Pi has set aside for Pj to know about. Once a reader Pi determines the latest of the values that other readers read (lines 2(b-d)), and the value written for it by the writer process (line 2a), the reader publishes this value in Last_Read_V alues[i, ∗] (lines 2e-2f). As there is a single writer, the format hdata, seq_noi for each register value and each Last_Read_V alue entry is adequate to give a total order on all the values written by it. The construction is shown in Figure 12.30 and illustrated in Figure 12.29. Here, q = n2 + n as there are n2 SRSW registers that act as personalized mailboxes between pairs of processes, and the n registers that are the mailboxs between writer P0 and each reader Pi . Complexity: This construction uses O(n2) integer registers. The time complexity is O(n). Achieving linearizability: All the Write operations form a total order. A Read by Pi returns the value of the latest preceding Write, as observed directly from the register Ri , or indirectly from the register Rj and communicated to Pi via Last_Read_V alues. In a linearized execution, a Read is placed after the Write whose value it reads. For nonoverlapping Reads, their relative order

434

(shared variables) SRSW atomic register of type hdata, seq_noi, where data, seq_no are integers: R1 . . . Rn ←− h0, 0i; SRSW atomic register array of type hdata, seq_noi, where data, seq_no are integers: Last_Read_V alues[1 . . . n, 1 . . . n] ←− h0, 0i; (local variables) array of hdata, seq_noi: Last_Read[0 . . . n]; integer: seq, count; (1) Write(R, val) executed by writer P0 (1a) seq ←− seq + 1; (1b) for count = 1 to n do (1c) Rcount ←− hval, seqi. // write to each SRSW register (2) Readi (R, val) executed by Pi , 1 ≤ i ≤ n (2a) hLast_Read[0].data, Last_Read[0].seq_noi ←− Ri ; // Last_Read[0] stores value of Ri (2b) for count = 1 to n do // read into Last_Read[count], the latest values stored for Pi by Pcount (2c) hLast_Read[count].data, Last_Read[count].seq_noi ←− hLast_Read_V alues[count, i].data, Last_Read_V alues[count, i].seq_noi; (2d) identify j such that for all k 6= j, Last_Read[j].seq_no ≥ Last_Read[k].seq_no; (2e) for count = 1 to n do (2f) hLast_Read_V alues[i, count].data, Last_Read_V alues[i, count].seq_noi ←− hLast_Read[j].data, Last_Read[j].seq_noi; (2g) val ←− Last_Read[j].data; (2h) return(val).

Figure 12.30: Construction 8: integer SRSW Atomic register to integer MRSW Atomic register R. represents the order in a linearizable execution, because of the indirect communication among readers. For overlapping Reads, their ordering in a linearized execution is consistent with the Writes whose values they read. Hence, the construction is a valid construction.

12.6 Wait-free Atomic Snapshots of Shared Objects Observing the global state of a distributed system is a fundamental problem. For message-passing systems, we have studied how to record global snapshots which represent an instantaneous possible global state that could have occurred in the execution. The snapshot algorithms used messagepassing of control messages, and were inherently inhibition-free, although some variants that use fewer control messages do require inhibition. In this section, we examine the counterpart of the global snapshot problem in a shared-memory system, where only Read and Write primitives can be used. The problem can be modeled as follows. Given a set of SWMR atomic registers R1 . . . Rn , where Ri can be written only by Pi and can 435

P1

Scan

Scan

UPDATE data seq_no old_snapshot R1

Pn UPDATE

data seq_no old_snapshot Rn

snapshot object composed of n MRSW atomic registers Figure 12.31: Atomic snapshot object, using MRSW atomic registers. be read by all processes, and which together form a compound high-level object, devise a wait-free algorithm to observe the state of the object at some instant in time. The following actions are allowed on this high-level object, as also illustrated in Figure 12.31. • Scani : This action invoked by Pi returns the atomic snapshot which is an instantaneous view of the object (R1 , . . . , Rn ) at some instant between the invocation and termination of the Scan. • Updatei(val): This action invoked by Pi writes the data val to register Ri . Clearly, any kind of locking mechanism is unacceptable because it is not wait-free. Consider the following attempt at a wait-free solution. The format of each register Ri is assumed to be the tuple: hdata, seq_noi in order to uniquely identify each Write operation to the register. A scanner would repeatedly scan the high-level object until two consecutive scans, called double-collect in the shared memory context, returned identical content. This principle of “double-collect” has been encountered in multiple contexts, such in two-phase deadlock detection and two-phase termination detection algorithms, and essentially embodies the two-phase observation rule (see Chapter 11. However, this solution in not wait-free because between the two observations of each doublecollect, an Update by another process can prevent the Scan from being successful. A wait-free solution is given in Figure 12.33. Process Pi can write to its MRSW register Ri and can read all registers R1 , . . . Rn . To design a wait-free solution, it needs to be ensured that a scanner is not indefinitely prevented from getting identical scans in the double-collect, by some writer process periodically making updates. The problem arises because of the imbalance in the roles of the scanner and updater – the updater is inherently more powerful in that it can prevent all scanners from being successful. One elegant solution therefore neutralizes the unfair advantage of the updaters by forcing the updaters to follow the same rules as the scanner. Namely, the updaters also have to perform a double-collect, and only after performing a double-collect can an updater write the value it needs to! Additionally, an updater also writes the snapshot it collected in the 436

Double collect Collect Collect

Pi (a) Double collect sees identical values in both Collects j

j

P i

j changed[j]=1

Pj writes in this period

Pj

j changed[j]=2 Pj writes in this period

Pj writes

Pj writes

(b) P_j’s Double−Collect nested within P_i’s SCAN. The Double−Collect is successful, or P_j borrowed snapshot from P_k’s Double−Collect nested within P_j’s SCAN. And so on recursively, up to n times.

Figure 12.32: Nesting of double-collects, in Scanning for Atomic snapshots of object. register, along with the new value of the data item. Now, if a scanner detects that an updater has made an update after the scanner initiated its Scan, then the scanner can simply ‘borrow’ the snapshot recorded by the updater in its register. The updater helps the scanner to obtain a consistent value. This is the principle of “helping” that is often used in designing wait-free solutions for various problems. A scanner detects that an updater has made an update after the scanner initiated its Scan, by using the local array changed. This array is reset to 0 when the Scan is invoked. Location changed[k] is incremented (line (2k)) if the Scan procedure detects (line (2j)) that process Pk has changed its data and seq_no (and implicitly the old_snapshot) fields in Rk . Based on the value of changed[k], different inferences can be made, as now explained with the help of Figure 12.32. • If changed[k] = 2 (line (2l)), then two updates (line (1b)) were made by Pk after Pi began its Scan. Between the first and the second update, the Scan preceding the second update must have completed successfully, and the scanned value was recorded in the old_snapshot field. This old snapshot can be safely borrowed by the scanner Pi (line (2m)) because it was recorded after Pk finished its first double-collect, and hence after the scanner Pi initiated its Scan. • However, if changed[k] = 1, it cannot be inferred that the old_snapshot recorded by Pk was taken after Pi ’s Scan began. When Pk does its Update (the first ‘write’ shown in Figure 12.32(b)), the value it writes in old_snapshot is only the result of a double-scan that preceded the ‘write’ and may be a value that existed before Pi ’s Scan began. There are two cases by which a snapshot can be captured, as illustrated using Figure 12.32. 437

1. A scanner can collect a snapshot (line (2g)) if the double-collect (lines (2d-2e)) returns identical views (line (2f)). See Figure 12.32(a). The returned snapshot represents an instantaneous state that existed at all times between the end of the first collect (line (2d)) and the start of the second collect (line (2e)). 2. Otherwise the scanner returns a borrowed snapshot (line (2m)) from Pk if Pk has been noticed to have made two updates (lines (2l)) and therefore Pk has made a Scan embedded inside Pi ’s Scan. This borrowed snapshot itself (i) may have been obtained directly via a double-collect, or (ii) indirectly been borrowed from another process (line (2l)). In case (i), it represents an instantaneous state in the duration of the double-collect. In case (ii), a recursive argument can be applied. Observe that there are n processes, so the recursive argument can hold at most n times. The n + 1th time, a double-collect must have been successful. See Figure 12.32(b). Note that between the two double-collects of Pi that are shown, there may be up to n − 2 other unsuccessful double-collects of Pi . Each of these (n − 2) other double-collects corresponds to some Pk k 6= i, j, having ‘changed’ once. The linearization of the Scan and Update operations follows in a straightforward manner. For example, nonoverlapping operations get linearized in the order of their occurrence. An operation by Pi that borrows a snapshot from Pk gets linearized after Pk . Complexity: The space complexity is O(n) integers. The shared space is O(n2 ) corresponding to each of the n registers of size O(n) each. The time complexity is O(n2 ). This is because the main Scan loop has a complexity of O(n) and the loop may be executed at most (n + 1) times – the n + 1-th time, at least one process Pk must have caused Pi ’s local changed[k] to reach a value of two, triggering an end to the loop (lines (2k-2l)).

12.7 Chapter Summary Distributed shared memory (DSM) is an abstraction whereby distributed programs can communicate with memory operations (Read and Write) as opposed to using message-passing. The main motivation is to simplify the burden on the programmers. The chapter surveyed this and other motivating factors for DSMs, as well as provided different ways to classify DSMs. The DSM has to be implemented by the middleware layer. Furthermore, in the face of concurrent operations on the shared variables, the expected behaviour seen by the programmers should be well-defined. The chapter examined the following consistency models – linearizability, sequential consistency, causal consistency, pipelined RAM (PRAM), and slow memory. Each model is a contract between the programmer and the system provider because the program logic must adhere to the consistency model being provided by the middleware. The chapter then examined the fundamental problem of mutal exclusion. The well-known Bakery algorithm was studied first. Next, Lamport’s algorithm for fast mutual exclusion – which gives an O(1) complexity when there are no contentions – was studied. Mutual exclusion using hardware instructions – Test&Set and Swap – was then examined. Such hardware instructions can 438

(shared variables) MRSW atomic register of type hdata, seq_no, old_snapshoti, where data, seq_no are of type integer, and old_snapshot[1 . . . n] is array of integer: R1 . . . Rn ; (local variables) array of int: changed[1 . . . n]; array of type hdata, seq_no, old_snapshoti: v1[1 . . . n], v2[1 . . . n], v[1 . . . n]; (1) Updatei (x) (1a) v[1 . . . n] ←− Scani ; (1b) Ri ←− (x, Ri .seq_no + 1, v[1 . . . n]). (2) Scani (2a) for count = 1 to n do (2b) changed[count] ←− 0; (2c) while true do (2d) v1[1 . . . n] ←− collect(); (2e) v2[1 . . . n] ←− collect(); (2f) if (∀k, 1 ≤ k ≤ n)(v1[k].seq_no = v2[k].seq_no) then (2g) return(v2[1].data, . . . , v2[n].data); (2h) else (2i) for k = 1 to n do (2j) if v1[k].seq_no 6= v2[k].seq_no then (2k) changed[k] ←− changed[k] + 1; (2l) if changed[k] = 2 then (2m) return(v2[k].old_snapshot).

Figure 12.33: Wait-free atomic snapshot of a shared MRSW object. perform a Read operation and a Write operation atomically. Hence, they are powerful, are also expensive to implement in a machine. In the context of DSM mutual exclusion, and more generally, DSM synchronization operations, fault-tolerance was then examined. The notion of wait freedom, which is the ability to complete all the operations of a process, irrespective of the behavior of other processes. This makes the system n − 1 fault tolerant. Next, wait-free register constructions were considered. Registers can be classified as being binary or multi-valued. An orthogonal classification allows single-reader or multiple reader, single-writer or multiple writer registers. Also orthogonally, registers cab be safe, regular, or atomic. This allows 24 possible configurations. The chapter considered some of these 24 possible wait-free constructions. The constructions provide insight into how different techniqes can be used in the DSM setting. Lastly, wait-free atomic snapshots of shared objects was considered. For an object, reading its value atomically in a wait-free manner (without locking) gives a ”instantaneous" snapshot of its state. Hence, this is an important problem for DSMs.

439

12.8 Bibliographic Notes A good survey on distributed shared memory systems is given by Protic, Tomasevic, and Milutinovic [25]. This includes coverage of the various DSM systems such as Firefly, Sequent, Alewife, Dash, Butterfly, CM∗ , Ivy, Mirage, Midway, Munin, Linda and Orca. The sequential consistency model was defined by Lamport [16]. The linearizability model was formalized by Lamport [18] and developed by Herlihy and Wing [10]. The implementations of linearizability and sequential consistency based on the broadcast primitive and assuming full replication are from Attiya and Welch [5], whereas a similar implementation of sequential consistency is given by Bal, Kaashoek, and Tanenbaum [6]. The causal consistency model was proposed by [3]. The PRAM model was proposed by Lipton and Sandberg [22]. The slow memory model was proposed by Hutto and Ahamad [11]. Other consistency models such as weak consistency [8], release consistency [9], and entry consistency [7] that apply to selected instructions in the code, were developed mainly in the computer architecture research community, and are discussed in [2, 1]. The bakery algorithm for mutual exclusion was presented by Lamport [21]. The fast mutual exclusion algorithm was presented by Lamport [19]. The two-process mutual exclusion algorithm was presented by Peterson [23]. Its modification that is asked as Exercise 11 is based on the algorithm by Peterson and Fischer [24]. The notion of wait-freedom was proposed by Lamport [21] and developed by Herlihy [12]. The definition and classification of registers as safe, regular, and atomic were given by Lamport [17, 18, 19]. Constructions 1 to 5 were proposed by Lamport [18]. Register Construction 6 was proposed by Vidyasankar [26]. Register Construction 7 was proposed by Vitanyi and Awerbuch [27]. Register Construction 8 was proposed by Israeli and Li [13]. A construction of a MRMR snapshot object using MRSW snapshot objects and MRMW registers was proposed by Anderson [4].

12.9 Exercise Problems 1. Why do the algorithms for sequential consistency (Section 12.2.2) not require the Read operations to be broadcast? 2. Give a formal proof to justify the correctness of the algorithm in Figure 12.7 that implements sequential consistency using local Read operations. 3. In the algorithm to implement sequential consistency using local Write operations, as given in Figure 12.8, why is a single counter counter sufficient for the algorithm’s correctness? In other words, why is a separate counter counterx not required to track the number of updates issued to each variable x, where a Read operation on x gets delayed only if counterx > 0? If such a separate counter were used for every variable, what consistency model would be implemented?

440

4.

• In Figure 12.9(a), analyze whether the execution is linearizable. • In Figure 12.9(b), what forms of memory consistency are satisfied if the two Read operations of P4 return 7 first and then 4?

5. Give a detailed implementation of causal consistency, and provide a correctness argument for your implementation. 6. Give a detailed implementation of PRAM consistency, and provide a correctness argument for your implementation. 7. Give a detailed implementation of slow memory, and provide a correctness argument for your implementation. Is the implementation less expensive than that of PRAM consistency which is a stricter consistency model? 8. Show that Constructions 1 and 2 (Figure 12.21) work for binary registers as well as integervalued registers. 9. Why are two passes needed by the reader in Construction 6, Figure 12.27, for a MRSW atomic register? Why does a single right-to-left pass not suffice? 10. Assume that the writer does a single pass from left to right in Construction 6, Figure 12.27, for a MRSW register. Can the code for the readers be modified to devise a correct algorithm? Justify your answer. 11. Peterson’s mutual exclusion algortihm for two processes is shown in Figure 12.34. (a) Show that it satisfies mutual exclusion, progress, and bounded waiting. (b) Use this algorithm as a building block to construct a hierarchical mutual exclusion algorithm for an arbitrary number of processes. (Hint: use a logarithmic number of steps in the hierarchy.) 12. Determine the average case time complexity of the wait-free atomic snapshot of a shared object, given in Figure 12.33.

441

(shared variables) boolean: turn ←− f alse; array of boolean: want[0, 1];

// shared register initialized

repeat (1) Pi executes the following for the entry section: (1a) wanting[i] ←− true; (1b) turn ←− 1 − i; (1c) while wanting[1 − i] and turn = 1 − i do (1d) no-op; (2) Pi executes the critical section (CS) after the entry section (3) Pi executes the following exit section after the CS: (3a) wanting[i] ←− f alse; (4) Pi executes the remainder section after the exit section until false;

Figure 12.34: Peterson’s mutual exclusion for two processes Pi = 0, 1. Modulo=2 artithmatic is used.

442

Bibliography [1] S. Adve, K. Gharachorloo, Shared memory consistency models: A tutorial, IEEE Computer Magazine 29(12): 66-76, 1996. [2] S. Adve, M. Hill, A unified formalization of four shared-memory models, IEEE Transactions on Parallel and Distributed Systems 4(6): 613-624, 1993. [3] M. Ahamad, G. Neiger, J. Burns, P. Kohli, P. Hutto, Causal memory: Definitions, implementation, and programming, Distributed Computing, 9(1): 37-49, 1995. [4] J. Anderson, Multi-writer composite registers, Distributed Computing, 7(4): 175-196, 1994. [5] H. Attiya, J. Welch, Sequential consistency versus linearizability, ACM Transactions on Computer Systems, 12(2): 91-122, 1994. [6] H. Bal, F. Kaashoek, A. Tanenbaum, Orca: A language for parallel programming of distributed systems, IEEE Transactions on Software Engineering, 18(3): 180-205, 1992. [7] B. Bershad, M. Zekauskas, W. Sawdon, The Midway distributed shared memory system, CMU Technical Report CMU-CS-93-119. (Also in Proceedings of COMPCON 1993.) [8] M. Dubois, C. Scheurich, Memory access dependencies in shared-memory multiprocessors, IEEE Transactions on Software Engineering, 16(6): 660-673, 1990. [9] K. Gharachorloo, D. Lenoski, J. Laudon, P. Gibbons, A. Gupta, and J. L. Hennessy, Memory consistency and event ordering in scalable shared-memory multiprocessors, Proceedings of the Seventeenth International Symposium on Computer Architecture, pages 15–26, Seattle, WA, May 1990. [10] M. Herlihy, J. Wing, Linearizability: A correctness condition for concurrent objects, ACM Transactions on Programming Languages and Systems, 12(3): 463-492, 1990. [11] P. Hutto, M. Ahamad, Slow memory: Weakening consistency to enchance oncurrency in distributed shared memories, Proc. IEEE International Conference on Distributed Computing Systems, 302-311, 1990. [12] M. Herlihy, Wait-free synchronization, ACM Transactions on Programming Languages and Systems, 13(1): 124-149, 1991. 443

[13] A. Israeli, M. Li, Bounded timestamps, Distributed Computing, 6(4): 205-209, 1993. [14] L. Lamport, A new solution of Dijkstra’s concurrent programming problem, Communications of the ACM, 17(8): 453-455, 1974. [15] L. Lamport, Proving the correctness of multiprocess programs, IEEE Transactions on Software Engineering, 3(2): 125-143, 1977. [16] L. Lamport, How to make a multiprocessor that correctly executes multiprocess programs, IEEE Transactions on Computers, 28(9): 690-691, 1979. [17] L. Lamport, On interprocess communication, Part I: Basic formalism, Distributed Computing, 1(2): 77-85, 1986. [18] L. Lamport, On interprocess communication, Part II: Algorithms, Distributed Computing, 1(2): 86-101, 1986. [19] L. Lamport, The mutual exclusion problem, Part II: Statement and solutions, Journal of the ACM, 33(2): 327-348, 1986. [20] L. Lamport, A fast mutual exclusion algorithm, ACM Transactions on Computer Systems, 5(1): 1-11, 1987. [21] L. Lamport, Concurrent reading and writing, Communications of the ACM, 20(11): 806-811, 1977. [22] R. Lipton, J. Sandberg, PRAM: A scalable shared memory, Technical Report CS-TR-180-88, Princeton University, Department of Computer Science, September 1988. [23] G.L. Peterson, Myths about the Mutual exclision problem, Information Processing Letters, 12: 115-116, 1981. [24] G.L. Peterson, M. Fischer, Economical solutions for the mutual exclusion problem in a distributed system, Proceedings 9th ACM Symposium on Theory of Computing, 91-97, 1977. [25] J. Protic, M. Tomasevic, V. Milutinovic, Distributed Shared Memory: Concepts and Systems, 4(2): 63-79, IEEE Concurrency, Computer Society Press, 1996. [26] K. Vidyasankar, Converting Lamport’s regular register to atomic register, Information Processing Letters, 28: 287-290, 1988. [27] P. Vitanyi, B. Awerbuch, Atomic shared register access byu asynchronous hardware, Proceedings 27th IEEE Symposium on Foundations of Computer Science, pp. 233-243, 1986.

444

Chapter 13 Checkpointing and Rollback Recovery 13.1 Introduction Distributed systems today are ubiquitous and enable many applications, including client-server systems, transaction processing, World Wide Web, and scientific computing, among many others. Distributed systems are not fault-tolerant and the vast computing potential of these systems is often hampered by their susceptibility to failures. Many techniques have been developed to add reliability and high availability to distributed systems. These techniques include transactions, group communication, and rollback recovery. These techniques have different tradeoffs and focus. This Chapter covers the rollback recovery protocols which restore the system back to a consistent state after a failure. Rollback recovery treats a distributed system application as a collection of processes that communicate over a network. It achieves fault tolerance by periodically saving the state of a process during the failure-free execution, and restarting from a saved state upon a failure to reduce the amount of lost work. The saved state is called a checkpoint, and the procedure of restarting from previously check pointed state is called rollback recovery. A checkpoint can be saved on either the stable storage or the volatile storage depending on the failure scenarios to be tolerated. In distributed systems, rollback recovery is complicated because messages induce inter-process dependencies during failure-free operation. Upon a failure of one or more processes in a system, these dependencies may force some of the processes that did not fail to roll back, creating what is commonly called a rollback propagation. To see why rollback propagation occurs, consider the situation where the sender of a message m rolls back to a state that precedes the sending of m. The receiver of m must also roll back to a state that precedes m’s receipt; otherwise, the states of the two processes would be inconsistent because they would show that message m was received without being sent, which is impossible in any correct failure-free execution. This phenomenon of cascaded rollback is called the domino effect. In some situations, rollback propagation may extend back to the initial state of the computation, losing all the work performed before the failure. In a distributed system, if each participating process takes its checkpoints independently, then the system is susceptible to the domino effect. This approach is called independent or uncoordinated checkpointing. It is obviously desirable to avoid the domino effect and therefore several 445

Output message

Input message Outside world Distributed system P 1 P 2

m

0

m

m

P 3

m

1

2

m

4

3

Figure 13.1: An example of a distributed system with three processes. techniques have been developed to prevent it. One such technique is coordinated checkpointing where processes coordinate their checkpoints to form a system-wide consistent state. In case of a process failure, the system state can be restrored such a consistent set of checkpoints, preventing the rollback propagation. Alternatively, communication-induced checkpointing forces each process to take checkpoints based on information piggybacked on the application messages it receives from other processes. Checkpoints are taken such that a system-wide consistent state always exists on stable storage, thereby avoiding the domino effect. The approaches discussed so far implement checkpoint-based rollback recovery, which relies only on checkpoints to achieve fault-tolerance. Log-based rollback recovery combines checkpointing with logging of nondeterministic events. Log-based rollback recovery relies on the piecewise deterministic (PWD) assumption, which postulates that all nondeterministic events that a process executes can be identified and that the information necessary to replay each event during recovery can be logged in the event’s determinant. By logging and replaying the nondeterministic events in their exact original order, a process can deterministically recreate its pre-failure state even if this state has not been checkpointed. Log-based rollback recovery in general enables a system to recover beyond the most recent set of consistent checkpoints. It is therefore particularly attractive for applications that frequently interact with the outside world, which consists of input and output devices that cannot roll back.

13.2 Background and Definitions 13.2.1 System Model A distributed system consists of a fixed number of processes P1 , P2,..., PN that communicate only through messages. Processes cooperate to execute a distributed application and interact with the outside world by receiving and sending input and output messages, respectively. Figure 13.1 shows a system consisting of three processes and interactions with the outside world. Rollback-recovery protocols generally make assumptions about the reliability of the inter-

446

process communication. Some protocols assume that the communication subsystem delivers messages reliably, in First-In-First-Out (FIFO) order, while other protocols assume that the communication subsystem can lose, duplicate, or reorder messages. The choice between these two assumptions usually affects the complexity of checkpointing and failure recovery. A generic correctness condition for rollback-recovery can be defined as follows [35]: “A system recovers correctly if its internal state is consistent with the observable behavior of the system before the failure”. Rollback-recovery protocols therefore must maintain information about the internal interactions among processes and also the external interactions with the outside world.

13.2.2 A Local Checkpoint In distributed systems, all processes save their local states at certain instants of time. This saved state is known as a local checkpoint. A local checkpoint is a snapshot of the state of the process at a given instance and the event of recording the state of a process is called local checkpointing. The contents of a checkpoint depend upon the application context and the checkpointing method being used. Depending upon the checkpointing method used, a process may keep several local checkpoints or just a single checkpoint at any time. We assume that a process stores all local checkpoints on the stable storage so that they are available even if the process crashes. We also assume that a process is able to roll back to any of its existing local checkpoints and thus restore to and restart from the corresponding state. Let Ci,k denote the k th local checkpoint at process Pi . Generally, it is assumed that a process Pi takes a checkpoint Ci,0 before it starts execution. A local checkpoint is shown in the process-line by the symbol “|”.

13.2.3 Consistent System States A global state of a distributed system is a collection of the individual states of all participating processes and the states of the communication channels. Intuitively, a consistent global state is one that may occur during a failure-free execution of a distributed computation. More precisely, a consistent system state is one in which a process’s state reflects a message receipt, then the state of the corresponding sender must reflect the sending of that message [9]. For instance, Figure 13.2 shows two examples of global states. The state in Figure 13.2(a) is consistent and the state in Figure 13.2(b) is inconsistent. Note that the consistent state in Figure 13.2(a) shows message m1 to have been sent but not yet received, but that is alright. The state in Figure 13.2(a) is consistent because it represents a situation in which every message that has been received, there is a corresponding message send event. The state in Figure 13.2(b) is inconsistent because process P2 is shown to have received m2 but the state of process P1 does not reflect to have sent it. Such a state is impossible in any failure-free, correct computation. Inconsistent states occur because of failures. For instance, the situation shown in Figure 13.2(b) may occur if process P1 fails after sending message m2 to process P2 and then restarts at the state shown in the Figure 13.2(b). 447

Consistent state

Inconsistent state P 0

P 0 m

m

1

1

P 1

P 1 m

m

2

2

P 2

P 2

(b)

(a)

Figure 13.2: Examples of a consistent and an inconsistent states. Thus, a local checkpoint is a snapshot of a local state of a process and a global checkpoint is a set of local checkpoints, one from each process. A consistent global checkpoint is a global checkpoint such that no message is sent by a process after its local checkpoint that is received by another process before its local checkpoint. The consistency of global checkpoints strongly depends on the flow of messages exchanged by processes and an arbitrary set of local checkpoints at processes may not form a consistent global checkpoint. Fundamental goal of any rollback-recovery protocol is to bring the system to a consistent state after a failure. The reconstructed consistent state is not necessarily one that occurred before the failure. It is sufficient that the reconstructed state be one that could have occurred before the failure in a failure-free execution, provided that it is consistent with the interactions that the system had with the outside world.

13.2.4 Interactions with the Outside World A distributed application often interacts with the outside world to receive input data or deliver the outcome of a computation. If a failure occurs, the outside world cannot be expected to a roll back. For example, a printer cannot roll back the effects of printing a character, and an automatic teller machine cannot recover the money that it dispensed to a customer. To simplify the presentation of how rollback-recovery protocols interact with the outside world, we model the latter as a special process that interacts with the rest of the system through message passing. We call this special process the “outside world process” (OWP). It is therefore necessary that the outside world see a consistent behavior of the system despite failures. Thus, before sending output to the OWP, the system must ensure that the state from which the output is sent will be recovered despite any future failure. This is commonly called the output commit problem. Similarly, input messages that a system receives from the OWP may not be reproducible during recovery, because it may not be possible for the outside world to regenerate them. Thus, recovery protocols must arrange to save these input messages so that they can be retrieved when needed for execution replay after a

448

2

0

6

4

8

failure X

P 1 3

0

6

P 2

9

0

1 m 3

4

1

2

3

4

5

6

7

5

8 m

0

2 m

m

P 3

m

8

4

P 4 recovery line

Figure 13.3: Different types of Messages failure. A common approach is to save each input message on the stable storage before allowing the application program to process it. An interaction with the outside world to deliver the outcome of a computation is shown on the process-line by the symbol “||”.

13.2.5 Different Types of Messages A procees failure and subsequent recovery may leave messages that were perfectly received (and processed) before the failure, in abnormal states. This is because a rollback of processes for recovery may have to rollback the send and receive operations of several messages. In this section, we identify several types such messages using the example shown in Figure 13.3. Figure 13.3 shows an example consisting of four processes. Process P1 fails at the point indicated and the whole system recovers to the state indicated by the recovery line; that is to global state {C1,8, C2,9 , C3,8 , C4,8 }. In-Transit Messages In Figure 13.3, the global state {C1,8 , C2,9 , C3,8, C4,8 } shows that message m1 has been sent but not yet received. We call such a message an in-transit message. Message m2 is also an in-transit message. When in-transit messages are part of a global system state, these messages do not cause any inconsistency. However, depending on whether the system model assumes reliable communication channels, rollback-recovery protocols may have to guarantee the delivery of in-transit messages when failures occur. For reliable communication channels, a consistent state must include intransit messages because they will always be delivered to their destinations in any legal execution

449

of the system. On the other hand, if a system model assumes lossy communication channels, then in-transit messages can be omitted from system state. Lost messages Messages whose send is not undone but receive is undone due to rollback are called lost messages. This type of messages occurs when the process rolls back to a checkpoint prior to reception of the message while the sender does not rollback beyond the send operation of the message. In the Figure 13.3, message m1 is a lost message. Delayed messages Messages whose receive are not recorded because the receiving process was either down or the message arrived after the rollback of the receiving process, are called delayed messages. For example, message m2 and m5 in Figure 13.3 are delayed messages. Orphan messages Messages with receive recorded but message send not recorded are called the orphan messages. For example, a rollback might have undone the send of such messages, leaving the receive event intact at the receiving process. Orphan messages do not arise if processes roll back to a consistent global state. Duplicate messages Duplicate messages arise due to message logging and replaying during a process recovery. For example, in Figure 13.3, message m4 was sent and received before the rollback. However, due to the rollback of process P4 to C4,8 and process P3 to C3,8 , both send and receipt of message m4 are undone. When process P3 restarts from C3,8 , it will resend the message m4 . Therefore, P4 should not replay the message m4 from its log. If P4 replays the message m4 , then message m4 is called a duplicate message. Message m5 is an excellent example of a duplicate message. No matter what, the receiver of m5 will receive a duplicate m5 message.

13.3 Issues in Failure Recovery In a failure recovery, we must not only restore the system to a consistent state, but also appropriately handle messages that are left in abnormal state due to the failure and recovery [33]. We now describe the issues involved in a failure recovery with the help of a distributed computation shown in the Figure 13.4. The computation comprises of three processes Pi , Pj and Pk , connected through a communication network. The processes communicate solely by exchanging messages over fault free, FIFO communication channels. Processes Pi , Pj , and Pk have taken checkpoints {Ci,0 , Ci,1 }, {Cj,0 , Cj,1 , Cj,2 }, and {Ck,0 , Ck,1 }, respectively, and these processes have exchanged messages A to J as shown in the Figure 13.4. 450

C i,0

C i,1

Failure

Pi H A

D

B

C j,0

C j,2

C j,1

Pj J C k,0

E

C

F

I

G

Pk C k,2

C k,1

Figure 13.4: Illustration of Issues in Failure Recovery Suppose process Pi fails at the instance indicated in the Figure. All the contents in the volatile memory of Pi is lost and after Pi recovers from the failure, the system needs to be restored to a consistent global state from where the processes resume their execution. The process Pi ’s state is restored to a valid state by rolling it back to its latest checkpoint Ci,1 . To restore the system to a consistent state process Pj rolls back to checkpoint Cj,1 because roll back of process Pi to checkpoint Ci,1 created an orphan message H (the receive event of H is recorded at process Pj while the send event of H has been undone at process Pi ). Note that process Pj does not roll back to checkpoint Cj,2 but to checkpoint Cj,1 because rolling back to checkpoint Cj,2 does not eliminate the orphan message H. Even this resulting state is not a consistent global state as an orphan message I is created due to the roll back of process Pj to checkpoint Cj,1 . To eliminate this orphan message process Pk rolls back to checkpoint Ck,1 . The restored global state {Ci,1, Ci,1 , Ci,1 } is a consistent state as it is free from orphan messages. Although the system state has been restored to a consistent state, several messages are left in an erroneous state which must be handled correctly. Messages A, B, D, G, H, I, and J had been received at the points indicated in the Figure and messages C, E and F were in transit when the failure occurred. Restoration of system state to checkpoints {Ci,1 , Cj,1 ,Ck,1 } automatically handles messages A, B, and J because send and receive events of messages A, B and J have been recorded and both the events for G, H, and I have been completely undone. These messages cause no problem and we call messages A, B and J normal messages and messages G, H and I as vanished messages [33]. Messages C, D, E, and F are potentially problematic. Message C is in transit during the failure and it is a delayed message. The delayed message C has several possibilities: C might arrive at process Pi before it recovers, it might arrive while Pi is recovering, or it might arrive after Pi has completed recovery. Each of these cases must be dealt with correctly. Message D is a lost message since the send event for D is recorded in the restored state for process Pj but the receive event has been undone at process Pi . Process Pj will not resend D without 451

additional mechanism, since the send D at Pj occurred before the checkpoint and communication system successfully delivered D. Messages E and F are delayed orphan messages and pose perhaps the most serious problem of all the messages. When messages E and F arrive at their respective destinations, they must be discarded since their send events have been undone. Processes, after resuming execution from their checkpoints, will generate both of these messages and recovery techniques must be able to distinguish between messages like C and those like E and F. Lost messages like D can be handled by having processes keep a message log of all the sent messages. So when a process restores to a checkpoint, it replays the messages from its log to handle the lost message problem. However, message logging and message replaying during recovery can result in duplicate messages. In the example shown in the Figure, when process Pj replays messages from its log, it will regenerate message J. Process Pk which has already received message J will receive it again, thereby, causing inconsistency in the system state. Therefore, these duplicate messages must be handled properly. Overlapping failures further complicate the recovery process. A process Pj that begins rollback/recovery in response to the failure of a process Pi can itself fail and develop amnesia with respect process Pi ’s failure; that is, process Pj can act in a fashion that exhibits ignorance of process Pi ’s failure. If overlapping failures are to be tolerated, a mechanism must be introduced to deal with amnesia and the resulting inconsistencies.

13.4 Checkpoint Based Recovery In the checkpoint based recovery approach, the state of each process and the communication channel is checkpointed frequently so that upon a failure, the system is restored to a globally consistent set of checkpoints. It does not rely on the PWD assumption, and so does not need to detect, log, or replay nondeterministic events. Checkpoint-based protocols are therefore less restrictive and simpler to implement than log-based rollback recovery. However, checkpoint-based rollback recovery does not guarantee that pre-failure execution can be deterministically regenerated after a rollback. Therefore, checkpoint-based rollback recovery may not be suitable for applications that require frequent interactions with the outside world. Checkpoint based rollback-recovery techniques can be classified into three categories: uncoordinated checkpointing, coordinated checkpointing, and communication-induced checkpointing [13].

13.4.1 Uncoordinated Checkpointing In uncoordinated checkpointing, each process has autonomy in deciding when to take checkpoints. This eliminates synchronization overhead as there is no need for coordination between processes and it allows processes to take checkpoints when it is most convenient or efficient. The main advantage is the lower runtime overhead during normal execution because no coordination among processes is necessary. Autonomy in taking checkpoints also allows each process to select ap-

452

I j,y cj,0

cj,1

cj,y−1

c j,y

Pj

(i,x)

m

Pi ci,0

ci,1

ci,x−1

ci,x I i,x

Figure 13.5: Checkpoint Index and checkpoint Interval propriate checkpoints positions. However, uncoordinated checkpointing has several shortcomings [13]. First, there is the possibility of the domino effect during a recovery, which may cause the loss of a large amount of useful work. Second, recovery from a failure is slow because processes need to iterate to find a consistent set of checkpoints. Since no coordination is done at the time of checkpoint is taken, checkpoints taken by a process may be useless checkpoints. (A useless checkpoint is never a part of any global consistent state). Useless checkpoints are undesirable because they incur overhead and do not contribute to advancing the recovery line. Third, uncoordinated checkpointing forces each process to maintain multiple checkpoints, and to periodically invoke a garbage collection algorithm to reclaim the checkpoints that are no longer required. Fourth, it is not suitable for applications with frequent output commits because these require global coordination to compute the recovery line, negating much of the advantage of autonomy. As each process takes checkpoints independently, we need to determine a consistent global checkpoint to rollback to, when a failure occurs. In order to determine a consistent global checkpoint during recovery, the processes record the dependencies among their checkpoints caused by message exchange during failure-free operation. The following direct dependency tracking technique is commonly used in uncoordinated checkpointing. Let ci,x be the xth checkpoint of process Pi , where i is the process id and x is the checkpoint index (we assume each process Pi starts its execution with an initial checkpoint ci,0 ). Let I i,x denote the checkpoint interval or simply interval between checkpoints ci,x−1 and ci,x . Consider the example shown in Figure 13.5. When process Pi at interval I i,x sends a message m to Pj , it piggybacks the pair (i,x) on m. When Pj receives m during interval I j,y , it records the dependency from I i,x to I j,y , which is later saved onto stable storage when Pj takes checkpoint cj,y . When a failure occurs, the recovering process initiates rollback by broadcasting a dependency request message to collect all the dependency information maintained by each process. When a process receives this message, it stops its execution and replies with the dependency information saved on the stable storage as well as with the dependency information, if any, which is associated 453

with its current state. The initiator then calculates the recovery line based on the global dependency information and broadcasts a rollback request message containing the recovery line. Upon receiving this message, a process whose current state belongs to the recovery line simply resumes execution; otherwise, it rolls back to an earlier checkpoint as indicated by the recovery line.

13.4.2 Coordinated Checkpointing In coordinated checkpointing, processes orchestrate their checkpointing activities so that all local checkpoints form a consistent global state [13]. Coordinated checkpointing simplifies recovery and is not susceptible to the domino effect, since every process always restarts from its most recent checkpoint. Also, coordinated checkpointing requires each process to maintain only one checkpoint on the stable storage, reducing the storage overhead and eliminating the need for garbage collection. The main disadvantage of this method is that large latency is involved in committing output, as a global checkpoint is needed before a message is sent to OWP. Also, delays and overhead are involved everytime a new global checkpoint is taken. If perfectly synchronized clocks were available at processes, the following simple method can be used for checkpointing: all processes agree at what instants of time they will take checkpoints, and the clocks at processes trigger the local checkpointing actions at all processes. Since perfectly synchronized clocks are not available, the following approaches are used to guarantee checkpoint consistency: either the sending of messages is blocked for the duration of the protocol, or checkpoint indices are piggybacked to avoid blocking. Blocking Coordinated Checkpointing A straightforward approach to coordinated checkpointing is to block communications while the checkpointing protocol executes. After a process takes a local checkpoint, to prevent orphan messages, it remains blocked until the entire checkpointing activity is complete. The coordinator takes a checkpoint and broadcasts a request message to all processes, asking them to take a checkpoint. When a process receives this message, it stops its execution, flushes all the communication channels, takes a tentative checkpoint, and sends an acknowledgment message back to the coordinator. After the coordinator receives acknowledgments from all processes, it broadcasts a commit message that completes the two-phase checkpointing protocol. After receiving the commit message, a process removes the old permanent checkpoint and atomically makes the tentative checkpoint permanent and then resumes its execution and exchange of messages with other processes. A problem with this approach is that the computation is blocked during the checkpointing and therefore, nonblocking checkpointing schemes are preferable. Non-blocking Checkpoint Coordination In this approach the processes need not stop their execution while taking checkpoints. A fundamental problem in coordinated checkpointing is to prevent a process from receiving application messages that could make the checkpoint inconsistent. Consider the example in Figure 13.6(a) 454

Initiator

Initiator

checkpoint request

P0

checkpoint request

c0,x

P0

c0,x

m

P1

m

c1,x

P1

(a)

c1,x

(b)

Figure 13.6: Non-blocking coordinated checkpointing: (a) checkpoint inconsistency; (b) a solution with FIFO Channels. [13]: message m is sent by P0 after receiving a checkpoint request from the checkpoint coordinator. Assume m reaches P1 before the checkpoint request. This situation results in an inconsistent checkpoint since checkpoint c1,x shows the receipt of message m from P0 , while checkpoint c0,x does not show m being sent from P0 . If channels are FIFO, this problem can be avoided by preceding the first post-checkpoint message on each channel by a checkpoint request, forcing each process to take a checkpoint before receiving the first post-checkpoint message, as illustrated in Figure 13.6(b). An example of a nonblocking checkpoint coordination protocol using this idea is the snapshot algorithm of Chandy and Lamport [9] in which markers play the role of the checkpoint-request messages. In this algorithm, the initiator takes a checkpoint and sends a marker (a checkpoint request) on all outgoing channels. Each process takes a checkpoint upon receiving the first marker and sends the marker on all outgoing channels before sending any application message. The protocol works assuming the channels are reliable and FIFO. If the channels are non-FIFO, the following two approaches can be used: first, the marker can be piggybacked on every post-checkpoint message. When a process receives an application message with a marker, it treats it as if it has received a marker message, followed by the application message. Alternatively, checkpoint indices can serve the same role as markers, where a checkpoint is triggered when the receiver’s local checkpoint index is lower than the piggybacked checkpoint index. Coordinated checkpointing requires all processes to participate in every checkpoint. This requirement generates valid concerns about its scalability. It is desirable to reduce the number of processes involved in a coordinated checkpointing session. This can be done since only those processes that have communicated with the checkpoint initiator either directly or indirectly since the last checkpoint need to take new checkpoints. A two-phase protocol by koo and Toueg achieves minimal checkpoint coordination.

455

13.4.3 Impossibility of Min Process Non-blocking Checkpointing A min-process, non-blocking checkpointing algorithm is one that forces only a minimum number of processes to take a new checkpoint, and at the same time it does not force any process to suspend its computation. Clearly, such checkpointing algorithms will be very attractive. Cao and Singhal [7] showed that it is impossible to design a min-process, non-blocking checkpointing algorithm. Of course, the following type of min-process checkpointing algorithms are possible: The algorithm consists of two phases. During the first phase, the checkpoint initiator identifies all processes with which it has communicated since the last checkpoint and sends them a request. Upon receiving the request, each process in turn identifies all processes it has communicated with since the last checkpoints and sends them a request, and so on, until no more processes can be identified. During the second phase, all processes identified in the first phase take a checkpoint. The result is a consistent checkpoint that involves only the participating processes. In this protocol, after a process takes a checkpoint, it cannot send any message until the second phase terminates successfully, although receiving a message after the checkpoint has been taken is allowable. Based on the concept called ’Z-dependency’, Cao and Singhal proved that there does not exist a non-blocking algorithm that allows a minimum number of processes to take their checkpoints. Here we give only a sketch of the proof and readers are referred to the original source [7] for a detailed proof. Z-dependency is defined as follows: if a process Pp sends a message to process Pq during its ith checkpoint interval and process Pq receives the message during its jth checkpoint interval, then Pq Z-depends on Pp during Pp ’s ith checkpoint interval and Pq ’s jth checkpoint interval, denoted by Pp →i j Pq . If Pp →i j Pq and Pq → j k Pr , then Pr transitively z-depends depends on Pp during Pr ’s kth checkpoint interval and Pp ’s ith checkpoint interval and this is denoted as Pp ∗ →i k Pr . A min process algorithm is one that satisfies the following condition: when a process Pp initiates a new checkpoint and takes checkpoint Cp,i , a process Pq takes a checkpoint Cq,j associated with Cp,i if and only if Pq ∗ →j−1i−1 Pp . In a min process non-blocking algorithm, process Pp initiates a new checkpoint and takes a checkpoint Cp,i and if a process Pr sends a message m to Pq after it takes a new checkpoint associated with Cp,i , then Pq takes a checkpoint Cq,i before processing m if and only if Pq ∗ → j−1i−1 Pp . According to min process definition, Pq takes checkpoint Cq,j if and only if Pq ∗ →j−1 i−1 Pp but Pq should take Cq,i before processing m. If it takes Cq,j after processing m, m becomes an orphan. Therefore, when a process receives a message m, it must know if the initiator of a new checkpoint transitively Z-depends on it during the previous checkpoint interval. But it has been proved that there is not enough information at the receiver of a message to decide whether the initiator of a new checkpoint transitively Z-depends on the receiver. Therefore, no min-process non-blocking algorithm exists.

13.4.4 Communication-Induced Checkpointing Communication-induced checkpointing is another way to avoid the domino effect, while allowing processes to take some of their checkpoints independently. Processes may be forced to take addi456

tional checkpoints (over and above their autonomous checkpoints) and thus, process independence is constrained to guarantee the eventual progress of the recovery line. Communication induced checkpointing reduces or completely eliminates the useless checkpoints. In communication induced checkpointing processes take two types of checkpoints, namely, autonomous and forced checkpoints. The checkpoints that a process takes independently are called local checkpoints, while those that a process is forced to take are called forced checkpoints. Communication-induced checkpointing piggybacks protocol-related information on each application message. The receiver of each application message uses the piggybacked information to determine if it has to take a forced checkpoint to advance the global recovery line. The forced checkpoint must be taken before the application may process the contents of the message, possibly incurring some latency and overhead. It is therefore desirable in these systems to minimize the number of forced checkpoints. In contrast with coordinated checkpointing, no special coordination messages are exchanged. There are two types of communication-induced checkpointing [13]: model-based checkpointing and index-based checkpointing. In model-based checkpointing, the system maintains checkpoints and communication structures that prevent the domino effect or achieve some even stronger properties. In index-based coordination, the system uses an indexing scheme for the local and forced checkpoints, such that the checkpoints of the same index at all processes form a consistent state. Model-based Checkpointing Model-based checkpointing prevents patterns of communications and checkpoints that could result in inconsistent states among the existing checkpoints. A process detects the potential for inconsistent checkpoints and independently forces local checkpoints to prevent the formation of undesirable patterns. A forced checkpoint is generally used to prevent the undesirable patterns from occurring. No control messages are exchanged among the processes during normal operation. All information necessary to execute the protocol is piggybacked on application messages. The decision to take a forced checkpoint is done locally using the information available. There are several domino-effect-free checkpoint and communication models. The MRS model avoids the domino effect by ensuring that within every checkpoint interval all message-receiving events precede all message-sending events. This model can be maintained by taking an additional checkpoint before every message-receiving event that is not separated from its previous messagesending event by a checkpoint. Wu and Fuchs proposed another way to prevent the domino effect by avoiding rollback propagation completely by taking a checkpoint immediately after every message-sending event. Recent work has focused on ensuring that every checkpoint can belong to a consistent global checkpoint and therefore is not useless. Index-based Checkpointing Index-based communication-induced checkpointing assigns monotonically increasing indexes to checkpoints, such that the checkpoints having the same index at different processes form a consistent state. Inconsistency between checkpoints of the same index can be avoided in a lazy fashion 457

P0 m0

m2

m3

m5

m7

P1 m1

m4

m6

P2

Figure 13.7: Deterministic and Nondeterministic Events if indexes are piggybacked on application messages to help receivers decide when they should take a forced a checkpoint. For instance, the protocol by Briatico et al. [5] forces a process to take a checkpoint upon receiving a message with a piggybacked index greater than the local index. More sophisticated protocols piggyback more information on application messages to minimize the number of forced checkpoints.

13.5 Log-based Rollback Recovery A log-based rollback recovery makes use of deterministic and nondeterministic events in a computation. So first we discuss these events.

13.5.1 Deterministic and Nondeterministic Events Log-based rollback recovery exploits the fact that a process execution can be modeled as a sequence of deterministic state intervals, each starting with the execution of a nondeterministic event. A nondeterministic event can be the receipt of a message from another process or an event internal to the process. Note that a message send event is not a nondeterministic event. For example, in Figure 13.7, the execution of process P0 is a sequence of four deterministic intervals. The first one starts with the creation of the process, while the remaining three start with the receipt of messages m0 , m3 , and m7 , respectively. Send event of message m2 is uniquely determined by the initial state of P0 and by the receipt of message m0 , and is therefore not a nondeterministic event. Log-based rollback recovery assumes that all nondeterministic events can be identified and their corresponding determinants can be logged into the stable storage. During failure-free operation, each process logs the determinants of all nondeterministic events that it observes onto the stable storage. Additionally, each process also takes checkpoints to reduce the extent of rollback during recovery. After a failure occurs, the failed processes recover by using the checkpoints and logged determinants to replay the corresponding nondeterministic events precisely as they

458

occurred during the pre-failure execution. Because execution within each deterministic interval depends only on the sequence of nondeterministic events that preceded the interval’s beginning, the pre-failure execution of a failed process can be reconstructed during recovery up to the first nondeterministic event whose determinant is not logged.

The No-Orphans Consistency Condition Let e be a nondeterministic event that occurs at process p. We define the following [13]: • Depend(e): the set of processes that are affected by a nondeterministic event e. This set consists of p, and any process whose state depends on the event e according to the Lamport’s happened before relation. • Log(e): the set of processes that have logged a copy of e’s determinant in their volatile memory. • Stable(e): a predicate that is true if e’s determinant is logged on the stable storage. Suppose a set of processes Ψ crashes. A process p in Ψ becomes an orphan when p itself does not fail and p’s state depends on the execution of a nondeterministic event e whose determinant cannot be recovered from the stable storage or from the volatile memory of a surviving process. Formally, it can be stated as follows [13]: ∀ e: ¬ Stable(e) : Depend(e) ⊆ Log(e) This property is called the always-no-orphans condition [13]. It states that if any surviving process depends on an event e, then either event e is logged on the stable storage, or the process has a copy of the determinant of event e. If neither condition is true, then the process is an orphan because it depends on an event e that cannot be generated during recovery since its determinant is lost. Log-based rollback-recovery protocols guarantee that upon recovery of all failed processes, the system does not contain any orphan process, i.e., a process whose state depends on a nondeterministic event that cannot be reproduced during recovery. Log-based rollback-recovery protocols are of three types: pessimistic logging, optimistic logging, and causal logging protocols. They differ in their failure-free performance overhead, latency of output commit, simplicity of recovery and garbage collection, and the potential for rolling back surviving processes.

13.5.2 Pessimistic Logging Pessimistic logging protocols assume that a failure can occur after any nondeterministic event in the computation. This assumption is “pessimistic” since in reality failures are rare. In their most straightforward form, pessimistic protocols log to the stable storage the determinant of each nondeterministic event before the event affects the computation. Pessimistic protocols implement 459

Maximum recoverable state

P0 A m0

m1

m7

m4

failure P1 B m2

m3

m5

m6 failure

P2 C

Figure 13.8: Pessimistic logging. the following property, often referred to as synchronous logging, which is a stronger than the always-no-orphans condition [13]. ∀ e: ¬ Stable(e) : | Depend(e) | = 0 That is, if an event has not been logged on the stable storage, then no process can depend on it. In addition to logging determinants, processes also take periodic checkpoints to minimize the amount of work that has to be repeated during recovery. When a process fails, the process is restarted from the most recent checkpoint and the logged determinants are used to recreate the pre-failure execution. Consider the example in Figure 13.8. During failure-free operation the logs of processes P0 , P1 and P2 contain the determinants needed to replay messages m0 , m4 , m7 , m1 , m3 , m6 and m2 , m5 , respectively. Suppose processes P1 and P2 fail as shown, restart from checkpoints B and C, and roll forward using their determinant logs to deliver again the same sequence of messages as in the pre-failure execution. This guarantees that P1 and P2 will repeat exactly their pre-failure execution and re-send the same messages. Hence, once the recovery is complete, both processes will be consistent with the state of P0 that includes the receipt of message m7 from P1 . In a pessimistic logging system, the observable state of each process is always recoverable. The price paid for these advantages is a performance penalty incurred by synchronous logging. Synchronous logging can potentially result in a high performance overhead. Implementations of pessimistic logging must use special techniques to reduce the effects of synchronous logging on the performance. This overhead can be lowered using special hardware. For example, fast nonvolatile semiconductor memory can be used to implement the stable storage. Another approach is to limit the number of failures that can be tolerated. The overhead of pessimistic logging is reduced by delivering a message or executing an event and deferring its logging until the process communicates with another process or with the outside world. Synchronous logging in such an implementation is orders of magnitude cheaper than with a

460

traditional implementation of stable storage that uses magnetic disk devices. Another form of hardware support uses a special bus to guarantee atomic logging of all messages exchanged in the system. Such hardware support ensures that the log of one machine is automatically stored on a designated backup without blocking the execution of the application program. This scheme, however, requires that all nondeterministic events be converted into external messages. Some pessimistic logging systems reduce the overhead of synchronous logging without relying on hardware. For example, the Sender-Based Message Logging (SBML) protocol keeps the determinants corresponding to the delivery of each message m in the volatile memory of its sender. The determinant of m, which consists of its content and the order in which it was delivered, is logged in two steps. First, before sending m, the sender logs its content in volatile memory. Then, when the receiver of m responds with an acknowledgment that includes the order in which the message was delivered, the sender adds to the determinant the ordering information. SBML avoids the overhead of accessing stable storage but tolerates only one failure and cannot handle nondeterministic events internal to a process. Extensions to this technique can tolerate more than one failure in special network topologies.

13.5.3 Optimistic Logging In optimistic logging protocols, processes log determinants asynchronously to the stable storage [13]. These protocols optimistically assume that logging will be complete before a failure occurs. Determinants are kept in a volatile log, and are periodically flushed to the stable storage. Thus, optimistic logging does not require the application to block waiting for the determinants to be written to the stable storage, and therefore incurs much less overhead during failure-free execution. However, the price paid is more complicated recovery, garbage collection, and slower output commit. If a process fails, the determinants in its volatile log are lost, and the state intervals that were started by the nondeterministic events corresponding to these determinants cannot be recovered. Furthermore, if the failed process sent a message during any of the state intervals that cannot be recovered, the receiver of the message becomes an orphan process and must roll back to undo the effects of receiving the message. Optimistic logging protocols do not implement the always-no-orphans condition. The protocols allow the temporary creation of orphan processes which are eventually eliminated. The always-no-orphans condition holds after the recovery is complete. This is achieved by rolling back orphan processes until their states do not depend on any message whose determinant has been lost. Consider the example shown in Figure 13.9. Suppose process P2 fails before the determinant for m5 is logged to the stable storage. Process P1 then becomes an orphan process and must roll back to undo the effects of receiving the orphan message m6 . The rollback of P1 further forces P0 to roll back to undo the effects of receiving message m7 . To perform rollbacks correctly, optimistic logging protocols track causal dependencies during failure free execution. Upon a failure, the dependency information is used to calculate and recover the latest global state of the pre-failure execution in which no process is in an orphan. Optimistic 461

P0 X

A m0

m1

m7

m4 D

P1 B m2

m3

m5

m6

P2 C

Figure 13.9: Optimistic logging. logging protocols require a nontrivial garbage collection scheme. Also note that pessimistic protocols need only keep the most recent checkpoint of each process, whereas optimistic protocols may need to keep multiple checkpoints for each process. Since determinants are logged asynchronously, output commit in optimistic logging protocols requires a guarantee that no failure scenario can revoke the output. For example, if process P0 needs to commit output at state X, it must log messages m4 and m7 to the stable storage and ask P2 to log m2 and m5 . In this case, if any process fails, the computation can be reconstructed upto the state X.

13.5.4 Causal Logging Causal logging combines the advantages of both pessimistic and optimistic logging at the expense of a more complex recovery protocol [13]. Like optimistic logging, it does not require synchronous access to the stable storage except during output commit. Like pessimistic logging, it allows each process to commit output independently and never creates orphans, thus isolating processes from the effects of failures at other processes. Moreover, causal logging limits the rollback of any failed process to the most recent checkpoint on the stable storage, thus minimizing the storage overhead and the amount of lost work. Causal logging protocols make sure that the always-no-orphans property holds by ensuring that the determinant of each nondeterministic event that causally precedes the state of a process is either stable or it is available locally to that process. Consider the example in Figure 13.10. Messages m5 and m6 are likely to be lost on the failures of P1 and P2 at the indicated instants. Process P0 at state X will have logged the determinants of the nondeterministic events that causally precede its state according to Lamport’s happened-before relation. These events consist of the delivery of messages m0 , m1 , m2 , m3 and m4 . The determinant of each of these nondeterministic events is either logged on the stable storage or is available in the volatile log of process P0 . The determinant of each of these events contains the order in which its original receiver delivered the corresponding message. The message sender, as in sender-based message logging, logs the message content. Thus, process

462

Maximum recoverable state P0 X

A m0

m1

m4 failure

P1 B m2

m3

m5

m6 failure

P2 C

Figure 13.10: Causal logging P0 will be able to “guide” the recovery of P1 and P2 since it knows the order in which P1 should replay messages m1 and m3 to reach the state from which P1 sends message m4 . Similarly, P0 has the order in which P2 should replay message m2 to be consistent with both P0 and P1 . The content of these messages is obtained from the sender log of P0 or regenerated deterministically during the recovery of P1 and P2 . Note that information about messages m5 and m6 is lost due to failures. These messages may be resent after recovery possibly in a different order. However, since they did not causally affect the surviving process or the outside world, the resulting state is consistent. Each process maintains information about all the events that have causally affected its state. This information protects it from the failures of other processes and also allows the process to make its state recoverable by simply logging the information available locally. Thus, a process does not need to run a multi-host protocol to commit output. It can commit output independently.

13.6 Koo-Toueg Coordinated Checkpointing Algorithm Koo and Toueg [22] ccordinated checkpointing and recovery technique takes a consistent set of checkpoints and avoids the domino effect and livelock problems during the recovery. Processes coordinate their local checkpointing actions such that the set of all checkpoints in the system is consistent [9].

13.6.1 The Checkpointing Algorithm The checkpoint algorithm makes the following assumptions about the distributed system: Processes communicate by exchanging messages through communication channels. Communication channels are FIFO. It is assumed that end-to-end protocols (such as the sliding window protocol) exist to cope with message loss due to rollback recovery and communication failure. Communication failures do not partition the network. 463

The checkpoint algorithm takes two kinds of checkpoints on the stable storage: permanent and tentative. A permanent checkpoint is a local checkpoint at a process and is a part of a consistent global checkpoint. A tentative checkpoint is a temporary checkpoint that is made a permanent checkpoint on the successful termination of the checkpoint algorithm. In case of a failure, processes roll back only to their permanent checkpoints for recovery. The checkpointing algorithm assumes that a single process invokes the algorithm at any time to take permanent checkpoints. The algorithm also assumes that no process fails during the execution of the algorithm. The algorithm consists of two phases. First Phase An initiating process Pi takes a tentative checkpoint and requests all other processes to take tentative checkpoints. Each process informs Pi whether it succeeded in taking a tentative checkpoint. A process says “no” to a request if it fails to take a tentative checkpoint, which could be due to several reasons, depending upon the underlying application. If Pi learns that all the processes have successfully taken tentative checkpoints, Pi decides that all tentative checkpoints should be made permanent; otherwise, Pi decides that all the tentative checkpoints should be discarded. Second Phase Pi informs all the processes of the decision it reached at the end of the first phase. A process, on receiving the message from Pi will act accordingly. Therefore, either all or none of the processes advance the checkpoint by taking permanent checkpoints. The algorithm requires that after a process has taken a tentative checkpoint, it can not send messages related to the underlying computation until it is informed of Pi ’s decision.

Correctness A set of permanent checkpoints taken by this algorithm is consistent because of the following two reasons: First, either all or none of the processes take permanent checkpoints. Second, no process sends a message after taking a tentative checkpoint until the receipt of the initiating process’s decision, by then all processes would have taken checkpoints. Thus a situation will not arise where there is a record of a message being received but there is no record of sending it. Thus, a set of checkpoints taken will always be inconsistent.

An Optimization Note that the above protocol may cause a process to take a checkpoint even when it is not necessary for consistency. Since taking a checkpoint is an expensive operation, we would like to avoid taking checkpoints if it is not necessary. Consider the example shown in Fig. 13.11. The set {x1 , y1 , z1 } is a consistent set of checkpoints. Suppose process X decides to initiate the checkpointing algorithm after receiving message 464

x2

x1

Tentative checkpoint

X m

Take a tentative checkpoint messages y2

y1 Y

z1

z2

Z Time

Figure 13.11: Example of checkpoints taken unnecessarily. m. It takes a tentative checkpoint x2 and sends “take tentative checkpoint" messages to processes Y and Z, causing Y and Z to take checkpoints y2 and z2 , respectively. Clearly, {x2 , y2, z2 } forms a consistent set of checkpoints. Note, however, that {x2 , y2, z1 } also forms a consistent set of checkpoints. In this example, there is no need for process Z to take checkpoint z2 because Z has not sent any message since its last checkpoint. However, process Y must take a checkpoint since has sent messages since its last checkpoint.

13.6.2 The Rollback Recovery Algorithm The rollback recovery algorithm restores the system state to a consistent state after a failure. The rollback recovery algorithm assumes that a single process invokes the algorithm. It also assumes that the checkpoint and the rollback recovery algorithms are not invoked concurrently. The rollback recovery algorithm has two phases. First Phase An initiating process Pi sends a message to all other processes to check if they all are willing to restart from their previous checkpoints. A process may reply “no” to a restart request due to any reason (e.g., it is already participating in a checkpointing or a recovery process initiated by some other process). If Pi learns that all processes are willing to restart from their previous checkpoints, Pi decides that all processes should roll back to their previous checkpoints. Otherwise, Pi aborts the roll back attempt and it may attempt a recovery at a later time. Second Phase Pi propagates its decision to all the processes. On receiving Pi ’s decision, a process acts accordingly. During the execution of the recovery algorithm, a process can not send messages related to the underlying computation while it is waiting for Pi ’s decision.

465

x1

Failure

x2

X

y1

y2

Y

z1

z2

Z Time

Figure 13.12: Example of an unnecessary rollback.

Correctness All processes restart from an appropriate state because if processes decide to restart, then they resume execution from a consistent state (the checkpointing algorithm takes a consistent set of checkpoints).

An Optimization The above recovery protocol causes all processes to roll back irrespective of whether a process needs to roll back or not. Consider the example shown in Fig. 13.12. The above protocol, in In the event of failure of process X, the above protocol will require processes X, Y, and Z to restart from checkpoints x2 , y2 , and z2 , respectively. However, note that process Z need not roll back because there has been no interaction between process Z and the other two processes since the last checkpoint at Z.

13.7 Juang and Venkatesan Algorithm for Asynchronous Checkpointing and Recovery We now describe the algorithm of Juang and Venkatesan [18] for recovery in a system that employs asynchronous checkpointing.

13.7.1 System Model and Assumptions The algorithm makes the following assumptions about the underlying system: The communication channels are reliable, deliver the messages in FIFO order and have infinite buffers. The message transmission delay is arbitrary, but finite. The processors directly connected to a processor via communication channels are called its neighbors. The underlying computation or application is assumed to be event-driven: a processor P waits until a message m is received, it processes the message m, changes its state from s to s′ , and sends zero or more messages to some of its neighbors. Then the processor remains idle until the 466

ex0

ex1

ex2

X

ey0

ey1

ey2

ey3

failure

Y

ez0 Z ez1

ez2

ez3 Time

Figure 13.13: An event driven computation. receipt of the next message. The new state s′ and the contents of messages sent to its neighbors depends on state s and the contents of message m. The events at a processor are identified by unique monotonically increasing numbers, ex0 , ex1 , ex2 , . . . (see Fig. 13.13). To facilitate recovery after a process failure and restore the system to a consistent state, two types of log storage are maintained, volatile log and stable log. Accessing the volatile log takes less time than accessing the stable log, but the contents of the volatile log are lost if the corresponding processor fails. The contents of the volatile log are periodically flushed to the stable storage.

13.7.2 Asynchronous Checkpointing After executing an event, a processor records a triplet {s, m, msgs_sent} in its volatile storage, where s is the state of the processor before the event, m is the message (including the identity of the sender of m, denoted as m.sender) whose arrival caused the event, and msqs_sent is the set of messages that were sent by the processor during the event. Therefore, a local checkpoint at a processor consists of the record of an event occurring at the processor and it is taken without any synchronization with other processors. Periodically, a processor independently saves the contents of the volatile log in the stable storage and clears the volatile log. This operation tantamounts to taking a local checkpoint.

13.7.3 The Recovery Algorithm Notations and data structure The following notations and data structure are used by the algorithm: • RCV Di←j (CkP ti) represents the number of messages received by processor pi from processor pj , from the beginning of the computation till the checkpoint CkP ti . • SENTi→j (CkP ti) represents the number of messages sent by processor pi to processor pj , from the beginning of the computation till the checkpoint CkP ti .

467

Basic idea Since the algorithm is based on asynchronous checkpointing, the main issue in the recovery is to find a consistent set of checkpoints to which the system can be restored. The recovery algorithm achieves this by making each processor keep track of both the number of messages it has sent to other processors as well as the number of messages it has received from other processors. Recovery may involve several iterations of roll backs by processors. Whenever a processor rolls back, it is necessary for all other processors to find out if any message sent by the rolled back processor has become an orphan message. Orphan messages are discovered by comparing the number of messages sent to and received from neighboring processors. For example, if RCV Di←j (CkP ti ) > SENTj→i (CkP tj ) (that is, the number of messages received by processor pi from processor pj is greater than the number of messages sent by processor pj to processor pi , according to the current states the processors), then one or more messages at processor pj are orphan messages. In this case, processor pj must roll back to a state where the number of messages received agrees with the number of messages sent. Consider an example shown in Fig. 13.13. Suppose processor Y crashes at the point indicated and rolls back to a state corresponding to checkpoint ey1 . According to this state, Y has sent only one message to X; however, according to X’s current state (ex2 ), X has received two messages from Y. Therefore, X must roll back to a state preceding ex2 to be consistent with Y’s state. We note that if X rolls back to checkpoint ex1 , then it will be consistent with Y’s state, ey1 . Likewise, processor Z must roll back to checkpoint ez2 to be consistent with Y’s state, ey1 . Note that similarly processors X and Z will have to resolve any such mutual inconsistencies (provided they are neighbors).

The Algorithm When a processor restarts after a failure, it broadcasts a ROLLBACK message that it had failed1 . The recovery algorithm at a processor is initiated when it restarts after a failure or when it learns of a failure at another processor. Because of the broadcast of ROLLBACK messages, the recovery algorithm is initiated at all processors. Procedure RollBack_Recovery processor pi executes the following: STEP (a) if processor pi is recovering after a failure then CkP ti := latest event logged in the stable storage else CkP ti := latest event that took place in pi {The latest event at pi can be either in stable or in volatile storage.} end if STEP (b) 1

Such a broadcast can be done using only O(|E|) messages where |E|is the total number of communication links.

468

for k = 1 1 to N {N is the number of processors in the system} do for each neighboring processor pj do compute SENTi→j (CkP ti) send a ROLLBACK(i, SENTi→j (CkP ti)) message to pj end for for every ROLLBACK(j, c) message received from a neighbor j do if RCV Di←j (CkP ti ) > c {Implies the presence of orphan messages} then find the latest event e such that RCV Di←j (e) = c {Such an event e may be in the volatile storage or stable storage.} CkP ti := e end if end for end for{for k} The rollback starts at the failed processor and slowly diffuses into the entire system through ROLLBACK messages. Note that the procedure has |N|iterations. During the kth iteration (k 6= 1), a processor pi does the following: (i) based on the state CkP ti it was rolled back in the (k 1)th iteration, it computes SENTi→j (CkP ti )) for each neighbor pj and sends this value in a ROLLBACK message to that neighbor and (ii) pi waits for and processes ROLLBACK messages that it receives from its neighbors in k th iteration and determines a new recovery point CkP ti for pi based on information in these messages. At the end of each iteration, at least one processor will rollback to its final recovery point, unless the current recovery points are already consistent.

13.7.4 An Example Consider an example shown in Figure 13.14 consisting of three processors. Suppose processor Y fails and restarts. If event ey2 is the latest checkpointed event at Y, then Y will restart from the state corresponding to ey2 . Because of the broadcast nature of ROLLBACK messages, the recovery algorithm is also initiated at processors X and Z. Initially, X, Y, and Z set CkP tX ← ex3 , CkP tY ← ey2 and CkP tZ ← ez2 , respectively, and X, Y, and Z send the following messages during the first iteration: Y sends ROLLBACK(Y,2) to X and ROLLBACK(Y,1) to Z; X sends ROLLBACK(X,2) to Y and ROLLBACK(X,0) to Z; and Z sends ROLLBACK(Z,0) to X and ROLLBACK(Z,1) to Y. Since RCV DX←Y (CkP tX ) = 3 > 2 (2 is the value received in the ROLLBACK(Y,2) message from Y), X will set CkP tX to ex2 satisfying RCV DX←Y (ex2 ) = 1 ≤ 2. Since RCV DZ←Y (CkP tZ ) = 2 > 1, Z will set CkP tZ to ez1 satisfying RCV DZ←Y (ez1 ) = 1 ≤ 1. At Y, RCV DY ←X (CkP tY ) = 1 < 2 and RCV DY ←Z (CkP tY ) = 1 = SENTZ←Y (CkP tZ ). Hence, Y need not roll back further. In the second iteration, Y sends ROLLBACK(Y,2) to X and ROLLBACK(Y,1) to Z; Z sends ROLLBACK(Z,1) to Y and ROLLBACK(Z,0) to X; X sends ROLLBACK(X,0) to Z and ROLLBACK(X, 1) to Y. Note that if Y rolls back beyond ey3 and loses the message from X that caused ey3 , X can resend this message to Y because ex2 is logged at X and this message available in the log. The second and third iteration will progress in the same manner. Note that the 469

ex0

x1

ex1

ex3

ex2

X

ey0

ey1

y1

ey2

failure

ey3

Y

ez0 Z

z1

ez2

ez1

Time

Figure 13.14: An example of Juan-Venkatesan algorithm. set of recovery points chosen at the end of the first iteration, {ex2 , ey2 , ez1 }, is consistent, and no further rollback occurs.

13.8 Manivannan-Singhal Quasi-Synchronous Checkpointing Algorithm When processes independently take their local checkpoints, there is a possiblity that some local checkpoints can never be included in any consistent global checkpoint. (Recall that such local checkpoints are called the useless checkpoints.) In the worst case, no consistent checkpoint can ever be formed. Manivannan-singhal quasi-synchronous checkpointing algorithm improves the performance by eliminating useless checkpoints. The algorithm is based on communication-induced checkpointing, where each process takes basic checkpoints asynchronously and independently, and in addition, to prevent useless checkpoints, processes take forced checkpoints upon the reception of messages with a control variable. Manivannan-singhal quasi-synchronous checkpointing algorithm combines both the coordinated and the uncoordinated checkpointing approaches to get the best of both: • It allows processes to take checkpoints asynchronously. • Uses communication-induced checkpointing to eliminates the “useless" checkpoints. • Since every checkpoint lies on consistent checkpoint, determination of the recovery line during a rollback a recovery is simple and fast. Each checkpoint at a process is assigned a unique sequence number. The sequence numbers assigned to basic checkpoints are picked from the local counters which are incremented periodically. When a process Pi sends a message, it appends the sequence number of its latest checkpoint to the message. When a process Pj receives a message, if the sequence number received in the 470

message is greater than the sequence number of the latest checkpoint of Pj , then before processing the message, Pj takes a (forced) checkpoint and assigns the sequence number received in the message as the sequence number of the checkpoint taken. When it is time for a process to take a basic checkpoint, it skips taking a basic checkpoint if its latest checkpoint has a sequence number greater than or equal to the current value of its counter. This strategy helps to reduce the checkpointing overhead, i.e., the number of checkpoints taken. An alternative approach to reduce the number of checkpoints is to allow a process to delay processing a received message until the sequence number of its latest checkpoint is greater than or equal to the sequence number received in the message.

13.8.1 Checkpointing Algorithm Now, we present the quasi-synchronous checkpointing algorithm formally. The variable nexti of process Pi represents its local counter. It keeps track of the current number of checkpoint intervals at process Pi . The value of the variable sni represents the sequence number of the latest checkpoint of Pi at any time. So, whenever a new checkpoint is taken, the checkpoint is assigned a sequence number and sni is updated accordingly. C.sn denotes the sequence number assigned to checkpoint C and M.sn denotes the sequence number piggybacked to message M. Data Structures at Process Pi sni := 0; nexti := 1;

{Sequence number of the current checkpoint, initialized to 0. This is updated every time a new checkpoint is taken.} {Sequence number to be assigned to the next basic checkpoint; initialized to 1.}

When it is time for process Pi to increment nexti nexti := nexti +1; {nexti is incremented at periodic time intervals of X time units} When process Pi sends a message M M.sn := sni ; {sequence number of the current checkpoint is appended to M} send (M); Process Pj receives a message from process Pi if snj < M.sn then {if sequence number of the current checkpoint is less than Take checkpoint C; checkpoint number received in the message, then C.sn := M.sn; take a new checkpoint before processing the message} snj := M.sn; Process the message. When it is time for process Pi to take a basic checkpoint if nexti > sni then {skips taking a basic checkpoint if nexti ≤ sni (i.e., if it Take checkpoint C; already took a forced checkpoint with sequence number ≥ sni := nexti ; nexti )} C.sn := sni ;

471

Properties When processes take checkpoints in this manner, checkpoints satisfy the following interesting properties: 1. Checkpoint Ci,m of process pi is concurrent with checkpoints C∗,m of all other processes. For example, in Figure 13.15, checkpoint C2,3 is concurrent with checkpoints C1,3 and C3,3 . 2. Checkpoints C∗,m of all processes form a consistent global checkpoint. For example, in Figure 13.15, checkpoints {C1,4 , C2,4 , C3,4 } form a consistent global checkpoint. An interesting application of this result is that if process P3 crashes and restarts from checkpoint C3,5 (in Figure 13.15), then P1 will need to take a checkpoint C1,5 (without rolling back) and the set of checkpoints {C1,5 , C2,5 , C3,5 } will form a consistent global checkpoint. Since there may be gaps in the sequence numbers assigned to checkpoints at a process, we have the following result: 3. The checkpoint Ci,m of process pi is concurrent with the earliest checkpoint Cj,n at process pj such that m ≤ n. For example, in Figure 13.15, checkpoints {C1,3 , C2,2 , C3,2 } form a consistent global checkpoint. The following corollary gives a sufficient condition for a set of local checkpoints to be a part of a global checkpoint. Corollary 4. Let S = {Ci1 ,mi1 , Ci2 ,mi2 , ..., Cik ,mik } be a set of local checkpoints from distinct processes. Let m = min{mi1 , mi2 , ..., mik }. Then, S can be extended to a global checkpoint if ∀ l (1 ≤ l ≤ k), Cil ,mil is the earliest checkpoint of Pil such that mil ≥ m. The following corollary gives a sufficient condition for a global checkpoint to be consistent. Corollary 5. Let S = {C1,m1 , C2,m2 ,..., CN,mN } be a set of local checkpoints one for each process. Let m = min{m1 , m2 , ..., mN }. Then, S is a global checkpoint if ∀ i (1 ≤ i ≤ N), Ci,mi is the earliest checkpoint of Pi such that mi ≥ m. These properties have a strong implication on the failure recovery. The task of finding a consistent global checkpoint after a failure is considerably simplified. If the failed process rolls back to a checkpoint with sequence number m, then all other processes simply need to roll back to the earliest local checkpoint C∗,n such that m ≤ n.

An Example We illustrate the basic idea behind the checkpoints algorithm using an example.

472

Consider a system consisting of three processes P1 , P2 , and P3 shown in Figure 13.15. The basic checkpoints are shown in the figure as “|” and forced checkpoints are shown as “|∗ ”. The sequence numbers assigned to checkpoints are also shown in the figure. Each process Pi increments its variable nexti every x time units. Process P3 takes a basic checkpoint every x time units, P2 takes a basic checkpoint every 2x time units, and P1 takes a basic checkpoint every 3x time units. Message M0 forces P3 to take a forced checkpoint with sequence number 2 before processing message M0 . As a result P3 skips taking a basic checkpoint with sequence number 2. Message M1 forces process P2 to take a forced checkpoint with sequence number 3 before processing M1 because M1.sn < sn2 while receiving the message. Similarly message M2 forces P1 to take a checkpoint before processing the message and M4 forces P2 to take a checkpoint before processing the message. However M3 does not force process P3 to take a checkpoint before processing it. Note that there may be gaps in the sequence numbers assigned to checkpoints at a process.

13.8.2 Recovery Algorithm The recovery process is asynchronous; that is, when a process fails, it just rolls back to its latest checkpoint and broadcasts a rollback request message to every other process and continues it’s processing without waiting for any reply message from them. The recovery is based on the assumption that if a process Pi fails, then no other process fails until the system is restored to a consistent state. In addition to the variables defined in the checkpoint algorithm, the processes also maintains two other variables; inci and rec_linei . The inci is the incarnation number for process Pi . It is incremented every time a process fails and restarts from its latest checkpoint. The rec_linei is the recovery line number. These variables are stored in the stable storage, so that they are made available for recovery. Initially, ∀i, inci = 0 and rec_linei =0. With each message M, the current values of the three variables inci , sni , and rec_linei are piggybacked. The values of these variable piggybacked to M is denoted by M.inc , M.sn , and M.rec_line , respectively. Now we present the basic recovery algorithm formally. Data structures at process Pi integer sni = 0; integer nexti = 1; integer inci = 0; integer rec_linei =0; Checkpointing algorithm When it is time for process Pi to increment nexti nexti = nexti +1; When it is time for process Pi to take a basic checkpoint If (nexti > sni) {

473

Take a checkpoint C; Csn = nexti; sni = Csn ; } When process Pi sends a message M M.sn =sni ; M.rec_line = rec_linei ; M.inc = inci ; send(M); When process Pj receives a message M if (M.inc > incj ) { rec_linej = M.rec_line ; incj = M.inc ; Roll_Back(Pj ); } If (Msn > snj ) { Take a checkpoint C; Csn = Msn ; snj = Csn ; } Process the message; Basic Recovery Algorithm (BRA) Recovery initiated by process Pi after failure Restore the latest checkpoint; inci =inci +1; rec_linei = sni ; send rollback(inci, rec_linei ) to all other processes; resume normal execution; Process Pj upon receiving rollback(inci, rec_linei ) from Pi If (inci > incj ) { incj =inci ; rec_linj = rec_linei ; Roll_back(Pj ); continue as normal; } else Ignore the rollback message; Procedure Rollback(Pj 474

If (rec_linej > snj ) { Take a checkpoint C; C.sn = rec_linej ; snj = C.sn ; } else { Find the earliest checkpoint C with C.sn ≥ rec_linej ; snj =C.sn ; Restore checkpoint C; Delete all checkpoints after C; }

An Explanation When process Pi fails, it rollbacks to its latest checkpoint and broadcasts a rollback(inci , rec_linei ) message to all other processes and continues its normal execution. Upon receiving this rollback message, a process Pj rolls back to its earliest checkpoint whose sequence number ≥ rec_linei , and continues normal execution. If process Pj does not have such a checkpoint, it takes a checkpoint with the sequence number equal to rec_linei , and continues normally. Due to message delays, the broadcast message might be delayed and a process Pj may come to know about a rollback indirectly through some other process that has already seen the rollback message. Since every message is piggybacked with M.inc , M.sn , and M.rec_line , the indirect application message that Pj receives indicates a rollback incarnation by some other process. If process Pj receives such a message M, and M.inc > incj , then Pj infers that some failed process had initiated a rollback with incarnation number M.inc and Pj rolls back to its earliest checkpoint whose sequence number ≥ M.rec_line ; if Pj later receives a rollback message corresponding to this incarnation, it ignores it. Thus, after knowing directly or indirectly about the failure of a process Pi , all other processes rollback to their earliest checkpoint whose sequence number is greater than equal to rec_linei . If any process does not have such a checkpoint, it takes a checkpoint and adds it to the rec-line and proceeds normally. Note that not all processes need to perform a rollback to its earliest checkpoint.

An Example We illustrate the basic recovery using the example in Figure 13.15. Suppose process P3 fails at the instant shown. When P3 recovers, it increments inc3 to 1, sets rec_line3 to sn3 (=5), rolls back to its latest checkpoint C3,5 and sends a rollback(1,5) message to all other processes. Upon receiving this message, P2 will rollback to checkpoint C2,5 since C2,5 is the earliest checkpoint at P2 with sequence number ≥ 5. However, since P1 does not have a checkpoint with sequence number greater than or equal to 5, it takes a local checkpoint and assigns 5 as its sequence number. Thus, {C1,5 , C2,5 , C3,5 } is the recovery line corresponding this failure. 475

3

0

4 *

P1 M2

M1 2

0

3 *

P2

4

5 *

6

B M0 0 P3

1

2 *

M4

M3 3

4

5

failure

C

Figure 13.15: An example illustrating Manivannan-Singhal algorithm Thus, the recovery is simple. The failed process (on recovering from the failure) rolls back to its latest checkpoint and requests other processes to rollback to a consistent checkpoint which they can easily determine solely based on the local information. There is no domino-effect and the recovery is fast and efficient. In this example, we find that the sequence number of all checkpoints in the recovery line is the same, but it need not be the case always.

13.8.3 Comprehensive Message Handling Roll back to a recovery line that is consistent may result in lost, delayed, orphan, or even duplicated messages. Existence of these types of message may lead the system to an inconsistent state. Next, we discuss on how to modify the BRA to handle these messages. Handling the replay of messages Not all messages stored in the stable storage need to be replayed. BRA has to be modified so that it can decide which messages need to be replayed. In Figure 13.16, if we assume that process P1 fails at the point marked X and initiates a recovery with a new incarnation. After failure it rolls back to its latest checkpoint, C1,10 , then increments the incarnation inc1 to 1 and sets the rec_line1 to 10, and sends a rollback(1,10) message to all other processes. Upon receiving the rollback message from P1 , process P2 rolls back to its checkpoint C2,12 . Consequently, all other process rolls back to appropriate checkpoint following the BRA approach. After all the processes have rolled back to a set of consistent checkpoints, these checkpoints form a recovery line with number 10. The messages sent to the left of the recovery line carry incarnation number 0 and messages sent to the right of the recovery line carry incarnation 1. To avoid lost messages, when a process rolls back it must replay all messages from its log whose receive was undone and whose send was not undone. In other words, a process must replay only those messages that originated from the left of the recovery line and delivered to the right of

476

inc =1 rec_line = 10 0

2

P1

4

6

8

failure

10 M1

3

0

4

6

M7

M6

9

12

P2 B M2 4

0

10

8

P3 C

M3

M4

M8 M5

0

1

2

3

4

P4

5

6

7

8

9

10

11

Message sent after the rollback Message sent before the rollback

Figure 13.16: Handling of messages during the recovery. the recovery line. In the example, after rollback process P2 must replay messages M1 and M2 from its log but must not replay M3 ; because the send of M1 and M2 were not undone but the send of M3 was undone. It is easy to determine the origin of the send of a message M by looking at the sequence number (M.sn ) piggybacked. Therefore, we can state a rule for replaying messages as follows: Message replay rule: After a process Pj rolls back to checkpoint C, it replays a message M only if it was received after C and if M.sn < recovery line number. Handling of received messages This section discusses how a process handles received messages. Suppose process Pj receives a message M from process Pi . At the time of receiving the message, if Pj is replaying messages from the message log, then Pj will buffer the message M and will process it only after it finishes with the replaying of messages from the message log. If Pj does not do this then the following three cases may occur. Case 1: M is a delayed message A delayed message with respect to a recovery line carries an incarnation number less than the incarnation number of the receiving process. The process Pi that sent such a message M was not

477

aware of the recovery process at the time of sending of M. Therefore, the piggybacked incarnation number of Pi is less than the latest incarnation number of Pj , the receiving process. In such a situation, if M.sn < rec_linej , then M is first logged in the message log and then processed; otherwise, it is discarded because Pi will eventually rollback and resend the message. In the figure, M4 is logged and then processed by P2 so that P2 might have to replay M4 due to a failure that may occur later, whereas M5 is discarded by P2 . P2 discards M5 because M.sn > rec_line2 (11>10) and M.inc (=0) is less than inc2 (=1). Therefore, we have the following rule for handling delayed messages: Rule for determining and handling delayed messages: A delayed message M received by process Pj has M.inc less than incj . Such a delayed message is processed by process Pj only if M.sn < rec_linej ; otherwise, it is discarded. Case 2: M was sent in the current incarnation Suppose Pj receives a message M such that incj =M.inc . In this case, if M.sn < snj , then Pj must log M before processing it. This is done because Pj might need to replay M due to a future failure. For example, in Figure 13.16, message M7 is sent by process P1 to process P2 after P1 ’s recovery and after P2 ’s rollback during the same incarnation. In this case, M.inc = inc2 =1 and (M.sn = 10) < (sn2 =12) and M7 must be logged before being processed because P2 might have to roll back to checkpoint C2,12 in case of a failure. In that case, P2 will need to replay message M7 . Therefore, the rule for message logging in this case is stated as follows: Message logging rule: A message received by process Pj is logged into the message log if M.inc < incj and M.sn < rec_linej ) or (M.inc =incj and M.sn < snj ). Case 3: Message M was sent in a future incarnation In this case, M.inc > incj and Pj handles it as follows: Pj sets rec_linej to M.rec_line and incj to M.inc , and then rolls back to the earliest checkpoint with sequence number ≥ rec_linej . After the roll back, message M is handled as in Case 2, because M.inc =incj .

Features Manivannan-Singhal quasi-synchronous checkpointing algorithm has several interesting features: • Communication-induced checkpointing intelligently guides the checkpointing activities to eliminates “useless" checkpoints. Thus, every checkpoint lies on consistent checkpoint. • There is no extra message overhead involved in checkpointing. Only a scalar is piggybacked on application messages. • Ensures the existence of a recovery line consistent with the latest checkpoint of any process all the time. This helps bound the depth of rollback during a rollback recovery.

478

• A failed process rolls back to its latest checkpoint and requests other processes to rollback to a consistent checkpoint (no domino-effect). • Helps in garbage collection. After a process has established a recovery line, all checkpoints preceding the line can be deleted. • The algorithm achieves the best of the both worlds: – It has easeness and low overhead of uncoordinated checkpointing. – It has recovery time advantages of coordinated checkpointing.

13.9 Peterson-Kearns Algorithm Based on Vector Time Peterson-Kearns checkpointing and recovery protocol is based on the optimistic rollback. Vector time is used to capture causality to identify events and messages which become orphans when a failed process rolls back.

13.9.1 System Model We assume that there are N processors in the system, which are logically configured as a ring. Each processor knows its successor on the ring and this knowledge is stored in its stable storage since it is critical that it be recoverable after a failure. We assume a single process is executing on each processor. These N processes are denoted as P0, P1, P2.... PN −1 . We assume that P(i+1) mod N is the successor of Pi for 0 ≤ iV i (rsm i (j))where s is a message that was sent to Pi , then it is possible that the failed process has lost the message and it must be resent. It is also possible that the message is not lost, but is still in transit; thus Pi must discard any duplicate messages. Because channels are FIFO, Pi can identify any duplicate message from its timestamp. After the logged messages have been replayed and retransmissions of the required messages are done, Pj instigates a rollback event, rbk j , to indicate that rollback at it is complete. Vector time is not incremented for this event so V(j rbk j )= V j (e’j ), where e’j is the last event replayed. Any logged event whose vector time exceeds tk(i, m).ts is discarded. If tk(i,m).ts ≮ Vj (pj ) when the token arrives, the state of Pj is not changed. However, for consistency, a rollback event is instigated to indicate that rollback is complete at Pj and to allow the token to be propagated. Note that after the rollback is complete, Vj (pj ) ≯ Vj (rsm i , that is, every event in Pj either ′ m happens before the restart event rsi or is concurrent to it. The property of vector time that ei → ′′ ′ ′′ ej iff Vi (ei ) < Vj (ej ) allows us to make this claim. The token is propagated from process pi to process p(i+1)modN . As the token propagates, it rolls back orphan events at every process. When the token returns to the originating process, the roll back recovery is complete.

Handling In-transit Orphan messages It is possible for orphan messages to be in transit during the rollback process. If these messages are received and processed during or after the rollback procedure, an inconsistent global state will result. To identify these orphan messages and discard them on arrival, it is necessary to include an incarnation number with each message and with the token. Inci denotes the current incarnation number of the process Pi . Inc(ei ) denotes the incarnation number of an event ei . The value returned for an event equals the current incarnation number of the process in which the event occurred. The incarnation number in the token is denoted by tk(i,m).inc. When Pi initiates the rollback process, it increments its current incarnation number by one and attaches it to the token. A process receiving the token saves both the vector timestamp of the token and the incarnation number in the stable storage. Because there is no bound on message transmission time, the vector timestamps and associated incarnation numbers which have arrived 481

in the token must be accumulated in a set denoted as OrVecti . The set OrVecti is composed of ordered pairs of token timestamps and incarnation numbers received by process Pi . When an application message is received by process Pi, the vector timestamp of the message is compared to the vector timestamps stored in OrVecti . If the vector timestamp of the message is found to be greater than a timestamp in OrVecti , then the incarnation number of the message is compared to the incarnation number corresponding to the timestamp in OrVecti . If the message incarnation number is smaller, then the message is discarded. Clearly, this is an orphan message that was in transit during the rollback process. In all other cases, the message is accepted and processed. Upon the receipt of a token, the receiving process sets its incarnation number to that in the token.

13.9.3 Formal Description of the RollBack Protocol The causal rollback protocol is described as set of six rules, CRB.1 to CRB.6. For each rule, we first present its formal description and then give a verbal explanation of the rule.

482

The Rollback Protocol CRB.1

wi,i (m) occurs iff there exists fi m , rsi m such that fi m 7→ rsm i → wi,i (m). A formerly failed process creates and propagates a token, event wi,i(m), only after restoring the state from the latest checkpoint and executing the message log from the stable storage.

CRB.2

The occurrence of wi,i (m) implies that tk.(i,m).ts =Vi (rsi m ) ∧ tk.(i,m).inc = Inc(Latest.ck(fm i ))+1 ∧ m Inci = Inc(Latest.ck(fi ))+1 The restart event increments the incarnation number at the recovering process, and the token carries the vector timestamp of the restart event and the newly incremented incarnation number.

CRB.3

wi,j (m), i 6= j occurs iff ∃ rbi k such that ci,j (m) → rbki → wi,j (m) ∧ ′ ′ ′ ∀ ej such that Vj (ej ) > tk(i,m).ts , ¬ Recorded(ej ) A non-failed process will propagate the token only after it has rolled back.

CRB.4

The occurrence of wi,j (m) implies that Inci = tk(i, m).inc ∧ (tk(i, m).ts, tk(i, m).inc) ∈ OrVectj A non-failed process will propagate the token only after it has incremented its incarnation number and has stored the vector timestamp of the token and the incarnation number of the token in its OrVect set.

CRB.5

Polling wave PWi (m) is complete when Ci,j (m) occurs. When the process which failed, recovered, and initiated the token, receives its token back, the rollback is complete.

CRB.6

Any message received by event, n(s), is discarded iff ∃ m ∈ OrVect(p(s)) such that Inc(s) < Inc(m) ∧ V(m) < V(s). Messages which were in transit and which were orphaned by the failure and subsequent restart and recovery must be discarded.

13.9.4 An Example Consider an example consisting of three processes shown in Figure 13.17. The processes have taken checkpoints c1 0 , c1 1, c1 2 . Each event on a process time line is tagged with the vector time (x, 483

P0 (0,0,0) (1,0,0)

(3,0,0) 1 C0

(2,0,0)

(4,4,0)

(5,4,0)

m5

m 4 [0](1,4,0)

[0](5,4,0)

1

C1

P1 (0,0,0) (1,1,0)

1

(1,3,0)

rb1

(1,4,0) C 1,1 (1)

(1,2,0) [0](1,3,0)

m3

[0](1,4,4)

[0](1,4,0)

m6

1

C2 (0,0,1)

(1,3,2)

(1,4,3)

(1,4,4)

W1,1 (1)

(1,4,0) [1](3,0,0)

m1 P2 (0,0,0)

rs 10

C 1,0 (1) [1](3,0,0)

m 0 [0](1,0,0)

(3,0,0) W (1) 1,0 [1](3,0,0)

[0](2,0,0)

m2

failure 1 f0

1

rb2 C 1,2 (1)

(1,4,4)

W1,2 (1)

Figure 13.17: An Example of Rollback recovery in Peterson-Kearns Algorithm. y, z) of its occurrence. Each message is tagged with [i](x, y, z), where i is the incarnation number associated with the message send event, and (x, y, z) is the vector time of the send event. Process P0 fails just after sending message m5 which increments its vector clock to (5,4,0). Upon restart of P0 , the checkpoint c1 0 is restored, and the restart event, rs10 is performed by the protocol. We assume that message m4 was not looged into the stable storage at P0 , henec it can not be replayed during the recovery. A token, [1](4,0,0), is created and propagated to P1 . This is shown in the Figure by a dotted vertical arrow. Upon the receipt of the token, P1 rolls back to a point such that its vector time is not greater than (3,0,0), the time in the token. Hence P1 rolls back to its state at time (1,4,0). P1 then records the token in its OrVect set and sends the token to P2 . P2 takes a similar action and rolls back to message send event with time (1,4,4). The token is then returned to P0 and recovery is complete. Three messages are in transit while the polling wave is executing. The message m2 from P0 to P2 with label [0](2,0,0) will be accepted when it arrives. Likewise, message m6 from P2 will be accepted by P1 when it arrives. However, application of rule CRB.6 will result in message m5 with label [0](5,4,0) being discarded when it arrives at P1 . The net effect of the recovery process is that the application is rolled back to a consistent global state indicated by the dotted line, and all processes have sufficient information to discard messages sent from orphan events on their arrival.

13.9.5 Correctness Proof First we show that all orphaned events are detected and eliminated [28]. Theorem 1: The completion of a wave in casual rollback protocol insures that every event orphaned by failure fm i is eliminated before the final polling wave. Proof: We prove that when the initiator process receives the token back, all the orphan events 484

have been detected and eliminated. That is, for an wi,j (m) event as it specified in causal rollback protocol, ¬ Orphan(wi,j (m), fm i ) First we prove that the token, as constructed during the restoration of a failed process, contains necessary information to determine if any event is orphaned by a failure. If there exists any orphan event e’i due to failure fj m , then the vector timestamp in the token will be less than the vector time of the event, i.e, tk(j,m).ts< Vi (e’i ).By the CRB.2, the vector timestamp in the token, tk(j,m).ts must equal to Vj (rsj m ), and Vj (rsj m ) = Vj (LastEvent(fm j )). In other words, timestamp in the token must be equal to the vector time of the restart event rsj m at process Pj denoted as Vj (rsj m ) and the vector time of the restart event at Pj will be one more than the vector time of the latest event before failure fj m . Since rsj m occupies the same position in causal partial order as e’j and LastEvent(fmj ) 7→ e’j , the following must hold: Vj (rsj m ) ≤ Vj (e’j ). If there exists an orphan e’i, then there exist e’j such that LastEvent(fmj ) → e’j → e’i . Therefore, Vj (e’j ) < Vi (e’i ) and Vj (rsj m ) < Vi (e’i ) which proves that when tk(j,m).ts < Vj (e’i )

— (1)

there exists an orphan event e’i . We use the above result to prove that there exists no orphan event at the end of the final polling wave. ¬ Orphan(wi,j (m), fm i )

— (2)

The proof is by contradiction. Let us assume that there exist a polling event wi,j (m) for which m orphan(wi,j (m), fm i ) is true. Then there exists an event e’i such that LastEvent(fi )→ e’i → wi,j (m). Then there must exist e’j such that e’i → e’j → wi,j (m). This implies orphan(e’j , fm i ). But according to eq (1), tk(i,m).tstk(i,m).ts, ¬ recorded(e’j ). Therefore, every event orphaned by a failure fm i is eliminated before the final polling wave is completed. ✷ Now we show that only all orphaned messages are discarded [28]. Theorem 2: All orphaned messages are discarded and all non-orphaned messages are eventually delivered. Proof: Let us consider a send event s, which is not orphaned by the failure fm i . In this case : n(s) → wi,p(s)(m) ∨ wi,p(s) (m) → n(s). 485

Given reliable channels, the message will eventually arrive. The receipt of a message can only disappear from the causal order if it is lost by a failed process, rolled back by the protocol, or discarded upon arrival. The first possibility is that process Pi lost the message due to its failure. In this case the receiving process p(s) is i. During the rollback at Pσ(s) (the process where the send event occurred), this message will be retransmitted as the occurrence of rb event associated with wi,σ(s) (m) guarantees this. Therefore wi,i →n(s). The second possibility is that n(s)→wi,p(s) and n(s) has rolled back because n(s) was orphaned m by the failure fm i . However if event s is not orphaned by fi , Pp(s) (the receiving process) will request retransmission before the occurrence of the rollback event rb, and wi,p(s)→n(s). The final possibility is that n(s) occurs after the wave but is discarded upon arrival. By CRB.6, n(s) will be discarded if and only if V(s)>tk(i, m).ts and inc(s) ckpti [k] → takeni [k]=m.taken[k] m.ckpt[k] = ckpti [k] → takeni [k]= (takeni [k] ∨ m.taken[k]) end docase With these data structures, the predicate C1 can be expressed as follows:

C1 ≡ (m1 .ckpt[i] = ckpti [i]) ∧ m1 .taken[i] Consider the example shown in Figure 13.21. The first part of the condition C1 states that there is a causal Z-path starting from Ci,x and arriving at Pi before Ci,x+1 , while the second part indicates that some process has taken a checkpoint along this causal Z-path.

13.10.2 The Checkpointing Protocol Now we describe Helary-Mostefaoui-Netzer-Raynal communication induced checkpointing protocol which takes as few forced checkpoints as possible and also ensures that no local checkpoint is useless. The protocol is executed by each process Pi . S0, S1, S2 describe the initialization, the statements executed by Pi when it sends a message, and statements it executes when it receives a message, respectively. The procedure take-checkpoint is called each time Pi takes a checkpoint (basic or forced). The protocol uses the following additional data structure: Every process Pi maintains an array clocki [1..n], where clocki [j] denotes the highest value of lcj known to Pi . clocki [1..n] is initialized to (0,0,. . . ,0) and is updated as follows: 491

C i,x Pi m2

m’

Pk Ck,z Figure 13.21: An example of a Z-cycle.

492

• When a process Pi takes a (basic or forced) checkpoint, it increases clocki [i] clock by 1. • When Pi sends a message m, the current value of clocki is sent on the message. Let m.clock the timestamp associated with a message m. • When a process Pi receives a message m from Pj , it updates its clock as follows: – clocki [i] = max (clocki [i], m.clock[j]) – ∀ k 6= i : clocki [k] = max (clocki [k], m.clock[k]) Note that clocki [i] is lci , so we do not need to keep lci . Procedure take-checkpoint ∀ k do sent_toi[k] = false end do; ∀ k do min_toi[k] = +∞ end do; ∀ k 6= i do takeni [k] = true end do; clocki [i]=clocki [i] + 1; Save the current local state with a copy of clocki [i]; /* let Ci,x denote this checkpoint. We have Ci,x .t= clocki [i] */ ckpti [i] = ckpti [i]+1; (S0) initialization ∀ k do clocki [k] = 0; ckpti [k] =0 end do; takeni [i] = false; take_checkpoint; (S1) When Pi sends a message to Pk if ¬ sent_toi[k] then sent_toi[k] = true; min_toi[k]= clocki [i] end if; Send (m, clocki , ckpti , takeni ) to Pk ; (S2) When Pi receives (m, clocki i, ckpti , takeni ) from Pj /* m.clock[j] is the Lamport’s timestamp of m (i.e., m.t) */ if (∃ k : sent_toi[k] ∧ (m.clock[j]> min_toi[k]) ∧ ((m.clock[j]> max(clocki [k], m.clock[k])) ∨ (m.ckpt[i] = ckpti[i] ∧ m.taken[i])) then take_checkpoint /*forced checkpoint */ end if; clocki [i] = max(clocki [i], m.clock[j]); /* update of the scalar clock lci ≡ clocki [i] */ ∀ k 6= i do clocki [k] = max(clocki[k], m.clock[k]); case m.ckpt[k] < ckpti [k] → skip m.ckpt[k] > ckpti [k] → ckpti [k] = m.ckpt[k]; takeni [k]=m.taken[k] m.ckpt[k] < ckpti [k] → takeni [k]= takeni[k] ∨ m.taken[k] end case end do deliver (m); Helary-Raynal-Netzer-Mostefaoui [15] showed that given a local checkpoint Ci,x with timestamp a, the checkpoint can be associated with the consistent global checkpoint it belongs to using 493

the following result: Theorem: Let a be a Lamport timestamp and Ca be a global checkpoint, {C1,x1 , C2,x2 , . . . , Cn,xn ,}. If ∀ k, Ck,xk is the last checkpoint of Pk such that Ck,xk .t ≤ a, then Ca is a consistent global checkpoint. For a proof, the readers are referred to the original source [15]. This result implies that given a local checkpoint at a process, it is easy to determine what local checkpoints at other processes form a consistent global checkpoint with it. This result has a strong implications on the recovery from a failure.

13.11 Bibliographic Notes Checkpoiting and failure recovery is a well studied topic and a large number of checkpoiting and failure recovery algorithms exist. A classical paper on fault tolerance is by Randell [32]. Classical failure recovery algorithms are Leu and Bhargava algorithm [4], Sistla and Welch algorithm [34], Kim [19], and Strom and Yemini algorithm [35]. Other checkpointing and failure recovery algorithms can be found in [3], [12], [8], [30], [31], [24], [38], [39], [40], [14], [11], [37], and [15]. An excellent review paper on the topic is by Elnozahy, Alvisi, Wang and Johnson [13]. Richard and Singhal give a comprehensive recovery protocol using vector timestamp [33]. An impossibility proof of min-process non-blocking in coordinated checkpointing is given in [7]. Cao and Singhal introduced the concept of mutable checkpointing [8] to improve the performance. Alvisi and Marzullo discuss various message logging techniques [1]. Netzer and Xu discuss necessary and sufficient conditions for consistent global snapshots in distributed systems [27]. Manivannan et al. [26] and Wang [40] discuss how to construct consistent global checkpoints that contain a given set of local checkpoints. Prakash and Singhal discuss how to take maximal global snapshot with concurrent initiators [29]. Other communication-induced checkpointing algorithms can be found in paper by Baldoni et al. [2, 3, 17]. Tong et al. [36] present rollback recovery using loosely synchronized clocks.

494

13.12 Exercise Problems 1. Consider the following simple checkpointing algorithm: A process takes a local checkpoint right after sending a message. Show that the last checkpoint at all processes will always be consistent. What are the trade-offs with this method? 2. Show by example that if Koo-Toueg checkpointing algrithm, if processes do not block after taking a tentative checkpoint, then global checkpoint taken by all processes may not be consistent. 3. Show that in Manivannan-Singhal algorithm, every checkpoint taken is useful. 4. Design a checkpointing and recovery algorithm that uses vector clocks, and does not assume any underlying topology (like ring or tree). 5. Give a rigorous proof of impossibility of a min-process, non-blocking checkpointing algorithm.

495

Bibliography [1] Lorenzo Alvisi , Keith Marzullo, Message Logging: Pessimistic, Optimistic, Causal, and Optimal, IEEE Transactions on Software Engineering, v.24 n.2, p.149-159, February 1998 [2] Roberto Baldoni, Jean Michel Helary, Achour Mostefaoui and Michel Raynal, A Communication-Induced Checkpointing Protocol that Ensures Rollback-Dependency Trackability, Symposium on Fault-Tolerant Computing, 1997, pp. 68-77. [3] Roberto Baldoni, A Communication-Induced Checkpointing Protocol that Ensures RollbackDependency Trackability, Proceedings of the 27th International Symposium on FaultTolerant Computing (FTCS ’97), p.68, June 25-27, 1997. [4] B. Bhargava and P. Leu, Concurrent Robust Checkpointing and Recovery in Distributed Systems, Proc of the IEEE Int. Conf. on Data Eng., pages 154- 163, February 1988. [5] Daniele Briatico, Augusto Ciuffoletti, and Luca Simoncini. A distributed domino-effect free recovery algorithm. In Proc. of Symposium on Reliability in Distributed Software and Database Systems, pages 207-215, Silver Spring (Maryland), October 1984. [6] Guohong Cao, Mukesh Singhal Mutable Checkpoints: A New Checkpointing Approach for Mobile Computing Systems, IEEE Transactions on Parallel and Distributed Systems, Volume 12 , Issue 2 (February 2001), Pages: 157 - 172. (http://www.cse.psu.edu/ gcao/paper/gcao/TPDS01.pdf.) [7] Guohong Cao and Mukesh Singhal, On the Impossibility of Min-Process Non-Blocking Checkpointing and An Efficient Checkpointing Algorithm for Mobile Computing Systems, Proceedings of the 1998 International Conference on Parallel Processing table of contents Pages: 37 - 44, 1998. [8] Guohong Cao and Mukesh Singhal, Checkpointing with mutable checkpoints, Theoretical Computer Science, Volume 290, Issue 2 (January 2003), (Special issue – Dependable computing), Pages: 1127 - 1148, 2003 (http://www.cse.psu.edu/ gcao/paper/gcao/TCS03.pdf). [9] K. Mani Chandy and Leslie Lamport, Distributed snapshots: determining global states of distributed systems, ACM Transactions on Computer Systems (TOCS), v.3 n.1, p.63-75, Feb. 1985. 496

[10] Chandy, M. and Ramamoorthy, C. V., Rollback and recovery strategies for computer programs. IEEE Trans. Comput. 21, 6, 546–556, 1972. [11] Om P. Damani , Yi-Min Wang , Vijay K. Garg, Distributed recovery with K-optimistic logging, Journal of Parallel and Distributed Computing, v.63 n.12, p.1193-1218, December 2003. [12] Elmootazbellah N. Elnozahy and Willy Zwaenepoel, Manetho: Transparent RollBackRecovery with Low Overhead, Limited Rollback, and Fast Output Commit, http://www.cs.utexas.edu/users/mootaz/cs372/Projects/paper2.pdf. [13] E. N. (Mootaz) Elnozahy, Lorenzo Alvisi, Yi-Min Wang and David B. Johnson, A Survey of Rollback-Recovery Protocols in Message-Passing Systems, ACM Computing Surveys, Volume 34 , Issue 3 (September 2002), Pages: 375 - 408. (http://www.cs.utexas.edu/users/lorenzo/papers/SurveyFinal.pdf) [14] Elmootazbellah N. Elnozahy and James S. Plank, Checkpointing for Peta-Scale Systems: A Look into the Future of Practical Rollback-Recovery, IEEE Transactions on Dependable and Secure Computing, v.1 n.2, p.97-108, April 2004 [15] J. M. Helary , A. MosteFaul, R. H. Netzer and Raynal, Communication-Based Prevention of Useless Checkpoints in Distributed Computations, In Proc. of Sixteenth Symposium on Reliable Distributed Systems, 183-190. [16] Jean-Michel Helary , Achour Mostefaoui , Michel Raynal, Preventing Useless Checkpoints in Distributed Computations, Proceedings of the 16th Symposium on Reliable Distributed Systems (SRDS’97), p.183, October 22-24, 1997. [17] Jean-Michel Helary , Achour Mostefaoui , Michel Raynal, Communication-Induced Determination of Consistent Snapshots IEEE Transactions on Parallel and Distributed Systems, Volume 10 , Issue 9 (September 1999), Pages: 865 - 877. [18] T. T-Y. Juang and S. Venkatesan, Crash Recovery with Little Overhead, in Proc. of 11th International Conf. on Distributed Comput. Syst., pages 4154-461, 1991. [19] K. H. Kim, Programmer-Transparent Coordination of Recovering Concurrent Processes: Philosophy and Rules for Efficient Implementation, IEEE Transactions on Software Engineering, Volume 14 , Issue 6 (June 1988), Pages: 810 - 821. [20] K. H. Kim, "Approach to mechanization of the conversation scheme based on monitor," IEEE Trans. Software Eng., vol. SE-8, no. 3, pp. 189- 197, May 1982. [21] K. H. Kim, "Software fault tolerance," in Handbook of Software Engineering, C. R. Vick and C. V. Ramamoorthy, Eds. New York: Van Nostrand Reinhold, 1984.

497

[22] Richard Koo , Sam Toueg, Checkpointing and rollback-recovery for distributed systems, IEEE Transactions on Software Engineering, v 13, no 1, p. 23-31, Jan. 1987. [23] Leslie Lamport, Time, clocks, and the ordering of events in a distributed system, Communications of the ACM, v.21 n.7, p.558-565, July 1978. [24] D. Manivannan , M. Singhal, Asynchronous recovery without using vector timestamps, Journal of Parallel and Distributed Computing, v.62 n.12, p.1695-1728, December 2002 [25] D. Manivannan and Mukesh Singhal, A Low Overhead Recovery Technique Using QuasiSynchronous Checkpointing, ICDCS 1996, pg 100-107. [26] D. Manivannan , Robert H. B. Netzer , Mukesh Singhal, Finding Consistent Global Checkpoints in a Distributed Computation, IEEE Transactions on Parallel and Distributed Systems, v.8 n.6, p.623-627, June 1997. [27] R. H. B. Netzer and J. Xu., Necessary and Sufficient Conditions for Consistent Global Snapshots, IEEE Transactions on Parallel and Distributed Systems, 6(2):165-169, 1995. [28] S. L. Peterson and Phil Kearns, Rollback Based on Vector Time, in Proc. of the Symposium on Reliable Distributed Systems, 1993: 68-77. [29] R. Prakash, M. Singhal, Maximal global snapshot with concurrent initiators, Proc. 6th IEEE Symp. on Parallel Distributed Processing, Dallas, Texas, 1994, pp. 344-351. [30] Ravi Prakash , Mukesh Singhal, Low-Cost Checkpointing and Failure Recovery in Mobile Computing Systems, IEEE Transactions on Parallel and Distributed Systems, v 7 n 10, p.1035-1048, October 1996. [31] Parameswaran Ramanathan , Kang G. Shin, Use of Common Time Base for Checkpointing and Rollback Recovery in a Distributed System, IEEE Transactions on Software Engineering, v.19 n.6, p.571-583, June 1993 [32] B. Randell, System Structure for Software Fault Tolerance, IEEE Trans. on Software Engineering, Vol 1, No 2, 1975, pp. 220-232. [33] Golden G. Richard III , Mukesh Singhal, Complete Process Recovery: Using Vector Time to Handle Multiple Failures in Distributed Systems, IEEE Parallel and Distributed Technology: Systems and Technology, v.5 n.2, p.50-59, April 1997. [34] A. P. Sistla and J. L. Welch, Efficient distributed recovery using message logging, Proceedings of the eighth annual ACM Symposium on Principles of distributed computing table of contents, Edmonton, Alberta, Canada, Pages: 223 - 238, 1989. [35] Rob Strom and Shaula Yemini, Optimistic recovery in distributed systems ACM Transactions on Computer Systems, Volume 3 , Issue 3 (August 1985), Pages: 204 - 226. 498

[36] Z. Tong , R. Y. Kain , W. T. Tsai, Rollback Recovery in Distributed Systems Using Loosely Synchronized Clocks, IEEE Transactions on Parallel and Distributed Systems, v.3 n.2, p.246251, March 1992. [37] S. Venkatesan , Tony Tong-Ying Juang , Sridhar Alagar, Optimistic Crash Recovery without Changing Application Messages, IEEE Transactions on Parallel and Distributed Systems, v.8 n.3, p.263-271, March 1997. [38] Yi-Min Wang and W. Kent Fuchs, Lazy Checkpoint Coordination for Bounding Rollback Propagation, IEEE Symposium on Reliable Distributed Systems, 1993. [39] Yi-Min Wang , Pi-Yu Chung , In-Jen Lin , W. Kent Fuchs, Checkpoint Space Reclamation for Uncoordinated Checkpointing in Message-Passing Systems., IEEE Transactions on Parallel and Distributed Systems, v.6 n.5, p.546-554, May 1995 [40] Yi-Min Wang, Consistent Global Checkpoints that Contain a Given Set of Local Checkpoints, IEEE Transactions on Computers, v.46 n.4, p.456-468, April 1997.

499

Chapter 14 Consensus and Agreement Algorithms 14.1 Problem Definition Agreement among the processes in a distributed system is a fundamental requirement for a wide range of applications. Many forms of coordination require the processes to exchange information to negotiate with one another and eventually reach a common understanding or agreement, before taking application-specific actions. A classical example is that of the commit decision in database systems, wherein the processes collectively decide whether to commit or abort a transaction that they participate in. In this chapter, we study the feasibility of designing algorithms to reach agreement under various system models and failure models, and where possible, examine some representative algorithms to reach agreement. We first state some assumptions underlying our study of agreement algorithms. Failure Models: Among the n processes in the system, at most f processes can be faulty. A faulty process can behave in any manner allowed by the failure model assumed. The various failure models – fail-stop, send omission and receive omission, and Byzantine failures – were discussed in Chapter 2. Recall that in the fail-stop model, a process may crash in the middle of a step, which could be the execution of a local operation or processing of a message for a send or receive event. In particular, it may send a message to only a subset of the destination set before crashing. In the Byzantine failure model, a process may behave arbitrarily. The choice of the failure model determines the feasibility and complexity of solving consensus. Synchronous/Asynchronous communication: If a failure-prone process chooses to send a message to process Pi but fails, then Pi cannot detect the non-arrival of the message in an asynchronous system because this scenario is indistinguishable from the scenario in which the message takes a very long time in transit. We will see this argument again when we consider the impossibility of reaching agreement in asynchronous systems in any failure model. However, in a synchronous system, the scenario in which a message has not been sent can be recognized by the intended recipient, at the end of the round. The intended recipient can deal with the non-arrival of the expected message by assuming the arrival of a message containing some default data, and then proceeding with the next round of the algorithm. 500

Network connectivity: The system has full logical connectivity, i.e., each process can communicate with any other by direct message passing. Sender identification: A process that receives a message always knows the identity of the sender process. This assumption is important – because even with Byzantine behaviour, even though the payload of the message can contain fictitious data sent by a malicious sender, the underlying network layer protocols can reveal the true identity of the sender process. When multiple messages are expected from the same sender in a single round, we implicitly assume a scheduling algorithm that sends these messages in sub-rounds, so that each message sent within the round can be uniquely identified. Channel reliability: The channels are reliable, and only the processes may fail (under one of various failure models). This is a simplifying assumption in our study. As we will see even with this simplifying assumption, the agreement problem is either unsolvable, or solvable in a complex manner. Authenticated vs. non-authenticated messages: In our study, we will be dealing only with unauthenticated messages. With unauthenticated messages, when a faulty process relays a message to other processes, (i) it can forge the message and claim that it was received from another process, and (ii) it can also tamper with the contents of a received message before relaying it. When a process receives a message, it has no way to verify its authenticity. An unauthenticated message is also called an oral message or an unsigned message. Using authentication via techniques such as digital signatures, it is easier to solve the agreement problem because if some process forges a message or tampers with the contents of a received message before relaying it, the recipient can detect the forgery or tampering. Thus, faulty processes can inflict less damage. Agreement variable: The agreement variable may be boolean or multi-valued, and need not be an integer. When studying some of the more complex algorithms, we will use a boolean variable. This simplifying assumption does not affect the results for other data types, but helps in the abstraction while presenting the algorithms. Consider the difficulty of reaching agreement using the following example, that is inspired by the long wars fought by the Byzantium Empire in the Middle Ages. 1 Four camps of the attacking army, each commanded by a general, are camped around the fort of Byzantium. They can succeed in attacking only if they attack simultaneously. Hence, they need to reach agreement on the time of attack. The only way they can communicate is to send messengers among themselves. The messengers model the messages. An asynchronous system is modeled by messengers taking an unbounded time to travel between two camps. A lost message is modeled by a messenger being captured by the enemy. A Byzantine process is modeled by a general being a traitor. The traitor 1 Byzantium was the name of present-day Instanbul in the Middle Ages; Byzantium itself was the new name of Constantinople in the Early Ages.

501

will attempt to subvert the agreement-reaching mechanism, by giving misleading information to the other generals. For example, a traitor may inform one general to attack at 10am, and inform the other generals to attack at noon. Or he may not send a message at all to some general. Likewise, he may tamper with messages he gets from other generals, before relaying those messages.

1 G1

G2

0 1 1

0

1

0

0

1

0 G3

0 G4

0 Figure 14.1: Byzantine generals sending confusing messages. A simple example of Byzantine behavior is shown in Figure 14.1. Four generals are shown, and a consensus decision is to be reached about a boolean value. The various generals are conveying potentially misleading values of the decision variable to the other generals, which results in confusion. In the face of such Byzantine behavior, the challenge is to determine whether it is possible to reach agreement, and if so under what conditions. If agreement is reachable, then protocols to reach it need to be devised.

14.1.1 The Byzantine Agreement and Other Problems 14.1.1.1 The Byzantine Agreement Problem Before studying algorithms to solve the agreement problem, we first define the problem formally. The Byzantine agreement problem requires a designated process, called the source process that has an initial value, to reach agreement with the other processes about its initial value, subject to the following conditions. Agreement: All non-faulty processes must agree on the same value. Validity: If the source process is non-faulty, then the agreed upon value by all the non-faulty processes must be the same as the initial value of the source. Termination: Each non-faulty process must eventually decide on a value. The validity condition rules out trivial solutions, such as one in which the agreed upon value is a constant. It also ensures that the agreed upon value is correlated with the source value. If the 502

source process is faulty, then the correct processes can agree upon any value. It is irrelevant what the faulty processes agree upon – or whether they terminate and agree upon anything at all. There are two other popular flavors of the Byzantine agreement problem – the Consensus problem, and the Interactive Consistency problem. 14.1.1.2 The Consensus Problem The Consensus problem differs from the Byzantine Agreement problem in that each process has an initial value and all the correct processes must agree on a single value. Formally, Agreement: All non-faulty processes must agree on the same (single) value. Validity: If all the non-faulty processes have the same initial value, then the agreed upon value by all the non-faulty processes must be that same value. Termination: Each non-faulty process must eventually decide on a value. 14.1.1.3 The Interactive Consistency Problem The Interactive Consistency problem differs from the Byzantine agreement problem in that each process has an initial value, and all the correct processes must agree upon a set of values, one value for each process. The formal specification is as follows. Agreement: All non-faulty processes must agree on the same array of values A[v1 . . . vn ]. Validity: If process i is non-faulty and its initial value is vi , then all non-faulty processes agree on vi as the ith element of the array A. If process j is faulty, then the non-faulty processes can agree on any value for A[j]. Termination: Each non-faulty process must eventually decide on the array A.

14.1.2 Equivalence of the Problems and Notations The three problems defined above are equivalent in the sense that a solution to any one of them can be used as a solution to the other two problems. This equivalence can be shown using a reduction of each problem to the other two problems. If problem A is reduced to problem B, then a solution to problem B can be used as a solution to problem A in conjunction with the reduction. Exercise 1 asks the reader to show these reductions. Formally, the difference between the agreement problem and the consensus problem is that in the agreement problem, a single process has the initial value, whereas in the consensus problem, all processes have an initial value. However, the two terms are used interchangably in much of the literature and hence we shall also use the terms interchangably.

503

Failure mode No failure Crash failure Byzantine failure

Synchronous system (message-passing and shared memory) agreement attainable; common knowledge also attainable agreement attainable f < n Byzantine processes Ω(f + 1) rounds agreement attainable f ≤ ⌊(n − 1)/3⌋ Byzantine processes Ω(f + 1) rounds

Asynchronous system (message-passing and shared memory) agreement attainable; concurrent common knowledge attainable agreement not attainable

agreement not attainable

Table 14.1: Overview of results on agreement. f denotes number of failure-prone processes. n is the total number of processes. Solvable Variants Reliable broadcast k-set agreement ǫ-agreement

Renaming

Failure model and overhead (MP and SM) crash failures crash failures. f < k.

Definition MP and SM Validity, Agreement, Integrity conditions (Section 14.5.7) size of the set of values agreed upon must be less than k (Section 14.5.4)

crash failures

values agreed upon are within ǫ of each other (Section 14.5.5)

up to f fail-stop processes, n > 2f + 1

select a unique name from a set of names (Section 14.5.6)

Table 14.2: Some solvable variants of the agreement problem in asynchronous system. The overhead bounds are for the given algorithms, and not necessarily tight bounds for the problem.

14.2 Overview of Results Table 14.1 gives an overview of the results and lower bounds on solving the consensus problem under different assumptions. It is worth understanding the relation between the consensus problem and the problem of attaining common knowledge of the agreement value. For the “no failure” case, consensus is attainable. Further, in a synchronous system, common knowledge of the consensus value is also attainable, whereas in the asynchronous case, concurrent common knowledge of the consensus value is attainable. Consensus is not solvable in asynchronous systems even if one process can fail by crashing. To circumvent this impossibility result, weaker variants of the consensus problem are defined in Table 14.2. The overheads given in this Table are for the algorithms described. Figure 14.2 shows further how asynchronous message-passing systems and shared memory systems deal with trying to solve consensus. 504

Circumventing the impossibility results for consensus in asynchronous systems Message−passing

Shared memory

k set consensus

k set consensus

epsilon− consensus

epsilon− consensus

Renaming Reliable broadcast

Consensus

using more powerful objects than atomic registers. This is the study of using atomic registers and universal objects and atomic snapshot objects constructed from atomic registers universal constructions. Renaming

Figure 14.2: Circumventing the impossibility result for consensus in asynchronous systems.

14.3 Agreement in a Failure-Free System (Synchronous or Asynchronous) In a failure-free system, consensus can be reached by collecting information from the different processes, arriving at a ‘decision’, and distributing this decision in the system. A distributed mechanism would have each process broadcast its values to others, and each process computes the same function on the values received. The decision can be reached by using an application-specific function – some simple examples being the majority, max and min functions. Algorithms to collect the initial values and then distribute the decision may be based on the token circulation on a logical ring, or the three-phase tree-based broadcast-convergecast-broadcast, or direct communication with all nodes. • In a synchronous system, this can be done simply in a constant number of rounds (depending on the specific logical topology and algorithm used). Furthermore, common knowledge of the decision value can be obtained using an additional round (See Chapter 4). • In an asynchronous system, consensus can similarly be reached in a constant number of message hops. Further, concurrent common knowledge of the consensus value can also be attained, using any of the algorithms in Chapter 4. Reaching agreement is straightforward in a failure-free system. Hence, we focus on failure-prone systems.

505

(global constants) integer: f ; (local variables) integer: x ←− local value;

// maximum number of crash failures tolerated

(1) Process Pi (1 ≤ i ≤ n) executes the Consensus algorithm for up to f crash failures: (1a) for round from 1 to f + 1 do (1b) if the current value of x has not been broadcast then (1c) broadcast(x); (1d) yj ←− value (if any) received from process j in this round; (1e) x ←− min(x, yj ); (1f) output x as the consensus value.

Figure 14.3: Consensus with up to f fail-stop processes in a system of n processes, n > f . Code shown is for process Pi , 1 ≤ i ≤ n.

14.4 Agreement in (Message-Passing) Synchronous Systems with Failures 14.4.1 Consensus Algorithm for Crash Failures (Synchronous System) Figure 14.3 gives a consensus algorithm for n processes, where up to f processes, where f < n, may fail in the fail-stop model. Here, the consensus variable x is integer-valued. Each process has an initial value xi . If up to f failures (f < n) are to be tolerated, then the algorithm has f + 1 rounds. In each round, a process i sends the value of its variable xi to all other processes if that value has not been sent before. Of all the values received within the round and its own value xi at the start of the round, the process takes the minimum, and updates xi . After f + 1 rounds, the local value xi is guaranteed to be the consensus value. • The agreement condition is satisfied because in the f + 1 rounds, there must be at least one round in which no process failed. In this round, say round r, all the processes that have not failed so far succeed in broadcasting their values, and all these processes take the minimum of the values broadcast and received in that round. Thus, the local values at the end of the round are the same, say xri for all non-failed processes. In further rounds, only this value may be sent by each process at most once, and no process i will update its value xri . • The validity condition is satisfied because processes do not send fictitious values in this failure model. (Thus, a process that crashes has sent only correct values until the crash). For all i, if the initial value is identical, then the only value sent by any process is that identical value which is the value agreed upon as per the agreement condition. • The termination condition is seen to be self-evidently satisfied.

506

Complexity: There are f + 1 rounds, where f < n. The number of messages is at most O(n2 ) in each round, and each message has one integer. Hence the total number of messages is O((f + 1) · n2 ). The worst case scenario is as follows. Assume that the minimum value is with a single process initially. In the first round, the process manages to send its value to just one other process before failing. In subsequent rounds, the single process having this minimum value also manages to send that value to just one other process before failing. The algorithm in Figure 14.3 requires f + 1 rounds, independent of the actual number of processes which fail. An early-stopping consensus algorithm terminates sooner; if there are f ′ actual failures, where f ′ < f , then the early-stopping algorithm terminates in f ′ + 1 rounds. Exercise 2 asks you to design an early-stopping algorithm for consensus under crash failures, and to prove its correctness. A Lower Bound on the Number of Rounds: At least f + 1 rounds are required, where f < n. The idea behind this lower bound is that in the worst-case scenario, one process may fail in each round; with f + 1 rounds, there is at least one round in which no process fails. In that guaranteed failure-free round, all messages broadcast can be delivered reliably, and all processes that have not failed can compute the common function of the received values to reach an agreement value.

14.4.2 Consensus Algorithms for Byzantine Failures (Synchronous System)

Pc

Pc

commander

0

0

commander

0 1

Pa

1

Pb

1

Pa

0

0

(a) malicious process

Pb (b)

correct process

first round message

second round message

Figure 14.4: Impossibility of achieving Byzantine agreement when n = 3 processes and f = 1 malicious process.

14.4.3 Upper Bound on Byzantine Processes In a system of n processes, the Byzantine agreement problem (as also the other variants of the agreement problem) can be solved in a synchronous system only if the number of Byzantine pro507

⌋. cesses f is such that f ≤ ⌊ n−1 3 We informally justify this result using two steps. • With n = 3 processes, the Byzantine agreement problem cannot be solved if the number of Byzantine processes f = 1. The argument uses the illustration in Figure 14.4 which shows a commander Pc and two lieutenant processes Pa and Pb . The malicious process is the lieutenant Pb in the first scenario and hence Pa should agree on the value of the loyal commander Pc , which is 0. But note the second scenario in which Pa receives identical values from Pb and Pc , but now Pc is the disloyal commander whereas Pb is a loyal lieutenant. In this case, Pa needs to agree with Pb . However, Pa cannot distinguish between the two scenarios and any further message exchange does not help because each process has already conveyed what it knows from the third process. In both scenarios, Pa gets different values from the other two processes. In the first scenario, it needs to agree on a 0, and if that is the default value, the decision is correct, but then if it is in the second indistinguishable scenario, it agrees on an incorrect value. A similar argument shows that if 1 is the default value, then in the first scenario, Pa makes an incorrect decision. This shows the impossibility of agreement when n = 3 and f = 1. • With n processes and f ≥ n/3 processes, the Byzantine agreement problem cannot be solved. The correctness argument of this result can be shown using reduction. Let Z(3, 1) denote the Byzantine agreement problem for parameters n = 3 and f = 1. Let Z(n ≤ 3f, f ) denote the Byzantine agreement problem for parameters n(≤ 3f ) and f . A reduction from Z(3, 1) to Z(n ≤ 3f, f ) needs to be shown, i.e., if Z(n ≤ 3f, f ) is solvable, then Z(3, 1) is also solvable. After showing this reduction, we can argue that as Z(3, 1) is not solvable, Z(n ≤ 3f, f ) is also not solvable. The main idea of the reduction argument is as follows. In Z(n ≤ 3f, f ), partition the n processes into three sets S1 , S2 , S3 , each of size ≤ n/3. In Z(3, 1), each of the three processes P1 , P2 , P3 simulates the actions of the corresponding set S1 , S2 , S3 in Z(n ≤ 3f, f ). If one process is faulty in Z(3, 1), then at most f , where f ≤ n/3, processes are faulty in Z(n, f ). In the simulation, a correct process in Z(3, 1) simulates a group of up to n/3 correct processes in Z(n, f ). It simulates the actions (send events, receive events, intra-set communication, and inter-set communication) of each of the processes in the set that it is simulating. With this reduction in place, if there exists an algorithm to solve Z(n ≤ 3f, f ), i.e., to satisfy the validity, agreement, and termination conditions, then there also exists an algorithm to solve Z(3, 1), which has been seen to be unsolvable. Hence, there cannot exist an algorithm to solve Z(n ≤ 3f, f ).

508

Pd 1 0

0

Pd 0

0

0

commander

1 Pa

Pc

0 1

0

commander

0

0

1 0

Pb

Pa

1 (a)

Pc 0 0 0 (b)

first round exchange

Pb

second round exchange correct process

malicious process

Figure 14.5: Achieving Byzantine agreement when n = 4 processes and f = 1 malicious process.

14.4.4 Byzantine Agreement Tree Algorithm: Exponential (Synchronous System) 14.4.4.1 Recursive formulation We begin with an informal description of how agreement can be achieved with n = 4 and f = 1 processes, as depicted in Figure 14.5. In the first round, the commander Pc sends its value to the other three lieutenants, as shown by dotted arrows. In the second round, each lieutenant relays to the other two lieutenants, the value it received from the commander in the first round. At the end of the second round, a lieutenant takes the majority of the values it received (i) directly from the commander in the first round, and (ii) from the other two lieutenants in the second round. The majority gives a correct estimate of the “commander’s” value. Consider Figure 14.5(a) where the commander is a traitor. The values that get transmitted in the two rounds are as shown. All three lieutenants take the majority of (1, 0, 0) which is ‘0’, the agreement value. In Figure 14.5(b), lieutenant Pd is malicious. Despite its behavior as shown, lieutenants Pa and Pb agree on ‘0’, the value of the commander. The first algorithm for solving Byzantine agreement was proposed by Lamport, Shostak, and Pease. We present two versions of the algorithm. The recursive version of the algorithm is given in Figure 14.6. Each message has the following parameters: a consensus estimate value (v), set of destinations (Dests), list of nodes traversed by the message, from most recent to least recent (List), and the number of Byzantine processes that the algorithm still needs to tolerate (f aulty). The list L = hPi, Pk1 . . . Pkf +1−f aulty i represents the sequence of processes (subscripts) in the knowledge expression Ki (Kk1 (Kk2 . . . Kkf +1−f aulty (v0 ) . . .)). This knowledge is what Pkf +1−f aulty conveyed to Pkf −f aulty conveyed to . . . Pk1 conveyed to Pi who is conveying to the receiver of this message, the value of the Commander (Pkf +1−f aulty )’s initial 509

(variables) boolean: v ←− initial value; integer: f ←− maximum number of malicious processes, ≤ ⌊(n − 1)/3⌋; (message type) Oral_Msg(v, Dests, List, f aulty), where v is a boolean, Dests is a set of destination process ids to which the message is sent, List is a list of process ids traversed by this message, ordered from most recent to earliest, f aulty is an integer indicating the number of malicious processes to be tolerated. Oral_Msg(f ), where f > 0: 1. The algorithm is initiated by the Commander, who sends his source value v to all other processes using a OM(v, N, hii, f ) message. The commander returns his own value v and terminates. 2. [Recursion unfolding:] For each message of the form OM(vj , Dests, List, f ′ ) received in this round from some process j, the process i uses the value vj it receives from the source, and using that value, acts as a new source. (If no value is received, a default value is assumed.) To act as a new source, the process i initiates Oral_Msg(f ′ − 1), wherein it sends OM(vj , Dests − {i}, concat(hii, L), (f ′ − 1)) to |N | − 1 − (f − f ′ + 1) destinations not in concat(hii, L) in the next round. 3. [Recursion folding:] For each message of the form OM(vj , Dests, List, f ′ ) received in Step 2, each process i awaits the computed value vk from each of the |N | − 2 − (f − f ′ ) processes (excluding itself) not in List in the folding phase of the recursion. If it receives no value in this round, it uses a default value. Process i then uses the value majorityk6∈List,k6=i (vj , vk ) as the agreement value and returns it to the next higher level in the recursive invocation. Oral_Msg(0): 1. [Recursion unfolding:] Process acts as a source and sends its value to each other process. 2. [Recursion folding:] Each process uses the value it receives from the other sources, and uses that value as the agreement value. If no value is received, a default value is assumed.

Figure 14.6: Byzantine generals algorithm – exponential number of unsigned messages, n > 3f . Recursive formulation. value. The commander invokes the algorithm with parameter f aulty set to f , the maximum number of malicious processes to be tolerated. The algorithm uses f + 1 synchronous rounds. Each message (having this parameter f aulty = k) received by a process invokes several other instances of the algorithm with parameter f aulty = k − 1. The terminating case of the recursion is when the parameter f aulty is 0. As the recursion folds, each process progressively computes the majority function over the values it used as a source for that level of invocation in the unfolding, and the values it has just computed as consensus values using the majority function for the lower level of

510

round number 1 2 ... x x+1 f +1

a message has already visited 1 2 ... x x+1 f +1

aims to tolerate these many failures f f −1 ... (f + 1) − x (f + 1) − x − 1 0

and each message gets sent to n−1 n−2 ... n−x n−x−1 n−f −1

total number of messages in round n−1 (n − 1) · (n − 2) ... (n − 1)(n − 2) . . . (n − x) (n − 1)(n − 2) . . . (n − x − 1) (n − 1)(n − 2) . . . (n − f − 1)

Table 14.3: Relationships between messages and rounds in the Oral Messages algorithm for Byzantine agreement.

invocations. There are an exponential number of messages used by this algorithm. Table 14.3 shows the number of messages used in each round of the algorithm, and relates that number to the number of processes already visited by any message as well as the number of destinations of that message. As multiple messages are received in any one round from each of the other processes, they can be distinguished using the List, or by using a scheduling algorithm within each round. A detailed iterative version of the high-level recursive algorithm is given in Figure 14.7. Lines (2a)-(2e) correspond to the unfolding actions of the recursive pseudo-code, and lines (2f)-(2h) correspond to the folding of the recursive pesudo-code. Two operations are defined in the list L: head(L) is the first member of the list L, whereas tail(L) is the list L after removing its first member. Each process maintains a tree of boolean variables. The tree data structure is used as follows. • There are f + 1 levels from level 0 through level f . hi

• Level 0 has one root node, v0 . • Level i, 0 < i ≤ f has 1 · (n − 2) · (n − 3) . . . (n − i) · (n − (i + 1)) nodes. Each node at level (i − 1) has (n − (i + 1)) child nodes. • Node vkL denotes the command received from the node head(L) by node k. The command was relayed to head(L) by head(tail(L)), which received it from head(tail(tail(L))), and so on. The very last element of L is the commander, denoted P0 . • In the f + 1 rounds of the algorithm (lines (2a)-(2e) of the iterative version), each level k, 0 ≤ k ≤ f , of the tree is successively filled to remember the values received at the end of round k + 1, and with which the process sends the multiple instances of the Oral_Msg message with the fourth parameter as f − (k + 1) in round k + 2 (other than the final terminating round). • For each message that arrives in a round (lines 2b-2c of the iterative version), a process tail(L) sets vhead(L) (line 2d). It then removes itself from Dests, prepends itself to L, decrements f aulty, and forwards the value v to the updated Dests (line 2e). 511

(variables) boolean: v ←− initial value; integer: f ←− maximum number of malicious processes, ≤ ⌊n/3⌋; tree of boolean: • level 0 root is v0L , where L = hi;

• level i(f ≥ i > 0) nodes: for each vjL at level sizeof (L)(= i − 1), its n − 2 − sizeof (L) descendants at level concat(hji,L)

i = sizeof (L) + 1 are vk

, ∀k such that k 6= j, i and k is not a member of list L.

(message type) OM(v, Dests, List, f aulty), where the parameters are as in the recursive formulation.

(1) Initiator (i.e., Commander) initiates Oral Byzantine agreement: (1a) send Oral_Msg(v, N − {i}, hPi i, f ) to N − {i}; (1b) return(v). (2) (Non-initiator, i.e., Lieutenant) receives Oral Message OM : (2a) for rnd = 0 to f do (2b) for each message OM that arrives in this round, the following is performed: (2c) receive Oral_Msg(v, Dests, L = hPk1 . . . Pkf +1−f aulty i, f aulty) from Pk1 ; // faulty + round = f; |Dests| + sizeof (L) = n tail(L) (2d) vhead(L) ←− v; // sizeof (L) + f aulty = f + 1. fill in estimate of node at level sizeof (tail(L)) (2e) send Oral_Msg(v, Dests − {i}, hPi , Pk1 . . . Pkf +1−f aulty i, f aulty − 1) to Dests − {i} ; (2f) for level = f − 1 down to 0 do (2g) for each of the 1 · (n − 2) · . . . (n − (level + 1)) nodes vxL in level level, the following is performed: concat(hxi,L) (2h) vxL (x 6= i, x 6∈ L) = majorityy 6∈ concat(hxi,L);y6=i(vxL , vy );

Figure 14.7: Byzantine generals algorithm – exponential number of unsigned messages, n > 3f . Iterative formulation. • Once the entire tree is filled from root to leaves, the actions in the folding of the recursion are simulated in lines (2f)-(2h) of the iterative version, proceeding from the leaves up to the root of the tree. These actions are crucial – they entail taking the majority of the values at each level of the tree. The final value of the root is the aagreement value, which will be the same at all processes. 14.4.4.2 Example Figure 14.8 shows the tree at a lieutenant node P3 , for n = 10 processes P0 through P9 and f = 3 processes. The commander is P0 . Only one branch of the tree is shown for simplicity. The reader is urged to work through all the steps to ensure a thorough understanding. Some key steps from P3 ’s perspective are outlined next, with respect to the iterative formulation of the algorithm. Round 1: P0 sends its value to all other nodes. This corresponds to invoking Oral_Msg(3) in the hi recursive formulation. At the end of the round, P3 stores the received value in v0 Round 2: P3 acts as a source for this value and sends this value to all nodes except itself and P0 . 512

This corresponds to invoking Oral_Msg(2) in the recursive formulation. Thus, P3 sends 8 messages. It will receive a similar message from all other nodes except P0 and itself; the h0i value received from Pk is stored in vk . Round 3: For each of the 8 values received in round 2, P3 acts as a source and sends the values to all nodes except (i) itself, (ii) nodes visited previously by the corresponding value, as remembered in the superscript list, and (iii) the direct sender of the received message, as indicated by the subscript. This corresponds to invoking Oral_Msg(1) in the recursive formulation. Thus, P3 sends 7 messages for each of these 8 values, giving a total of 56 messages it sends in this round. Likewise it receives 56 messages from other nodes; the values are stored in level 2 of the tree. Round 4: For each of the 56 messages received in round 3, P3 acts a source and sends the values to all nodes except (i) itself, (ii) nodes visited previously by the corresponding value, as remembered in the superscript list, and (iii) the direct sender of the received message, as indicated by the subscript. This corresponds to invoking Oral_Msg(0) in the recursive formulation. Thus, P3 sends 6 messages for each of these 56 values, giving a total of 336 messages it sends in this round. Likewise, it receives 336 messages, and the values are stored at level 3 of the tree. As this round is Oral_Msg(0), the received values are used as estimates for computing the majority function in the folding of the recursion. An example of the majority computation is as follows. h5,0i

• P3 revises its estimate of v7 by taking h5,0i h7,5,0i h7,5,0i h7,5,0i h7,5,0i h7,5,0i h7,5,0i majority(v7 , v1 , v2 , v4 , v6 , v8 , v9 ). Similarly for the other nodes at level 2 of the tree. h0i

• P3 revises its estimate of v5 by taking h0i h5,0i h5,0i h5,0i h5,0i h5,0i h5,0i h5,0i majority(v5 , v1 , v2 , v4 , v6 , v7 , v8 , v9 ). Similarly for the other nodes at level 1 of the tree. hi

• P3 revises its estimate of v0 by taking hi h0i h0i h0i h0i h0i h0i h0i h0i majority(v0 , v1 , v2 , v4 , v5 , v6 , v7 , v8 , v9 ). This is the consensus value. 14.4.4.3 Correctness The correctness of the Byzantine agreement algorithm in Figure 14.7 can be observed from the following two informal inductive arguments. Here we assume that the Oral_Msg algorithm is invoked with parameter x, and that there are a total of f malicious processes. There are two cases depending on whether the commander is malicious. A malicious commander causes more chaos than an honest commander. This is the second case considered below.

513

v 0

level 0

enter after round 1 round 2

level 1

v4 v1 v 2

v 5

v 6

v 6

level 2

v4 v v 1 2

v v v9 7 8 round3

v 7

v v 9 8 round4

level 3

v v4 v6 v v8 v9 1 2

Figure 14.8: Local tree at P3 for solving Byzantine agreement, for n = 10 and f = 3. Only one branch of the tree is shown for simplicity. Loyal commander: Given f and x, if the commander process is loyal, then Oral_Msg(x) is correct if there are at least 2f + x processes. This can easily be seen by induction on x. • For x = 0, Oral_Msg(0) is executed, and the processes simply use the (loyal) commander’s value as the consensus value. • Now assume the above induction hypothesis for any x. • Then for Oral_Msg(x + 1), there are 2f + x + 1 processes including the Commander. Each loyal process invokes Oral_Msg(x) to broadcast the (loyal) commander’s value v0 – here it acts as a commander for this invocation it makes. As there are 2f + x processes for each such invocation, by the induction hypothesis, there is agreement on this value (at all the honest processes) – this would be at level 1 in the local tree in the folding of the recursion. In the last step, each loyal process takes the majority of the direct order received from the commander (level 0 entry of the tree), and its estimate of the commander’s order conveyed to other processes as computed in the level 1 entries of the tree. Among the 2f + x values taken in the majority calculation (this includes the Commanders’s value but not its own), the majority is loyal because x > 0. Hence, taking the majority works. No assumption about commander: Given f , Oral_Msg(x) is correct if x ≥ f and there are a total of 3x + 1 or more processes. 514

This case accounts for both possibilities – the commander being malicious or honest. An inductive argument is again useful. • For x = 0, Oral_Msg(0) is executed, and as there are no malicious processes (0 ≥ f ) the processes simply use the (loyal) commander’s value as the consensus value. Hence the algorithm is correct. • Now assume the above induction hypothesis for any x. • Then for Oral_Msg(x+1), there are at least 3x+4 processes including the Commander and at most x + 1 are malicious. – (Loyal commander:) If the commander is loyal, then we can apply the argument used for the “loyal Commander” case above, because there will be more than (2(f + 1) + (x + 1)) total processes. – (Malicious commander:) There are now at most x other malicious processes and 3x + 3 total processes (excluding the commander). From the induction hypothesis, each loyal process can compute the consensus value using the majority function in the protocol.

?

?

commander

commander

1

0

0

Oral_Msg(k−1) Oral_Msg(k) correct process

Oral_Msg(k−1) Oral_Msg(k) malicious process

(a)

(b)

Figure 14.9: The effects of a loyal or a disloyal commander in a system with n = 14 and f = 4. The subsystems that need to tolerate k and k − 1 traitors are shown for two cases. (a) Loyal commander. (b) No assumptions about commander. Illustration of arguments (Figure 14.9): In part (a), the commander who invokes Oral_Msg(x) is loyal, so all the loyal processes have the same estimate. Although the subsystem of 3x processes has x malicious processes, all the loyal processes have the same view to begin with. Even if this case repeats for each nested invocation of Oral_Msg, even after x rounds, among the processes, the loyal processes are in a simple majority, so the majority function works in having them maintain 515

the same common view of the loyal commander’s value. (Of course, had we known the commander was loyal, then we could have terminated after a single round, and neither would we be restricted by the n > 3x bound.) In part (b), the commander who invokes Oral_Msg(x) may be malicious and can send conflicting values to the loyal processes. The subsystem of 3x processes has x − 1 malicious processes, but all the loyal processes do not have the same view to begin with. Complexity: The algorithm requires f + 1 rounds, an exponential amount of local memory, and (n − 1) + (n − 1)(n − 2) + . . . + [(n − 1)(n − 2) . . . (n − f − 1)] messages. 14.4.4.4 Phase-King Algorithm for Consensus: Polynomial (Synchronous System) ⌋ The Lamport-Shostak-Pease algorithm requires f + 1 rounds and can tolerate up to f ≤ ⌊ n−1 3 malicious processes, but requires an exponential number of messages. The phase-king algorithm solves the consensus problem under the same model, requiring f + 1 phases, and a polynomial number of messages (which is a huge savings), but can tolerate only f < ⌈n/4⌉ malicious processes. The algorithm is so called because it operates in f + 1 phases, each with two rounds, and a unique process plays an asymmetrical role as a leader in each round.

P 0 P 1 P f+1 P k phase 1

phase 2

phase f+1

Figure 14.10: Message pattern for the phase-king algorithm. The phase king algorithm is given in Figure 14.11, and assumes a binary decision variable. The message pattern is illustrated in Figure 14.10. Round 1: In the first round (lines (1b) - (1f)) of each phase, each process broadcasts its estimate of the consensus value to all other processes, and likewise awaits the values broadcast by others. At the end of the round, it counts the number of ‘1’ votes and the number of ‘0’ votes. If either number is greater than n/2, then it sets its majority variable to that consensus value, and sets mult to the number of votes received for the majority value. If neither number is greater than n/2, which may happen when the malicious processes do not respond, and the 516

(variables) boolean: v ←− initial value; integer: f ←− maximum number of malicious processes, f < ⌈n/4⌉; tree of boolean: (1) Each process executes the following f + 1 phases, where f < n/4: (1a) for phase = 1 to f + 1 do (1b) Execute the following Round 1 actions: // actions in round one of each phase (1c) broadcast v to all processes; (1d) await value vj from each process Pj ; (1e) majority ←− the value among the vj that occurs > n/2 times (default value if no majority); (1f) mult ←− number of times that majority occurs; (1g) Execute the following Round 2 actions: // actions in round two of each phase (1h) if i = phase then // only the phase leader executes this send step (1i) broadcast majority to all processes; (1j) receive tiebreaker from Pphase (default value if nothing is received); (1k) if mult > n/2 + f then (1l) v ←− majority; (1m) else v ←− tiebreaker; (1n) if phase = f + 1 then (1o) output decision value v.

Figure 14.11: Phase-king algorithm – polynomial number of unsigned messages, n > 4f . Code is for process Pi , 1 ≤ i ≤ n. correct processes are split among themselves, then a default value is used for the majority variable. Round 2: In the second round (lines (1g)-(1o)) of each phase, the phase king initiates processing – the phase king for phase k is the process with identifier Pk , where k ∈ {1 . . . n}. The phase king broadcasts its majority value majority, which serves the role of a tie-breaker vote for those other processes which have a value of mult of less than n/2 + f . Thus, when a process receives the tie-breaker from the phase king, it updates its estimate of the decision variable v to the value sent by the phase king if its own mult variable < n/2 + f . The reason for this is that among the votes for its own majority value, f votes could be bogus and hence it does not have a clear majority of votes (i.e., > n/2) from the non-malicious processes. Hence, it adopts the value of the phase king. However, if mult > n/2 + f (lines (1k)-(1l)), then it has received a clear majority of votes from the non-malicious processes, and hence it updates its estimate of the consensus variable v to its own majority value, irrespective of what tie-breaker value the phase king has sent in the second round. At the end of f + 1 phases, it is guaranteed that the estimate v of all the processes is the correct consensus value. Correctness: The correctness reasoning is in three steps. 517

1. Among the f + 1 phases, the phase king of some phase k is non-malicious because there are at most f malicious processes. 2. As the phase king of phase k is non-malicious, all non-malicious processes can be seen to have the same estimate value v at the end of phase k. Specifically, observe that any two non-malicious processes Pi and Pj can set their estimate v in three ways. (a) Both Pi and Pj uses their own majority values. Assume Pi ’s majority value is x, which implies that Pi ’s mult > n/2 + f , and of these voters, at least n/2 are nonmalicious. This implies Pj must also have received at least n/2 votes for x, implying that its majority value majority must also be x. (b) Both Pi and Pj use the phase king’s tie-breaker value. As Pk is non-malicious it must have sent the same tie-breaker value to both Pi and Pj . (c) Pi uses its majority value as the new estimate and Pj uses the phase king’s tie-breaker as the new estimate. Assume Pi ’s majority value is x, which implies that Pi ’s mult > n/2 + f , and of these voters, at least n/2 are non-malicious. This implies phase king Pk must also have received at least n/2 votes for x, implying that its majority value majority that it sends as tie-breaker must also be x. For all three possibilities, any two non-malicious processes Pi and Pj agree on the consensus estimate at the end of phase k, where the phase king Pk is non-malicious. 3. All non-malicious processes have the same consensus estimate x at the start of phase k + 1 and they continue to have the same estimate at the end of phase k + 1. This is self-evident because we have that n > 4f and each non-malicious process receives at least n − f > n/2 + f votes for x from the other non-malicious processes in the first round of phase k + 1. Hence, all the non-malicious processes retain their estimate v of the consensus value as x at the end of phase k + 1. The same logic holds for all subsequent phases. Hence, the consensus value is correct. Complexity: The algorithm requires f + 1 phases and two sub-rounds each, and (f + 1)[(n − 1)(n + 1)] messages.

14.5 Agreement in Asynchronous Message-Passing Systems with Failures 14.5.1 Impossibility Result for the Consensus Problem Fischer, Lynch, and Paterson showed a fundamental result on the impossibility of reaching agreement in an asynchronous (message-passing) system, even if a single process is allowed to have a crash failure. This result, popularly known as the FLP impossibility result, has a significant impact 518

on the field of designing distributed algorithms in a failure-susceptible system. The correctness proof of this result also introduced the important notion of valency of global states. For any global state GS, let v(GS) denote the set of possible values that can be agreed upon in some global state reachable from GS. |v(GS)| is defined as the valency of global state GS. For a boolean decision value, a global state can be bivalent, i.e., have a valency of two, or monovalent, i.e., having a valency of one. A monovalent state GS is 1-valent if v(GS) = {1} and it is 0-valent if v(GS) = {0}. Bivalency of a global state captures the idea of uncertainty in the decision, as either a 0-valent or a 1-valent state may be reachable from this bivalent state. In an (asynchronous) failure-free system, Section 14.3 showed how to design protocols that can reach consensus. Observe that the consensus value can be solely determined by the inputs. Hence, the initial state is monovalent! In the face of failures, it can be shown that a consensus protocol necessarily has a bivalent initial state (assuming each process can have an arbitrary initial value from {0, 1}, to rule out trivial solutions). This argument is by contradiction. Clearly, the initial state where inputs are all 0 is 0-valent and the initial state where inputs are all 1 is 1-valent. Transforming the input assignments from the all-0 case to the all-1 case, observe that there must exist input assignments I~a and I~b that are 0-valent and 1-valent, respectively, and that differ in the input value of only one process, say Pi . If a 1-failure tolerant consensus protocol exists, then: • Starting from I~a , if Pi fails immediately, the other processes must agree on 0 due to the termination condition. • Starting from I~b , if Pi fails immediately, the other processes must agree on 1 due to the termination condition. However, execution (2) looks identical to execution (1), to all processes, and must end with a consensus value of 0, a contradiction. Hence, there must exist at least one bivalent initial state. Observe that reaching consensus requires some form of exchange of the intial values (either by message-passing or shared memory, depending on the model). Hence, a running process cannot make a unilateral decision on the consensus value The key idea of the impossibility result is that in the face of a potential process crash, it is not possible to distinguish between a crashed process and a process or link that is extremely slow. Hence, from a bivalent state, it is not possible to transition to a univalent state. More specifically, the argument runs as follows. For a protocol to transition from a bivalent global state to a monovalent global state, and using the global time interleaved model for reasoning in the proof, there must exist a critical step execution that changes the valency by making a decision on the consensus value. There are two possibilities. • The critical step is an event that occurs at a single process. However, other processes cannot tell apart the two scenarios in which this process has crashed, and in which this process is extremely slow. In both scenarios, the other processes can continue to wait forever and hence the processes may not reach a consensus value, remaining in bivalent state.

519

• The critical step occurs at two or more independent (i.e., not send-receive related) events at different processes. However, as independent events at different processes can occur in any permutation, the critical step is not well-defined and hence this possibility is not admissible. Thus, starting from a bivalent state, it is not possible to transition to a monovalent state. This is the key to the impossibility result for reaching consensus in asynchronous systems. The impossibility result is significant because it implies that all problems to which the agreement problem can be reduced are also not solvable in any asynchronous system in which crash failures may occur. As all real systems are prone to crash failures, this result has practical significance. We can show that all the problems, such as the following, requiring consensus are not solvable in the face of even a single crash failure. • the leader election problem. • the computation of a network-side global function using broadcast-convergecast flows. • terminating reliable broadcast. • atomic broadcast. The common strategy is to use a reduction mapping from the consensus problem to the problem X under consideration. We need to show that using an algorithm to solve X, we can solve consensus. But as consensus is unsolvable, so must be problem X.

14.5.2 Terminating Reliable Broadcast As an example, consider the Terminating Reliable Broadcast problem which states that a correct process always gets a message even if the sender crashes while sending. If the sender crashes while sending the message, the message may be a null message but it must be delivered to each correct process. The formal specification of Reliable Broadcast was studied in Chapter 3; here we have the additional termination condition which states that each correct process must eventually deliver some message. Validity: If the sender of a broadcast message m is nonfaulty, then all correct processes eventually deliver m. Agreement: If a correct process delivers a message m, then all correct processes deliver m. Integrity: Each correct process delivers at most one message. Further, if it delivers a message different from the null message, then the sender must have broadcast m. Termination: Every correct process eventually delivers some message.

520

The reduction from consensus to Terminating Reliable Broadcast is as follows. A commander process broadcasts its input value using the Terminating Reliable Broadcast. A process decides on a ‘0’ or ‘1’ depending on whether it receives ‘0’ or ‘1’ in the message from this process. However, if it receives the null message, it decides on a default value. As the broadcast is done using the Terminating Reliable Broadcast, it can be seen that the conditions of the consensus problem (Section 14.1.1) are satisfied. But as consensus is not solvable, an algorithm to implement Terminating Reliable Broadcast cannot exist.

14.5.3 Distributed Transaction Commit Database transactions require the Commit operation to preserve the ACID properties (atomicity, consistency, integrity, durability) of transactional semantics. The commit operation requires polling all participants whether the transaction should be committed or rolled back. Even a single rollback vote requires the transaction to be rolled back. Whatever the decision, it is conveyed to all the participants in the transaction. Clearly, this can be seen to be a consensus problem. Exercise 5 asks you to formally prove that distributed commit is not solvable under a crash failure. Despite the unsolvability of the distributed commit problem under crash failure, the (blocking) two-phase commit and the non-blocking three-phase commit protocols do solve the problem. This is because the protocols use a somewhat different model in practice, than that used for our theoretical analysis of the consensus problem. The two-phase protocol waits indefinitely for a reply, and it is assumed that a crashed node eventually recovers and sends in its vote. Optimizations such as presumed abort and presumed commit are pessimistic and optimistic solutions that are not guaranteed to be correct under all circumstances. Similarly, the three-phase commit protocol uses timeouts to default to the ‘abort’ decision when the coordinator does not get a reply from all the participants within the timeout period.

14.5.4 k-set consensus Although consensus is not solvable in an asynchronous system under crash failures, a weaker version, known as the k-set consensus problem, is solvable as long as the number of crash failures f is less than the parameter k. The parameter k indicates that the nonfaulty processes agree on different values, as long as the size of the set of values agreed upon is bounded by k. Assuming that the consensus value is from a multi-valued domain, the problem specification is as follows. k-Agreement: All non-faulty processes must make a decision, and the set of values that the processes decide on can contain up to k values. Validity: If a non-faulty process decides on some value, then that value must have been proposed by some process. Termination: Each non-faulty process must eventually decide on a value. 521

(variables) integer: v ←− initial value; (1) A process Pi , 1 ≤ i ≤ n, initiates k-set consensus: (1a) broadcast v to all processes. (1b) await values from |N| − f processes and add them to set V ; (1c) decide on max(V ). Figure 14.12: Protocol for k-set consensus. Code shown is for process Pi , 1 ≤ i ≤ n. The k-Agreement condition is new, the Validity condition is different from that for regular consensus, and the Termination condition is unchanged from that for regular consensus. The protocol in Figure 14.12 can be seen to solve k-set consensus in a straightforward manner, as long as the number of crash failures f is less than k.

14.5.5 Approximate Agreement Another weaker version of consensus that is solvable in an asynchronous system under crash failures is known as the approximate consensus problem. Like k-set consensus, approximate agreement also assumes the consensus value is from a multi-valued domain. However, rather than restricting the set of consensus values to a set of size k, ǫ-approximate agreement requires that the agreed upon values by the nonfaulty processes be within ǫ of each other. The problem specification is as follows. ǫ-Agreement: All non-faulty processes must make a decision and the values decided upon by any two non-faulty processes must be within ǫ range of each other. Validity: If a non-faulty process Pi decides on some value vi , then that value must be within the range of values initially proposed by the processes. Termination: Each non-faulty process must eventually decide on a value. 14.5.5.0.1 Algorithm Outline. The Dolev et al. algorithm to solve approximate agreement in the message-passing model is studied next. The algorithm for the message-passing model assumes n ≥ 5f + 1, although the problem is solvable for n > 3f + 1. The asynchronous approximate agreement algorithm simulates synchronous communication by operating in rounds. The algorithm in given in Figure 14.13. Steps (1a)-(1c) perform the initialization computation to decide the number of synchronous rounds to be simulated. We will examine this logic after examining the rest of the algorithm. The main loop, in lines (1d)-(1f), performs an all-to-all message exchange asynchronously for the determined number of rounds. In each round (simulated by Asynchronous_Exchange), a process broadcasts its estimate of the agreement value, and awaits n − f such messages from other processes before moving to the next 522

(variables) real: v ←− input value; multiset of real V ; integer r ←− 0;

//initial value // number of rounds to execute

(1) Execution at process Pi , 1 ≤ i ≤ n: (1a) V ←− Asynchronous_Exchange(v, 0); (1b) v ←− any element in(reduce2f (V )); (1c) r ←− ⌈logc (dif f (V ))/ǫ⌉, where c = c(n − 3f, 2f ). (1d) for round from 1 to r do (1e) V ←− Asynchronous_Exchange(v, round); (1f) v ←− new2f,f (V ); (1g) broadcast (hv, halti, r + 1); (1h) output v as decision value. (2) Asynchronous_Exchange(v,h) returns V : (2a) broadcast (v, h) to all processes; (2b) await n − f responses belonging to round h; (2c) for each process Pk that sent hx, halti as value, use x as its input henceforth; (2d) return the multiset V .

Figure 14.13: Asynchronous approximation agreement algorithm. Here, n ≥ 5f + 1. round. After each round, each process revises its estimate of the consensus value. The estimate is revised in such a way that the choices of the different processes are guaranteed to converge at a certain rate. Consider any sorted collection U. The new estimate of a process is chosen by computing newk,f (U), which is parameterized by k and f , and defined as mean(selectk (reducef (U))) reducef (U) removes the f largest and f smallest members of U. selectk (U) selects every kth member of U, beginning with the first. If U has m members, selectk (U) has c(m, k) = ⌊(m − 1)/k⌋ + 1 members. This constant c represents a convergence factor towards the final agreement value, i.e., if x is the range of possible values held by correct processes before a round, then x/c is the possible range of estimate values held by those processes after that round. Illustration of definitions: Figure 14.14 shows the selectk (reducef (U)) operation, with k = 5 and f = 4. The mean of the selected members is the new estimate new5,4 (U). The algorithm uses m = n − 3f and k = 2f . So c(n − 3f, 2f ) will represent the convergence factor towards reaching approximate agreement and new2f,f is the new estimate after each round. The choice of these parameters will be justified. 14.5.5.0.2 Notation. The algorithm uses multisets, which are sets with repeating elements included. Union, intersection, and set difference operations on multisets are natural extensions of the 523

U reduce f(U)

f=4 k=5

u0

u5

u 10

u 15

u 20

u 25

shaded members belong to select (reduce 4(U)) 5

Figure 14.14: Illustrating selectk (reducef (U)), with k = 5 and f = 4. reduce4 (U) has 26 members, hence c(26, 5) = 6 members are selected. counterparts for regular sets. mean(U) is the arithmetic mean of U, calculated by considering each instance in the multiset. min(U) and max(U) are defined as for sets. range(U) is the interval [min(U), max(U)]. dif f (U) is max(U) − min(U). Some essential combinatorial results are first proved. Let |U| = m, and let the m elements u0 . . . um−1 of multiset U be in nondecreasing order. The following properties on nonempty multisets U, V , and W can easily be seen. Property 1. The number of the elements in multisets U and V is reduced by at most 1 when the smallest element is removed from both. Similarly for the largest element. Property 2. The number of elements common to U and V before and after j reductions differ by at most 2j. Thus, for j ≥ 0 and |V |, |W | ≥ 2j, |V ∩W |−|reducej (V )∩reducej (W )| ≤ 2j. Property 3. Let V contain at most j values not in U, i.e., |V − U| ≤ j, and let size of V be at least 2j. Then by removing the j low and j high elements from V , it is easy to see that remaining elements in V must belong to the range of U, see Figure 14.15. Thus, • each value in reducej (V ) is in the range of U, i.e., range(reducej (V )) ⊆ range(U). • newk,j (V ) ∈ range(U). 14.5.5.0.3 Convergence Rate of Approximation. Let U be the multiset of estimates, one estimate per correct process, at the start of a round. Let V and W be the multisets received at two arbitrary correct proceses in that round. The processes use the approximation function to choose their values for the next round. The new estimates chosen by any two arbitrary correct processes, using the approximation function newk,f , are guaranteed to be within range(U)/c(m, k) of each other, when (i) |V | = |W | = m, (ii) |W − V |, |V − W | ≤ k and (iii) (|V − U|, |W − U| ≤ f . 524

Convergence Rate. Let k > 0, f ≥ 0, and m > 2f . For the multisets received, |V | = |W | = m. Let the multisets received differ from U in at most f elements (|V −U|, |W −U| ≤ f ), and let the multisets received differ from each other in at most k elements (|W − V |, |V − W | ≤ k). Then |newk,f (V ) − newk,f (W )| ≤ dif f (U)/c(m − 2f, k) (14.1) The proof of this relationship is outlined next. There are exactly m − 2f members in each of M = reducef (V ) and N = reducef (W ). Hence, selectk (M) = {m0 , m1 . . . mc−1 } and selectk (N) = = {n0 , n1 . . . nc−1 }, where selectk (M) and selectk (N) each have c = c(m − 2f, k) members. Observe that (i) at least ki + 1 members of M are less than or equal to any mi (likewise for N). Also, (ii) at most ki members of M are less than mi (likewise for N). The following can be shown using the earlier properties and definitions. max(mi , ni ) ≤ min(mi+1 , ni+1 ), where 0 ≤ i ≤ c − 2.

(14.2)

This directly follows if mi ≤ ni+1 and ni ≤ mi+1 can be shown.

f range(reduce (V))

V range(U)

U

new

new (V) k,f

k,f

(W)

W f range(reduce (W)) ni+1 . From (i), at least k(i + 1) + 1 elements of N are less than or equal to ni+1 , and hence less than mi . But from (ii), at most ki elements of M are less than mi . Hence, at least k + 1 elements in N are not in M, i.e., |N − M| ≥ k + 1. Observe that |W − V | ≤ k and |W ∩ V | ≥ m − k. Using Property (2), This implies that |N ∩ M| ≥ m − k − 2f and hence |N − M| ≤ (m − 2f ) − (m − k − 2f ) ≤ k. This contradicts the conclusion of the assumption about mi > ni+1 . Hence, mi ≤ ni+1 . Symmetrically, ni ≤ mi+1

525

can be shown. So Equation (14.2) holds. c−1

c−1

c−1

1 X 1X 1X |newk,f (V )−newk,f (W )| = | |mi −ni | = (max(mi , ni )−min(mi , ni )) (mi −ni )| ≤ c i=0 c i=0 c i=0 Using Equation 14.2 in the R.H.S., expanding terms, and simplifying: 1 |newk,f (V ) − newk,f (W )| ≤ (max(mc−1 , nc−1 ) − min(m0 , n0 )) c Using Property 3, max(mc−1 , nc−1) − min(m0 , n0 ) ≤ range(U) and Equation (14.1) follows. 14.5.5.0.4 Correctness. Let T , the set of correct processes, be such that |T | ≥ n − f . Let U and U ′ be the multiset of estimates (one estimate from each process) before and after some round h. |V | = |W | = n − f . Also, |V − U|, |W − U| ≤ f because at most f processes are faulty. |V ∩ W | ≥ n − 3f because both p and q would have received the same values from the correct processes from which both received messages. Hence, the difference between V and W |V − W | = |W − V | = |V | − |V ∩ W | ≤ 2f (the upper bound on this was denoted as k in Equation 14.1). Then, we have the following. • ǫ-agreement. |new2f,f (V ) − new2f,f (W )| ≤ dif f (U)/c(n − 3f, 2f ). This immediately follows by observing that the multisets U, V , and W satisfy Equation 14.1 when m is set to n − f and k is set to 2f , and hence c(m − 2f, k) becomes c(n − 3f, 2f ). This inequality implies that the range of the multiset of estimates chosen by all processes in T reduces by a factor of c(n − 3f, 2f ). This ≥ 2 as the algorithm assumes that n ≥ 5f + 1. Hence, after a logarithmic number of iterations (determined in lines (1a)-(1c) and described below), this range reduces to below ǫ. • Validity. range(U ′ ) ⊆ range(U). As the multisets U and V satisfy Property 14.5.5.0.2, we have that new2f,f (V ) ∈ range(U). For each round, it can be seen that the value of each correct process is within the range of the values of the correct processes at the start of the first round. Initialization (lines 1a-1c): The upper bound on the number of iterations is determined in the initialization phase, in lines (1a)-(1c). Let the multisets of estimates received by two arbitrary correct processes Pp and Pq after line (1a) be Vp and Vq . |Vp |, |Vq | > 4f because n ≥ 5f + 1; and |Vp − Vq |, |Vq − Vp | ≤ 2f (shown above). We can apply Property 2 to both Vp and Wq with respect to each other (and by setting j = 2f ) – to get that range(reduce2f (Vp )) ⊆ range(Vq ) and range(reduce2f (Vq )) ⊆ range(Vp ). It follows that vp ∈ range(Vq ) and vq ∈ range(Vp ) after line (1b). This guarantees that each correct process Pq knows at the end of the initialization round that its range range(Vq ) contains all the values vp of all correct processes Pp at the end of this initialization round. Knowing ǫ and the convergence rate c, epsilon ≥ [dif f (V )/cround ] and hence it is adequate to execute round = 526

⌈logc (dif f (V )/ǫ)⌉ rounds. Hence, the number of rounds computed in line (1c) is an upper bound on the number of iterations in which every two correct processes are guaranteed to converge to within ǫ. Termination (lines 1g-1h): Observe that each process may determine a different number of rounds to execute at line (1c). When a process finishes the required number of rounds, it executes (lines 1g1h) wherein it sends a special symbol “halt” and terminates itself. When some process Pq receives such a message from Pp , it should use the value of Pq for this and all of its subsequent rounds until it finishes its own precomputed number of rounds. This detail is left out of the pseudo-code for simplicity. 14.5.5.0.5 Complexity Time complexity: ⌈logc (dif f (V )/ǫ)⌉ + 1 rounds. Message complexity: n × [⌈logc (dif f (V )/ǫ)⌉ + 1] messages of size O(1) each.

14.5.6 Renaming Problem Problem Definition The consensus problem which was a problem about agreement required the processes to agree on a single value, or a small set of values (k-set consensus), or a set of values close to one another (approximate agreement), or reach agreement with high probability (probabilistic or randomized agreement). A different agreement problem introduced by Attiya et al. requires the processes to agree on necessarily distinct values. This problem is termed as the renaming problem. The renaming problem assigns to each process Pi , a name mi from a domain M, and is formally specified as follows. Agreement: For nonfaulty processes Pi and Pj , mi 6= mj . Termination: Each nonfaulty process is eventually assigned a name mi . Validity: The name mi belongs to M. Anonymity: The code executed by any process must not depend on its initial identifier. The renaming problem is useful for name space transformation. A specific example where this problem arises is when processes from different domains need to collaborate, but must first assign themselves distinct names from a small domain. A second example of the use of renaming is when processes need to use their names as “tags” to simply mark their presence, as in a priority queue. A third example is when the name space has to be condensed. This can occur when, for a system consisting of a large number of processes, k-mutual exclusion has to be enforced. Of the large pool of processes, only k can be in the mutual exclusion at any time to use the k copies of a replicated resource. Each resource can be viewed as holding a permit, 1 through k. For a process to gain access to the resource, it has to gain a permit. The assumptions about the renaming problem are as follows. 527

• The n processes P1 . . . Pn have their identifiers in the old name space. Pi knows only its identifier, and the total number of processes, n. The names of other processes are not known to a process. • The n processes take on new identifiers m1 . . . mn , respectively, from the name space M. • Due to asynchrony, each process that chooses its new name must continue to cooperate with the others until they have chosen their new names. The above formulation of the renaming problem is called the one-time renaming problem. If processes continually acquire and release names from a common pool, then the formulation becomes the long-lived renaming problem. Long-lived renaming is a resource acquisition problem.

count=0 broadcast own name as Most Recent View (MRV)

V not new view same as V arrives MRV

MRV

V=MRV count++

S T A R T

No

Yes

No pick new name based on rank as own name

Yes

rank < f+2

count > n−f

Yes

name conflict No

decide MRV as name and help others to decide

Figure 14.16: Flow-chart of the asynchronous renaming algorithm in a message-passing system. Algorithm Figure 14.17 gives Attiya et al.’s algorithm for one-time renaming when n ≥ 2f + 1, and up to f processes may fail in a fail-stop manner. The size of the transformed name space M is n + f . The high-level functioning of the algorithm is given in Figure 14.16. Each process has a list V iew in which it tracks the latest proposed name by each process, as and when it learns of it. Its own proposed name is tracked in V iew[1]. In more details, the view of a name has four components, as described in the View data structure in Figure 14.17. V iew is a list of up to n objects of type bid. Various views are ordered by the ≤ relation, defined as follows. • V iew ≤ V iew ′ if and only if for each process Pi such that V iew[k].P = Pi , we also have that for some k ′ , V iew ′ [k ′ ].P = Pi and V iew[k].attempt ≤ V iew ′ [k ′ ].attempt. If V iew ′ 6≤ V iew (line 1n), then V iew is updated using V iew ′ (line 1o) by: 1. including all process entries from V iew ′ that are missing in V iew (i.e., V iew ′ [k ′ ].P is not equal to V iew[k].P , for all k), so such entries V iew ′ [k ′ ] are added to V iew. 528

2. replacing older entries for the same process with more recent ones, (i.e., if V iew ′ [k ′ ].P = Pi = V iew[k].P and V iew ′ [k ′ ].attempt > V iew[k].attempt, replace V iew[k] by V iew ′ [k ′ ]). Any new information learnt is broadcast to all processes (lines 1c, 1v), and a process uses a counter count to track the number of other processes that have broadcast the exact same view as the latest view of this process (line 1k). If the view in a received message contains information that is not in the current view (line 1n), the current view is updated (line 1o). Note that this is similar to taking the pairwise maximum of vector clocks. However, a crucial difference is that the ordering of the components is not predetermined, as each process may order the other processes differently. When count reaches n − f (line 1l), no more messages may arrive because the other f processes may have failed. Such a view for which n − f affirmations were received is said be a stable view. Once a process determines a view to be stable (lines 1m, 1q), the process checks if there is a conflict with its choice of a new name and the choices of other processes (lines 1r, 1s). If there is no conflict, it finalizes its choice of the new name (lines 1t, 1u) and goes to the loop (lines 1G-1K) wherein it helps other processes to gain stable views and finalize their new name choices. If there is a conflict (lines 1w-1F), a new name must be chosen once again and competed with other processes. There are two cases here, depending on the rank of the process among all the processes that have not yet finalized their new names (i.e., among all processes except those for which V iew[j].decide = 1). Let the set of such processes be denoted as UNDECIDED(V iew). Clearly, as the new names of such processes are not finalized, the rank is determined based on the old names (line 1x). • If the rank r is less than f + 2 (line 1y), the process chooses the rth free name from F REE(V iew), the “free” names from M that have not been finalized by the processes (which have their decide component set to 1 in V iew). The process has to restart the bidding process, by going back to step (1a), broadcasting its updated view (line 1c), and so on. • If the rank r exceeds f + 1 (lines 1C,ID), the process goes to line (1e) and then waits for some other process to send its updated views. The logic here is that at least one correct process will have a rank up to f + 1 among UNDECIDED, and will pick and stabilize its new name before processes with rank greater than f + 1 begin to compete for a new name. Some definitions and properties are now given. P1. An algorithm is locally proper if for each run and each process, the sequence of the V iew list is totally ordered by ≤. The algorithm in Figure 14.17 is seen to be locally proper, from lines (1j)-(1o). P2. A view is stable with respect to a process if the process has received n − f − 1 messages containing identical information in the accompanying view. (Along with its own identical view, there are n − f affirmations.) A view is stable in a run if it is stable with respect to some process.

529

(local variables) struct bid: integer P ; integer x; integer attempt; boolean decide; list of bid: V iew[1 . . . n] ←− hhi, 0, 0, f alseii; integer count; boolean: restart, stable, no_choose;

// old name of process // new name being bid by the process // the number of bids so far, including this current bid // whether new name x is finalized // initialize list with an entry for Pi // number of copies of the latest local view, received from others // loop control variables

(1) A process Pi , 1 ≤ i ≤ n, participates in renaming: (1a) repeat (1b) restart ←− f alse; (1c) broadcast message(V iew); (1d) count ←− 1; (1e) repeat (1f) no_choose ←− 0; (1g) repeat (1h) await message(V iew′ ); (1i) stable ←− f alse; (1j) if V iew′ = V iew then (1k) count ←− count + 1; (1l) if count ≥ n − f then (1m) stable ←− true; (1n) else if V iew′ 6≤ V iew then (1o) update V iew using V iew′ by taking latest information for each process; (1p) restart ←− true; (1q) until (stable = true or restart = true); // n − f copies received, or new view obtained (1r) if restart = f alse then // V iew[1] has information about Pi (1s) if V iew[1].x 6= 0 and V iew[1].x 6= V iew[j].x for any j then (1t) decide V iew[1].x; (1u) V iew[1].decide ←− true; (1v) broadcast message(V iew); (1w) else (1x) let r be the rank of Pi in U N DECIDED(V iew); (1y) if r ≤ f + 1 then (1z) V iew[1].x ←− F REE(V iew)(r), the rth free name in V iew; (1A) V iew[1].attempt ←− V iew[1].attempt + 1; (1B) restart ←− 1; (1C) else (1D) no_choose ←− 1; (1E) until no_choose = 0; (1F) until restart = 0; (1G) repeat (1H) on receiving message(V iew′ ) (1I) update V iew with V iew′ if necessary; (1J) broadcast message(V iew); (1K) until f alse.

Figure 14.17: Asynchronous renaming in the message passing model. Code shown is for process Pi , 1 ≤ i ≤ n.

530

P3. If an algorithm is locally proper, then in any run, the set of stable views is totally ordered. This is seen as follows. Let views V iew and V iew ′ be stable with respect to processes i and j, respectively. Then n − f processes (say, set Ai ) agree on V iew, and n − f processes (say, set Aj ) agree on V iew ′ . If V iew and V iew ′ are not totally ordered, Ai ∩ Aj = ∅. Disjointness implies size of Aj is at most n − (n − f ) = f . Thus, n − f ≤ f , implying, n ≤ 2f . This contradicts the assumption that n ≥ 2f + 1, hence, at least one process must have sent both V iew and V iew ′ . So V iew and V iew ′ must be totally ordered. P4. As the algorithm in Figure 14.17 is locally proper, its set of stable views is totally ordered. Correctness Safety: A process finalizes a new name once it has a stable view. Pi and Pj cannot finalize the same name because the stable views are totally ordered. Without loss of generality, assume that Pi stable view ≤ Pj ’s stable view when they respectively finalize their names. Then Pj ’s stable view must include the name finalized by Pi , and Pj will not pick the same name. Liveness/Termination: Observe that when a process picks a new name (line 1z), there are at most n − 1 names used by others, so f + 1 names are available. To show that all processes eventually finalize a name, let F REE(V iew) be the set of free names from M as per V iew. Let DECIDED be the set of processes that finalize their new names (i.e., for which bid.decide is true). Then N − DECIDED is UNDECIDED, the set of processes which cannot finalize a new name. We now argue using contradiction that UNDECIDED is empty. • Consider the execution after the time that all processes in DECIDED have decided their new names, and at least one bid sent by every other correct process has been received by each correct process, implying that |V iew| ≥ n − f . As no correct process blocks, this point in time will occur. Let V iewmin be the smallest stable view after this point in time. By (P4), all the views are totally ordered and hence V iewmin is uniquely defined. Let the set of free names at this time be denoted as F REE(V iewmin ) and the set of undecided processes at this time be denoted as UNDECIDED(V iewmin ). • Among the processes in UNDECIDED(V iewmin ), consider the process Pmin with the smallest rank, based on the old names. The rank is at most f + 1, and hence the process will select a new name (lines (1y, 1z, 1A)). As rank is unique, no other process in UNDECIDED(V iewmin ) will now or henceforth choose this name chosen by Pmin . • Pmin updates and broadcasts its view. When other processes receive this view, they update their local views with this new information, and will also broadcast their updated views – either in the loop (lines 1G-1K), or – via execution of lines (1C-1D), then lines (1n-1o), and then (1b-1c). 531

(variables) boolean: clean ←− 1;

//variable at each process

(1) Process P0 initiates Reliable Broadcast: (1a) broadcast message M to all processes. (2) A process Pi , 1 ≤ i ≤ n, receives message M : (2a) if clean then broadcast M to all processes; (2b) clean ←− 0.

Figure 14.18: Protocol for reliable broadcast. Pmin and all other correct processes receive at least n − f confirmations, making the view containing Pmin ’s choice of a new name a stable view. Hence, Pmin can decide a new name, leading to a contradiction that UNDECIDED(V iewmin ) is empty. Complexity: Each time a process bids with a new name for itself, a broadcast is sent (n − 1 messages) and each recipient of the broadcast, seeing as new view, also does a broadcast (n − 1 messages). This leads to O(n2 ) messages per new name bid. Let the final stable view be denoted by V iewf inal . The total number of messages is Σni=1 V iewf inal .attempti × n2 . Exercise 9 asks you to analyze the bound on the number of attempts made by the processes.

14.5.7 Reliable Broadcast Although Reliable Terminating Broadcast (RTB) is not solvable under failures (recall that we showed a reduction from consensus to that problem), a weaker version of RTB, namely Reliable Broadcast, in which the Termination condition is dropped, is solvable under crash failures. The protocol is shown in Figure 14.18. This protocol uses up to O(n2 ) messages to broadcast message M and works in the face of any number of failures. The key difference between RTB and Reliable Broadcast is that RTB requires eventual delivery of some message – even if the sender fails just when about to broadcast. In this case, a null message must get sent, whereas this null message need not be sent under Reliable Broadcast. Thus, RTB requires the recognition of the failure (as described above) as opposed to no message getting sent. This reduces to the ability of distinguishing between a slow process and a failed process, which was the crux in solving the consensus problem under crash failure.

532

14.6 Wait-free Shared Memory Consensus in Asynchronous Systems 14.6.1 Impossibility Result The impossibility of achieving consensus in asynchronous message-passing systems in a system prone to crash failures (discussed in Section 14.5.1) also extends to asynchronous shared memory systems. A shared memory system can be emulated by a message-passing system – if consensus could be reached in a shared memory system, it could also be reached in a message-passing system, leading to a contradiction. Thus, consensus cannot be reached in an asynchronous shared memory system in the crash failure model. The intuition behind the impossibility result in shared memory systems is similar – in the face of a potential process crash, it is not possible to distinguish between a crashed process and a process that is extremely slow in doing its Read or Write operation. The FLP argument using 0-valent and 1-valent states and the critical step used earlier for asynchronous message-passing systems can also be used here for asynchronous shared memory systems. The reasoning to show that consensus cannot be achieved even if a single process fails runs informally along the following lines.

Y

Z

X z

i

0−val

j i

i

i

0−val 1−val 1−val

Figure 14.19: Execution prefix used to show impossibility of 1-failure tolerant consensus. Assume there exists a protocol in which consensus can be reached even if a single process fails. Recall from Section 14.5.1 that there exists a bivalent initial state. Due to the termination requirement of the problem, there must exist some process i that makes a transition from a bivalent state to an univalent state even if there are no failures. (For a wait-free consensus, this is also true.) So there must be some execution prefix X that is bivalent, but from which a step by i makes it 0valent, whereas a step by i after an extension Y of X leads to a 1-valent state. (See Figure 14.19.) If there are multiple events between X and Y , then there must be a prefix Z such that a step by i leads to 0-valence but a step by another process j (j 6= i as processes are assumed to be deterministic) followed by a step by i leads to 1-valence. The argument now uses a simple case analysis based on the actions of i and j after Z, to show that the configuration of Z as shown in Figure 14.19 is impossible, showing the impossibility of a 1-failure consensus protocol. The notation extend(Z, i ◦ j) denotes the state after processes i and j take steps in that order, after execution Z. 533

Process i’s event is a Read. (See Figure 14.20(a).) Then extend(Z, i ◦ j) and extend(Z, j ◦ i) are identical to all processes except i. If i does not take any step after extend(Z, i ◦ j), then all process must eventually terminate with consensus on 0 while executing a suffix, say δ. But if the same suffix is executed after extend(Z, j ◦ i), they must reach a consensus on 1. As extend(Z, i ◦ j) and extend(Z, j ◦ i) are isomorphic to all processes except the stopped process i, we have a contradiction. Process j’s event is a Read. The states after extend(Z, i) and extend(Z, j ◦ i) are identical to all processes except j. The same logic as for the previous case, this time letting j stop instead of i, leads to a similar contradiciton. Processes i and j execute Write on different variables. (See Figure 14.20(b)). The system state after extend(Z, i ◦ j) which is 0-valent is the same as the system state after extend(Z, j ◦ i), which is 1-valent. There now arises a contradiction, irrespective of whether all processes decide on 0 or on 1. Processes i and j execute Write on the same variable. (See Figure 14.20(c)). The system state after extend(Z, i) and extend(Z, j ◦ i) are identical to all processes except j. If all processes except j run after extend(Z, i), the consensus value must be 0. If all processes except j run after extend(Z, j ◦ i), the consensus value must be 1. As extend(Z, j ◦ i) and extend(Z, i) are isomorphic to all processes except the stopped process j, we have a contradiction. Hence, there cannot exist any bivalent state that allows any process to go a univalent state.

Z

j

Read by i 0−val

j

1−val

Read by i

Write by i

Z

Write by j

Write by j

Write by i

1−val Write

by i

all processes except j

0−val

(a) i does a Read (same logic if j does a Read)

Write by j

0−val

all processes except i 0−val

Z

Write by i

0−val

(b) i and j write to different variables

0−val

(c) i and j write to the same variable

Figure 14.20: Various cases to show impossibility of 1-failure tolerant consensus in the asynchronous message-passing model. 534

The key reason why this result for the 1-failure case is different from that for the failure-free case is that the 1-failure case allows for a bivalent initial state, whereas the initial state for a failurefree execution is univalent. Between the time a process reads various registers and (deciding on a consensus value) writes its consensus value, the values of the other registers read can get updated by other processes. Herein lies the difficulty for shared memory systems – the reads and the writes are not together guaranteed to be an atomic action – and hence taking action about deciding a consensus value, independent of processes that are “suspected" to have failed, can lead to an erroneous decision on consensus. Hence, from a bivalent state, it is not possible to transition to a univalent state. This leads to the following two results – the second one follows trivially from the first. • It is not possible to reach consensus in an asynchronous shared memory system using Read/Write atomic registers, even if a single process can fail by crashing. • There is no wait-free consensus algorithm for reaching consensus in an asynchronous shared memory system using Read/Write atomic registers. There are two ways of overcoming the impossibility result. • Weakening the consensus problem, as was done for message-passing systems. This area covers the design of asynchronous algorithms for k-set consensus, approximate consensus, and renaming using atomic registers and atomic snapshot objects which are built from atomic registers, studied in Chapter 12. These algorithms are studied in Sections 14.6.4-14.6.6. • Using memory that is stronger than atomic Read/Write memory to design wait-free consensus algorithms. Such a memory would have corresponding access primitives. Recall that a wait-free algorithm in a system of n processes is a (n − 1)-crash resilient algorithm. Thus, any process should be able to perform its execution, independent of any other processes. The above results lead to the question: – are there objects (with supporting operations), using which there is a wait-free (i.e., (n − 1)-crash resilient) algorithm for reaching consensus in a n-process system? In the remainder of this section, we assume only the crash failure model, and also require the solutions to be wait-free. As it turns out, the answer is Yes. Objects/primitives such as Test-&-Set, Swap, Compare&-Swap, and Memory Move, that were designed in the context of efficient computer architectures, do indeed allow consensus to be reached in a wait-free manner. Such objects are stronger than the safe, regular, or atomic Read/Write registers. The notion of consensus number provides a metric to measure the degree to which these various primitives allow consensus to be reached. This study of these more complex objects also extends our study of the register simulations of Chapter 6, wherein stronger register types were simulated from weaker register types. 535

Object Consensus number Read/Write objects 1 Test-&-Set, stack, FIFO queue, Fetch-&-Inc 2 Augmented queue with peek - size k k Compare-&-Swap, Augmented queue, memory-memory move ∞ memory-memory swap, Fetch-&-Cons, store-conditional Table 14.4: Consensus numbers of some object types. Some of these objects are described in Figure 14.22.

14.6.2 Consensus Numbers and Consensus Hierarchy Definition 28. An object of type X has consensus number k, denoted as CN(X) = k, if k is the largest number for which the object X can solve wait-free k-process consensus in an asynchronous system subject to k − 1 crash failures, using only objects of type X and read/write objects. Consensus numbers of some well-known objects are shown in Table 14.4. Figure 14.22 gives the definitions of some of these objects. As seen from Definition 28, there is an infinite hierarchy - called the consensus hierarchy - that gets defined, according to the power of the objects to solve wait-free consensus under crash failures. A natural consequence of the definition of consensus number is the following result. Theorem 14. For objects X and Y such that CN(X) < CN(Y ), there is no wait-free simulation of object Y using X and read/write registers (whose consensus number is 1) in a system with more than CN(X) processes. If such a simulation did exist, then by Definition 28, CN(X) = CN(Y ), leading to a contradiction. Note that if there are up to CN(X) processes, it is possible (as shown in Section 14.6.3) for X and read/write registers to wait-free simulate Y because the full power of reaching consensus among more than CN(X) processes is never required to be exercised. A corollary of this result is that there is no wait-free simulation of any object with consensus number more than one, using only read/write atomic registers. This corollary is important because it implies that objects with stronger properties than the read/write atomic register are needed. The ability to read and write, perhaps conditionally, in an atomic manner was earlier found to be useful in designing semaphores in operating systems, and certain primitives in computer architecture and design. Several of the objects in Figure 14.22 were first designed in hardware in these specialized contexts. We will now see two examples of achieving wait-free consensus – one using the FIFO queue, and another using the Compare-&-Swap instruction. 14.6.2.1 FIFO queue Figure 14.21 shows how 2-consensus is achieved using a FIFO queue. The queue operations are enqueue and dequeue. The queue is initialized with a single value, 0. Both processes try to 536

(shared variables) queue: Q ←− h0i; array of integer: Choice[1, 2] ←− [⊥, ⊥] (local variables) integer: temp ←− 0; integer: x ←− initial choice;

// queue Q initialized // preferred value of each process

(1) Process Pi , 1 ≤ i ≤ 2, executes this for 2-process consensus using a FIFO queue: (1a) Choice[i] ←− x; (1b) temp ←− dequeue(Q); (1c) if temp = 0 then (1d) output(x) (1e) else output(Choice[1 − i]). Figure 14.21: Protocol for 2-process wait-free consensus using a FIFO queue. Code for Pi , 1 ≤ i ≤ 2. dequeue from the queue. However, due to the atomicity of the dequeue operation, access is always serialized. The first process that dequeues the ‘0’ element uses its own initial value (local x) as the consensus value and outputs it. The other process, on completing its dequeue operation, gets ⊥, and learns that the first process has dequeued first, and therefore borrows the value set aside by the first process in Choice[1 − i]. Thus, both processes agree on the same value and hence 2-process consensus is achieved. The operations of any process can be seen to be wait-free. The same logic cannot be extended to three processes because of the following informal reasoning. Some one process will dequeue the ‘0’. When the other two processes dequeue and get a ⊥, they know that one of the other two processes’ value is the consensus value, but do not know which of the other two processes it is. This is because the queue object does not atomically allow the first process to leave behind (i.e., write) its identifier as an imprint for the second and third processes to learn about when they issue their dequeue. Therefore, CN(queue) = 2. 14.6.2.2 Compare&Swap Figure 14.23 shows how wait-free consensus is achieved among any number of processes using the Compare&Swap operation (see Figure 14.22) on a shared register Reg. The Compare&Swap performs all actions of an invocation atomically, thus serializing all concurrent accesses. Each process executes Compare&Swap(Reg, ⊥, x). The value of the object Reg is read into local variable val, and if this value val equals the key ⊥, then the process’s preference x gets written to Reg atomically. Due to the serialization of the operations, some process always gets serialized first, even if accesses are concurrent. There are thus two cases. • Consider the process that gets serialized first. The value of Reg read via Compare&Swap(Reg, ⊥, x) equals the key ⊥, and the preference x of this process gets written to Reg. The process returns its x as the consensus value. 537

(shared variables among the processes accessing each of the different object types) register: Reg ←− initial value; // shared register initialized (local variables) integer: old ←− initial value; // value to be returned integer: key ←− comparison value for conditional update; (1) RMW (Reg, function f ) returns value: (1a) old ←− Reg; (1b) Reg ←− f (Reg); (1c) return(old). (2) Compare&Swap(Reg, key, new) returns value: (2a) old ←− Reg; (2b) if key = old then (2c) Reg ←− new; (2d) return(old). (3) F etch&Inc(Reg) returns value: (3a) old ←− Reg; (3b) Reg ←− r + 1; (3c) return(old). Figure 14.22: Definitions of synchronization operations RMW , Compare&Swap, F etch&Inc. • Any other process executing Compare&Swap(Reg, ⊥, x) will find that the value of Reg (which is the value x set by the first process) does not match the key ⊥. Hence it leaves Reg unmodified and returns the value of Reg as the consensus value. The implication is that another process has earlier found Reg =⊥ and set its own preference as the value of Reg. So this process borrows the value set by the earlier process in Reg as the consensus value. Due to the atomicity of the Compare&Swap operation and the fact that this logic works for any number of processes, the code for consensus is wait-free and can tolerate up to n − 1 failures, for all n. Hence, CN(Compare&Swap) is ∞. 14.6.2.3 Read-Modify-Write abstraction The Read-Modify-Write (abbreviated as RMW ) abstracts several objects wherein a register can be read and modified using an arbitrary function f atomically. Such objects include F etch&Inc, Swap, and T est&Set. The RMW object has a consensus number of at least 2 because the first process to read the object can atomically modify its value to leave an imprint that the object has been accessed at least once (e.g., as in the FIFO queue). If the imprint can also include the identity of the first process to read, or of the choice of the first process, processes that subsequently access the object can by pointed to the choice made by the first process, and the consensus number may 538

(shared variables) integer: Reg ←−⊥; (local variables) integer: temp ←− 0; integer: x ←− initial choice;

// shared register Reg initialized // temp variable to read value of Reg // initial preference of process

(1) Process Pi , (∀i ≥ 1), executes this for consensus using Compare&Swap: (1a) temp ←− Compare&Swap(Reg, ⊥, x); (1b) if temp =⊥ then (1c) output(x) (1d) else output(temp). Figure 14.23: Protocol for wait-free consensus using Compare&Swap, for any number of processes. Code for Pi , 1 ≤ i ≤ ∞. then be more than 2. The various RMW objects differ in their function f . A function is termed as interfering if for all process pairs i and j, and for all legal values v of the register, (i) fi (fj (v)) = fj (fi (v)), i.e., function is commutative, or (ii) the function is not write-preserving, i.e., fi (fj (v)) = fi (v) or vica-versa with the roles of i and j interchanged. Examples: The F etch&Inc commutes even though it is write-preserving. The T est&Set commutes and is not write-preserving. The Swap does not commute but it is not write-preserving. Hence, all three objects uses functions that are interfering. Figure 14.25 shows how wait-free consensus is achieved among two processes using the RMW operation (see Figure 14.22) on a shared register Reg. The RMW performs all actions of an invocation atomically, thus serializing all concurrent accesses. Each process executes RMW (Reg, f, x), where x is the initial choice of the process. The shared data structures are shown in Figure 14.24. Reg has an initial distinguished value ⊥, known to all processes. The assumption here is that the function f is non-trivial, meaning, it is not the identity function. RMW register

Reg

Choice [0] [1]

Figure 14.24: Shared data structures for solving 2-process wait-free consensus using the RMW operation. Although any nontrivial RMW operation has a consensus number of at least 2, it can be seen that a nontrivial interfering RMW operation has a consensus number of exactly 2, i.e., there is no 539

(shared variables) integer: Reg ←−⊥; (local variables) integer: Choice[0, 1] ←− [⊥, ⊥]; integer: x ←− initial choice;

// shared register Reg initialized // data structure // initial preference of process

(1) Process Pi , (0 ≤ i ≤ 1), executes this for consensus using RMW: (1a) Choice[i] ←− x; (1b) if val =⊥ then (1c) output(Choice[i]) (1d) else output(1 − i).

Figure 14.25: Protocol for wait-free consensus for two processes using RMW . Code is for Pi , 0 ≤ i ≤ 1. algorithm to reach consensus with three processes. An informal argument to see this is as follows. Consider the third process to access the object. If the RMW operation is commutative, the third process cannot tell which of the other two processes accessed the object first, and hence does not know what consensus value to use. If the RMW operation is not write-preserving, the third process cannot tell if it is the second or the third process to access the object; and hence does not know what consensus value to use. Operations such as Compare&Swap are noninterfering operations, and hence have consensus numbers higher than 2.

14.6.3 Universality of Consensus Objects In Chapter 6, we studied the wait-free simulations of various types of registers using weaker forms of registers. We now build on this notion of wait-free simulation of one object type using another object type, in the context of consensus under crash failures. An object is defined to be universal if that object along with read/write registers can simulate any other object in a wait-free manner. The main result of this section is that in any system containing up to k processes, an object X such that CN(X) = k is universal, i.e., it can simulate any other object. The condition on the number of processes in the system is essential; because X does not and can not manifest the greater power that is required when the number of objects exceeds CN(X). If the condition were removed, then an object X would truly wait-free simulate another object with a greater consensus number in a system with more than CN(X) processes, leading to a violation of the definition of consensus number. For any system with up to k processes, the universality of objects X with consensus number k is shown by giving a universal algorithm to wait-free simulate any object using only objects of type X and read/write registers. This is shown in two steps. 1. A universal algorithm to wait-free simulate any object whatsoever using read/write registers and arbitrary k-processor consensus objects is given. This is the main step.

540

2. Then, the arbitrary k-process consensus objects are simulated with objects of type X, also having consensus number k. This trivially follows after the first step. Hence, any object X with consensus number k is universal in a system with n ≤ k processes. In the rest of this subsection, we study a universal algorithm to wait-free simulate any object whatsoever using read/write registers and arbitrary k-processor consensus objects (step 1). The following two concepts are useful. • An arbitrary consensus object X allows a single operation, Decide(X, vin ) and returns a value vout , where both vin and vout have to assume a legal value from known domains Vin and Vout , respectively. For the correctness of this shared object version of the consensus problem, all vout values returned to each invoking process must equal the vin of some process. • A nonblocking operation, in the context of shared memory operations, is an operation that may not complete itself but is guaranteed to complete (i.e., provide a response indication (see Chapter 6) to) at least one of the pending operations in a finite number of steps. This operation is a weaker version of a wait-free operation. We will first study a universal algorithm that does a nonblocking simulation of any object, and then refine this algorithm to get a wait-free algorithm. 14.6.3.1 A Nonblocking Universal Algorithm The algorithm shown in Figure 14.26 uses a linked list (with the initial record termed anchor_record) to store the linearized sequence of operations and resulting states on an arbitrary object Z. The data structure op defines the format of one such element in this linked list. The linked list and data structure format are illustrated in Figure 14.27. Operations to the arbitrary object Z are simulated in a nonblocking way using only an arbitrary consensus object (namely, the field op.next in each record) which is accessed via the Decide call. We are not concerned with how the consensus object itself or Decide is implemented. When an operation Z being simulated is invoked using invoc, a record called my_new_record is allocated and the record’s operation field is set to the invoked operation (lines 1a-1b). The main challenge in simulating Z is to linearize all the operations being invoked on it concurrently by the various processes – there is competition among the processes to apply their own operation next, i.e., to thread their own operation next to the tail of the linked list. This is where the consensus object comes in useful – with respect to the current most recent operation that has been linearized, the consensus object “decides” on the next operation that is to be linearized. Before a process competes, it first needs to identify the tail of the linked list which is dynamically changing. Array Head stores pointers to the tail of the linked list; Head[i] is Pi ’s best estimate of the pointer that points to the tail record. In loop (1c)-(1e), Pi selects the most up to date estimate of the tail pointer. However, observe that this may still be hopelessly out of date due to the nonatomic nature of scanning the array Head. Still, Head[i] is Pi ’s best estimate of the record that is at the tail of the linked list. In the main loop, lines (1f)-(1k), Pi competes on the consensus object Head[i].next to thread itself next to the list (line (1g)). The following possibilities arise. 541

(shared variables) record op integer: seq ←− 0; // sequence number of serialized operation integer: operation ←−⊥; // operation, with associated parameters integer: state ←− initial state; // the state of the object after the operation integer: result ←−⊥; // the result of the operation, to be returned to invoker integer: next ←−⊥; // pointer to the next record (local variables) array of integer Head[1 . . . k] ←− ∗(anchor_record); (1) Process Pi , 1 ≤ i ≤ k performs operation invoc on an arbitrary consensus object: (1a) my_new_record ←− malloc(op); (1b) my_new_rec.operation ←− invoc; (1c) for count = 1 to k do (1d) if Head[i].seq < Head[count].seq then (1e) Head[i] ←− Head[count]; (1f) repeat (1g) winner ←− Decide(Head[i].next, &my_new_record); (1h) winner.seq ←− Head[i].seq + 1; (1i) winner.state, winner.result ←− apply(winner.operation, Head[i].state); (1j) Head[i] ←− winner; (1k) until winner = my_new_record; (1l) enable the response to invoc, that is stored at winner.result.

Figure 14.26: Nonblocking universal algorithm to simulate an arbitrary object using any consensus object. Code for Pi , 1 ≤ i ≤ k. 1. Head[i] is indeed the correct tail of the list. The process Pi invokes Decide on the consensus object which is the next field of the record pointed to by Head[i] – to learn if it succeeds in threading its operation next. But there may be concurrent calls to Decide. The winner of the “race” is pointed to by winner. (We do not yet know if Pi won.) The fields of winner – its new state, new sequence number, new result – are computed and stored in winner in lines (1h)-(1i). Head[i] is updated to point to winner (line (1j)). (a) If winner is the same as my_new_record (line (1k)), then Pi won the race and succeeded in threading its operation after the Head[i] record before the current iteration of the repeat loop. The process exits after returning the value stored in the result field (line (1l)). (b) If winner is not the same as my_new_record, then Pi lost the race. The record of the true winner of the race was returned in winner by the consensus object. The record of the true winner got filled in again by Pi in (1h)-(1j). But now Head[i] is pointing to the next record, i.e., the record with sequence number one more than in the previous iteration. The process competes again by going through the next iteration of the repeat loop. 542

op seq operation state result

Head[1..n]

Announce[1..n]

n e x t

Anchor_Record

Figure 14.27: Wait-free simulation of a universal consensus object. For a nonblocking simulation of the object, the array Announce is not used. 2. Head[i] is an old tail of the list. The process executes the repeat loop (see case (1b) that repeats itself) until Head[i] points to the record that is the most recent tail. It then competes to thread its own operation my_new_record as in step 1. We make some notes that give an insight into the design of this algorithm. • We cannot use a single consensus object because consensus has to be reached on-line with respect to the current most recent operation, on the next operation to be linearized. A consensus object always returns the same decision value. Thus the algorithm uses as many consensus objects (the next fields of the records) as there are records on whose order to reach consensus. • A single pointer in a read/write object cannot be used instead of the array Head to point to the latest operation record. This is because reading the pointer to contend, and updating it after contention is over and threaded to the list, cannot be done atomically in a wait-free manner. • The linearization of the operations is given by the sequence numbers. The sequence numbers increase monotonically along the linked list. • A process may never succeed in threading its own operation to the list. It continues the repeat loop forever. This may happen if it loses the contention every time to another process trying to thread concurrently. This can be used to observe that the algorithm is not wait-free but the algorithm is nonblocking. • The estimate of the tail of the list in lines (1c-1e) may be very out of date due to the way it is computed. This is a drawback as the process has to iterate through the repeat loop at least as many times as the number of operations by which the estimate is out of date.

543

(shared variables) record op integer: seq ←− 0; // sequence number of serialized operation integer: operation ←−⊥; // operation, with associated parameters integer: state ←− initial state; // the state of the pbject after the operation integer: result ←−⊥; // the result of the operation, to be returned to invoker integer: next ←−⊥; // pointer to the next record (local variables) array of integer Head[1 . . . k], Announce[1 . . . k] ←− ∗(anchor_record); (1) Process Pi , 1 ≤ i ≤ k performs operation invoc on an arbitrary consensus object: (1a) Announce[i] ←− malloc(op); (1b) Announce[i].operation ←− invoc; Announce[i].seq ←− 0; (1c) for count = 1 to k do (1d) if Head[i].seq < Head[count].seq then (1e) Head[i] ←− Head[count]; (1f) while Announce[i].seq = 0 do (1g) turn ←− (Head[i].seq + 1)mod (k); (1h) if Announce[turn].seq = 0 then (1i) my_new_record ←− Announce[turn]; (1j) else my_new_record ←− Announce[i]; (1k) winner ←− Decide(Head[i].next, &my_new_record); (1l) winner.seq ←− Head[i].seq + 1; (1m) winner.state, winner.result ←− apply(winner.operation, Head[i].state); (1n) Head[i] ←− winner; (1o) enable the response to invoc, that is stored at winner.result.

Figure 14.28: Wait-free universal algorithm to simulate an arbitrary object using any consensus object. Code for Pi , 1 ≤ i ≤ k. Complexity: The worst-case time complexity to thread a specific operation isnot bounded due to the nonblocking nature of the algorithm. Exercise 14 asks you to perform an average-case analysis. 14.6.3.2 A Wait-free Universal Algorithm The nonblocking algorithm in the previous section is enhanced to make it wait-free. To ensure that a process does not happen to continually lose the contention, a round-robin approach of “helping” is used. If a process Pj determines that the next operation is to be assigned sequence number x, then it first checks whether the process Pi such that i = x (mod n) is contending for threading its operation. If so, then Pj tries to thread Pi operation instead of its own. The algorithm is shown in Figure 14.28. The implementation of the round-robin “helping” is done using the array Announce[1 . . . n]. When a process Pi wants to thread its operation, it first announces it by making Announce[i] point to the record where the operation is stored (lines (1a)-(1b)). It then proceeds as before to estimate the latest tail of the list, using the Head array (lines (1c)-(1e)). Each process is required to determine whether it should try to thread the record of the rightful process (lines (1g)-(1h)), as determined by the modulo function, or its own (line (1j)). Only if the “rightful” process is not interested in threading its own operation does a process try to 544

(variables) integer: v ←− initial value; array of integer local_array ←− ⊥; (shared variables) atomic snapshot object Obj[1 . . . n] ←− ⊥; (1) A process Pi , 1 ≤ i ≤ n, initiates k-set consensus: (1a) updatei (Obj, x) with v; (1b) repeat (1c) local_array ←− scani (Obj); (1d) until there are at least |N| − f non-null values in Obj; (1e) v ←− minimum of the values in local_array. Figure 14.29: Asynchronous protocol for k-set consensus in the shared memory model using an atomic snapshot object. Code shown is for process Pi , 1 ≤ i ≤ n. thread its own operation (line (1j)). We argue using contradiction that within n iterations of the while loop, process Pi will have succeeded in having its operation threaded to the linked list, and exit the loop. Assume by way of contradiction that Pi ’s record is not threaded by Pi ’s (n + 1)th iteration of the while loop. After the Announce[i] having been set in lines (1a-1b), n other records initiated by other processes must have been threaded to the linked list. But of these n sequence numbers, one of them modulo n must have equalled i and the other processes would have threaded Pi ’s record instead of their own (see lines (1g)-(1i)). Complexity: Each process completes its operation within n iterations of the main while loop, irrespective of the other processes.

14.6.4 Shared Memory k-set Consensus The message-passing version of k-set consensus was presented in Section 14.5.4. Here, its counterpart for the shared memory model assuming an atomic snapshot object is given in Figure 14.29. The algorithm can be easily derived from the message-passing algorithm. A process writes its initial value to its component within the shared object, and repeatedly scans the shared object until n − f processes have written to the object. It then takes the minimum of the values scanned.

14.6.5 Shared Memory Renaming The renaming problem was introduced in Section 14.5.6 and an algorithm to solve renaming in the message passing model was given. An asynchronous algorithm for wait-free renaming for the shared memory model is given in Figure 14.30. The algorithm assumes an atomic snapshot object Obj, which has the nice property that it linearizes all asynchronous operations to it. Each process begins by bidding a new name of ‘1’ for itself (line 1a). The process then repeats the following 545

(variables) integer: mi ←− 0; integer: Pi ←− name from old domain space; array of integer tuples local_array ←− h⊥, ⊥i; (shared variables) atomic snapshot object Obj[1 . . . n] ←− h⊥, ⊥i; (1) A process Pi , 1 ≤ i ≤ n, participates in wait-free renaming: (1a) mi ←− 1; (1b) repeat (1c) updatei (Obj, hPi , mi i); // update ith component with bid mi (1d) local_array(hP1 , m1 i, . . . hPn , mn i) ←− scani (Obj); (1e) if mi = mj for some j 6= i then (1f) Determine rank ranki of Pi in {Pj | Pj 6=⊥ ∧j ∈ [1, n]}; (1g) mk ←− ranki th smallest integer not in {mj | mj 6=⊥ ∧j ∈ [1, n] ∧ j 6= i}; (1h) else (1i) decide(mk ); exit; (1j) until false.

Figure 14.30: Asynchronous wait-free renaming using an atomic snapshot object in the shared memory model. Code shown is for process Pi , 1 ≤ i ≤ n. loop. It writes its latest bid to its component of Obj (line 1c); it reads the entire object using a scan into its local array (line 1d). Pi examines the local array for a possible conflict with its proposed new name (line 1e). • If Pi detects a conflict with its proposed name mi (line 1e) it determines its rank rank among the old names (line 1f); and selects the rank th smallest integer among the names that have not been proposed in the view of the object just read (line 1g). This will be used as Pi ’s bid for a new name in the next iteration. • If Pi detects no conflict with its proposed name mi (line 1e), it selects this name and exits (line 1i). We now consider the following properties of this algorithm. Correctness: If two processes were to choose the same new name, then the Scans returned to them in their final iteration must have indicated that the name they bid was unique. However, due to the linearizability property of the atomic snapshot object Obj, the Scan that was returned to the “later” process could not have indicated that the name it bid was unique. Hence, no two processes can choose the same name when they terminate. Size of name space: At any time, there are at most n − 1 names that are bid by other processes, and the rank of a process is at most n. Hence, a process will never bid a name greater than 2n − 1. The name space is confined to [1, 2n − 1]. 546

Termination: Assume there is a subset T ⊆ N of processes that never terminate. Let min(T ) be the process in T with the lowest ranked process identifier (old name). Let rank(min(T )) be the rank of this process among all the processes P1 . . . Pn . Once every process in T has done at least one update, and once all the processes in T have terminated, we have the following. • The set of names of the terminated processes, say MT , remains fixed. • The process min(T ) will choose a name not in MT , that is ranked rank(min(T )). As rank(min(T )) is unique, no other process in T will ever choose this name. • Hence, min(T ) will not detect any conflict with rank(min(T )) and will terminate. As min(T ) cannot exist, the set T = ∅. Wait-freedom: A process can choose its new name independent of the actions of the other processes. Complexity: Exercise 17 asks you to perform a time complexity analysis of this algorithm, and show the following lower bounds. Lower bounds: Let M be the new name space. For crash-failures, the following lower bounds can be seen to exist. • For wait-free renaming, wherein all other n − 1 processes may fail, the name space must be of size 2n − 1. • To tolerate up to f failures, the name space must be of size n + f .

14.6.6 Shared Memory Renaming using Splitters Moir and Anderson presented a very elegant wait-free renaming algorithm using the splitter concurrent object defined as follows. When n (n ≥ 1) processes invoke the splitter, each is returned a value from the set {stop, down, right} subject to the following constraints. • At most one process is returned stop. • At most n − 1 processes are returned down. • At most n − 1 processes are returned right. Figure 14.31 shows a schematic definition of a splitter. Figure 14.32 shows a wait-free implementation of a splitter. • The first time that some process Pi finds X equal to its own identifier in line (1d), Y must be true, and hence all other processes must get the value right (unless they fail) while Pi must get value stop. Hence, at most one process is returned stop.

547

n

processes STOP RIGHT at most n−1 processes

at most 1 process DOWN

at most n−1 processes

Figure 14.31: The structure for a splitter. (shared variables) MRMW atomic snapshot object X, Y ←− f alse; (1) splitter(), executed by process Pi , 1 ≤ i ≤ n: (1a) X ←− i; (1b) if Y then (1c) return(right); (1d) else (1e) Y ←− true; (1f) if X = i then return(stop) (1g) else return(down).

Figure 14.32: A wait-free implementation of a splitter. Code shown is for process Pi , 1 ≤ i ≤ n. • Let Pi be the last process to execute (1a). Unless Pi crashes, it will either get value right (if it is th first process to execute line (1b)) or it will get the value stop. Hence at most n − 1 processes are returned down. • The first process that reads Y in line (1b) cannot get value right because Y is initialized to false. Hence, not all processes can are returned right. The renaming algorithm is now constructed using n(n + 1)/2 splitters arranged as shown in Figure 14.33. Each splitter is labelled by coordinates r, d. Observe that each process is guaranteed to get a stop value from one of the n(n + 1)/2 splitters, and no two processes will stop at the same splitter. So the coordinates of the splitter where a process stops can serve as the new label. The code is shown in Figure 14.34. Complexity: The new name space is n(n + 1)/2 when the number of processes is n. Each process takes O(n) steps to select its new name. The algorithm is clearly wait-free.

548

r 0,0

0,1

0,2

1,0

1,1

1,2

2,0

2,2

0,3

d

3,0

Figure 14.33: The Moir-Anderson wait-free renaming algorithm using splitters. Code shown is for Pi , 1 ≤ i ≤ n. (local variables) next, r, d, new_name ←− 0; (1) Process Pi , 1 ≤ i ≤ n, participates in wait-free renaming: (1a) r, d ←− 0; (1b) while nexti 6= stop do (1c) nexti ←− splitter(r, d); (1d) case (1e) next = right then r ←− r + 1; (1f) next = down then d ←− d + 1; (1g) next = stop then break() (1h) return(new_name = n · d − d(d − 1)/2 + r).

Figure 14.34: Moir and Anderson’s asynchronous wait-free renaming using splitters. Code shown is for process Pi , 1 ≤ i ≤ n.

14.7 Chapter Summary Consensus problems are fundamental aspects of distributed computing because they require inherently distributed processes to reach agreement. This chapter first covers different forms of the consensus problem, which are shown to be equivalent to one another. Consensus is attainable in fault-free systems. The chapter then gives an overview of what forms of consensus are solvable under different failure models and different assumptions on the synchrony /asynchrony. The chapter then covers agreement in the following categories. (1) Synchronous messagepassing systems with failures. Here, different fault models are considered - the fail-stop model and the Byzantine model. Lower bounds on the number of failure-prone processes are given. Also, representative algorithms under different asumptions and fault models are given. (2) Asynchronous message-passing systems with failures. The first result here is that it is impossible to reach con-

549

sensus in this model. Hence, several weaker versions of the consensus problem, such as k-set consensus, approximate consensus, the renaming problem, and reliable broadcast are considered. Algorithms to solve the weakened forms of consensus in these models are then given. (3) Waitfree shared memory consensus in asynchronous systems. Here, the first result is the impossibility result, analogous to that for message-passing systems. The chapter then solves consensus using registers (or objects) that are stronger than the atomic read/write registers. The consensus hierarchy that naturally emerges for stronger consensus objects is then studied. Algorithms for shared memory renaming and k-set consensus are also covered.

14.8 Exercise Problems 1. For each of the six ordered pairs of problems among: the Byzantine agreement problem, the Consensus problem, and the Interactive consistency problem, demonstrate a reduction from the former to the latter. 2. Modify the algorithm in Figure 14.3 to design an early-stopping algorithm for consensus under failstop failures, that terminates within f ′ + 1 rounds, where f ′ , the actual number of stop-failures, is less than f . Prove the correctness of your algorithm. Hint: A process can be required to send a mesage in each round, even if the value was sent in the earlier round. Processes should also track the other processes that failed, which is detectable by identifying the processes from which no message was received. 3. Modify the iterative Byzantine Agreement algorithm and the tree data structure specification given in Figure 14.7, as well as the example in Figure 14.8, to now solve the consensus problem. 4. Examine the phase-king algorithm for consensus in the face of Byzantine failures, as given in Figure 14.11. This algorithm works when n > 4f . Presumably, the algorithm will fail for 4f ≥ n > 3f , even though this condition is a sufficient condition for the existence of a solution to the consensus problem in a synchronous message-passing system. (a) Why will the algorithm fail for 4f ≥ n > 3f ?

(b) Even though the algorithm is not correct for 4f ≥ n > 3f , under some circumstance(s), the correct processors will end up with the same value. Characterize one such circumstance, independent of the behavior of the malicious processes. (c) To derive a correct solution for 4f > n > 3f , change line (1k) to read: if mult > f Will this solution work? (d) To derive another correct solution for 4f ≥ n > 3f , run the algorithm for 4(f + 1) rounds instead of for 2(f +1) rounds of the original algorithm. Will this solution work? 550

5. Prove that the distributed commit problem is not solvable under a crash failure. Hint: Show a reduction from the consensus problem to the distributed commit problem. 6. Prove that the leader election problem is not solvable under a crash failure. 7. In the ǫ-agreement problem, can a correct process halt if it receives f + 1 halting tags from other processes, even before it has completed its precomputed number of rounds? Justify your answer. 8. How can the algorithm for ǫ-agreement, given in Figure 14.13, be simplified if a synchronous system is available? Identify all the changes to the various parameter values. Can a better value be obtained for the convergence rate? 9. Analyze the number of bids for a new name made by each process in the asynchronous renaming algorithm given in Figure 14.17. 10. How can the algorithm for asynchronous renaming, given in Figure 14.17, be simplified if a synchronous system is available? 11. Examine the Test-&-Set instruction in Figure 14.22. What is the consensus number x of this register object? Give an algorithm to achieve consensus for this consensus number. 12. (k-Write instruction). (a) Consider the 2-Write instruction that can write two locations atomically. Show how the 2-Write instruction can be used to implement a wait-free 2-consensus protocol. (Hint: structure the solution using a structure similar to that of the protocols for RMW and Swap.) (b) Consider the k-Write instruction. Can this k-Write instruction be used to implement a wait-free consensus protocol for k processes? Justify your answer. 13. Examine the standard stack object, having its standard push and pop operations. What is the consensus number x of the stack? Give the code for achieving 2-process consensus using the stack. 14. Perform an average-case time complexity analysis of the nonblocking universal algorithm for consensus objects given in Figure 14.26. 15. Simplify the nonblocking universal algorithm for consensus objects (Figure 14.28) by using the specific Compare&Swap object, but also eliminating the Head array. 16. Adapt the message-passing asynchronous approximate agreement algorithm given in Section 14.5.5 for a shared memory system.

551

17. Perform a time complexity analysis of the wait-free renaming algorithm using the atomic snapshot object in asynchronous systems, given in Figure 14.30. Also prove the lower bounds on the size of the name space, as indicated in Section 14.5.6. 18. Show how the number of splitters used in the renaming algorithm of Section 14.6.6 can be reduced to n(n − 1)/2.

14.9 Bibliographic Notes The Byzantine agreement and the consensus problems were defined by Lamport, Shostak, and Pease [24, 20]. The exponential messages algorithm for solving consensus in the face of Bzyantine failures and the 3f + 1 lower bound were given in these papers. A later proof of the exponential algorithm was given by Bar-Noy, Dolev, Dwork, and Strong [3], and a later proof of the 3f + 1 lower bound was given by Fischer, Lynch, and Merritt [11]. The polynomial-message phase-king algorithm to solve consensus in the same Byzantine failure model was given by Berman and Garey [4]. A polynomial-message algorithm requiring t + 1 rounds and n > 3t processes has been given by Gary and Moses [13]. The result on the impossibility of reaching consensus in an asynchronous message-passing system was given by Fischer, Lynch, and Paterson [12]. The same impossbility result for an asynchronous shared memory system was given by Loui and Abu-Amara [21]. Fischer and Lynch [10] and Dolev and Strong [9] proved the lower bound of f + 1 rounds for reaching consensus in the Byzantine failure and crash failure models, respectively. The k-set consensus problem was defined by Chaudhuri [6]. This work also presented the first algorithm for solving k-set consensus under f faults, where f < k. The lower bound of f < k crash-failure processes for solving this problem was shown by Borowski and Gafni [5], Herlihy and Shavit [15], and Saks and Zaharoglou [26]. The approximate agreement problem was proposed, and solved for crash failure and Byzantine failures in the message-passing model by Dolov, Lynch, Pinter, Stark, and Weihl [8]. The wait-free shared memory solution to this problem was proposed by Moran [23]. Wait-free synchronization was introduced by Lamport [18] and developed by Peteron [25]. The theory of wait-free synchronization, consensus hierarchy, and the universal constructions for arbitrary consensus objects was given by Herlihy [14]. The discussion of RMW operations and the analysis of the consensus number of RMW objects with interfering operations is given by Kruskal, Rudolph, and Snir [17]. The renaming problem was proposed and solved for the message-passing model by Attiya, Bar-Noy, Dolev, Peleg, and Reischuk [1]. They also showed that at least n+1 new names are needed if f crash failures are to be tolerated. This lower bound was tightened to n + f by Herlihy and Shavit [15]. This lower bound, as well as the lower bound for k-set consensus are derived from a theorem that characterizes the solvable problems by a f -resilient algorithm using only Read and Write operations, as shown by Herlihy and Shavit [16]. The wait-free renaming algorithm for the shared memory algorithm is adapted from [1] and Attiya and Welch [2]. The wait-free shared memory renaming algorithm using splitters was proposed by Moir and Anderson [22]. The abstraction of wait-free splitters was proposed and implemented by Lamport [19]. 552

Bibliography [1] H. Attiya, A. Bar-Noy, D. Dolev, D. Peleg, R. Reischuk, Renaming in an asynchronous environment, Journal of the ACM, 41(1): 524-548, 1990. [2] H. Attiya, J. Welch, Distributed Computing: Fundamentals, Simulations, and Advanced Topics, Wiley Interscience, 2nd edition, 2004. [3] A. Bar-Noy, D. Dolev, C. Dwork, H. R. Strong, Shifting gears: Changing algorithms on the fly to expedite Byzantine agreement, Information and Computation, 92(2): 205-233, 1992. [4] P. Berman, J. Garay, Closure votes: n/4-resilient distributed consensus in (t + 1) rounds, Mathematical Systems Theory, 26(1): 3-19, 1993. [5] E. Borowsky, E. Gafni, Generalized FLP impossibility result for t-resilient asynchronous computations, Proceedings of the 25th IEEE STOC, 91-100, 1993. [6] S. Chaudhuri, More choices allow more faults: Set consensus problems in totally asynchronous systems, Information and Computation, 105(1): 132-158, 1993. [7] S. Chaudhuri, M. Herlihy, N. Lynch, M. Tuttle, A tight lower bound for k-set agreement, Proceedings IEEE FOCS, pp. 206-215, 1993. [8] D. Dolev, N. Lynch, S. Pinter, E. Stark, W. Weihl, Reaching approximate agreements in the presence of faults, Journal of the ACM, 33(3): 499-516, 1986. [9] D. Dolev, H.R. Strong, Authenticated algorithms for Byzantine agreement, SIAM Journal of Computing, 12(4): 656-666, 1983. [10] M. Fischer, N. Lynch, A lower bound for the time to assure interactive consistency, Information Processing Letters, 14(4): 183-186, 1982. [11] M. Fischer, N. Lynch, M. Merritt, Easy impossibility proofs for distributed consensus problems, Distributed Computing, 1(1): 26-39, 1986. [12] M. Fischer, N. Lynch, M. Paterson, Impossibility of distributed consensus with one faulty processor, Journal of the ACM, 32(2): 374-382, 1985.

553

[13] J. Garey, Y. Moses, Fully polynomial Byzantine agreement for n > 3t processors in t + 1 rounds, SIAM Journal of Computing, 27(1): 247-290, 1998. [14] M. Herlihy, Wait-free synchronization, ACM Transactions on Programming Languages and Systems, 11(1): 124-149, 1991. [15] M. Herlihy, N. Shavit, The asynchronous computability theorem for t-resilient tasks, Proceedings of the 25th IEEE STOC, 111-120, 1993. [16] M. Herlihy, N. Shavit, The topological structure of asynchronous computability, Journal of the ACM, 46(6): 858-923, 1999. [17] C. Kruskal, L. Rudolph, M. Snir, Efficient synchronization of multiprocessors with shared memory, Proceedings of ACM Principles of Distributed Computing, Aug. 1986. [18] L. Lamport, Concurrent reading and writing, Communications of the ACM, 20(11): 806-811, 1977. [19] L. Lamport, A fast mutual exclusion algorithm, ACM Trans. on Computer Systems, 5(1): 1-11, 1987. [20] L. Lamport, R. Shostak, M. Pease, The Byzantine generals problem, ACM Transactions on Programming Languages and Systems, 4(3): 382-401, 1982. [21] M.C. Loui, H.H. Abu-Amara, Memory requirements for agreement among unreliable asynchronous processes, Advances in Computinng Research (Vol. 4): Parallel and Distributed Computing, JAI Press, 1987. [22] M. Moir, J. Anderson, Wait-free algorithms for fats long-lived renaming, Science of Computer Programming, 25(1): 1-39, 1995. [23] S. Moran, Using approximate agreement to obtain complete disagreement: The output structure of input free asynchronous computations, Proc. 3rd Israeli Symposium on Theory of Computing and Systems, 251-257, 1995. [24] M. Pease, R. Shostak, L. Lamport, Reaching agreement in the presence of faults, Journal of the ACM, 27(2): 228-234, 1980. [25] G. Peterson, Concurrent reading while writing, ACM Transactions on Programming Languages and Systems, 5(1): 46-55, 1983. [26] M. Saks, F. Zaharoglou, Wait-free k-set agreement is impossible: The topology of public knowledge, Proc. 25th IEEE STOC, 101-110, 1993.

554

Chapter 15 Failure Detectors 15.1 Introduction This chapter deals with the design of fault-tolerant distributed systems. It is widely known that the design and verification of fault-tolerent distributed systems is a difficult problem. Consensus and atomic broadcast are two important paradigms in the design of fault-tolerent distributed systems and they find wide applications. Consensus allows a set of processes to reach a common decision or value that depends upon the initial values at the processes, regardless of failures. In atomic broadcast, processes reliably broadcast messages such that they agree on the set of messages delivered and the order of message deliveries. This chapter focuses on solutions to consensus and atomic broadcast problems in asynchronous distributed systems. In asynchronous distributed systems, there is no bound on the time it takes for a process to execute a computation step or for a message to go from its sender to its receiver. In an asynchronous distributed system, there is no upper bound on the relative processor speeds, execution times, clock drifts, and delay during the transmission of messages although they are finite. This is mainly casued by unpredictable loads on the system that causes asynchrony in the system and one can not make any timing assumptions of any types. On the other hand, synchronous systems are characterized by strict bounds on the execution times and message transmission delays. The asynchronous model of distributed system has simpler semantics when compared to synchronous model. Applications based on the asynchronous model are easily portable because there are no strict timing assumptions to take care of. The asynchronous model of distributed systems is very popular and has attracted lot of attention due to these reasons. Inspite of the attractiveness of asynchronous distributed systems, it is well known that consensus, atomic broadcast, and several other reliable broadcast problems cannot be solved deterministically even for a single process failure due to the unbounded timing characteristics. The main cause of this impossibility result is that it is very difficult to determine in asynchronous systems whether a process has failed or is simply taking a long time for execution; so it is difficult to deal with failures in these systems. On the other hand, in synchronous systems due to strict timing constraints, failures can easily be detected. The asynchronous model of distributed systems is widely used, and such systems are prone to failures. Thus, detection and/or prevention of failures in these systems is of vital importance. 555

The detection of process failures is a crucial task in the design of fault tolerant distributed systems. Detection of crashed processes is especially difficult in asynchronous systems as it is impossible to determine whether a process has really crashed or is very slow (as there are no timing constraints present). In this chapter, we discuss the concept of unreliable failure detectors to deal with the impossibility results in asynchronous distributed systems with crash failures. Basically, the asynchronous model of computation is extended with a failure detection mechanism that is prone to errors in the sense that a process can brand another process as crashed even though the process is running. We study failure detectors in asynchronous distributed systems. We investigate two major problems faced in asynchronous distributed environments, namely, consensus and atomic broadcast. We study several solutions for these problems.

15.2 Unreliable Failure Detectors Chandra and Toueg [3] introduced the concept of unreliable failure detectors and showed how unreliable failure detectors can be used to solve two fundamental paradigms of asynchronous distributed systems with crash failures, namely, consensus and atomic broadcast.

15.2.1 The System Model We consider asynchronous distributed systems in which there is no bound on message delay, clock drift, or the time taken to execute a step. The system consists of a finite set of n processes, Q = p1 , p2 , ..., pn . Each pair of processes is connected by a reliable communication channel. A process can fail by crashing only, i.e., by prematurely halting. A process behaves correctly (i.e., according to its specification) until it crashes. A discrete global clock is assumed, and the range of the clock’s ticks, Φ, is the set of natural numbers. The global clock is used for the sake of simplicity of presentation and reasoning and is not accessible to the processes. A process pi is said to crash at time t if pi does not perform any action after time t. Process failures are permanent; once a process crashes, it does not recover. A correct process is a process that does not crash. Informally, a run is an infinite execution of the system. Given any run σ, Crashed(t,σ) is the set of processes that have crashed by time t and Up(t,σ) is the set of processes that are correct (i.e., have not crashed) by time t, that is, Up(t,σ) = Q − Crashed(t,σ). Crashed(σ) is the set S of processes that have crashed in a run σ and is equal to t Crashed(t, σ). Up(σ) is the set of processes that are correct in a run σ and is equal to Q − Crashed(σ). If a process p ∈ Crashed(σ), we say that p is a faulty process in σ. If a process p ∈ Up(σ), we say that p is a correct process in σ. We consider only execution runs where at least one process is correct.

556

Failure Patterns and Environments A failure pattern is a function F from Φ to 2Q , where F(t) denotes the set of processes that have crashed through time t. An environment E is a set of failure patterns. Environments describe the crashes that can occur in a system. In general, we consider the environments that contain all possible failure patterns; i.e., there is no bound on the number of processes that crash. Each process pi has a local failure detector module of D, denoted by Di . Associated with each failure detector D is a range RD of values output by the failure detector. A failure detector history H with range R is a fuction H from ΩXΦ to R. D(F) denotes the set of possible failure detector histories permitted for the failure pattern F, i.e., each history represents a possible behaviour of D for the failure pattern F. For any failure detector D, any failure pattern F and any history H in D(F), H(pi ,t) is the set of processes suspected by process pi at time t.

15.2.2 Failure Detectors A failure detector D is a distributed oracle that gives hints about failure patterns. Each process pi in the distributed environment has its own local failure detector Di , which monitors all other processes and maintains a list of processes, currently pi suspects to have crashed. The suspicion is based on relative timeouts of other processes at pi . Thus, a failure detector D as the vector D =< Dp1 , Dp2 , ....Dpn >, where Di is the failure detector module at process pi which outputs the set of processes that it currently suspects to have crashed. Formally, a failure detector is a function “from time and the set of all runs" to 2Q . Dp (t, σ) is the set of processes that are suspected to have crashed by p’s failure detector module at time t in run σ. If q ∈ Dp (t, σ), we say that p suspects q at time t in run σ. After a process crashes, it is immaterial what its failure detector module indicates. We formalize this by assuming that if p ∈ Crashed(t,σ), then Dp (t, σ) = φ. The failure detectors can make mistakes, i.e., a correct process may be added to the list of suspects and can later be removed if the failure detector realizes that it was a mistake. Thus, a failure detector may continually add and remove processes from its list of suspects. Processes can be added and removed from the list of suspects by each failure detector module any number of times. At any time, failure detector modules at two processes may have different lists of suspects. It should be noted that addition of a correct process to the list of suspects by any other processes or by all other processes should not prevent this process from behaving correctly, according to its specifications.

15.2.3 Completeness and Accuracy Properties Chandra and Toueg classified failure detectors in terms of their completeness and accuracy properties. Informally, completeness requires that a failure detector eventually suspects all processes that have crashed and accuracy resticts the mistakes a failure detector can make (i.e., a correct process suspect another correct process). They define two types of completeness and four types of accuracy properties, giving rise to eight classes of failure detectors. 557

Chandra and Toueg introduced the concept of reducibility among failure detectors. Informally, a failure detector D is reducible into another failure detector D’ if there exists a distributed algorithm that can transform D into D’. In this case, any problem that can be solved using D’ can also be solved using D. If two failure detectors are reducible to each other, they are said to be equivalent. Chandra and Toueg put failure detectors into eight classes and ordered them into a hierarchy according to the reducibility relationship. In this hierarchy, some failure detectors can solve the consensus problem with any number of process failures, while others require a certain number of correct processes to solve the consensus problem. This requirement and the boundary where this requirement becomes necessary have been clearly specified. We now define completeness and accuracy properties of a failure detector.

Completeness Definition 29. Completeness: There is a time after which every process that has crashed is permanently suspected by a correct process. Completeness can be of two types: 1. Strong completeness: Eventually every process that crashes is permanently suspected by every correct process. Notationally, ∀σ, ∀p ∈ Crashed(σ), ∀q ∈ Up(σ), ∃t such that ∀t′ ≥ t : p ∈ Dq (t′ , σ) 2. Weak completeness: Eventually every process that crashes is permanently suspected by some correct process. Notationally, ∀σ, ∀p ∈ Crashed(σ), ∃q ∈ Up(σ), ∃t such that ∀t′ ≥ t : p ∈ Dq (t′ , σ) Note that completeness by itself may not be of much use. For example, a failure detector may satisfy the strong completeness property by having every process permanently suspect all other processes. Such a failure detector is useless because it provides no information about actual failures. Thus, a failure detector must satisfy some accuracy property to be useful. We define this property next.

Accuracy Definition 30. Accuracy: There is a time after which a correct process is never suspected by any correct process. There are two types of accuracy properties: 1. Strong accuracy: Correct processes are never suspected by any correct process. Formally, 558

∀σ, ∀t, ∀p, q ∈ Up(t, σ) : p 6∈ Dq (t, σ) Since in any practical system it is extremely difficult to achieve accuracy, we weaken it as follows: 2. Weak accuracy: Some correct process is never suspected by any correct process. Formally, ∀σ, ∃p ∈ Up(σ), ∀t, ∀q ∈ Up(t, σ) : p 6∈ Dq (t, σ) We collectively refer to strong accuracy and weak accuracy as the perpetual accuracy properties because these properties hold all the time. Note that even weak accuracy is difficult to achieve because a failure detector (even at a correct process) may suspect a correct process and then later correct its mistake. The weak accuracy property does not permit this. Thus, we further weaken the accuracy requirement and allow failure detectors that may suspect a correct process at some points in the run, but they eventually satisfy the strong and weak accuracy properties.

Eventual Accuracy Definition 31. Eventual Accuracy: We need not require accuracy property to be satisfied by each process at all the time. Instead, we require the accuracy property to be eventually satisfied. There are two types of eventual accuracies: 1. Eventual strong accuracy: There is a time after which correct processes are not suspected by any correct process. Formally, ∀σ, ∃t, ∀t′ ≥ t, ∀p, q ∈ Up(t′ , σ) : p 6∈ Dq (t′ , σ) 2. Eventual weak accuracy: There is a time after which some correct process is not suspected by any correct process. Formally, ∀σ, ∃t, ∀t′ ≥ t, ∃p ∈ Up(σ), ∀q ∈ Up(σ) : p 6∈ Dq (t′ , σ) We collectively refer to eventual strong accuracy and eventual weak accuracy as the eventual accuracy properties because these properties hold eventually.

559

15.2.4 Types of Failure Detectors Based on types of accuracies and completeness defined above, failure detectors can be classified into the following categories: 1. Perfect Failure Detectors (P): Failure detectors that satisfy the strong completeness and the strong accuracy properties are called the Perfect Failure Detectors. 2. Eventually Perfect Failure Detectors (♦P): Failure detectors that satisfy the strong completeness and the eventual strong accuracy properties are termed as the Eventually Perfect Failure Detectors. 3. Strong Failure Detectors (S): Failure detectors that satisfy the strong completeness and the weak accuracy properties are called the Strong Failure Detectors. 4. Eventually Strong Failure Detectors (♦S): Failure detectors that satisfy the strong completeness and the eventual weak accuracy properties are called the Eventually Strong Failure Detectors. 5. Weak Failure Detectors (W): Failure detectors that satisfy the weak completeness and the weak accuracy properties are called the Weak Failure Detectors. 6. Eventually Weak Failure Detectors (♦W): Failure detectors that satisfy the weak completeness and the eventual weak accuracy properties are called the Eventually Weak Failure Detectors. 7. Another class of failure detectors is the one that satisfies weak completeness and strong accuracy properties. This class is denoted by ϑ. 8. The last class is the set of failure detectors that satisfy weak completeness and eventually strong accuracy properties. This class is denoted by ♦ϑ.

15.2.5 Reducibility of Failure Detectors A failure detector D is reducible to another failure detector D’ if there is an algorithm that transforms a failure detector D into another failure detector D’. A natural question is: what does it mean that an algorithm transforms D into D’? An algorithm TD → D′ transforms a failure detector D into another failure detector D’ if and only if for every run R of TD → D′ under a failure pattern F using D, outputR ∈D’(F), where outputR is the output of run R using failure detector D and D’(F) denotes the set of histories of failure detector D’ for failure pattern F. That is, variable outputp at process p emulates the output of D’. Thus, TD → D′ can emulate D’ using D. TD → D′ need not emulate all failure detector histories of D’; however, all failure detector histories it emulates must be histories of D’. Algorithm TD → D′ is called the reduction algorithm. Given a reduction algorithm TD → E , any problem that can be solved using E, can also be solved using D. We illustrate this with an example: suppose a given algorithm A requires failure 560

detector E, but only failure detector D is available. We can execute A using failure detector D as follows. Concurrently with A, processes run TD → E to transform D to E. Algorithm A is modified at process p as follows: whenever A requires that p queries its failure detector module, p reads the current value of outputp which is concurrently maintained by TD → E . Since T D → E is able to use D to emulate E, D must provide at least as much information about process failures as E does. Thus, if there is an algorithm TD → E , that transforms D into E, we say that E is weaker than D and denote it by D ⊑ E. Note that ⊑ is a transitive relation. If D ⊑ E and E ⊑ D, then we say that D and E are equivalent and denote it by D ≡ E. If D and ε are two classes of failure detectors and there exists an algorithm TD → E that can transform every failure detector D ∈ D into a failure detector E ∈ ε, then we say that the class of failure detectors D is reducible to the class of failure detectors ε and this is denoted by D ⊑ ε. In this case, ε is weaker than D. If D ⊑ ε and ε ⊑ D, then D and ε are equivalent and this is denoted by D ≡ ε. From a trivial reduction algorithm where each process p periodically writes the current output of its failure detector module into outputp , the following relations between the classes of failure detectors are obvious:

Observation 1: P ⊑ ϑ, S ⊑ W, ♦P ⊑ ♦ϑ, ♦S ⊑ ♦W .

15.2.6 Reducing Weak Failure Detector W to a Strong Failure Detector S In Figure 15.1, we give a reduction algorithm TD → D′ (due to Chandra and Toueg) that transforms any given failure detector D that satisfies weak completeness, into a failure detector D’ that satisfies strong completeness. D’ satisfies the same accuracy property that D satisfies. Thus, this algorithm strenghtens the completeness while preserving the accuracy. Informally, the conversion of any weak failure detector W to a strong failure detector S is as follows: Initially, for every process p, outputp is set to null. (Recall that outputp is the variable emulating the output of the failure detector module Dp′ .) Every process p periodically sends (p,suspectsp ) to every process, where suspectsp denotes the set of processes that p suspects according to its failure detector module Dp . When a process p recieves a message (q, suspectsq ) from a process q, process p adds the suspect list of process q, suspectsq , to its output, outputp , and removes the process q from its output as it is a correct process.

A Correctness Argument The correctness proof of the algorithm involves showing the following three properties: 1. It transforms weak completeness into strong completeness. 2. It preserves the perpetual accuracy. 561

Every process p executes the following: outputp ← φ cobegin || Task 1: repeat forever suspectsp ← Dp {p queries its local failure detector module Dp } send(p,suspectsp ) to all other processes. || Task 2: when recieve (q,suspectsq ) for a process q outputp ← (outputp ∪ suspectsq ) − {q}

{outputp emulates Ep }

coend Figure 15.1: Transforming weak completeness to strong completeness. 3. It preserving the eventual accuracy. We show these properties in the following three lemmas. Lemma 1: Let p be any process that crashes. If eventually some correct process permanently suspects p in HD , then eventually all correct processes permanently suspect p in outputR , where HD is the history of failure detector D and outputR is the output of an arbitrary run R using failure detector D. Since process p crashes, there is a time t’ after which no process recieves a message from p. Suppose there is a correct process q that permanently suspects p in HD after time t. Consider the execution of Task 1 by process q after time tp = max(t,t’). Process q sends a message (q,suspectsq ) such that p ∈ suspectsq to all processes. Eventually, every correct process recieves (q,suspectsq ) and adds p to output (in Task 2). Since no correct process recieves any messages from p after time t’ and tp ≥ t’, no correct process removes p from its output after tp . Thus, there is a time after which every correct process permanently suspects p in outputR . Lemma 2: Let p be any process. If no process suspects p in HD before time t, then no process suspects p in outputR before time t. Suppose there is a time t before which no process suspects process p in HD . Thus, no process sends a message of type (-,suspects) such that p ∈ suspects before time t. Thus, no process q adds p to outputq before time t. Lemma 3: Let p be a correct process. If there is a time after which no correct process suspects p in HD , then there is a time after which no correct process suspects p in outputR .

562

Suppose there is a time t after which no correct process suspects p in HD . Thus, all processes that suspect p after time t eventually crash. Thus, there is time after which no process will send messages of type (-, suspects) such that p ∈ suspects. Thus, there is a time t’ after which no correct process recieves a message of type (-, suspects) such that p ∈ suspects. Let q be a correct process. We need to show that there is a time after which q does not suspect p in outputR . Consider the execution of Task 1 by process p after time t’. Process p sends the message (p, suspectsp ) to q. When q receives this message, it removes p from outputq if p is present in outputq (Task 2). Note that q does not recieve any messages of type (-, suspects) such that p ∈ suspects after time t’, therefore, q does not add p to outputq after time t’. Thus, there is a time after which q does not suspect p in outputR . Theorem 1: ϑ ⊑ P, W ⊑ S, ♦ϑ ⊑ ♦P and ♦W ⊑ ♦S. Proof: Let D be any failure detector in ϑ, W, ♦ϑ or ♦ W. We show that TD → E transforms D into a failure detector E in P, S, ♦P , or ♦S. Since D satisfies weak completeness, E satisfies strong completeness (from Lemma 1). We now argue that D and E have the same accuracy properties. If D is in ϑ or W, then D and E have the same accuracy property (from Lemma 2). If D is in ♦ ϑ or ♦ W, then D and E have the same accuracy property (from Lemma 3). Thus we have, ϑ ⊑ P, W ⊑ S, ♦ϑ ⊑ ♦P and ♦W ⊑ ♦S ✷ From Theorem 1 and Observation 1, we have the following result: P ≡ ϑ, S ≡ W, ♦P ≡ ♦ϑ, and ♦S ≡ ♦W. A significance of this result is that if we solve a problem for the four failure detectors with strong completeness, the problem is automatically solved for the remaining four failure detectors.

15.2.7 Reducing an Eventually Weak Failure Detector ♦W to an Eventually Strong Failure Detector ♦S Figure 15.2 gives an algorithm that converts any eventually weak failure detector D ∈ ♦W into an eventually strong failure detector E ∈ ♦S. Q is the set of all processes. At process p, variable suspectedp (r, q) denotes how many times process q has suspected process r and variable refutedp (r, q) denotes how many times process r has refuted process q. Both variables are initialized to zero. Sp denotes the suspect list of process p.

An Explanation of the Algorithm The algorithm consists of four tasks. 563

Process p runs the following: for all q,r ∈ Q {Number of times q suspected r according to p} suspectedp (r, q) ← 0 {Number of times r refuted q according to p} refutedp (r, q) ← 0 cobegin || Task 1: repeat forever if (r ∈ Dp and refutedp (r, p) ≤ suspectedp (r, p)) then p rbcasts (p, suspects, r, refutedp (r, p) + 1) || Task 2: when p rbdelivers (q, suspects, r, k) suspectedp (r, q) ← k if p = r then p rbcasts (p, refutes, q, k) || Task 3: when p redelivers (r, refutes, q, k) refutedp (r, q) ← k || Task 4: repeat forever for all processes r if ∃ q : suspectedS p (r, q) > refutedp (r, q) then Sp ← Sp {r} else Sp ← Sp - {r} coend Figure 15.2: An algorithm to reduce an eventually weak failure detector into an eventually strong failure detector. In Task 1, a process p continuously performs the following for every process r that it suspects according to its failure detector module Dp : if the number of times process r is suspected by p is greater than the number of times r has refuted p, then p broadcasts a suspect message which contains the incremented refuted value. In Task 2, when process p recieves a suspect message (q, suspects, r, k) from a process q, it updates suspectedp (r, q) to k. If process p discovers that it was erroneously suspected by process q, p broadcasts an appropriate refutation, refuting the suspicion of process q. In Task 3, when process p receives a refutation message (r, refutes, q, k) from process r, it updates refutedp (r, q) to k. In Task 4, the following is repeatedly done for every process r: if there exists a process q such that the number of times q suspects process r is greater than the number of times the process r refutes q according to p, then process r is added to the suspect list of process p. Otherwise, r is removed from the suspect list of process p.

564

Correctness Argument A correctness argument of the algorithm is as follows. When a process q recieves a suspect message accusing process p, process q may add p to its list of suspects Sq . However, upon receiving p’s refutation, process q will remove p from its list of suspects Sq . However, p can be suspected again and added to Sq a second time. However, a further refutation from p will cause p to be again removed from Sq . Thus, a possibly infinite sequence of suspicions followed by corresponding refutations may occur, resulting in p being repeatedly added to and removed from Sq . However, from the eventual weak accuracy property of D, there is a time after which some correct process is not suspected. That is, there is a process p such that there is a time after which no correct process receives a message of type (*, suspects, p, k), suspecting p. Thus, after a time no correct process adds process p to its suspect list. Together with the refutation mechanism, this ensures the eventual weak accuracy property of the constructed E. Now let us see why E satisfies the strong completeness property. Since D satisfies the weak completeness property, eventually every process that crashes is permanently suspected by some correct process, say p. Thus, eventually process p will repeatedly broadcast (p, suspects, *, k) messages for these crashed processes and since these processed have crashed, no one will send refute messages for them. Thus all crashed processes will eventually belong to the suspect list of all correct processes. Thus, due to the broadcast of suspect messages and weak completeness property of D, E satisfies the strong completeness property. Thus E satisfies strong completeness and weak accuracy.

15.3 The Consensus Problem In the consensus problem, each correct process proposes a value and all processes must reach a unanimous and irrevocable decision on a value that is related to the proposed values [9]. The consensus problem is defined in terms of the following properties: 1. Termination: Every correct process eventually decides some value. 2. Uniform Integrity: Every process decides at most once. 3. Agreement: No two correct processes decide differently. 4. Uniform Validity: If a process decides a value v, then some process proposed v. It is widely known that the Consensus can not be solved in asynchronous systems in the presence of even a single crash failure. This is primarily because one can not distinguish between a process that has crashed and a process that is responding very slow (may be due to the slow network).

565

15.3.1 Solutions to the Consensus Problem Chandra and Toueg showed how to solve the consensus problem using unreliable failure detectors for each of the eight classes of failure detectors. From the following property, the classes of failure detectors P, S, ♦P, ♦S are, respectively, equivalent to failure detectors ϑ, W, ♦ϑ, ♦W . Notationally, P ≡ ϑ, S ≡ W, ♦P ≡ ♦ϑ, and ♦S ≡ ♦W So the problem of solving the consensus problem using unreliable failure detectors reduces to solving it for four classes of failure detectors that satisfy strong completeness (i.e., P, S, ♦ P and ♦ S), instead of solving it for all eight classes. Since P is reducible to S and ♦ P is reducible to ♦S (i.e., P ⊑S and ♦P⊑♦S), the algorithms for solving consensus using S also solves the consensus using P and the algorithms for solving consensus using ♦S also solves the consensus using ♦P. Next, we present algorithms that solve consensus using S and ♦ S. The consensus algorithm using S can tolerate any number of process failures. However, the consensus algorithm using ♦ S requires a majority of the processes to be up.

15.3.2 A Solution Using Strong Failure Detector S The algorithm in Figure 15.3 solves the consensus problem in an asynchronous system using a failure detector D that satisfies strong completeness and weak accuracy (i.e., D ∈ S). This algorithm tolerates any number of process failures (upto n-1 faulty processes among a total of n processes). Notations: • ip is the value proposed by process p. • ⊥ is null value. • Vp [q] is the process p’s estimate of process q’s proposed value. • Vp is process p’s estimate of the proposed values by all other processes. • △p contains all the values of Vp . • rp is the current round number of process p. • msgsp (rp ) is the set of messages that p recieves from other processes about the proposed values in round rp . • lastmsgsp contains the recieved Vq for all processes q by the process p.

566

Every process p executes the following: procedure propose(ip ) Vp ← {p’s estimate of the proposed values} Vp [p] ← ip ; △ p ← Vp Phase 1: {Execute round rp , 1 ≤ rp ≤ n-1} for rp ← l to n-1 p sends (rp , △p , p) to all other processes wait until [∀q:received(rp , △q ,q) or q∈ Dp ] {query the failure detector} msgsp[rp ] ← {(rp , △q , q) | received(rp , △q ,q)} △p ← for k ← 1 to n if (Vp [k] =⊥ and ∃(rp , △q , q) ∈msgsp (rp ) with △q [k] 6=⊥) then Vp [k] ← △q [k] △p [k] ← △q [k] Phase 2: p sends Vp to all processes wait until [∀q:received Vq or q∈ Dp ] {query the failure detector} lastmsgsp ← {Vq | received Vq } for k ← 1 to n if ∃Vq ∈ lastmsgsp with Vq [k] =⊥ then Vp [k] ←⊥ Phase 3: decide on the first non-⊥ element of Vp Figure 15.3: An Algorithm to Solve the consensus problem Using a Strong Failure Detector D ∈ S .

An Explanation of the Algorithm This algorithm has 3 phases. Initially, Vp is set to null and Vp [p] contains the value, ip , proposed by process p. In the first phase, each process executes n-1 asynchronous rounds. In each round, processes broadcast and relay their proposed values. Then, each process p waits until it receives a round r message from every process that is not in Dp , before proceeding to round r + 1. While p is waiting for a message from a process q in round r, it is possible that q is added to Dp . If this is the case, p does not wait for q’s message before it proceeds to round r + 1. All messages recieved by p in round rp are stored in msgsp (rp ). If p’s estimate of some process k’s proposed value is null and it has recieved a message of the form (rp , △q , q) such that q’s estimate of process k’s proposed value is not null, then p updates its estimate of k’s proposed value to q’s estimate of process k’s proposed value. In the second phase, a process p broadcasts its estimate of the proposed values of the processes

567

and waits until it receives the estimate from every process that is not in Dp . While p is waiting for an estimate from q, it is possible that q is added to Dp . If this occurs, p stops waiting for q’s estimate. By the end of the second phase, correct processes agree on a vector based on the proposed values of all processes. The ith element of this vector either contains the proposed value of process pi or ⊥. If any of the correct processes does not agree with the proposed value of a process, say pi , then the ith element in the vector is set to null and consensus is not reached on the proposed value. It has been shown that this vector contains the proposed value of at least one process. In the third phase, all correct processes decide the first non-trivial component of this vector. This solution for the consensus problem using strong failure detectors, even one having weak accuracy property, has an excellent fault tolerance capacity; the solution tolerates any number of process failures. Also, since a weak failure detector W is reducible to a strong failure detector S using the algorithm given above, this algorithm also solves the consensus using a weak failure detector W.

15.3.3 A Solution Using Eventually Strong Failure Detector ♦S The previous solution to the Consensus problem used failure detectors with weak accuracy: some correct process is never suspected. We now present a solution to the consensus problem using a failure detector that satisfies the eventual weak accuracy: all processes may be erroneously added to the lists of suspects at one time or another, but there is a time after which a correct process p is permanently removed from the list of suspects. However, at any given time t, processes cannot determine if a particular process is correct, or whether a correct process will never be suspected after time t. Figure 15.4 presents a solution to the consensus using an eventually strong failure detector D∈♦S. Such failure detectors satisfy strong completeness and eventual weak accuracy. The algorithm requires that a majority of the processes are always up. If f is the maximum number of processes that may crash at any time, this algorithm requires that f < ⌈ n/2 ⌉, that is, at least (n + 1)/2 processes are correct at all times.

An Explanation of the Algorithm This algorithm proceeds in asynchronous rounds and makes use of the rotating coordinator paradigm until a decision is reached. All processes know that during round r, the coordinator is process c=(r mod n) + 1. All messages are either to or from the "current" coordinator. The "current" coordinator tries to determine a consistent decision value. If the current coordinator is correct and is not suspected by any surviving process, then it succeeds and broadcasts the decision value. The algorithm goes through three asynchronous stages where each stage can contain several asynchronous rounds. In the first stage, several decision values are proposed. In second stage, a value gets locked: no other decision value is possible. In the third and final stage, the processes decide on the locked value and consensus is reached. 568

Every process p executes the following: estimatep ← ip {p’s estimate of the decision value} statep ← undecided rp ← 0 {rp denotes the current round number} tsp ← 0 {the round in which estimatep was last updated, initially 0} cobegin ||Task 1:{Rotate through coordinators until a decision is reached} while statep = undecided rp ← rp + l cp ← (rp mod n) + 1 {cp is the current coordinator} Phase 1:{All processes p send estimatep to the current coordinator} p sends (p, rp , estimatep , tsp ) to cp Phase 2:{The current coordinator gathers ⌈(n + 1)/2⌉ estimates and proposes a new estimate} if p = cp then wait until [for ⌈(n + 1)/2⌉ processes q: received(q,rp ,estimateq ,tsq ) from q] msgsp [rp ] ← {(q,rp ,estimateq ,tsq )| p received(q,rp ,estimateq ,tsq ) from q} t ← largest tsq such that (q,rp ,estimateq ,tsq )∈msgsp [rp ] estimatep ← select one estimateq such that (q,rp ,estimateq ,t)∈msgsp [rp ] p sends (p, rp , estimatep ) to all processes Phase 3: {All processes wait for the new estimate proposed by the current coordinator} wait until [received(cp ,rp ,estimatecp ) from cp or cp ∈ Dp ] {Query the failure detector} if [received(cp ,rp ,estimatecp ) from cp ] then {p received estimatecp from cp } estimatep ← estimatecp tsp ← rp p sends (p, rp , ack) to cp else p sends(p, rp , nack) to cp {p suspects that cp crashed} Phase 4:{The current coordinator waits for ⌈(n + 1)/2⌉ replies. If these replies indicate that ⌈(n + 1)/2⌉ processes adopted its estimate, the coordinator broadcatss a request to decide.} if p = cp then wait until [for ⌈(n+1)/2⌉ processes q: received (q, rp , ack) or (q, rp , nack)] if [for ⌈(n+1)/2⌉ processes q: received (q, rp , ack)] then p R-broadcasts (p, rp , estimatep , decide) || Task 2: {When p receives a decide message, it decides} when p R-delivers (q,rq ,estimateq ,decide) for some q if statep = undecided then decide on estimateq statep ← decided coend

Figure 15.4: An algorithm to solve the consensus problem using an eventually strong failure detector D∈♦S. 569

Initially, the state of a process p is "undecided" and its estimate of the decision value is ip . A timestamp tsp is associated with every process p which contains the round number when its estimate was last updated. Each round of Task 1 consists of four asynchronous phases. In phase 1, every process p sends its current estimate of the decision value to the current coordinator cp . It also sends the round number (tsp ) in which it adopted this estimate. In phase 2, the coordinator c gathers ⌈(n + 1)/2⌉ such estimates and proposes a new estimate. The current coordinator waits until it receives estimates from ⌈(n + 1)/2⌉ processes. It stores all these estimates in the array msgsp [rp ], selects one with the largest timestamp, and sends it to all the processes as the new estimate, estimatep . In phase 3, all processes wait for the new estimate proposed by the current coordinator. For each process p, there are two possibilities: 1. Process p recieves estimatecp from the coordinator cp : in this case, p updates its timestamp to the current round number and sends an ack to cp to indicate that it adopted estimatecp as its own estimate. 2. Process p does not recieve an estimatecp from the coordinator cp and upon consulting its failure detector module Dp , p suspects that the coordinator cp has crashed: in this case, p sends a nack to cp . In phase 4, the coordinator cp waits for ⌈(n + 1)/2⌉ replies (acks or nacks). If all replies are acks, then cp knows that a majority of processes changed their estimates to estimatecp and thus estimatep is locked and cp broadcasts a request to decide value estimatep . In Task 2, at any time, if a process receives such a request, it decides accordingly, i.e., when a process p recieves a message of the form (q, rq , estimateq , decide) from a process q, then p decides on the estimate of q provided it has not already decided. In this case, process p changes its state to "decided". For correctness of the algorithm, we have to show that the algorithm satisfies termination, uniform validity, agreement, and uniform integrity properties. The readers are referred to the original source for a correctness proof. This algorithm requires that f < ⌈n/2⌉, i.e., at least ⌈n/2⌉ processes are correct and assumes that processes have a priori knowledge of the list of potential coordinators.

15.4 Atomic Broadcast Atomic broadcast is one of the fundamental problems in fault-tolerant distributed computing. It is a powerful paradigm in the design of fault-tolerant distributed computing systems. Chandra and Toueg showed that the results of consensus can be applied to solve the problem of atomic broadcast. Informally, atomic broadcast requires that all correct processes deliver the same set of

570

messages in the same order (i.e., deliver the same sequence of messages). Formally, atomic broadcast can be defined as a reliable broadcast with the total order property. The Total Order property: If two correct processes p and q deliver two messages m and m′ , then p delivers m before m′ if and only if q delivers m before m′ . The total order and agreement properties of atomic broadcast ensure that all correct processes deliver the same sequence of messages. In asyncronous sytems with crash failures, consensus and atomic broadcast are equivalent and this can be shown by reducing one to the another. Consensus can be reduced to atomic broadcast as follows: In the consensus problem, to propose a value, a process atomically broadcasts it. To decide a value, a process picks the value of the first message that it atomically delivers. The total order property of atomic broadcast ensures that all correct processes deliver the same first message. Hence, all correct processes choose the same value and the agreement property of the consensus is satisfied. In the next section, we show how to reduce atomic broadcast to consensus. A consequence of this equivalence is that a solution for one can be used to solve the other. In addition, it implies the following for solving atomic broadcast in asynchronous systems: 1. Since consensus has no deterministic solution in aynchronous systems, even if we assume that at most one process may fail by crashing, atomic broadcast cannot be solved by a deterministic algorithm even if at most one process may fail by crashing. 2. As consensus is solvable using randomization or unreliable failure detectors in asynchronous systems, atomic broadcast can be solved using these techniques.

15.5 A Solution to Atomic Broadcast Figure 15.5 presents a solution (due to Chandra and Toueg) to atomic broadcast problem using the consensus in asynchronous systems. This algorithm shows how to transform any consensus algorithm into an atomic broadcast algorithm in asynchronous systems. This atomic broadcast algorithm tolerates as many faulty processes as the consensus algorithm does. This atomic broadcast algorithm uses repeated executions of consensus. The kth execution of consensus is used to decide on the kth batch of messages to be atomically delivered. Processes distinguish between these executions by tagging all the messages pertaining to the kth execution of consensus with the counter k. The atomic broadcast algorithm uses R_broadcast(m) and R_deliver(m) primitives of reliable broadcast. To avoid any confusion, note that the primitives A_broadcast(m) and A_deliver(m) respectively refer to a broadcast and a delivery in atomic broadcast, while primitives R_broadcast(m) and R_deliver(m) respectively refer to a broadcast and a delivery associated with reliable broadcast. propose(k, −) and decide(k, −) are the propose and decide primitives corresponding to the kth execution of consensus. 571

Every process p executes the following: Initialization: R_delivered ← ∅ A_delivered ← ∅ k←0 To execute A-broadcast(m): R-broadcast(m)

{Task 1}

A_deliver(-) occurs as follows: when R_deliver(m) {Task 2} S R_delivered ← R_delivered {m} when R_delivered - A_delivered 6= ∅ {Task 3} k←k+1 A_undelivered ← R_delivered - A_delivered propose(k, A_undelivered) wait until decide(k, msgSetk ) A_deliverk ← msgSetk - A_delivered k atomically deliver all messages in some determinisic order S in A_deliver k A_delivered ← A_delivered A_deliver

Figure 15.5: A Solution to Atomic Broadcast Using Consensus.

An Explanation of the Algorithm The algorithm consists of three tasks such that: (1) a task that is enabled is eventually executed and (2) a task i can execute concurrently with another Task j provided i6=j. In Task 1, when a process p wants to A-broadcast a message m, it R_broadcasts m. In Task 2, a message m is added to set R_deliveredp when process p R_delivers it. In Task 3, when a process p A_delivers a message m, it adds m to set A_deliveredp . A_undeliveredp (defined as R_deliveredp − A_deliveredp ) is the set of messages that p has R_delivered but has not A_delivered yet. Process p periodically checks whether A_undeliveredp contains messages. If A_undeliveredp contains messages, p enters its next execution of consensus, say the kth one, and proposes A_undeliveredp as the next batch of messages to be A_delivered. Process p then waits for the kth consensus decision, which is denoted by msgSetk . msgSetk contains messages which are R_delivered but they are yet to be A_delivered. Finally, p A_delivers all the messages in msgSetk except those already A_delivered by it (i.e, all the messages in the set A_deliverpk =msgSetk − A_deliveredp ) in some deterministic order that was agreed a priori by all processes. For a correctness proof of the algorithm, the readers should refer to the original source.

572

15.6 The Weakest Failure Detectors to Solve Fundamental Agreement Problems Delporte-Gallet et al. [5] showed that if exclude unrealistic failure detectors1 , then in an environment where we do not bound the number of faulty processes, the class of Perfect failure detectors P is the weakest to solve fundamental agreement problems like unifrom consensus, atomic broadcast and terminating reliable broadcast (also called the Byzantine Generals). Delporte-Gallet et al. [5] collapsed the Chandra-Toueg failure detector hierarchy in this environment, and showed that P is the only useful class to solve these agreement problems. This explains why most reliable distributed systems, we know of, rely on a group membership service that precisely aims at emulating a Perfect failure detector P, that is, when a process is suspected due to a time-out, it is excluded from the group. Thus, every suspicion is taken as being accurate.

Uniform Consensus In consensus, the agreement property allows the bad processes to decide differently from good processes. This fact can be sometimes undesirable as it does not prevent a bad process from propagating a different decision in the system before crashing. In the unifrom consensus, the uniform-agreement property allows no two processes (good or bad) to decide differently, which enforces the same decision on any process that decides.

Terminating Reliable Broadcast Solving the consensus problem is equivalent to solving the atomic broadcast problem, in any system with reliable channels (i.e., where only a finite number of messages can be lost). Atomic broadcast entails delivering messages to processes in a reliable and totally ordered manner. Terminating reliable broadcast is a stronger form of atomic broadcast. In terminating reliable broadcast, the processes deliver messages in the same sequence as atomic broadcast does, but, in addition, processes should deliver a specific nil value for every message that was broadcast by a faulty process but was not delivered by any correct process. This problem is a rephrasing of the famous Byzantine Generals problem in the fail-stop model. Delporte-Gallet et al. [5] showed that in environments where the number of faulty processes is not bounded, uniform consensus is strictly harder than consensus, and uniform consensus and atomic broadcast are strictly weaker than terminating reliable broadcast. In environments where the number of faulty processes is not bounded, the exact information about failures needed to solve consensus (hence atomic broadcast) and terminating reliable broadcast, is captured by P. Thus, in the failure detector hierarchy, P is the only useful class to solve the agreement problems. 1

Unrealistic failure detectors are failure detectors that can guess the future and thus, can not be implemented even in a perfectly synchronous systems.

573

15.6.1 Realistic Failure Detectors Note that a failure detector has been defined as any function of the failure pattern and this function may be able to provide information about the future failures. Such a failure detector does not factor out synchrony assumptions of the system and can not be implemented even in a perfectly synchronous system. Delporte-Gallet et al. [5] restricted the scope of failure detectors as functions of the "past" failure patterns and defined the class of realistic failure detectors R, which can not guess the future. A failure detector is realistic if it cannot guess the future, i.e., there is no time t and no failure pattern F at which the failure detector can provide exact information about crashes that will hold after t in F. Formally, the class of realistic failure detector R is the set of failure detectors D that satisfy the following property: ∀(F, F′ ) ∈ E, ∀t ∈ φ such that ∀t1 ≤t; F(t1 ) = F′ (t1 ), we have: ∀H ∈ D(F), ∃H′ ∈ D(F′ ) such that ∀t1 ≤t; ∀pi ∈ Ω: H(pi ;t1 )=H′ (pi ;t1 ) That is, a failure detector D is realistic if for any pair of failure patterns F and F′ that are similar up to a given time t, whenever D outputs some information at a time t-k in F, D could output the very same information at t-k in F′ . Thus, a realistic failure detector cannot distinguish two failure patterns according to what will happen in the future. In other words, the output of a realistic failure detector depends only upon the past. For a realistic failure detector D, for any failure pattern F, the output of D at time t is a function of F up to time t. Two Examples We now present two failure detector examples to illustrate the concept. The first failure detector is realistic and the second is non-realistic. 1. Scribe(C): A scribe,’C’, is a failure detector which sees what happens at all processes in real time and outputs a list of processes based on what it sees. For any failure pattern F, failure detector C outputs, at any time t, the list of values of F up to time t, denoted by F[t]. For each failure pattern F, C(F) is the singleton set that contains the failure detector history H such that: ∀t ∈ φ, ∀pi ∈ Ω, H(pi ,t) = F[t] C is an example of a realistic failure detector.

574

2. The Marabout (M): Failure detector M (Marabout) outputs a list of processes. For any failure pattern F and at any process pi , the output of the failure detector M is constant and it is the list of faulty processes in F. Thus, M outputs the list of processes that have crashed or will crash in F. This is an example of an unrealistic failure detector. To better understand why M is an unrealistic failure detector, consider the failure patterns F and F′ such that (i) all processes are correct in F except p1 which crashes at time 10, (ii) all processes are correct in F′ , and (iii) F and F′ are same up to time t=9. Consider any history H of M(F) and any history H ′ of M(F′ ). By the definition of M, • the output at any process and at any time of H′ is φ and

• for any history H ∈ M(F), for any process pi , and any time t∈Φ, the output, H(pi ,t), is {p1 }.

However, if M was realistic, its failure detector histories H in M(F) and H’ in M(F’) should be such that H′ and H are dentical up to time 9. Thus, M is unrealistic because itis accurate about the future.

15.6.2 The weakest failure detector for consensus Recall that in the consensus problem, every process proposes an initial value and all processes must agree on one of these values such that termination, agreement, and validity properties are satisfied. Delporte-Gallet et al. [5] showed that if the number of faulty processes is not restricted, then P is the weakest “realistic" failure detector class to solve consensus. Prcisely, they showed that if the number of faulty processes is not restricted, any realistic failure detector that solves consensus can be tranformed into a failure detector of class P. We next give an intuitive proof of this lower bound, which includes the following two parts. 1. First, we show that "any consensus algorithm is total", that is, the causal chain of any decision event contains a message from every process that has not crashed at the time of the decision. We argue that a consensus decision cannot be reached by any process without having consulted every other correct process. If this is not true, a situation is possible where, after the decision, all the consulted processes crash except the one which is not consulted and this process later decides differently. If all the processes are consulted before every decision, we call such an algorithm total. 2. Second part of the proof entails showing that "if a realistic failure detector D implements a total consensus algorithm, then D can be transformed into a perfect failure detector P." This proof uses the fact that D is realistic and the algorithm is total. Therefore, for accurate tracking of process failures, no decision is taken without consulting every correct process. A process is suspected to have crashed in a sequence of consensus instances, if and only if a decision is reached and the process was not consulted in the decision. 575

15.6.3 The Weakest Failure Detector for Terminating Reliable Broadcast Terminating reliable broadcast is a strong form of reliable broadcast in which processes must deliver a specific value nil if the sender process has crashed, else, the processes must deliver the message m, broadcast by sender(m). A general variant of the problem is considered where every process is a potential initiator of the broadcast. The kth instance of the broadcast initiated by process pi is denoted by (i,k). Instance (i,*) is defined by the following properties: 1. Validity: If a correct process pi broadcasts a message m, then pi eventually delivers m. 2. Agreement: If a process delivers a message m, then every correct process delivers m. 3. Integrity: If a process delivers a message m and pi is correct, then sender(m)= pi . If we do not bound the number of processes that can crash, then among realistic failure detectors, the weakest class to solve terminating reliable broadcast is P. A sketch of the proof is as follows. Sufficient condition: Terminating reliable broadcast problem can be solved by any perfect failure detector, including realistic failure detectors. When instance (k, k′ ) of the terminating reliable broadcast is executed, each process waits until it receives the value from pk or it suspects pk . In the former case, it proposes the received value to consensus, and in the latter case, it proposes value nil. The value delivered is the consensus value. Necessary condition: Suppose A is any terminating reliable broadcast algorithm using a failure detector D. We can emulate the output of D, a failure detector of class P, using terminating reliable broadcast algorithm A in a distributed variable output(P) in the following way: whenever a process pj delivers nil for an instnace (i, *) of the algorithm, pj adds pi to output(P)j . Any process that crashes will eventually be permanently added to output(P) at every correct process. Thus, strong completeness will be ensured. A process pi is added to output(P)j at some time t only if pi is faulty. Since D is assumed to be realistic, pi must have crashed by time t.

15.7 An Implementation of a Failure Detector Now we present an algorithm to implement a dailure fetector. The algorithm is a timeout based implementation of eventually perfect failure detector D ∈♦P in partially synchronous models. The concept of partial synchrony in a distributed system lies between the cases of a synchronous system and an asynchronous system. In partial synchrony, the system is asynchronous initially but after an unknown time t, the system becomes synchronous. This assumption captures the fact that the system does not behave always as synchronous. Generally distributed systems are synchronous

576

Every process p executes the following: Outputp ←Q ∅ for all q ∈ ∆p (q) ← default time-out interval Cobegin Task 1: repeat periodically send “p-is-alive” to all

{Initializes output set to empty} {Set the timeout interval}

Task 2: repeat Q periodically for all q ∈ if q 6= Outputp and p did not receive “q-is-alive” during the last ∆p (q) ticks of p’s clock Outputp ← Outputp ∪ {q} {p times-out on q and starts suspecting that q has crashed} Task 3: when receive “q-is-alive” for some q If q ∈ Outputp Outputp ←Outputp− (q) ∆p (q) ← ∆p (q) + 1 Coend

{p knows that it prematurely timed-out on q} {p repents on q} {p increases its time-out period for q}

Figure 15.6: A time-out based implementation of D ∈ ♦ P in the partial synchrony model. most of the time and then they experience bounded asynchrony periods. We expect from partial synchrony a period of synchrony long enough to terminate the distributed algorithm. Each process p maintains a default timeout interval for every other process in the system. A process sets a timeout based on worst case round trip of a message exchange. To measure the elapsed time, each process p maintains a local clock, say, by counting the number of steps that it takes. Variables used in the algorithm: • Outputp (called the suspect list of p) is a set to hold all the suspected processes by process p. This set is initially empty. This set is local to process p which is executing the algorithm. • q is the loop variable used to identify each process in the system. • Π is a set of all processes in the system. • ∆p (q) is the duration of p’s timeout interval for q. The algorithm is presented in Figure 15.6.

Explanation of the Algorithm Task 1: Each process p periodically sends a “p-is-alive” message to all other processes. This is like a heart-beat message that informs other processes that process p is alive. 577

Task 2: If a process p does not receive a “q-is-alive” message from a process q within ∆p (q) time units on its clock, then p adds q to its set of suspects if q is not already in the suspect list of p. Task 3: When a process delivers a message from a suspected process, it corrects its error about the suspected process and increases its time-out for that process. If process p receives “q-is-alive” message from a process q that it currently suspects, p knows that its previous time-out on q was premature – p removes q from its set of suspects and increases its time-out period for process q, ∆p (q). Correctness of the Algorithm The algorithm insures the properties of an Eventually Perfect Failure Detector as discussed below: • Strong completeness: If a process p crashes, it will stop sending “p-is-alive” messages. Eventually every process that crashes is permanently detected by every correct process. Therefore, a crashed process will be suspected by any correct process and no process will revise the judgement. • Eventual strong accuracy: After time t, the system becomes synchronous, i.e., after time t, a message sent by a correct process p to another process q will be delivered within a bounded time. If p was wrongly suspected by q, then q will revise its suspicious. Eventually, no correct process is ever suspected.

15.8 An Adaptive Failure Detection Protocol In this section, we discuss an adaptive failure detection protocol that allows a process to monitor other processes and eventually detects its crash. The protocol relies as much as possible on application messages to do this monitoring and uses control messages only when no application message is sent by the monitoring process to the observed process. More precisely, the proposed protocol allows a process to monitor another process using the application messages it is exchanging to communicate with the other process, saving failure detection messages. A failure detector (thus, failure detection messages) are used when the processes are not communicating. The cost associated with the implementation of a failure detector incurs only when the failure detector is used (hence, it is called a lazy failure detector). When the underlying system satisfies the partial synchrony assumption, the protocol implements an eventually perfect failure detector D∈♦P. Recall that an eventually perfect failure detector makes no mistake (i.e, the list of suspects at a process includes all crashed processes, but no correct process) after a finite, but unknown time. For any failure detector in ♦P, after it becomes perfect, if the average observed transmission delay is finite and the upper layer application terminates within a bounded number of steps, then it terminates correctly when run with the proposed protocol. These properties make the protocol attractive: it is inexpensive, implementable, and powerful. The basic failure detection protocol (denoted by F DL ) ensures that if a process queries another process that has crashed, then it will definitely suspect it. Thus, completeness of the detection is 578

satisfied. The failure detection protocol is plugged into two particular contexts. The first context is defined by the properties to be satisfied by the lower layer, namely, partial synchrony. When the failure detection protocol is plugged in such a system, the protocol provides a failure detector of the class ♦P. The second context is defined by a property assumed to be satisfied by the upper layer, i.e., the application and some weaker properties to be satisfied by the lower level. The first context is defined by partial synchrony. The second context defines a property (called ♦P−terminating) that the application has to satisfy. A failure detector-based application (the failure detector it uses belongs to ♦P) is ♦P−terminating if it terminates correctly within at most some l steps after the failure detector becomes perfect. When run with a ♦P−terminating application, the protocol provides the application with the same properties as ♦P if the average observed transmission delay is finite. Interestingly, unlike the first context, the second context does not require an upper bound on message transfer delays. These two contexts show that this failure detection protocol is inexpensive, implementable, and powerful.

15.8.1 Lazy Failure Detection Protocol (F DL ) Assumptions The basic system consists of a finite set of processes P = {p1 , p2 , ...pn }. Each process pi has a local hardware clock hci that strictly monotonically increases. The local clocks are not required to be synchronized, and there is no assumption on their possible drift. The behaviour of a process can be modeled by a finite state automaton. Each step of a process is triggered by a message. An event is the execution of communication statement by a process. The history hi of a process pi is the sequence of communication events it produces. Every pair of processes is connected by a channel and they communicate by sending and receiving messages through channels. Channels are not required to be FIFO. They are only assumed to be reliable in the following sense: they do not create, duplicate, alter or loose messages, i.e., if a process pj is correct, message sent by a process pi to pj is eventually received by pj . Primitives Provided The protocol provides the following primitives to each upper layer application process pi : 1. SEND M to pj : used by pi to send an application message M to pj . 2. RECEIVE M: used by pi to receive an application message M. 3. QUERY(j): used to know whether pj is suspected to have crashed. This primitive returns an answer, namely, the value suspect or no_suspect. At operational level, the protocol uses three types of messages: "appl", "ack" and "ping". To send an application message M to pj , a process pi invokes "sendappl(m) to pj " where the protocol message m includes M plus some control information. When it receives such a message, 579

pj systematically acknowledges it by sending back ack(m). When it receives ack(m), pi computes the round trip delay of the pair appl(m)+ack(m). For each destination process pj , pi aditionally computes maximum round trip delay for the messages that have been acknowledged by pj . The answer provided by QUERY(j) when it is invoked by the upper layer depends on the existence of a "pending" message,i.e., a message m such that appl(m) has been sent to pj but the corresponding ack(m) has not yet been received by pi :(1) If there is no such message, the answer is nos uspect, but pi sends a ping message to pj inorder to verify its answer. (2) If there are such "pending" messages, the answer depends on the maximum round trip delay already experienced. The Protocol F DL The protocol manages two arrays of local variables for each process pi : 1) pending_msg_sti[j]: this set is initially empty and it contains the sending times of the messages sent by pi to pj , whose acknowledgements have not yet been received by pi . 2) max_rtdi[j]: contains the biggest round trip time of the messages that pi sent to pj and that have been acknowledged. Initially, this variable has the value zero. If the value of max_rtdi[j] from the previous execution is known, then max_rtdi[j] can be initialized to this value. A call to SEND M is interpreted as a message reception from the upper layer. Similarly, RECEIVE M is interpreted as a message sent to the upper layer. A protocol message m has a type (appl/ack/ping). In addition to a content (m.content), a message m also carries the local send time (m.st). More precisely, appl(m) and ping(m) carry their local send time and ack(m) carries the send time of the appl(m) or ping(m) message it is associated with. The protocol for process pi is defined as follows: The protocol works as follows: When SEND M to pj is invoked by pi , m.content is initialized to the application message M and m.st is initialized to the local hardware clock time. Since the acknowledgement of this message is not yet received by pi , m.st is added to the set pending_msg_sti[j]. Now, pi sends the application message appl(m) to pj . When pi receives a message from pj , it acts as follows: If the message received by process pi is of type ’appl’, then the message content(m.content) is transmitted to the upper layer and an acknowledgement message, ack(m)is sent to pj . If the message is an acknowledgement, ’ack’, then the maximum round trip delay time of the messages sent to pj by process pi is updated to the maximum of the previous and current round trip delay times. Since this is an acknowledgement message, its sending time is deleted from the pending time set. When the message of type ping is received by pi , it sends an acknowledgement message ack(m) to pj . When QUERY(j) is invoked by the process pi , the following two conditions arise: (1) If pending_msg_sti[j] is empty, then a control message m is created and is used to ping process pj . A control message is used as there is no communication between the processes. The ping message send time is added to the pending time set and a value ’no_suspect’ is returned. (2) When pending_msg_sti[j] is non-empty, if the time taken to receive an acknowledgement from process pj 580

when SEND M to pj is invoked: m.content ← M; m.st ← hci ; S pending_msg_sti[j] ← pending_msg_sti[j] {m.st} send appl(m) to pj when type(m) is recieved from pj : case type=appl then transmit M=m.content to upper layer, {* RECIEVE M *} send ack(m) to pj {* m.st keeps its value *} type=ack then rt ← hci ; max_rtdi[j]←max(max_rtdi[j], rt-m.st); pending_msg_sti[j]←pending_msg_sti[j] - {m.st} type=ping then send ack(m) to pj {* m.st keeps its value *} endcase when QUERY(j) is invoked: if pending_msg_sti[j] = ∅ then create a control message m; m.content ← null; m.st ← hci send ping(m) to pj ; pending_msg_sti[j] ← {m.st}; return(no_suspect) else rt ← hci ; if rt-min(pending_msg_sti[j]) > max_rtdi [j] then return (suspect) else return (no_suspect) endif endif Figure 15.7: Lazy Failure Detection Protocol for process pi . is greater than the max_rtdi[j], then the process pj is suspected to be crashed and a value ’suspect’ is returned else ’no_suspect’ is returned.

Properties of F DL If from some time t, a process pi obtains the answer suspect each time it invokes QUERY(j), we say that from that time it "permanently suspects pj " from t. Completeness Property: Let us assume that pi is correct, while pj is faulty (i.e., it has crashed). Then, FDL ensures that eventually pi permanently suspects pj to have crashed. The Protocol in Partially Synchronous Systems2 : If the underlying system is partially synchronous, there is a time t after which FDL ensures that no correct process is suspected by a 2

This means that there is a time after which there are upper bounds on messages transfer delays and associated processing times

581

correct process. ♦P Terminating Protocol: If the upper layer protocol is ♦P-Terminating, then it terminates with probability 1 when, instead of using a failure detector of ♦P, it uses FDL. Message Cost: Each appl() or ping() message generates atmost one ack() message. Both appl() and ping() are due to the application layer. appl() when it sends an application message and ping() when it invokes QUERY(). The cost of invocation of QUERY(j) by a process pi after pj has crashed: According to the current state of pending_msg_sti[j], pi can be forced to send ping(m) message to pj . But from now, the condition pending_msg_sti[j] 6= Φ remains permanently true. Consequently, the next invocations of QUERY(j) do not send messages, and their communication cost is zero.

15.9 Bibliographic Notes The area of failure detectors was initiated by Chandra and Toueg [3] and a large number of researchers followed it. An excellent short review paper on the topic is by Raynal [22]. DelporteGallet et al. [5] present a realistic failure detector. An adaptive failure detector can be found in Fetzer et al. [8]. Implementations of failure detectors can be found in [17, 18, 19, 20]. Garg and Mitchell [11] describe implementable failure detectors. Gupta et al. [14] discuss scalable failure detectors. Hurfin et al. [15, 16] present a family of consensus protocols based on failure detectors. Schiper [23] discusses early consensus using weak failure detectors. Chandra et al. [4] discuss the weakest failure detector to solve the consensus. Guerraoui [12] present non-blocking atomic commit using failure detectors. Delporte-Gallet et al. [6] discuss how to achieve mutual exclusion in asynchronous distributed systems with failure detectors.

15.10 Exercise Problems 1. It is well known fact that consensus and atomic broadcast problems cannot be solved deterministically in asynchronous distributed systems even for a single process failure. Then how failure detectors solve these problems?

582

Bibliography [1] Marcos Aguilera Wei Chen Sam Toueg, Heartbeat: A Timeout-Free Failure Detector for Quiescent Reliable Communication, Proc. of DISC, 1997. [2] Marcos Aguilera, Wei Chen, Sam Toueg, Using the heartbeat failure detector for quiescent reliable communication and consensus in partitionable networks, Theoretical Computer Science, Volume 220, Issue 1, June 1999, Pages: 3 - 30. [3] Chandra T.D. and Toueg S., Unreliable Failure Detectors for Reliable Distributed Systems. Journal of the ACM, 43(2):225-267, 1996. (First version published in the proceedings of the 10th ACM Symposium on Principles of Distributed Computing, 1991.) [4] Tushar Deepak Chandra, Vassos Hadzilacos and Sam Toueg, The Weakest Failure Detector for Solving Consensus, Journal of the ACM, 43:4, July 1996, 685-722. [5] Delporte-Gallet C., Fauconnier H. and Guerraoui R., A Realistic Look at Failure Detectors. Proc. IEEE Inter. Conference on Dependable Systems and Networks (DSN’02), IEEE Computer Society Press, pp. 345352, Washington D.C., 2002. [6] Delporte-Gallet Carole, Fauconnier Hugues, Guerraoui Rachid, Kouznetsov Petr, Mutual exclusion in asynchronous systems with failure detectors, Journal of parallel and distributed computing, 2005, vol. 65, no4, pp. 492-505. [7] Delporte-Gallet C., Fauconnier H. and Guerraoui R., Failure Detection Lower Bounds on Registers and Consensus. Proc. 16th Symposium on Distributed Computing (DISC’02), SpringerVerlag LNCS #2508, pp. 237251, 2002. [8] Fetzer C., Raynal M. and Tronel F., An Adaptive Failure Detection Protocol. Proc. 8th IEEE Pacific Rim Int. Symposium on Dependable Computing (PRDC’01), IEEE Computer Society Press, pp. 146153, Seoul (Korea), 2001. [9] M.J.Fischer,N.A.Lynch,and M.S.Paterson.Impossibility of distributed consensus with one faulty process.Journal of the ACM ,32(3):374 .382, April 1985. [10] Friedman R., Mostefaoui A. and Raynal M., A Weakest Failure DetectorBased Asynchronous Consensus Protocol for f < n, Information Processing Letters, Volume 90, Issue 1, April 2004, Pages: 39 - 46. 583

[11] Vijay K. Garg and J. Roger Mitchell, Implementable Failure Detectors in Asynchronous Systems, Lecture Notes in Computer Science, Springer Berlin/Heidelberg, Volume 1530/2004, Foundations of Software Technology and Theoretical Computer Science Pages 158-170, 1998. [12] Guerraoui R., NonBlocking Atomic Commit in Asynchronous Distributed Systems with Failure Detectors. Distributed Computing, 15:17-25, 2002. [13] Guerraoui R., Indulgent Algorithms. Proc. 19th ACM Symposium on Principles of Distributed Computing, (PODC’00), ACM Press, pp. 289298,Portland (OR), 2000. [14] Indranil Gupta, Tushar D. Chandra, German S. Goldszmidt, On scalable and efficient distributed failure detectors, Proc. of the twentieth annual ACM symposium on Principles of distributed computing, p.170-179, August 2001, Newport, Rhode Island. [15] M. Hurfin , A. Mostefaoui, M. Raynal, A Versatile Family of Consensus Protocols Based on Chandra-Toueg’s Unreliable Failure Detectors, IEEE Transactions on Computers, v.51 n.4, p.395-408, April 2002. [16] Michel Hurfin, Michel Raynal, A simple and fast asynchronous consensus protocol based on a weak failure detector, Distributed Computing, v.12 n.4, p.209-223, September 1999. [17] Mikel Larrea, Sergio Arevalo, Antonio Fernandez, Efficient Algorithms to Implement Unreliable Failure Detectors in Partially Synchronous Systems, Proceedings of the 13th International Symposium on Distributed Computing, p.34-48, September 27-29, 1999. [18] Mikel Larrea, Antonio Fernandez, Sergio Arevalo, Optimal Implementation of the Weakest Failure Detector for Solving Consensus, Proceedings of the 19th IEEE Symposium on Reliable Distributed Systems (SRDS’00), p.52, October 16-18, 2000. [19] Gerard Le Lann and Ulrich Schmid, How to Implement a Time-Free Perfect Failure Detector in Partially Synchronous Systems, Technical University of Vienna, Institute for Technische Informatik, Research Report, Number 28/2005, 2005. [20] Mostefaoui A., Mourgaya E., and Raynal M., Asynchronous Implementation of Failure Detectors. Proc. Int. IEEE Conference on Dependable Systems and Networks (DSN’03), IEEE Computer Society Press, pp. 351360, San Francisco (CA), 2003. [21] Achour Mostefaoui, Eric Mourgaya, Michel Raynal, An introduction to oracles for asynchronous distributed systems, Future Generation Computer Systems, v.18 n.6, p.757-767, May 2002. [22] Raynal M., A Short Introduction to Failure Detectors for Asynchronous Distributed Systems, ACM SIGACT News, Volume 36, Issue 1, March 2005, Pages: 53 - 70.

584

[23] Andre Schiper, Early consensus in an asynchronous system with a weak failure detector, Distributed Computing, v.10 n.3, p.149-157, April 1997. [24] Lynda Temal, Denis Conan, Failure, connectivity and disconnection detectors, Proceedings of the 1st French-speaking conference on Mobility and ubiquity computing, June 01-03, 2004, Nice, France.

585

Chapter 16 Authentication in Distributed System 16.1 Introduction A fundamental concern in building a secure distributed system is authentication of local and remote entities in the system [41]. In a distributed system, the hosts communicate by sending and receiving messages over the network. Various resources (like files and printers) distributed among the hosts are shared across the network in the form of network services provided by servers. The entities in a distributed system like users, clients, servers, processes are collectively referred to as principals. A distributed system is susceptible to a variety of threats mounted by intruders as well as legitimate users of the system. In an environment where a principal can impersonate other principal, principals must adopt a mutually suspicious attitude toward one another and authentication becomes an important requirement. Authentication is a process by which one principal verifies the identity of other principal. For example, in a client-server system, the server may need to authenticate the client. Likewise, the client may want to authenticate the server so that it is assured that it is talking to the right entity. Authentication is needed for both authorization and accounting functions. In one-way authentication, only one principal verifies the identity of the other principal and in mutual authentication, both communicating principals verify each other’s identity. A user gains access to a distributed system by logging to a host in the system. In an open access environment where hosts are scattered across unrestricted areas, a host can be arbitrarily compromised, necessitating mutual authentication between the user and host. In a distributed system, authentication is carried out using a protocol involving message exchanges and these protocols are termed authentication protocols [41].

16.2 Background and Definitions In simple terms, authentication is identification plus verification. Identification [41] is the procedure whereby an entity claims a certain identity, while verification is the procedure whereby that claim is checked. Authentication is a process of verifying that the principal’s identity is as claimed. The correctness of authentication relies heavily on the verification procedure employed.

586

A successful identity authentication results in a belief held by the authenticating principal (the verifier) that the authenticated principal (the claimant) possesses the claimed identity. The other types of authentication include message origin authentication and message content authentication. In this chapter, we restrict our attention to the identity authentication only. Authentication in distributed systems is carried out with protocols. A protocol is a precisely defined sequence of communication and computation steps. A communication step transfers messages from one principal (the sender) to another (the receiver), while a computation step updates a principal’s internal state. Two distinct states can be identified upon the termination of the protocol: one signifying successful authentication and the other failure. Although the goal of any authentication is to verify the claimed identity of a principal, specific success and failure states are highly protocol dependent. For example, the success of an authentication during the connection establishment phase of a communication protocol is usually indicated by the distribution of a fresh session key between two mutually authenticated peer processes. On the other hand, in a user login authentication, success usually results in the creation of a login process on behalf of the user.

16.2.1 Basis of Authentication Authentication is based on the possession of some secret information, like password, known only to the entities participating in the authentication. When an entity wants to authenticate another entity, the former will verify if the latter possesses the knowledge of the secret. If the entity demonstrates the knowledge of the right secret information, the authentication succeeds, else authentication fails. Examples of secret information for the purpose of authentication include the following: something known (e.g., a shared key), something possessed (e.g., smartcard), or something inherent (e.g., biometrics). However, the verification process should not allow an attacker to reuse an authentication exchange to impersonate an entity. The verification process must provide the verifier with enough confidence that an attacker is not trying to impersonate an entity.

16.2.2 Types of Principals In a distributed system, the entities that require identification are hosts, users and processes [26]. They thus are the principals involved in an authentication. Hosts. These are addressable entities at the network level. A host is usually identified by its name (for example, a fully qualified domain name) or its network address (for example, an IP address). Users. These entities are ultimately responsible for all system activities. Users initiate and are accountable for all system activities. Most access control and accounting functions are based on users. Typical users include humans, as well as accounts maintained in the user database. Users are considered to be outside the system boundary. Processes. The system creates processes within the system boundary to represent users. A process requests and consumes resources on the behalf of its user. 587

Processes fall into two classes: client and server. Client processes are consumers who obtain services from server processes, who are service providers. A particular process can act as both a client and a server.

16.2.3 A Simple Classification of Authentication Protocols Authentication protocols can be categorized based on the following criteria [28]: type of cryptography (symmetric vs. asymmetric), reciprocity of authentication (mutual vs. one-way), key exchange, real-time involvement of a third party (on-line vs. off-line), nature of trust required from a third party, nature of security guarantees, and storage of secrets. In this chapter, we classify authentication protocols [41] primarily based on the cryptographic technique used. There are two basic types of cryptographic techniques: symmetric ("private key") and asymmetric ("public key"). Symmetric cryptography uses a single private key to both encrypt and decrypt data. Any party that has the key can use it to encrypt and decrypt data. Symmetric cryptography algorithms are typically fast and are suitable for processing large streams of data. Asymmetric cryptography, also called Public-key cryptography, uses a secret key that must be kept from unauthorized users and a public key that is made public. Both the public key and the private key are mathematically linked: data encrypted with the public key can be decrypted only by the corresponding private key, and data signed with the private key can only be verified with the corresponding public key. Both keys are unique to a communication session.

16.2.4 Notations We specify authentication protocols [39] with precise syntax and semantics and define a system model that characterizes protocol executions. We assume a given set of constant symbols which denote the names of principals, nonces, and keys. In symmetric key cryptography, let {X}k denote the encryption of X using a symmetric key k and {Y }k−1 denote the decryption of Y using a symmetric key k. In asymmetric key cryptography, for a principal x, Kx and K−1 x denote its public and private keys, respectively. We present authentication protocols using the following format. A communication step whereby P sends a message M to Q is represented as P → Q : M, whereas a computation step of P is written as P : . . . , where “. . . ” is a specification of the computation step. For example, a typical login protocol between a host H and a user U is given in Table 16.1 (f denotes a one-way function, that is, given y, it is computationally infeasible to find an x such that f(x) =y). Since authentication protocols for distributed systems directly use cryptosystems, their basic design principles also follow the type of cryptosystem used. Specifically, we identify two basic categories of authentication: one based on symmetric cryptosystems and other on asymmetric cryptosystems. Protocols presented in this chapter are intended to illustrate basic design principles and a realistic protocol is certainly a refinement of these basic protocols.

588

U→H : H→U : U→H : H: : :

U "Please enter password" p compute y = f (p) Retrieve user record (U, f (password) U) from the database If y = f (password) U, then accept; otherwise reject Table 16.1: A login protocol

16.2.5 Design Principles for Cryptographic Protocols Abadi and Needham set out a set of principles [2, 36] to denote prudent engineering practices for cryptographic protocols design [2, 4]. They are not meant to apply to every protocol in every instance, but they do provide rules of thumb that should be considered when designing a cryptographic protocol. We next present these principles and briefly comment on them [2, 4]. • Principle 1. Every message should say what it means: The interpretation of the message should depend only on its content. It should be possible to write down a straightforward English sentence describing the content—though if there is a suitable formalism available, which is good, too. • Principle 2. The conditions for a message to be acted upon should be clearly set out so that someone reviewing the design may see whether they are acceptable or not. • Principle 3. If the identity of a principal is essential to the meaning of a message, it is prudent to mention the principal’s name explicitly in the message. • Principle 4. Be clear as to why encryption is being done. Encryption is not wholly cheap, and not asking precisely why it is being done can lead to redundancy. Encryption is not synonymous with security, and its improper use can lead to errors. • Principle 5. When a principal signs material that has already been encrypted, it should not be inferred that the principal knows the content of the message. On the other hand, it is proper to infer that the principal that signs a message and then encrypts it for privacy knows the content of the message. • Principle 6. Be clear about what properties you are assuming about nonces. What may do for ensuring temporal succession may not do for ensuring association—and perhaps association is best established by other means. • Principle 7. The use of a predictable quantity (such as the value of a counter) can serve in guaranteeing newness, through a challenge-response exchange. But if a predictable quantity is to be effective, it should be protected so that an intruder cannot simulate a challenge and later replay a response. 589

• Principle 8. If timestamps are used as freshness guarantees by reference to absolute time, then the difference between local clocks at various machines must be much less than the allowable age of a message deemed to be valid. Furthermore, the time maintenance mechanism everywhere becomes part of the trusted computing base. • Principle 9. A key may have been used recently, for example, to encrypt a nonce, yet be quite old, and possibly compromised. Recent use does not make the key look any better than it would otherwise. • Principle 10. If an encoding is used to present the meaning of a message, then it should be possible to tell which encoding is being used. In the common case where the encoding is protocol dependent, it should be possible to deduce that the message belongs to this protocol, and in fact to a particular run of the protocol, and to know its number in the protocol. • Principle 11. The protocol designer should know which trust relations his protocol depends on, and why the dependence is necessary. The reasons for particular trust relations being acceptable should be explicit though they will be founded on judgment and policy rather than on logic.

16.3 Protocols Based on Symmetric Cryptosystems In a symmetric cryptosystem, knowing the shared key lets a principal encrypt and decrypt arbitrary messages [41]. Without such knowledge, a principal cannot create the encrypted version of a message, or decrypt an encrypted message. Hence, authentication protocols can be designed using to the following principle, “If a principal can correctly encrypt a message using a key that the verifier believes is known only to a principal with the claimed identity (outside of the verifier), this act constitutes sufficient proof of identity.” Thus the principle embodies the fact that a principal’s knowledge is indirectly demonstrated through its ability to encrypt or decrypt.

16.3.1 Basic Protocol Using the above principle, we immediately obtain the basic protocol (shown in Table 16.2) where principal P is authenticating itself to principal Q. ‘k’ denotes a secret key that is shared between only P and Q [41]. In this protocol, the principal P prepares a message m and encrypts the message and identity of Q using the symmetric key k and sends to Q both the plain text message and the encrypted message. Principal Q on receiving the message encrypts the plaintext message and its identity to get the encrypted message. If it is equal to the encrypted message sent by P, then Q has authenticated P, else, the authentication fails.

590

P: : P→Q : Q: :

Create a message m = "I am P." Compute m′ ={m, Q}k m, m′ verify {m, Q}k = m′ if equal then accept; otherwise the authentication fails Table 16.2: Basic Protocol

Weaknesses Clearly, this method is sound only if the underlying cryptosystem is strong (one cannot create the encrypted version of a message without knowing the key) and the key is secret (it is shared only between the real principal and the verifier). Note that this protocol performs only one-way authentication, mutual authentication can be achieved by reversing the roles of P and Q. One major weakness of the protocol is its vulnerability to replays. More precisely, an adversary could masquerade as P by recording the message m, m’ and later replaying it to Q. As mentioned, replay attacks can be countered by using nonces or timestamps. Since both plaintext message m and its encrypted version m’ are sent together by P to Q, this method is vulnerable to known plaintext attacks. Thus the cryptosystem must be able to withstand known plaintext attacks.

16.3.2 Modified Protocol with Nonce To prevent replay attacks, we modify the protocol by adding a challenge-and-response step using nonce (shown in Table 16.3). A nonce is a large random or pseudo-random number that is drawn from a large space so that it is difficult to guess by an intruder. This property of a nonce helps ensure that old communications cannot be reused in replay attacks. P→Q : Q: Q→P : P: P→Q : Q: :

"I am P." generate nonce n n compute m′ ={P, Q, n}k m′ verify {P, Q, n}k = m′ if equal then accept; otherwise the authentication fails

Table 16.3: Challenge-and-response protocol using a nonce In the modified version of the protocol [41], the principal P wants to authenticate itself to Q. Q generates a nonce and sends this nonce to P. P then encrypts Q, the nonce and its own identity with the secret key and sends this encrypted message to Q. Q verifies this encrypted message by encrypting its identity, P’s identity and the nonce with the key k. Q authenticates P if encrypted information equals that sent by P, else the authentication fails.

591

Replay is foiled by the freshness of nonce n and because n is drawn from a large space. Therefore, it is highly unlikely that the nonce n generated by Q in the current session is the same as one used in a previous session. Thus an attacker cannot use a message of type m′ from a previous session to mount a replay attack. In addition, even if an eavesdropper has monitored all previous authentication conversations between P and Q, it is impossible to produce the message m because it does not know the secret key k. The challenge-and-response step can be repeated any number of times until the desired level of confidence is reached by Q.

Weaknesses This protocol has scalability problems because each principal must store the secret key for every other principal it would ever want to authenticate [41]. This presents major initialization (the predistribution of secret keys) and storage problems. Moreover, the compromise of one principal can potentially compromise the entire system. Note that this protocol is also vulnerable to known plaintext attacks.

16.3.3 Wide-Mouth Frog Protocol The above raised problems can be significantly reduced by postulating a centralized server S. The wide-mouth frog protocol [28] uses a similar approach where a principal A authenticates itself to principal B using a Server S. The protocol works as follows: A→S : A, {TA , KAB , B}KAS S→B : {TS , KAB , A}KBS A decides that it wants to set up communication with B. A sends to S its identity and a packet encrypted with the key, KAS , it shares with S. The packet contains the current timestamp, A’s desired communication partner, and a randomly generated key KAB , for communication between A and B. S decrypts the packet to obtain KAB and then forwards this key to B in an encrypted packet that also contains the current timestamp and A’s identity. B decrypts this message with the key it shares with S and retrieves the identity of the other party and the key, KAB . Any principal receiving a message with an out-of-date timestamp during this protocol discards it to prevent replay attacks. This protocol achieve two objectives: First, it securely establishes a secret key between two principals A and B. Second, A authenticates itself to B with the help of the server S. This is because, only the server S could have constructed the message {TS , KAB , A}KBS in Step (2) only after receiving a message from A in step 1. A weakness of the protocol is that a global clock is required and the protocol will fail if the server S is compromised.

592

16.3.4 A Protocol Based On an Authentication Server Another approach to solve the problem is by using a centralized authentication server S that shares a secret key KXS with every principal X in the system [41]. The basic authentication protocol is shown in Table 16.4. P→Q : Q: Q→P : P: P→Q : Q: Q→A : A: : : A→Q : Q: :

"I am P." generate nonce n n compute x = {P, Q, n}KP S x compute y = {P, Q, x}KQS y recover P, Q, x from y by decrypting y with KQS recover P,Q, n from y by decrypting x with KP S compute m = {P, Q, n}KQS m independently compute {P, Q, n}KQS and verify {P, Q, n} KQS = m if equal, then accept; otherwise, the authentication fails Table 16.4: A protocol using an authentication server

In the protocol using an authentication server, the principal P sends its identity to Q. Q generates a nonce and sends this nonce to P. P then encrypts P, Q, n with the key KP S and sends this encrypted value x to Q. Q then encrypts P, Q, x with KQS and sends this encrypted value y to authentication server S. Since S knows both the secret keys, it decrypts y with KQS , recovers x , decrypts x with KP S and recovers P, Q, n. Server S then encrypts P, Q, n with key KQS and sends the encrypted value m to Q. Q then computes P, Q, nK QS and verifies if this value is equal to the value received from S. If both values are equal, then authentication succeeds, else it fails. Thus Q’s verification step is preceded by a key-translation step by S. Since P and Q do not share a secret key, the authentication server S does the key translation because it shares a secret key with both principals P and Q. Q sends the message (encrypted with KP S that it received from P) to S. S does the key translation by decrypting it with KP S , encrypting P, Q, n with KQS , sending the message encrypted with KQS to Q. This is termed as the key-translation step [41]. The basis of this protocol is a challenge for Q to P if P can encrypt the nonce n with the secret key that it shares with server S. The protocol correctness rests on S’s trustworthiness—that S will properly decrypt using P’s key and reencrypt using Q’s key. The initialization and storage problems are greatly alleviated because each principal needs to keep only one key. The risk of compromise is mostly shifted to S, whose security can be guaranteed by various measures, such as encrypting stored keys using a master key and putting S in a physically secure room.

593

16.3.5 One-Time Password Scheme In the One-Time Password scheme [24], a password can only be used once. A one-time password system generates a list of passwords and secretly communicates this list to the client and the server. The client uses the passwords in the list to log on to a server. Once a password has been used, it cannot be used again. To log on again, the client must use the next password in the list. The server always expects the next password in the list at the next logon. Therefore, even if a password is disclosed, the possibility of replay attacks is eliminated because the system expects the next password in the subsequent logon. This protocol is best suited for distributed systems where an authentication mainly takes place between client and the server.

Protocol Description Protocol consist of two stages: 1. Registration stage: where the client registers with the server and gets a list of passwords. 2. Login and authentication stage: where the server authenticates the client.

(1) Registration Stage 1. Every client shares a pre-shared secret key, represented as SEED with the server. It is a large random number secretly communicated by the server to the client. 2. The server generates a session key (SK) with the help of a random number D and a timestamp T, i.e., SK = D||T. The server computes and sends SEED ⊕ SK to the client. When the client receives SEED ⊕ SK, it computes the value of SK as follows: SK = SEED ⊕ (SEED ⊕ SK) The client then generates an initial key IK with the help of a randomly generated secret key K, IK = K ⊕ SEED The client then decides the number of times (N) it wants to login to the server and sends the generated initial key (IK) to the server. To do this, the client performs IK ⊕ SK and N ⊕ SK and sends these values to the server. 3. When the server receives IK ⊕ SK and N ⊕ SK, it retrieves IK and N from the received values and computes p0 = HN (IK) for the user where H is a Hash Function and performs p0 = p0 ⊕ SK and stores p0 and N in its database and sends p0 ⊕ SK back to the client as a response. It also computes p1 and p2 as follows: 594

p1 = HN −1 (IK) and p2 = HN −2 (IK) The server then sends p0 ⊕ SK, p1 ⊕ SK and p2 ⊕ SK to the client 4. On receiving p0 ⊕ SK, p1 ⊕ SK and p2 ⊕ SK from the server, the client performs the XOR operation on SK and p0 ⊕ SK, p1 ⊕ SK and p2 ⊕ SK separately, to obtain p0, p1 and p2, respectively. The client hashes IK for N times and then compares it with p0 . If both values are equal, the client is sure of the authenticity of the server and that it is not communicating with an intruder. It then saves the values of p0 , p1 , p2 and N for future communication with the server. This marks the end of the registration stage. The above steps are described in Table 16.5. Server→ Client : Server→ Client : Client→ Server : Server→ Client :

SEED SEED ⊕ SK IK ⊕ SK and N ⊕ SK p0 ⊕ SK, p1 ⊕ SK, p2 ⊕ SK

Table 16.5: The Registration Stage If N is 50, the user can log in to the server 50 times and p0 =H 50 (IK). After 50 logins, the user must repeat the steps in the registration.

(2) Login and Authentication Stage Once the client is registered, every time it needs to access a service provided by the server, the client needs to get authenticated. Authentication requires the following steps: 1. If the client is logging in for the tth time, the server generates a new session key (SK) SK = D||T where T is the timestamp and D is a random number. The server also computes pt−1 = H C+1 (IK) where C=N-t. It then performs pt−1 ⊕ SK and SK ⊕ SEED (SEED is stored in the database) and sends these values to the client. 2. On the receipt of the values from the server, the client computes SK as follows: SK = pt−1 ⊕ (pt−1 ⊕ SK) Then the Client checks the timestamp T of the session key SK. If the timestamp is valid, the client computes SEED = SK ⊕ (SK ⊕ SEED) and checks the value of SEED with the one saved to make sure of the server’s identity. If they match, the server’s authenticity is verified. 3. Now the client proves its identity to the server as follows: It sends SK ⊕ pt to the server. The client uses the pt saved in the previous login in this EX-OR operation. Server calculates pt from SK ⊕ pt received from the client as follows: 595

pt = SK ⊕ (SK ⊕ pt ) From the received pt value, it calculates pt−1 = H (pt ) and compares it with pt−1 obtained in the Step 1. If both match, the identity of the client is verified. Finally, the server updates N with C, where C=N-t and computes pt+1 using p0 and sends pt+1 ⊕ SK to the client. 4. The client computes value of pt+1 as pt+1= SK ⊕ (SK ⊕ pt+1 ) and stores it for its next login. For example, if t =10 and N=100, then pt−1 = H91 (IK), pt = H90 (IK) and pt+1 = H89 (IK). The above steps are described in Table 16.6. Server→Client : pt−1 ⊕ SK, SEED ⊕ SK Client→Server : pt ⊕ SK Server→Client : pt+1 ⊕ SK Table 16.6: The Login and Authentication In this protocol, the client and the server communicate with each other by passing parameters which are encrypted, i.e., exclusive ORed with either SK or SEED. SK is the session key of a particular session and SEED is the pre-shared secret key. Since these two values are known only to the client and server, eavesdropping of the connection does not have any effect. Since SK is obtained by using the timestamp, replay of previous session does not work and thus the scheme is robust against replay attacks. The use of hash function makes the Dictionary attacks impossible.

Weaknesses One-time passwords that are not time-synchronized are vulnerable to phishing. Phishing usually occurs when a fraudster sends an email that contains a link to a fraudulent website where the users are asked to provide personal account information. The email and website are usually disguised to appear to recipients as though they are from a bank or another well-known brand. In late 2005, customers of a Swedish bank were tricked into giving up their one-time passwords.

16.3.6 Otway-Rees Protocol The Otway-Rees protocol [28] is a server-based protocol that provides authenticated key transport only in four messages without requiring timestamps. It provides key authentication and key freshness assurances. It does not, however, provide entity authentication or key confirmation. The notations used in the protocol are as follows: KAB is a session key that the sever S generates for users A and B to share. NA and NB are nonces chosen by A and B, respectively, to allow verification of key freshness (thereby, detecting replay attacks). M is another nonce chosen by A which serves as a transaction identifier. S shares symmetric keys KAS and KBS with A, B, respectively. This protocol is shown in Table 16.7. 596

(1) A →B : (2) B →S : (3) S →B : (4) B →A :

M, A, B, (NA , M, A, B)KAS M, A, B, (NA , M, A, B)KAS , (NB , M, A, B)KBS (NA , KAB )KAS , (NB , KAB )KBS M, (NA , KAB )KAS Table 16.7: Otway Rees Protocol

In the step (1), user A encrypts two nonces, NA and M, and the identities of itself and the identity of the party B to whom it wishes to communicate, with the key KAS and sends this to B along with M, A, B in plaintext. On the receipt of this message, user B creates its own nonce NB and an analogous encrypted message, (NB , M, A, B)KBS , in step (2) and sends this along with A’s message to server S. When the server S receives this message, it uses the clear text identifiers in the message to retrieve KAS and KBS , then verifies if the clear text (M, A, B) matches that recovered upon decrypting both parts of the message in step (2). Verifying M in particular confirms the encrypted parts are linked. If so, S decides on a new key KAB for communication between A and B, prepares two distinct messages (NA , M, A, B)KAS and (NB , M, A, B)KBS for A and B, respectively, and sends both to B in step (3). When B receives this message, it decrypts the second part of the message received in step (3) and checks if NB matches that sent in step (2). If so, it sends the first part to A in step (4). When A receives this message, it decrypts message received in step (4) and checks if NA matches that sent in step (1). If all checks pass, A and B are assured that KAB is fresh (due to their respective nonces), and trust that (NA , KAB )KAS and (NB , KAB )KBS have been constructed by the server S. A knows that B is active as verification of step (4) implies that B sent message in step (2) recently; B however has no assurance that A is active until subsequent use of KAB by A, since B cannot determine if message in step (1) is fresh.

Weaknesses One problem with this protocol is that a malicious intruder can arrange for A and B to end up with different keys as follows: A and B execute the first three messages; at this point, B has received the key KAB . The intruder intercepts the fourth message. He/She replays step (2), which results in S generating a new key K′A B and sending it to B in step (3). The intruder intercepts this message, too, but sends to A the part of it that B would have sent to A. So A has finally received the expected fourth message, but with K′A B instead of KAB . Another problem is that although the server tells B that A used a nonce, B doesn’t know if this was a replay of an old message.

16.3.7 Kerberos Authentication Service Kerberos [20, 30] primarily addresses client-server authentication using a symmetric cryptosystem. Kerberos is an authentication system designed for MIT’s Project Athena [1]. The goal of Project Athena was to create an educational computing environment based on high-performance worksta-

597

Kerberos

User/ Work station

Authentication System (AS)

r et t fo tick s e g ey qu in nk Re graot o i ess ket ds tic n a et ket tick Tic t a rver s e u e Req r the s fo

ion

ess nd s

Data base

Ticket Granting Server (TGS)

key

et a

Tick

Re

qu

Pr o au vid th e s en er tic ve ato r r

est

ser

vic

e

Server

Figure 16.1: Steps in Authentication in Kerberos. tions, high-speed networking, and servers of various types. Researchers envisioned a large-scale (10,000 workstations to 1,000 servers) open network computing environment in which individual workstations can be privately owned and operated. Therefore, a workstation cannot be trusted to identify its users correctly to network services. Kerberos is not a complete authentication service required for secure distributed computing in general; it only addresses issues of client-server interactions. In this section, we describe the Kerberos authentication protocol. Kerberos’ design is based on the use of a symmetric cryptosystem together with trusted third-party authentication servers. The basic components include authentication servers (Kerberos servers) and ticket-granting servers (TGSs).

Initial Registration Every Client/user registers with the Kerberos server by providing its user id, U and a password, passwordu . The Kerberos server computes a key ku = f(passwordu ) using a one-way function f and stores this key in a database. Note that ku is a secret key that depends on the password of the user and is shared by client U and Kerberos server only. 598

The Authentication Protocol Authentication in Kerberos proceeds in three steps: 1. Initial Authentication at Login: Kerberos Server authenticates user login at a host and installs a ticket for the ticket granting server, TGS, at the login host. 2. Obtain a ticket for the server: Using the ticket for the ticket granting server, the client requests the ticket granting server, TGS, for a ticket for the server. 3. Requesting Service from the server: The client uses the server ticket obtained from the TGS to request services from the server. These steps are shown in Figure 16.1. Next, we explain these steps in detail.

(i) Initial Authentication at Login Initial Authentication at Login uses Kerberos server and is shown in Table 16.8. Let U be a user who is attempting to log in a host H. 1) U→ H : 2) H→Kerberos : 3) Kerberos : : : : 4) Kerberos →H : 5) H→ U : 6) U→ H : 7) H : : TGS, k, T, L, tickT GS kU with k’U : tickT GS and k. :

U U, TGS retrieve kU and kT GS from database generate new session key k create a ticket-granting ticket tickT GS = {U, TGS, k, T, L}KT GS {TGS, k, T, L, tickT GS }kU “Password?” password compute k′U = f(password) recover k, tickT GS by decrypting if decryption fails, abort login, otherwise, retain erase password from the memory

Table 16.8: Initial Authentication at Login In step (1), user U initiates login by entering his/her user name. In step (2), the login host H forwards the login request and the id of TGS to a Kerberos server. In step (3), the Kerberos server retrieves kU and kT GS from the database, generates a new session key k and creates a ticketgranting ticket tickT GS = {U, TGS, k, T, L}KT GS where U is the identity of the user who wishes to communicate with the server, TGS is the identity of the ticket granting server, k is the session key, T is a timestamp, L is the ticket’s lifetime and kT GS is the key shared between TGS and Kerberos Server. In Step 4, Kerberos server encrypts the ticket tickT GS , the identity of the TGS, the session key, timestamp and lifetime with kU and sends it to host H. 599

In step (5), on receiving this message from the Kerberos server, host H prompts the user for his/her password, which the user supplies in step (6). In step (7), host H computes the key, K′U , corresponding to the password using the one-way function f. The host recovers the session key k by decrypting {TGS, k, T, L, tickT GS }kU with k′U . If the password supplied by the user is not the valid password of U, k′U would not be identical to kU , and the authentication will fail. Thus, the user is authenticated if the host is able to decrypt the message for the Kerberos server. Upon successful authentication, the host saves the new session key k and the ticket-granting ticket, tickT GS , for further use and erases the user password from the memory. The ticket-granting ticket is used to request server tickets from a TGS. Note that tickT GS is encrypted with kT GS , the key shared between TGS and Kerberos.

(ii) Obtain a ticket for the server The client executes steps shown in Table 16.9 to request a ticket for the server from TGS. Basically the client sends the ticket tickT GS to TGS, requesting it a ticket for the server S. (T1 and T2 are timestamps). Because a ticket is susceptible to interception and replay, it does not by itself constitute sufficient proof of identity. For authentication, a principal presenting a ticket must also demonstrate the knowledge of the session key k named in the ticket. An authenticator {C, T}k , where C is the client identity, T is the timestamp and k is the session key, provides the demonstration. Unlike the ticket which is reusable, an authenticator can be used only once and has a very short lifetime. The ticket proves the client’s identity and also distributes the key; however, it is susceptible to replay attacks. The authenticator is used to counter this attack. Because an authenticator can be used only once and has a very short lifetime, the threat of an opponent stealing the ticket for a replay attack is countered. 1) C → TGS : S, tickT GS , {C, T1 }k 2) TGS : recover k from tickT GS by decrypting with kT GS , recover T1 from {C, T1 }k by decrypting with k check timelines of T1 with respect to local clock generate new session key k. Create server ticket tickS = {C, S, k, T, L}kS 3) TGS→ C : {S, k, T, L, tickS }k 4) C : recover k, tickS by decrypting the message with k Table 16.9: Obtain a ticket for the server In step (1), to request a ticket for server S, client C presents its ticket-granting ticket tickT GS along with the authenticator to TGS. C’s knowledge of k is demonstrated using the authenticator {C, T1 }k . In step (2), TGS decrypts tickT GS with kT GS to recover k, verifies the authenticity of the authenticator by decrypting C, T1 }k with k, and checks the timeliness of T1 in the authenticator and T in the tickT GS . If both decryptions in step (2) timeliness of T1 of the authenticator and T 600

in the tickT GS . If both decryptions in step(2) are successful and T1 is timely, TGS is convinced of the authenticity of the ticket, creates a ticket tickS = {C, S, k, T, LkS for server S, where C is the identity of the client, S is the server identity, k is the new session key, T is the timestamp of the TGS, L is the lifetime of the ticket, kS is the key shared between TGS and server S. This ticket is returned to C in step (3). In step (4), C recovers k and tickS from {S, k, T, L, tickS }k by decrypting it with k.

(iii) Requesting Service from the server Client C sends the ticket and the authenticator to server. The server decrypts the tickS and recovers k. It then uses k to decrypt the authenticator {C, T2 }k′ and checks if the timestamp is current and the client identifier matches with that in the tickS before granting service to the client. If mutual authentication is required, the server returns an authenticator. 1) C→S : 2) S :

tickS , {C, T2 }k′ recover k from tickS by decrypting it with kS recover T2 from {C, T2 }k by decrypting with k check timeliness of T2 with respect to the local clock

3) S→C : {T2 + 1}k Table 16.10: Requesting Service from the server In step (1), C presents S with tickS and a new authenticator. In step (2), S recovers k from tickS by decrypting it with kS and uses k obtained to decrypt C,T2k. If both decryptions are successful and T2 is timely, then S is assured of the authenticity of the Client. Finally, step (3) assures C of the server’s identity.

Weaknesses Kerberos [21] makes no provisions for host security; it assumes that it is running on trusted hosts with an untrusted network. If host security is compromised, then Kerberos is compromised as well. Kerberos uses a principal’s password (encryption key) as the fundamental proof of identity. If a user’s Kerberos password is stolen by an attacker, then the attacker can impersonate that user with impunity. Since the Kerberos’ password database holds all the passwords for all of the principals in a realm, if the host security on the database is compromised, then the entire realm is compromised. In Kerberos version 4, authenticators are valid for a particular time. If an attacker sniffs the network for authenticators, they have a small time window in which they can re-use it and gain access to the same service. Kerberos version 5 introduced a replay cache which prevents any authenticator from being used more than once. Since anybody can request a Ticket Granting Ticket for any user, and that ticket is encrypted with the user’s secret key (password), it is simple to perform an offline attack on this ticket by trying to decrypt it, say using the dictionary attack. Kerberos version 5 introduced pre-authentication to solve this problem. 601

16.4 Protocols Based on Asymmetric Cryptosystems In an asymmetric cryptosystem [41], each principal P publishes his public key kp and keeps secret −1 his private key k−1 p . Thus only P can generate {m}kp−1 for any message m by signing it using kp . The signed message {m}kp−1 can be verified by any principal with the knowledge of kp (assuming a commutative asymmetric cryptosystem). Asymmetric authentication protocols can be constructed using a design principle called ASYM which is as follows: “If a principal can correctly sign a message using the private key of the claimed identity, this act constitutes a sufficient proof of identity.” This ASYM principle follows the proof-by-knowledge principle for authentication, in that a principal’s knowledge is indirectly demonstrated through its signing capability.

16.4.1 The Basic Protocol Using ASYM, we obtain a basic protocol as follows [41]: P→Q : Q: Q→P : P: P→Q : Q: :

“I am P.” generate nonce n n compute m = {P, Q, n}kp−1 m verify (P, Q, n) = {m}kp if equal, then accept; otherwise, the authentication fails Table 16.11: Basic protocol

In this protocol, Q sends a random number n to P and challenges it to encrypt with its private key. P encrypts (P, Q, n) with its private key k−1 p and sends it to Q. Q verifies the received message by decrypting it with P’s public key kp and checking with the identity of P, Q and n. This protocol depends on the guarantee that {P, Q, n}kp−1 cannot be produced without the knowledge of k−1 p and the correctness of kp as published by P and kept by Q.

16.4.2 A Modified Protocol with a Certification Authority The basic protocol requires that Q has the knowledge of P’s public key. A problem arises if Q does not know P’s public key. This problem is alleviated by postulating a centralized certification authority CA that maintains a database of all published public keys [41]. If a user A does not have the public key of another user B, A can request B’s public key from the CA. The basic protocol can be modified as shown in Table 16.12 to address this issue. This protocol is similar to the basic protocol described above but a certification authority CA is involved. When Q receives a message encrypted with P’s private key from P, it requests the authentication server for P’s public key. CA retrieves public key of P from the key database and

602

P→Q : Q: Q→P : P: P→Q : Q→CA : CA :

“I am P.” generate nonce n n compute m = {P, Q, n}kp−1 m “I need P’s public key.” retrieve public key kP of P from key Database Create certificate c = {P, kP }k−1 CA CA→Q : P, c Q : recover P, kP from c by decrypting with kCA verify (P, Q, n) = {m}kP : if equal, then accept; otherwise, the authentication fails

Table 16.12: A Modified Protocol with a certification authority CA provides Q with a certificate for P’s public key. The certificate, {P, kP }k−1 contains P’s identity and CA its public key, encrypted with the private key of the certification authority. Q retrieves the public key of P by decrypting the certificate with the public key of CA. Then it decrypts the message m, it received from P using the public key kP and checks if {m}kP equals {P, Q, n}. If both are equal, authentication succeeds, else it fails. Note that c, called a public key certificate, represents a certified statement by CA that P’s public key is kp . Other information such as an expiration date and the classification of principal P can also be included in the certificate. However, each principal in the system must know the public key kCA of CA. In this protocol, CA is an example of an on-line certification authority. It supports interactive queries and is actively involved in authentication exchanges. A certification authority can also operate off-line. In this case, a public key certificate is issued to a principal when it first registered. The certificate is kept by the principal and is forwarded during an authentication exchange, thus eliminating the need to make a separate query to a CA. Forgery is impossible, since a certificate is signed by the certification authority.

16.4.3 Needham and Schroeder Protocol The Needham-Schroeder public key protocol [29] uses a trusted key server that issues certificates containing the public key of a user. The protocol is described in Table 16.13. In this protocol, the initiator A seeks to establish a session with responder B with the help of trusted key server S. (Recall that for a principal x, Kx and K−1 x denote its public and private keys, respectively.) In step 1, A sends a message to the server S, requesting B’s public key. S responds by returning B’s public key Kb along with B’s identity (to prevent attacks based upon diverting key deliveries), encrypted using S’s secret key (to assure A that this message originated from S). A then seeks to establish a connection with B by selecting a nonce Na , and sending it along with its identity to B (message 3) encrypted using B’s public key. When B receives this message, it decrypts the 603

1. A→ S : 2. S→ A : 3. A→ B : 4. B→ S : 5. S→ B : 6. B→ A : 7. A→ B :

A, B {Kb , B}Ks−1 {Na , A}Kb B, A {Ka , A}Ks−1 {Na , Nb }Ka {Nb }Kb

Table 16.13: Needham-Schroeder Protocol message to obtain the nonce Na and to learn that user A is trying to communicate with it. It then requests the public key of A from server S (message 4) which the server sends to B in message 5. B then returns nonce Na , along with a new nonce Nb , to A, encrypted with A’s public key (message 6). When A receives this message, it decrypts it with its private key and is assured that it is talking to B, since only B could have decrypted message in step 3 to obtain Na . A then returns nonce Nb to B, encrypted with B’s key. When B receives this message, it is assured that it is talking to A, since only A could have decrypted message in step 6 to obtain Nb . Thus, after step 7, A and B have mutually authenticated themselves. This protocol can be considered as the interleaving of two logically disjoint protocols: messages 1, 2, 4 and 5 are concerned with obtaining public keys, whereas messages 3, 6 and 7 are concerned with the authentication of A and B.

Weaknesses This protocol provides no guarantee that the public keys obtained are current and not replays of old, possibly compromised keys. This problem can be overcome in various ways. For example, one way is that the server S includes timestamps in messages 2 and 5; however, this requires synchronized clocks at processes. Another method is that A sends a nonce in message 1 and S returns the same nonce in message 2.

An Impersonation attack on the protocol We now show how an intruder can mount an impersonation attack on this protocol [25]. We assume that the intruder I is a user of the computer network, and so is able to set up standard sessions with other users, and other users may try to set up sessions with I. We assume that the intruder can intercept any messages in the system and introduce new messages. However, we make some assumptions about what sort of messages the intruder may introduce. We assume that the intruder cannot guess the value of nonces being passed in encrypted messages, unless those messages are encrypted with his own key. Thus the intruder can only produce new messages using nonces that it invented itself, or that it has previously seen and understood. It can also replay complete encrypted messages, even if it is unable to understand the contents. The attack shown in Table 16.14, starts with a user A trying to establish a session with I. 604

The attack on the protocol allows an intruder I to impersonate the user A to set up a false session with a user B. The attack involves two simultaneous runs of the protocol: In run 1, A establishes a valid session with I; in run 2, I impersonates A to establish a fake session with B. In Table 16.14, 1.3 represents message 3 in run 1 and I(A) represents the intruder I impersonating A. 1.3 A→ I : 2.3 I(A) → B : 2.6 B→ I(A) : 1.6 I→ A : 1.7 A→ I : 2.7 I(A) → B :

{Na , A}Ki {Na , A}Kb {Na , Nb }Ka {Na , Nb }Ka {Nb }Ki {Nb }Kb

Table 16.14: An Impersonation attack on Needham-Schroeder Protocol In step 1.3, A starts to establish a session with I, sending it a nonce Na . In step 2.3, the intruder impersonates A to try to establish a false session with B sending it the nonce Na obtained in the previous message from A. B responds in step 2.6 by selecting a new nonce Nb and returning it, along with Na to A. The intruder intercepts this message, but cannot decrypt it because it is encrypted with A’s public key. The intruder uses A as an oracle, by forwarding the message to A in step 1.6; note that this message is of the form expected by A in run 1 of the protocol. A decrypts the message to obtain Nb and returns this to I in step 1.7. I decrypts this message to obtain Nb and returns it to B in step 2.7, thus completing run 2 of the protocol. After B receives the message in step 2.7, B is led to believe that A has correctly established a session with it.

A Solution to the Attack The main cause of this attack is that step 6 does not contain the identity of the responder. If we include the responder’s identity in step 6 of the protocol: 6. B→ A : {B, Na , Nb }ka then step 2.6 of the attack would become 2.6. B→ I(A) : {B, Na , Nb }ka and the intruder I can not successfully replay this message in step 1.6 because A is expecting a message containing I’s identity.

16.4.4 SSL Protocol SSL stands for Secure Sockets Layer protocol [37] developed by Netscape and is the standard Internet protocol for secure communications. The secure hypertext transfer protocol (HTTPS) is a communications protocol designed to transfer encrypted information between computers over the 605

World Wide Web. HTTPS is http using a Secure Socket Layer (SSL). SSL resides between TCP/IP and upper layer applications, requiring no changes to the application layer. SSL is used typically between server and client to secure the connection. One advantage of SSL is that it is application protocol independent. A higher level protocol can layer on top of the SSL Protocol transparently. SSL protocol allows client/server applications to communicate in a way so that eavesdropping, tampering, and message_forgery are prevented. The SSL protocol, in general, provides the following features: • End point authentication: The server is the “real” party that a client wants to talk to, not someone faking the identity. • Message integrity: If the data exchanged with the server has been modified along the way, it can be easily detected. • Confidentiality: Data is encrypted. A hacker cannot read your information by simply looking at the packets on the network.

SSL record protocol The record protocol takes an application message to be transmitted, fragments the data into manageable blocks, optionally compresses the data, applies MAC, encrypts adds a header and transmits the resulting unit into a TCP segment. Received data are decrypted, verified, decompressed and reassembled and then delivered into high level users.

SSL handshake protocol The SSL Handshake Protocol [37] allows the server and client to authenticate each other and to negotiate an encryption algorithm and cryptographic keys before the application protocol transmits or receives its first byte of data. The following steps, shown in Figure 16.2, are involved in the SSL handshake: 1. The SSL client sends a "client hello" message that lists cryptographic information such as the SSL version and, in the client’s order of preference, the CipherSuites supported by the client. The message also contains a random byte string that is used in subsequent computations. 2. The SSL server responds with a "server hello" message that contains the CipherSuite chosen by the server from the list provided by the SSL client, the session ID and another random byte string. The SSL server also sends its digital certificate. If the server requires a digital certificate for client authentication, the server sends a "client certificate request" that includes a list of the types of certificates supported and the Distinguished Names of acceptable Certification Authorities (CAs). 3. The SSL client verifies the digital signature on the SSL server’s digital certificate and checks that the CipherSuite chosen by the server is acceptable. 606

4. The SSL client, usind all data generated in the handshake so far, creates a premaster secret for the session that enables both the client and the server to compute the secret key to be used for encrypting subsequent message data. The premaster secret itself is encrypted with the server’s public key. 5. If the SSL server sent a "client certificate request", the SSL client sends another signed piece of data which is unique to this handshake and known only to the client and server, along with the encrypted premaster secret and the client’s digital certificate, or a "no digital certificate alert". This alert is only a warning, but with some implementations the handshake fails if client authentication is mandatory. 6. The SSL server verifies the signature on the client certificate. 7. The SSL client sends the SSL server a "finished" message, which is encrypted with the secret key, indicating that the client part of the handshake is complete. 8. The SSL server sends the SSL client a "finished" message, which is encrypted with the secret key, indicating that the server part of the handshake is complete. 9. For the duration of the SSL session, the SSL server and SSL client can now exchange messages that are encrypted with the shared symmetric secret key.

How SSL provides authentication During both client and server authentication, there is a step that requires data to be encrypted with one of the keys in an asymmetric key pair and is decrypted with the other key of the pair[37]. For server authentication, the client uses the server’s public key to encrypt the data that is used to compute the secret key. The server can generate the secret key only if it can decrypt that data with the correct private key. For client authentication, the server uses the public key in the client certificate to decrypt the data the client sends during step 5 of the handshake. The exchange of finished messages that are encrypted with the secret key (steps 7 and 8 in the overview) confirms that authentication is complete. If any of the authentication steps fails, the handshake fails and the session terminates. The exchange of digital certificates during the SSL handshake is a part of the authentication process. The certificates required are as follows, where CA X issues the certificate to the SSL client, and CA Y issues the certificate to the SSL server: For server authentication only, the SSL server needs: • The personal certificate issued to the server by CA Y • The server’s private key 607

SSL Client

SSL Server

1. "client hello" Cryptograhpic information 2. "server hello" CipherSuite Server certificate "client certificate request" (optional)

3. Verify server certificate. Check cryptograohic paremeters.

4. Client key exchange Send secret key information (encrypted with server public key)

5. Send client certificate 7. Client "finished"

6. Verify client certificate. (if required).

8. Server "finished"

9. Exchange messages (encrypted with the shared secret key)

Figure 16.2: SSL Handshake Protocol and Data Exchange and the SSL client needs: • The CA certificate for CA Y or the personal certificate issued to the server by CA Y If the SSL server requires client authentication, the server verifies the client’s identity by verifying the client’s digital certificate with the public key for the CA that issued the personal certificate to the client, in this case CA X. For both server and client authentication, the SSL server needs: • The personal certificate issued to the server by CA Y • The server’s private key • The CA certificate for CA X or the personal certificate issued to the client by CA X and the SSL client needs: • The personal certificate issued to the client by CA X 608

• The client’s private key • The CA certificate for CA Y or the personal certificate issued to the server by CA Y Both the SSL server and the SSL client might need other CA certificates to form a certificate chain to the root CA certificate.

16.5 Password-based Authentication The use of passwords is a highly popular technique to achieve authentication because of low cost and convenience. This section is concerned with authentication techniques that are based on passwords. A problem with passwords is that people tend to pick a password that is convenient, i.e., short and easy to remember. Such passwords are vulnerable to a password-guessing attack which works as follows: an adversary builds a database of possible passwords, called a dictionary. The adversary picks a password from the dictionary and checks if it works. This may amount to generating a response to a challenge or decrypting a message using the password or a function of the password. After every failed attempt, the adversary picks a different password from the dictionary and repeats the process. This non-interactive form of attack is known as the off-line dictionary attack.

Preventing Off-line Dictionary Attacks Thus, a major problem is that users tend to choose weak passwords which are chosen from a sample space small enough to be enumerated by an adversary. Hence, protocols which are stronger than simple challenge-response protocols are needed which can use these cryptographically weak passwords to securely authenticate entities. A password-based authentication protocol aims at preventing off-line dictionary attacks by producing a cryptographically strong shared secret key, called the session key, after a successful run of the protocol. This session key can be used by both entities to encrypt subsequest messages for a seceret session. In this section, we focus on protocols designed to prevent off-line dictionary attacks on passwordbased authentication. Next, we present two password-based authentication protocols.

16.5.1 Encrypted Key Exchange (EKE) Protocol The first attempt to protect a password protocol against off-line dictionary attacks was made by Bellovin and Merritt [6] who developed a password-based encrypted key exchange (EKE) protocol using a combination of symmetric and asymmetric cryptography. Figure 16.3 describes the EKE protocol that works as follows: suppose users A and B are participating in a run of the protocol. (Recall that {X}k denotes the encryption of X using a symmetric key k and {Y }k−1 denotes the decryption of Y using a symmetric key k.) In Step 1, user A generates a public/private key pair (EA , DA ) and also derives a secret key Kpwd from his password pwd. In Step 2, A encrypts his public key EA with Kpwd and sends it 609

to B. In Steps 3 and 4, B decrypts the message and uses EA together with Kpwd to encrypt a session key KAB and sends it to A. In Steps 5 and 6, A uses this session key to encrypt a unique challenge CA and sends the encrypted challenge to B. In Step 7, B decrypts the message to obtain the challenge and generates a unique challenge CB . In Step 8, B then encrypts {CA , CB } with the session key KAB and sends it to A. In Step 9, A decrypts this message to obtain CA and CB and compares the former with the challenge it had sent to B. If they match, the correctness of B’s response is verified (i.e., B is authenticated). In Step 10, A encrypts B’s challenge CB with the session key KAB and sends it to B. When B receives this message, it decrypts the message to obtain CB and uses it verify the correctness of A’s response and to authenticate A. Note that the protocol results in a session key (stronger than the shared password) which the users can later use to encrypt sensitive data.

1. A : (EA , DA ), Kpwd =f(pwd). {* f is a function. *} 2. A → B : A, {Kpwd}EA . 3. B : Compute EA = {{EA }Kpwd }K −1 and generate a random secret key KAB . pwd

4. B → A : {{KAB }EA }{Kpwd } . 5. A : KAB = {{{{KAB }EA }{Kpwd } }K −1 }DA . Generate a unique challenge CA . pwd

6. A → B : {CA }KAB . 7. B : Compute CA = {{CA }KAB }K −1 and generate a unique challenge CB . AB

8. B → A : {CA , CB }KAB . 9. A : Decrypt message sent by B to obtain CA and CB . Compare the former with his own challenge. If they match, go to the next step, else abort. 10. A → B : {CB }KAB . Figure 16.3: Encrypted Key Exchange Protocol The EKE protocol suffers from the plain-text equivalence, which means the user and the host have access to the same secret password or hash of the password.

16.5.2 Secure Remote Password (SRP) Protocol Wu [44] combined the technique of zero-knowledge proof with asymmetric key exchange protocols to develop a verifier-based protocol, called secure remote password (SRP) protocol. SRP protocol eliminates plain-text equivalence.

610

All computations in SRP are carried out on the finite field Fn , where n is a large prime. Let g be a generator of Fn . Let A be a user and B be a server. Before initiating the SRP protocol, A and B do the following: 1. A and B agree on the underlying field. 2. A picks a password pwd, a random salt s and computes the verifier v = g x , where x = H(s, pwd) is the long-term private-key and H is a cryptographic hash function. 3. B stores the verifier v and the salt s. Now, A and B can engage in the SRP protocol (shown in Figure 16.4). The SRP protocol works as follows: In Step 1, A sends its username “A” to server B. In Step 2, B looks-up A’s verifier v and salt s and sends A his salt. In Steps 3 and 4, A computes its long-term private-key x = H(s, pwd), generates an ephemeral public-key KA = g a where a is randomly chosen from the interval 1 < a < n and sends KA to B. In Steps 5 and 6, B computes ephemeral public-key KB = v + g b where b is randomly chosen from the interval 1 < a < n and sends KB and a random number r to A. In Step 7, A computes S = (KB − g x )a+rx = g ab+brx and B computes S = (KA v r )b = g ab+brx . The values of S computed by A and B will match if the password A entered in Step 3 matches the one that A used to calculate the verifier v which is stored at B. In Step 8, both A and B use a cryptographically strong hash function to compute a session key KAB = H(S). In Step 9, A computes CA = H(KA , KB , KAB ) and sends it to B as an evidence that it has the session key. CA also serves as a challenge. In Step 10, B computes CA itself and matches it with A’s message. B also computes CB = H(KA , CA , KAB ). In Step 11, B sends CB to A as an evidence that it has the same session key as A. In Step 12, A verifies CB , accepts if the verification passes and aborts otherwise. Note that unlike EKE, none of the protocol are messages encrypted in the SRP protocol. Since neither the user nor the server has access to the same secret password or hash of the password, SRP eliminates plain-text equivalence. SRP was unique in its swapped-secret approach in building a verifier-based, zero-knowledge protocol, resisting off-line dictionary attacks.

16.6 Authentication Protocol Failures Despite the apparent simplicity of the basic design principles, realistic authentication protocols [11, 29] are notoriously difficult to design [39]. There are several reasons for it. • First, most realistic cryptosystems satisfy algebraic additional identities. These extra properties may generate undesirable effects when combined with a protocol logic. • Second, even assuming that the underlying cryptosystem is perfect, unexpected interactions among the protocol steps can lead to subtle logical flaws.

611

1. A → B : A. 2. B → A : s. 3. A : x = H(s, pwd); KA = g a . 4. A → B : KA . 5. B : KB = v + g b . 6. B → A : KB , r. 7. A : S = (KB − g x )a+rx and B : S = (KA v r )b . 8. A, B : KAB = H(S). 9. A → B : CA = H(KA , KB , KAB ). 10. B verifies CA and computes CB = H(KA , CA , KAB ). 11. B → A : CB . 12. A verifies CB . Accept if verification passes; abort otherwise. Figure 16.4: Secure Remote Password (SRP) Protocol • Third, assumptions regarding the environment and the capabilities of an adversary are not explicitly specified, making it extremely difficult to determine when a protocol is applicable and what final states are achieved. We illustrate the difficulty by showing an authentication protocol proposed, with a subtle weakness. consider the following authentication protocol: (kp and kq are symmetric keys shared between P and A, and Q and A, respectively, where A is an authentication server. k is a session key.) 1) P → A : 2) A → P : 3) P → Q : 4) Q → P : 5) P → Q :

P, Q, np {np , Q, k , {k, P}kQ }kp {k, P}kQ {nQ }K {nQ +1}K

The message {k, P}kQ in step (3) can only be decrypted by Q and hence can only be understood by Q. Step (4) reflects Q’s knowledge of k, while step (5) assures Q of P’s knowledge of k; hence the authentication handshake is based entirely on the knowledge of k. The subtle weakness in the protocol arises from the fact that the message {k, P}kQ sent in step (3) contains no information for Q to verify its freshness. This is the first message sent to Q about 612

P’s intention to establish a secure connection. An adversary who has compromised an old session key k′ can impersonate P by replaying the recorded message {k′ , P}kQ in step (3) and subsequently executing the steps (4) and (5) using k′ . To avoid protocol failures, formal methods may be employed in the design and verification of authentication protocols. A formal design method should embody the basic design principles. For example, informal reasoning such as “If you believe that only you and Bob know k , then you should believe any message you receive encrypted with k was originally sent by Bob.” should be formalized by a verification method.

16.7 Bibliographic Notes Authentication in distributed systems is a well studied topic and a large number of authentication protocols exist. An excellent survey on the topic is by Woo amd Lam [39]. Burrows, Abadi and Needham discuss the logic of authentication [7]. A classical paper on the topic is by Needham and Schroeder [29]. A review paper on password based authentication is by Chakrabarti and Singhal [8]. Biometric authentication has been very popular recently. Information on this topic can be found in [16, 17, 34, 15, 31]. King and Dos Santos [22] discuss AI based methods for human authentication. Kaminsky et al. [18] discuss user authentication in a global file system. A list of papers on authentication can be found at: http://www.passwordresearch.com/papers/pubindex.html.

16.8 Exercise Problems 1. List three attacks/threats that are associated with user authentication on the Internet. 2. What is a nonce? What security problem does it solve? 3. Consider the following simple method to handle attacks on the password based authentication: If a user fails to login in three successive attempts, the system locks his account suspecting an attack/intrusion. What major problem do you see with this method? 4. Choose two principles given by Needham and Abadi for designing cryptographic protocols. For each, give an example where their principle applies and results in an improved protocol. 5. Consider the following protocol for Authentication/Key Distribution: (X and Y are two principals, A is a Certificate Authority or a Key Distribution Center, RX is a randon number, and EX means encrypted with the secret key of X.) 1. X→A: X, Y, RX 2. A→X: EX (RX , Y, K, EY (K, X)) 3. X→Y: EY (K, X) 4. Y→X: EK (RY ) 613

5. X→Y: EK (RY − 1) (a) What does the presence of RX in message 2 assure? (b) What problem will be created if an attacker were to break an old K (and the attacker has also copied messages for that session)? Explain your answer. (c) Suggest a method to solve this problem? 6. Discuss two biometric based methods for authentication. What are pros and cons of biometric based methods for authentication?

614

Bibliography [1] Arfman, J. M.; Roden, Peter. Project Athena: Supporting distributed computing at MIT IBM Systems Journal Volume 31, Number 3, 1992. [2] Martin Abadi and Roger Needham. Prudent engineering practices for cryptographic protocols. In Proceedings of the IEEE Computer Society Symposium on Research in Security and Privacy, pages 122–136. IEEE CS Press, May 1994. [3] M. Abadi, M. Burrows, C. Kaufman, and B.W. Lampson. Authentication and delegation with smart-cards. Science of Computer Programming, 21(2):93–113, October 1993. [4] Ross Anderson and Roger Needham. Robustness principles for public key protocols. In D. Coppersmith, editor, Advances in Cryptology — CRYPTO’95, pages 236–247. SpringerVerlag, LNCS 963, August 1995. [5] S.M. Bellovin and M. Merritt. Limitations of the Kerberos authentication system. In Proceedings of USENIX Winter Conference, pages 253–267, Dallas, TX, January 1991. [6] S.M. Bellovin and M. Merritt, Encrypted Key Exchange: Password-based Protocol Secure Against Dictionary Attacks, in Proc. of the IEEE Symposium on Security and Privacy, Washington, DC, 1992. [7] M. Burrows, M. Abadi, and R.M. Needham. A logic of authentication. ACM Transactions on Computer Systems, 8(1):18–36, February 1990. [8] S. Chakrabarti and M. Singhal, Password-based Authentication, to appear in IEEE Computer. [9] CCITT Recommendation X.509 The Directory—Authentication framework, 1988. See also ISO/IEC 9594-8, 1989. [10] Art Conklin, Glenn Dietrich, Diane Walz, Password-Based Authentication: A System Perspective, Proceedings of the 37th Hawaii International Conference on System Sciences, January 2004. [11] D.E. Denning. Cryptography and Data Security. Addison-Wesley, 1982. [12] D. Dolev and A.C. Yao. On the security of public key protocols. IEEE Transactions on Information Theory, IT-29(2):198–208, March 1983. 615

[13] M. Gasser, A. Goldstein, C. Kaufman, and B.W. Lampson. The Digital distributed system security architecture. In Proceedings of 12th National Computer Security Conference, pages 305–319, Baltimore, Maryland, October 1989. [14] M. Gasser and E. McDermott. An architecture for practical delegation in a distributed system. In Proceedings of 11th IEEE Symposium on Research in Security and Privacy, pages 20–30, Oakland, California, May 7–9 1990. [15] L. O’Gorman, Practical Systems for Personal Fingerprint Authentication, IEEE Computer, 33, No. 2, 58.60 (2000). [16] A. Jain, L. Hong, and S. Pankanti, Biometrics Identification, Communications of the ACM, 43, No. 2, 91.98 (2000). [17] M. Indovina, U. Uludag, R. Snelick, A. Mink and A. Jain, "Multimodal Biometric Authentication Methods: A COTS Approach", Proc. MMUA 2003, Workshop on Multimodal User Authentication, pp. 99-106, Santa Barbara, CA, December 11-12, 2003. [18] M. Kaminsky, G. Saviddes, D. Mazieres and M. F. Kaashoek, Decentralized User Authentication in a Global File System, Symp. on Oper. Sys. Principles, 2003. [19] C. Kaufman, DASS Distributed Authentication Security Service, September 1993. RFC 1507. [20] J.T. Kohl, B.C. Neuman, and T.Y. Ts’o. The evolution of the Kerberos authentication system. In F. Brazier and D. Johansen, editors, Distributed Open Systems, pages 78–94. IEEE Computer Society Press, 1994. [21] Kerberos Frequently Asked Questions, URL: http://www.nrl.navy.mil/CCS/ people/kenh/kerberos-faq.html. [22] Jeffrey King and Andre dos Santos, "A User-Friendly Approach to Human Authentication of Messages", in Financial Cryptography and Data Security, LNCS 3570, pp 225-239. February/March 2005. [23] Lampson, B., Abadi, M. and Burrows, M, Authentication in Distributed Systems: Theory and Practice, ACM Transactions on Computer Systems, 1992. [24] Min-Hui Lin and Chin-Chen Chang, A secure one-time password authentication scheme with low-computation for mobile communications, ACM SIGOPS Operating Systems Review, Volume 38, Issue 2, April 2004, Pages: 76 - 84. [25] Gavin Lowe, An Attack on the Needham-Schroeder Public-Key Authentication Protocol, Information Processing Letters, Aug 22, 1995, pages 131-133.

616

[26] J. Linn. Practical authentication for distributed computing. In Proceedings of 11th IEEE Symposium on Research in Security and Privacy, pages 31–40, Oakland, California, May 7–9 1990. [27] Cheng-Chi Lee, Min-Shiang Hwang, Li-Hua Li, A New Key Authentication Scheme Based on Discrete Logarithms, Applied Mathematics and Computation, Volume 139, Issue 2-3, July 2003. [28] A. Menezes, P. van Oorschot, and S. Vanstone, Handbook of Applied Crytography, Chapter 12 - Key Establishment Protocols, CRC Press, October 1996. [29] R.M. Needham and M.D. Schroeder. Using encryption for authentication in large networks of computers. Communications of the ACM, 21(12):993–999, December 1978. [30] B.C. Neuman and T.Y. Ts’o. An authentication service for computer networks. IEEE Communications Magazine, 32(9):33–38, September 1994. [31] N. K. Ratha, J. H. Connell, and R. M. Bolle Enhancing security and privacy in biometricsbased authentication systems, IBM Systems Journal, issue 40-3, End-to-End Security, 2001. [32] J.G. Steiner, C. Neuman, and J.I. Schiller. Kerberos: An authentication service for open network systems. In Proceedings of USENIX Winter Conference, pages 191–202, Dallas, TX, February 1988. [33] B. Schneier, Applied Cryptography, John Wiley & Sons, Inc., New York (1996). [34] B. Schneier, The Uses and Abuses of Biometrics, Communications of the ACM, 42, No. 8, 136 (1999). [35] William Stallings, Cryptography and Network Security: Principles and Practice, PrenticeHall, 4th edition. [36] Paul Syverson and Iliano Cervesato, The Logic of Authentication Protocols, Lecture Notes in Computer Science", Vol 2171, 2001. [37] The Secure Socket Layer, URL: http://publib.boulder.ibm.com/ infocenter/wmqv6/v6r0/index.jsp?topic=/com.ibm.mq.csqzas. doc/cssauthentication.htm. [38] J.J. Tardo and K. Alagappan. SPX: Global authentication using public key certificates. In Proceedings of 12th IEEE Symposium on Research in Security and Privacy, pages 232–244, Oakland, California, May 20–22 1991 [39] T.Y.C. Woo and S.S. Lam. Authentication for distributed systems. Computer, 25(1):39–52, January 1992. See also “Authentication” revisited. Computer, 25(3):10, March 1992.

617

[40] T.Y.C. Woo and S.S. Lam. A lesson on authentication protocol design. ACM Operating Systems Review, 28(3):24–37, July 1994. [41] Thomas Y.C. Woo, Simon Lam , Authentication for Distributed Systems, revised version of a paper with the same title published in Computer, Volume 25, Number 1, pages 39–52, January 1992. [42] T.Y.C.Woo and S.S. Lam. Design, verification, and implementation of an authentication protocol. In Proceedings of International Conference on Network Protocols, Boston, Massachusetts, October 25–28 1994. (Also available from http://www.cs.utexas.edu/users/lam/NRL/.) [43] T.Y.C. Woo, R. Bindignavle, S. Su, and S.S. Lam. SNP: An interface for secure network programming. In Proceedings of USENIX Summer Technical Conference, Boston, Massachusetts, June 6–10 1994. (Also available from http://www.cs.utexas.edu/users/lam/NRL/.) [44] T.D. Wu, The Secure Remote Password Protocol, in Proc. of the Network and Distributed Systems Security, NDSS 1998, San Diego, CA, 1998.

618

Chapter 17 Self-Stabilization 17.1 Introduction The idea of self-stabilization in distributed computing [36] was first proposed by Dijkstra in 1974. The concept of self-stabilization is that regardless of its initial state, the system is guaranteed to converge to a legitimate state in a bounded amount of time by itself without any outside intervention. A non self-stabilizing system may never reach a legitimate state or it may reach a legitimate state only temporarily. The main complication in designing a self-stabilizing distributed system is that nodes do not have a global memory that they can access instantaneoulsy. Each node must make decisions based on the local knowledge available to it and actions of all nodes must achieve a global ojective. The definition of legitimate and illegitimate states depends on the particular application. Generally, all illegitimate states are defined to be those states which are not legitimate states. Dijkstra also gave an example of the concept of self-stabilization using a self-stabilizing token ring system. For any given token ring when there are multiple tokens or there is no token, then such global states are known as illegitimate states. When we consider a distributed system where a large number of systems are widely distributed and communicate with each other using message passing or shared memory approach , there is a possibility for these systems to go into an illegitimate state, for example, if a message is lost. The concept of self-stabilization can help us recover from such situations in distributed system. Let us explain the concept of self-stabilization using an example. Let us take a group of children and ask them to stand in the form of a circle. After few minutes, you will get almost a perfect circle without having to take any further action. In addition, you will discover that the shape of this circle is stable, at least until you ask the children to disperse. If you force one of the children out of the position, the others will move accordingly, moving the entire circle in another position, but keeping its shape unchanged. In this example, the group of children build a self-stabilizing circle: if some thing goes wrong with the circle, they are able to rebuild the circle by themselves, without any external intervention. The time required for stabilization varies from experiment to experiment, depending on the (random) initial position. However, if the field size is limited, this time will be bounded. The algorithm 619

does not define the position of the circle in the field and so it will not always be the same. The position of each child relative to each other will also vary. The self-stabilization principle applies to any system built on a significant number of components which are evolving independently from one another, but which are cooperating or competing to achieve common goals. This applies, in particular, to large distributed systems which tend to result from the integration of many subsystems and components developed separately at earlier times or by different people. In this chapter, we first present the system model of a distributed system and present definitions of self-stabilization. Next, we discuss Dijkstra’s seminal work and use it to motivate the topic. We discuss the issues arising from the Dijkstra’s original presentation as well as several related issues in the design of self-stabilizing algorithms and systems. After that, we discuss three important themes that have recently emerged. In particular, we discuss the methods that have been used to design complex self-stabilizing systems, we discuss the role of compilers in designing selfstabilization, and we enumerate factors that have been found to interfere with self-stabilization. We also discuss self-stabilizing protocols for construction of spanning trees and present a selfstabilizing algorithm for 1-maximal independent set. We conclude the chapter with limitations of self-stabilization.

17.2 System Model The term distributed system is used to describe set of computers that communicate over network. Variants of distributed systems have similar fundamental coordination requirements among the communicating entries, whether they are computers, processors or processes. Thus an abstract model that ignores the specific settings and captures the important characteristics of a distributed system is usually used. In a distributed system, each computer runs a program composed of executable statements. Each execution changes the content of the computer’s logical memory. An abstract way to model a computer that executes a program is to use the state machine model. A distributed system model comprises of a set of n state machines called processors that communicate with each other. We usually denote the ith processor in the system by Pi . Neighbors of processor are processors that are directly connected to it. A processor can directly communicate with its neighbors. A distributed system can be conveniently represented by a graph in which each processor is represented by a node and every pair of neighboring nodes are connected by a link. The communication between neighboring processors can be carried out either by message passing or shared memory. Communication by writing in and reading from the shared memory usually fits systems with processors that are geographically close together, such as multiprocessor computer. A message-passing distributed model fits both processors that are located close to each other as well as that are widely distributed over a network. In the message-passing model, neighbors communicate by sending and receiving messages. In asynchronous distributed systems, the speed of processors and message transmission can vary. 620

First-in first-out (FIFO) queues are used to model asynchronous delivery of messages. A communication link is either unidirectional or bidirectional. A unidirectional communication link from processor Pi to Pj transfers messages only from Pi to Pj . The abstraction used for such a unidirectional link is a first-in first-out (FIFO) queue Qi,j that contains all messages sent by a processor Pi to its neighbor Pj that have not yet been received. Whenever Pi sends a message m to Pj , the message is en-queued (added to the tail of the queue). The bidirectional communication link between processors Pi and Pj is modeled by two FIFO queues, one from Pi to Pj and the other from Pj to Pi . It is convenient to identify the state of a computer or a distributed system at a given time, so that no additional information about the past of the computation is needed in order to predict the future behavior (state transitions) of the computer or the distributed system. A full description of a message passing distributed system at a particular time consists of the state of every processor and the content of every queue (messages traveling in the communication links). The term system configuration (or configuration) is used for such a description. A configuration is denoted by c=(s1 , s2 . . . sn , q1,2 , q1,3 , . . . qi,j . . . . . . .qn,n−1), where si , 1≤ i ≤n is the state of Pi and qi,j , i 6=j is the state of queue Qi,j , that is, messages sent by Pi to Pj but not yet received. The behavior of a system consists of a set of states, a transition relation between those states, and a set of fairness criteria on the transition relation [81]. The system is usually modeled as a graph of processing elements (modeled as state machines), where edges between these elements model unidirectional or bidirectional communication links. Let N be an upper bound on n (the number of nodes in the system). Communication network is usually restricted to the neighbors of a particular node. Let δ denote the diameter of the network (i.e., the length of the longest unique path between two nodes) and let ∆ denote the upper bound on δ. A network is static if the communication topology remains fixed. It is dynamic if links and network nodes can go down and recover later. In the context of dynamic systems, self-stabilization refers to the time after the “final” link or node failure. The term “final failure” is typical in the literature on self-stabilization. Since stabilization is only guaranteed eventually, the assumption that faults eventually stop to occur implies that there are no faults in the system for “sufficiently long period” for the system to stabilize. In any case, it is assumed that the topology remains connected, i.e., there exists a path between any two nodes. In the shared memory model, processors communicate using shared communication registers (hereafter, called registers). Processors may write in a set of registers and may read from a possibly different set of registers. Two neighboring nodes have access to a common data structure, variable or register which can store a certain amount of information. These variables can be distinguished between input and output variables (depending on which process can modify them). When executing a step, a process may read all its input variables, perform a state transition and write all its output variables in a single atomic operation. This is called composite atomicity. A weaker notion of a step (called read/write atomicity) also exists where a process can only either read or write its communication variables in one atomic step. The configuration of a system with n processors and m communication registers is denoted by

621

c= ( s1 , s2 , s3 . . . ..sn , r1 , r2 . . . , rm ), where si , 1≤ i ≤n, is the state of Pi and rj , 1≤ j ≤ m, is the contents of a communication register. Algorithms are modeled as state machines performing a sequence of steps. A step consists of reading input and the local state, then performing a state transition and writing output. Communication can be by exchanging messages over the communication channels. An algorithm may be randomized, i.e., have access to a source of randomness (a random number generator or a random coin flip). If an algorithm is not randomized, we will call it deterministic. A related characteristic of a system model is its execution semantics. In self-stabilization, this has been encapsulated within the notion of a scheduler or daemon (also demon). Under a central daemon, at most one processing element is allowed to take a step at the same time.

17.3 Definition of Self-Stabilization We have seen an informal definition of self-stabilization at the beginning. Formally, we define self-stabilization for a system S with respect to a predicate P over its set of global states, where P is intended to identify its correct execution [81]. States satisfying P are called legitimate states and those not satisfying P are called illegitimate states. We use the terms safe and unsafe interchangeably with legitimate and illegitimate, respectively. A system S is self-stabilizing with respect to predicate P if it satisfies the following two properties: 1. Closure — P is closed under the execution of S. That is, once P is established in S, it cannot be falsified. 2. Convergence — Starting from an arbitrary global state, S is guaranteed to reach a global state satisfying P within a finite number of state transitions. Arora and Gouda [13] introduced a more generalized definition of self-stabilization, called stabilization, which is defined as follows. We define stabilization for a system S with respect to two predicates P and Q, over its set of global states. Predicate Q denotes a restricted start condition. S satisfies Q → P (read as Q stabilizes to P) if it satisfies the following two properties: 1. Closure — P is closed under the execution of S. That is, once P is established in S, it cannot be falsified. 2. Convergence — If S starts from any global state that satisfies Q, then S is guaranteed to reach a global state satisfying P within a finite number of state transitions. Note that self-stabilization is a special case of stabilization where Q is always true, that is, if S is self-stabilizing with respect to P, then this may be restated as TRUE → P in S. Next, we define two terms that relevant to the discussion of self-stabilization.

622

Reachable Set: Often when a programmer writes a program, he does not have a particular definition of safe and unsafe states in mind but develops the program to function from a particular set of start states. In such siruations, it is reasonable to define as safe those states that are reachable under normal program execution from the set of legitimate start states. These states are referred to as the reachable set. So, when we say that a program is self- stabilizing without mentioning a predicate, we mean with respect to the reachable set. By definition, the reachable set is closed under program execution, and it corresponds to a predicate over the set of states [81]. We use transient failure model in the discussion. Transient failure: A transient failure is temporary (short lived) and it does not persist. A transient failure may be caused by corruption of local state of processes or by corruption of chennels or shared memory. A transient failure may change the state of the system, but not its behavior.

Randomized and Probabilistic Self-Stabilization Randomized methods for self-stabilization are useful in achieving self-stabilization under process symmetry (i.e., all processes are identical). Depending on the stabilization time, self-stabilization can be classified as randomized and probabilistic self-stabilization. Randomized Self-Stabilization: A system is said to be randomized self-stabilizing system, if and only if it is self-stabilizing and the expected number of rounds needed to reach a correct state (legal state) is bounded by some constant k. Probabilistic Self-Stabilization: A system S is said to be probabilistically self stabilizing with respect to a predicate P if it satisfies the following two properties: 1. Closure: P is closed under the execution of S. That is, once P is established in S, it cannot be falsified. 2. Convergence: There exists a function f from natural numbers to [0,1] satisfying lim k→∞ f(k) = 0, such that the probability of reaching a state satisfying P, starting from an arbitrary global state within k state transitions, is 1- f(k). Pseudo-stabilizing system is one, which if started in an arbitrary state is guaranteed to reach a state after which it does not deviate from its intended specification. A stabilizing system is one, which if started at an arbitrary state is guaranteed to reach a state after which it cannot deviate from its intended specification. Thus, the difference between the two notions comes down to the difference between cannot and does not - a difference that hardly matters in many practical situations. The stronger requirement of self-stabilization is advantageous over pseudo-stabilization in finite-state systems, since self-stabilization property implies a bounded convergence span while the pseudo stabilization does not. Algorithms have been proposed for probabilistic orientation of an

623

asynchronous bi-directional ring, as well as for a synchronous ring with odd number of processes and one token. In the next section, we discuss the issues in the design of self-stabilization algorithms.

17.4 Issues in the design of self-stabilization algorithms A distributed system comprises of many individual units and many issues arise in the design of self-stabilization algorithms in distributed system. Some of the main issues are as follows: 1. Number of states in each of the individual units in a distributed system 2. Uniform and Non-uniform Algorithms in distributed systems 3. Central and Distributed Demon 4. Reducing the number of states in a token ring 5. Shared memory models 6. Mutual exclusion. 7. Costs of self-stabilization

Dijkstra’s Self-Stabilizing Token Ring System We explain the above stated issues with the help of Dijkstra’s landmark self-stabilizing token ring system [36]. His system consisted of a set of n finite state machines connected in the form a ring. He defines a privilege of a machine to be the ability to change its current state. This ability is based on a Boolean predicate that consists of its current state and the states of its neighbors. When a machine has a privilege, it is able to change its current state, which is referred to as a move. Furthermore, when multiple machines enjoy a privilege at the same time, the choice of the machine that is entitled to make a move is made by a central demon, which arbitrarily decides which privileged machine will make the next move. A legitimate state must satisfy the following constraints: 1. There must be at least one privilege in the system (liveness or no deadlock). 2. Every move from a legal state must again put the system into a legal state (closure). 3. During an infinite execution, each machine should enjoy a privilege an infinite number of times (no starvation). 4. Given any two legal states, there is a series of moves that change one legal state to the other (reachability). 624

Dijkstra [36] considered a legitimate (or legal) state as one in which exactly one machine enjoys the privilege. This corresponds to a form of mutual exclusion, because the privileged process is the only process that is allowed in its critical section. Once the process leaves the critical section, it passes the privilege to one of its neighbors. With this background, let us see how the above stated issues effect the design of a selfstabilization algorithm.

17.4.1 The Number of States in Each of the Individual Units An interesting issue in self-stabilizing systems is the number of states that each machine is required to have. Dijkstra offered three solutions for a directed ring with n machines, 0, 1, . . . . . . . . . , n-1, each having K states, (i) K ≥ n, (ii) K=4, (iii) K=3. It was later proven that a minimum of three states is required in a self-stabilizing ring. In all three algorithms, Dijkstra assumed the existence of at least one exceptional machine that behaved differently from the others. The first solution (K ≥ n) is described below.

First Solution For any machine, we use the symbols S, L, and R to denote its own state, the state of the left neighbor and the state of the right neighbor on the ring, respectively. The exceptional machine If L = S then S: = (S+ 1) mod K End If; The other machines If L 6= S then S: = L End If; In this algorithm, except the exceptional machine (machine 0), all other machines follow the same algorithm. In the ring topology, each machine compares its state with the state of the anticlockwise neighbor and if they are not same, it updates its state to be the same as that of its anti-clockwise neighbor. So, if there are n machines and each of them is initially at a random state r d K, then all the machines (except the exceptional machine, machine 0) whose states are not the same as their anticlockwise neighbor are said to be privileged and there is a central demon which decides which of these privileged machines will make the move. Suppose machine 6 (assume n≫6) makes the first move. It is obvious that it’s state is not the same as that of machine 5 and hence it had the privilege to make the move and finally sets its state to be the same as that of machine 5. Now machine 6 loses its privilege as its state is 625

same as that of its anti-clockwise neighbor (machine 5). Next, suppose machine 7, whose state is different from the state of machine 6, is given the privilege. It results in making the state of machine 7 same as that of machine 6. Now machines 5, 6 and 7 are in the same state. Eventually, all the machines will be in the same state in the similar manner. At this point, only the exceptional machine (machine 0) will be privileged as its condition L = S is satisfied, i.e., it’s state is same as that of its anti-clockwise neighbor. Now there exists only one privilege or token in the system (at machine 0). Machine 0 makes a move and changes its state from S to (S+1) mod K. This will make the next machine, machine 1, privileged as its state is not the same as its anti-clockwise neighbor, i.e., machine 0. Thus, it can be interpreted as the token is currently with machine 1. Machine 1, as per the algorithm, changes its state to the same state as that of machine 0. This will move the token to machine 2 as its state is now not same as that of machine 1. Likewise, the token keeps circulating around the ring and the system is stable. This is a simple algorithm, but it requires a number of states, which depends on the size of the ring, which may be awkward for some applications.

Second Solution The second solution uses only 3-state machines and is presented below. The state of each machine is in {0, 1, 2}. In the first algorithm, there is only one exceptional machine, machine 0. In the second solution, there are two such machines, machine 0, referred to as the bottom machine and machine n-1, referred to as the top machine. Algorithm The bottom machine, machine 0 If (S+ 1) mod 3 = R then S: = (S - 1) mod 3 The top machine, machine n - 1 If L = R and (L + 1) mod 3 6=S then S: = (L+1) mod 3 The other machines If (S + 1) mod 3 = L then S: = L If (S + 1) mod 3 = R then S: = R

In this algorithm, the bottom machine, machine 0 behaves, as follows: If (S+ 1) mod 3 = R then

626

S: = (S - 1) mod 3 Thus, the state of the bottom machine depends upon its current state and the state of its right neighbor. The condition (s+1) mod 3 covers the three possible states; for s=0, 1, 2, we have (s+1) mod 3 = 1, 2, 0. These result in the following three possibilities: 1. if s=0 and r=1, then the state of s is changed to 2 2. if s=1 and r=2, then the state of s is changed to 0 3. if s=2 and r=0 then, the state of s is changed to 1

The top machine, machine n - 1, behaves as follows: If L = R and (L + 1) mod 3 6= S then S: = (L+1) mod 3 The state of the top machine depends upon both its left and right neighbors (the bottom machine). The condition specifies that the left neighbor (L) and the right neighbor (R) should be in the same state and (L+1) mod 3 should not be equal to S. (Note that (L+1) mod 3 is 1, 2, 0 when L is 0, 1, 2, respectively) Thus, the state of the top machine is as follows: 1. 1, when its left neighbor is 0 2. 2, when its left neighbor is 1 3. 0 when its left neighbor is 2

All other machines behave as follows: If (S + 1) mod 3 = L then S: = L If (S + 1) mod 3 = R then S: = R While finding out the state of the other machines (machine 1, 2 in the example below), we first compare the state of a machine with its left neighbor: 1. If s=0 and L=1, then s=0 2. If s=1 and L=2, then s=2 3. If s=2 and L=0, then s=1 627

If the above conditions are not satisfied, then the machine compares its state with its right neighbor.

628

State of machine 0 0 2 2 2 1 1 1 1 1 0 0 0 0

State of machine 1 1 1 2 0 0 1 1 1 2 2 0 0 0

State of machine 2 0 0 0 0 0 0 1 2 2 2 2 0 0

State of machine 3 2 2 2 2 2 2 2 2 2 2 2 2 1

Privileged machines 0,2,3 1,2 1 0 1 2 2 1 0 1 2 3 2

Machine to make move 0 1 1 0 1 2 2 1 0 1 2 3 2

Table 17.1: An Example of Dijkstra’s Three-State Algorithm

A sample execution of Dijkstra’s three-state algorithm is shown in Table 17.1. The example is for a ring of four processes (0, 1, 2, 3). Machine 0 is the bottom machine and machine 3 is the top machine. The last column in the table gives the number of the machine chosen to make the next move. Initially, three privileges exist in the system. The number of privileges decreases until there is only one privilege in the system. We make the following observations: 1. There are no deadlocks in any state (at least one privilege is present). 2. The closure property is satisfied (the system moves from a legal state to a legal state). 3. No starvation (each machine has a chance of making more than 1 move). 4. Reachability (there are always a series of moves to reach from one legal state to other). All four properties given in the beginning of this section are satisfied. So the system is stabilized.

Special Networks In the above two algorithms, each processor needs K states and 3 states, respectively. There are special networks, where the number of states required by each processor is two. A network organized like the one in Figure 17.1 needs only two states per machine. The algorithm uses information from all of its neighbors. The following algorithm uses s[i] to denote the state of machine i and there are two possible states for each machine, 0 and 1. In the algorithm, 629

1

3

5

7

2n−3

2n−1

0

2

4

6

8

2n−2

Figure 17.1: A Special Network Needing Only Binary State Machines b is used to denote an arbitrary state (0 or 1) and b∼ is used to denote the complementary state of b. For machine 0: If (s[0], s[1]) = (b∼ , b) then s[0]:=b For machine 2n- 1: If (s[2n-1], s[2n-2]) = (b, b) then s [2n-1]:= b∼ For even numbered machines: If (s[2i-2], s[2i-1 ], s[2i], s[2i+1 ]) = (b, b, b∼ , b) then s[2i]:= b For odd numbered machines: If (s [2i-2], s [2i-1], s [2i], s [2i +1]) = (b, b, b, b∼ ) then s [2i-1]:= b∼ In this algorithm, each machine must examine the states of all its neighbors. Thus, a large atomicity is assumed because each machine must be able to examine the states of all its neighbors in one atomic step. The algorithm also requires an even number of machines (at least 6). However, the algorithm shows that self-stabilizing algorithms requiring a small number of states can be designed. Dolev et al.’s Solution For a system with odd number of machines in a ring, a solution for self-stabilization by Dolev et al. [42] is as follows: Each node has two states, 0 and 1. Given a global state, the nodes make moves according to the following rules: 1. If the local state is different from its left neighbor’s state, then the state is changed to be the same as its left neighbor. 2. If the local state is the same as its left neighbor’s state, the state is chosen randomly from 0 and 1. At each step, the nodes make their moves in synchronization. A node has a privilege if its state is the same as its left neighbor’s state. It is shown, using probability theory, that eventually only 630

one privilege exists in the system. This algorithm requires that the nodes operate synchronously, but it shows that the number of states required for each node may be reduced using a probabilistic algorithm.

17.4.2 Uniform Vs. Non-uniform Networks Whether processes are uniform or nor is an important aspect of self-stabilization. In a distributed, it is desirable to have each machine use the same algorithm. In self-stabiliziing systems, it is desirable to have non-uniformity among machines. In the preceding section, at least one of the machines had a privilege and a move that was different from the rest of the machines. These machines are known as exceptional machines, and the algorithms are non-uniform. The individual processes can be anonymous, meaning they are indistinguishable and all run the same algorithm. Often, anonymous networks are called uniform networks. A network is semiuniform if there is one process (the root) which executes a different algorithm. While there is no way to distinguish nodes, in uniform or semi-uniform algorithms nodes usually have a means of distinguishing their neighbors by ordering the incoming communication links. In the most general case it is assumed that processes have globally unique identifiers. Self-stabilization algorithms for distributed systems should be uniform, but this is not always possible. As a simple example, consider the ring of four processors shown in Figure 17.2.

0

1

2

3

Figure 17.2: A Ring of Four Processors Assume there is a uniform self-stabilizing algorithm for this ring. In a distributed system, the state of a machine/process is changed depending on the state of its neighbors. In this example, if all processors have the same state when started, all must have privileges because there must be at least one privilege in the system (property 1 of a legal state). Note that 0 and 2 make a move (because if one makes a move, it does not affect the neighbors of the other), and change their states. In this example, 0 and 2 make an independent set. After the transition, 0 and 2 are in the same state and so are 1 and 3. The system is partitioned into two sets: {0,2} and {1,3}. At least two machines must have a privilege because 0 and 2 have the same states and also their neighbors 1 and 3 have the same states. Thus once again, machines 0 and 2 can make moves and leave the network in a similar situation. The scenario with 1 and 3 is also the same, they both are in the same state and their neighbors 0 and 2 are in the same state. So, if 1 has privilege, then 3 will also have privilege and 631

both machines can make moves and leave the network in a similar situation. So, in either case, there will be two privileged machines at any time in the network. Even though uniformity is a desirable property, most algorithms that have been developed to use at least one exceptional machine. However, uniformity is sometimes attainable. A uniform self-stabilizing algorithm for a ring of n processors, where n is prime, was developed by Burns and Pachl [22] and it was observed that for a ring of composite size, the algorithm failed only because it could deadlock. Thus, if deadlock can be tolerated or can be corrected easily from outside the system, then the algorithm may be useful. These examples show that uniformity may be achieved if we are willing to sacrifice a property of self-stabilization.

17.4.3 Central and Distributed Demons Dijkstra original assumption was that there is a central demon that decides which machine with a privilege will make the next move. However, the presence of a central demon is an undesirable constraint. A distributed demon is more desirable where each privileged machine makes its own decision on whether to make a move. In a self-stabilizing system without a central demon, each machine makes a decision locally. Even though the decisions are made locally, these decisions will eventually take the system towards a global goal. Once this global goal is achieved, the system is self-stabilized. Interestingly, many early algorithms (e.g., Dijkstra’s three, four, and K-state algorithms) were developed assuming the presence of a central demon and they did not deal with the possibility of having a distributed demon, yet these algorithms also work with distributed demons. Even though a central demon is not desirable, it is usually easier to verify a weak correctness criterion on an algorithm using this assumption. For this reason, self-stabilizing systems are often developed assuming the presence of a central demon. After the weak correctness is verified, the system is examined to see if it is still self-stabilizing when the assumption of a central demon is removed. If it is not, the algorithm is extended so that a central demon is not necessary. Burns et al. [23, 25] examine the extensibility of some algorithms. They showed that letting all machines operate simultaneously will not affect the correctness of some algorithms. Such interleaving assumption is very useful in the verification of self-stabilizing systems. As an example, Burns et al. [25] verified that Dijkstra’s algorithms are correct even in the presence of a distributed demon. Originally, Dijkstra’s algorithms were only proven to be correct in the presence of a central demon. However, they showed that the central demon assumption is unnecessary for both, the three and four state algorithms. The K-state solution is shown to be valid for a distributed demon only if K > n (n is the number of machines), because there is a cycle of illegal global states if K = n. Burns et al. also developed results, which can be used to show an algorithm that is correct in the presence of a central demon, is also correct when the central demon assumption is removed. This is useful in the verification process because once the algorithm is verified in the presence of a central demon, the algorithm may be correct even when the central demon assumption is lifted without any modification to the algorithm. This of course may not be the case for all algorithms, 632

but these results can be helpful in the process of verification.

17.4.4 Reducing the number of states in a token ring A natural question is: what is the number of states of a machine to achieve self-stabilization in various configurations. Clearly, the objective is to minimize the number of states of a machine for efficient implementation. It has been shown that if self-stabilization is not a requirement, then there exists an asymmetric token ring with two states per machine. In a self-stabilizing token ring with a central demon and deterministic execution, Ghosh [51] showed that a minimum of three states per machine is required. However, for a non-ring topology, the number of states can be reduced to two per machine. There exists a non-trivial self-stabilizing system with two states per machine [49]. It requires a high degree of atomicity in each action. Each non-exceptional process reads from three of its neighbors. Thus, obviously, the topology is non-ring. Herman [61] presented a unidirectional and symmetric solution, that has only two states, for a "probabilistically" self-stabilizing synchronous token ring with randomized actions. a solution requiring two states per machine exists. Flatebo and Datta [43] developed a two-state, unidirectional and asymmetric solution for a "probabilistically" self-stabilizing token ring with randomized actions under the assumption of a randomized central demon. With a randomized central demon, a demon is chosen randomly among privileged machines and it minimizes the problem of malicious scheduling on the part of the demon. Thus, it appears that to obtain self-stabilizing systems with two states per machine, we must either relax the objective to "probabilistic self-stabilization" using randomized actions, or use a non-ring topology with higher atomicity in the actions.

17.4.5 Shared memory Models Distributed systems having shared memory between machines where processes communicate with each other by reading and writing to registers have also been used as self-stabilizing systems. In this type of model, no processor has direct access to the state of its neighbors, and the only way to determine it is by passing information through shared registers. If two processors, Pi and Pj are neighbors, then there are two registers, i and j between the two nodes. To communicate, Pi writes to i and reads from j and Pj writes to j and reads from i. It is convenient to represent a distributed system by a graph in which each processor is represented by a node and the neighboring nodes are connected by a link which shows the communication between a node and its neighbors. The self-stabilization algorithms work for an arbitrarily connected graph. They also work if the graph is changed during execution (due to a node failure, etc.). So, there is no problem even if the system is dynamic. In a self-stabilization algorithm, eventually only one process can change a register at any instance, and this is when the system is stabilized. The only assumption made is that all read/write operations performed on the registers are atomic. Later in this chapter, we study a dynamic self-stabilizing algorithm. 633

Dolev et al. [42] present a dynamic self-stabilizing algorithm for mutual exclusion. The algorithm only requires that all nodes be connected (that is, the network should not be partitioned). Node failures may cause an illegal global state, but the protocol is dynamic and self-stabilizing. This means that even with a node failing, the system will again converge to a legal state. If a node is restarted, an illegal global state may again occur, but the system will automatically correct itself. The size of the registers are on the order of log (n), where n is the number of processors. The only assumption made is that the read/write operations on the registers are atomic. This weak assumption makes the implementation of the algorithm feasible.

17.4.6 Mutual Exclusion In previous sections, we discussed self-stabilizing systems where there is only one action being done after a finite amount of time. The action could be changing a state or the contents of a register. In a mutual exclusion algorithm, each process has a critical section of code, and only one process can enter its critical section at any time, and every process that wants to enter its critical section, will be allowed to enter its critical section within a finite amount of time. If a process has a privilege, it can enter its critical section, and once it is finished (execuing the critical section), it passes the privilege to the neighbor. If the process does not want to enter its critical section, it simply passes the privilege to its neighbor. Since the self-stabilizing algorithms mentioned adhere to the four properties discussed previously, mutual exclusion is also satisfied. Since eventually, there is only one privilege in the system and each process enjoys a privilege an infinite number of times, a process is guaranteed to enter its critical section in a finite amount of time. A self-stabilizing mutual exclusion system can also be described in terms of a token system. A token system has the processes circulating tokens. If a process has one of these tokens, it is allowed to enter its critical section. Brown, Gouda, and Wu [21] used this system to develop self-stabilizing mutual exclusion systems. At first, there may be more than one token in the system, but after a finite amount of time, only one token exists in the system which is circulated among the processes. Such systems are easier to implement in circuits, and Brown et al. showed how the implementation is done using flip-fops. All of the models, token systems, privileges, shared memory, are forms of mutual exclusion, and the algorithms also tolerate node failures and restarts or a bad initialization. So these algorithms are more tolerant of errors than other mutual exclusion algorithms.

17.4.7 Costs of self-stabilization The definition of self-stabilization does not put any upper bound on the number of transitions required by the system to reach a safe state starting from an unsafe one. Thus, the system might remain in an unsafe state for a considerable amount of time before reaching a safe state. A study and assessment of these cost factor is very important in any practical implementation. Gouda and Evangelist [55] introduced the following two concepts related to the cost of selfstabilization:

634

• Convergence span: It is the maximum number of transitions that can be executed in a system, starting from an arbitrary state, before it reaches a safe state. • Response span: It is the maximum number of transitions that can be executed in a system to reach a specified target state, starting from some initial state. The choice of initial state and target state depends upon the application. Clearly, the aim of the designer of a self-stabilizing algorithm is to reduce the convergence span and response span. Time-complexity measure for self-stabilizing algorithms is the number of rounds. In synchronous models, algorithms execute in rounds, i.e., processors execute steps at the same time and at a constant rate. Rounds can be defined in asynchronous models too, where the first round ends in a computation when every processor has executed at least one step. In general, the ith round ends, when every processor has executed at least i steps. Generally, communication between any two processors in a particular system takes at least (d) rounds. This is because it normally takes at least one round to propagate information between two adjacent processors.

17.5 Methodologies for designing self-stabilizing systems Having seen the issues in the design of self-stabilizing system, let us now discuss the methodologies for designing self-stabilizing systems. Self-stabilization is characterized in terms of a "malicious adversary" whose objective is to disrupt the normal operation of the system. This adversary (e.g., a virus or a hardware problem) may destroy some portions of the system, or disrupt the operation of one or more portions. Furthermore, it might not be possible for a system to detect that it has been "attacked", as soon as the attack appears. To be called self-stabilizing, a system must have the capability to recover normal operation when exposed to such attacks. If the system (or parts of it) is destroyed completely, so that it is no longer possible for the system to operate, then no self-stabilizing system can work. The adversary succeeds in achieving his goals. However, if enough components are left for the system to operate, then a self-stabilizing system will slowly resume normal operation after the attack. It is up to the designer to decide under what conditions the system may be termed "completely destroyed" or "still capable of operating".

Layering and Modularization The most commonly used techniques for building self-stabilizing systems are layering and modularization. The basic idea is to divide the system into smaller component, make each component self-stabilizing independently, and then integrate them to compose the system. Self-stabilization is amenable to layering because the self-stabilization relation is transitive, i.e. if P→Q (P Stabilizes Q) and Q→R, then P→R. Thus, different layers of self-stabilizing programs (each by itself self stabilizing) can be composed. First step is to build a self-stabilizing "platform" 635

and any program written on that platform automatically becomes self-stabilizing. The basic idea behind a self-stabilizing platform is to provide primitives using which, other programs can be written. To develop self-stabilizing systems using the technique of layering, we require primitives to provide structures on which algorithms may be built. There are two basic structuring mechanism primitives: common clock primitives, and topology based primitives.

(1) Common Clock Primitives Unison is the process of maintaining time through the use of local clocks in shared memory systems. The properties required here are the safety property and the progress property. For a synchronous shared memory system, safety and progress properties for unison are as follows: • Safety: All clocks have the same value. • Progress: At each step, each clock is incremented by the same amount. For asynchronous systems with shared memory, safety and progress properties for unison are as follows: • Safety: Clocks of two neighboring nodes can differ by at most 1. • Progress: A clock is incremented to i +1 when clocks at all neighboring nodes have value i or i +l.

(2) Topology Based Primitives Leader election is perhaps the most basic primitive with respect to an arbitrary dynamic topology. Once a leader has been found, a spanning tree might be constructed. Algorithms for mutual exclusion and reset can be easily developed on the top of self-stabilizing spanning tree algorithms for arbitrarily connected graphs. We now discuss two examples of self-stabilizing programs, namely, mutual exclusion and reset, developed using the concept of layering. Example 1: A two-layered self-stabilizing algorithm for mutual exclusion [42]. The first layer creates a spanning tree from an arbitrarily connected graph, whose topology might change dynamically with the exception of a distinguished process (the root). The selfstabilizing spanning tree protocol is based on breadth first search of the graph, rooted at the distinguished node. The distinguished node is needed to break symmetry and all other nodes execute identical program. The second layer achieves mutual exclusion on a dynamic tree structured system. It is a token based system. When a node receives the token/privilege, it executes its critical section (if it wants 636

to) and then it passes to the token to its children in left-to-right order. Thus, the token traverses the tree in depth first manner. Finally, the two protocols are superposed to obtain a single self-stabilizing protocol for mutual exclusion an arbitrarily connected graph. Example 2: Self-stabilizing reset algorithm for asynchronous shared memory system [13]. Arora and Gouda [13] used layering technique to develop a self-stabilizing reset algorithm for asynchronous shared-memory systems. The algorithms allows dynamic topology as long as the underlying graph remains connected. There is no distinguished process, hoever, each process has a unique identifier. The algorithm consists of three layers. In the first layer, a root is elected forming a spanning tree. In the second layer, the root initiates a diffusing computation in which reset requests are propagated to the leaf nodes and are refelected back to the root node. The reset request passes through every node, detecting any anomaly in the global state. When the reset returns to the root, the reset is complete. A self-stabilizing "platform" resets the system upon encountering an illegitimate state. Platform writes to variables of original program only if an illegitimate state is detected. Platform does not affect the original program under normal execution.

17.6 Communication Protocols A communication protocol is a collection of processes that exchange messages over communication links in a network. A protocol may be adversely affected for several reasons: (1) Initialization to an illegal state. (2) A change in the mode of operation. Not all processes get the request for the change at the same time, so an illegal global state may occur. (3) Transmission errors because of message loss or corruption. (4) Process failure and recovery. (5) A local memory crash which changes the local state of a process. Previously, these five types of errors have been treated separately. However, if a protocol is self-stabilizing, they will all be corrected in a finite number of steps, regardless of the reason for the loss of coordination. A communication protocol is stabilizing if and only if starting from any unsafe state (i.e., one that violates the intended invariant of the protocol), the protocol is guaranteed to converge to a safe state within a finite number of state transitions. Stabilization allows the processes in a protocol to reestablish coordination between one another whenever coordination is lost due to some failure. 637

Gouda and Multari [57] showed that a communication protocol must satisfy the following three properties to be self-stabilizing: 1. It must be non-terminating. 2. There are an infinite number of safe states. 3. There are timeout actions in a non-empty subset of processes. Self-stabilizing systems can automatically recover from arbitrary state perturbations in finite time. They are therefore well-suited for dynamic, failure prone environments. Spanning-tree construction in distributed systems is a fundamental operation that forms the basis for many other network algorithms (like token circulation or routing). Next, we discuss some self-stabilizing algorithms that construct a spanning tree within a network of processing entities. Let us consider an arbitrary distributed algorithm, e.g., for termination detection, and start it in a state where one of its variables has been set to a random value from its domain. Usually, the behavior is not predictable: either the algorithm will output garbage (e.g., declare a computation as finished although it is still running), or (most probably) it will deadlock (e.g., it will fail to output anything at all). It may be argued that changing the value of a variable is unfair: no algorithm can tolerate such manipulations since algorithms have to rely on proper initialization. This argument, however, is not true. Self-stabilizing algorithms are guaranteed to recover from an arbitrary perturbation of their local state in a finite number of execution steps. This means that the variables of such algorithms do not need to be initialized properly. If we assign each variable an arbitrary value from its domain, still the algorithm will eventually start to behave as expected. Arbitrary state perturbations can also happen without curious users playing around with their algorithm: Cosmic rays in spacecraft for example can arbitrarily change the contents of memory cells in random access memory. Selfstabilizing algorithms have the desirable property to recover from such faults automatically.

17.7 Self-Stabilizing Distributed Spanning Trees In distributed systems, a spanning tree is the basis for many complex distributed protocols. To define a spanning tree, the network is modeled as a graph G = (V, E) where V is the set of network nodes (vertices) and E is the set of communication links (edges) between network nodes (formally it is a subset of E × E). A spanning tree T = (V, E′ ) of G is a graph consisting of the same set of nodes V, but only a subset E′ of edges E such that there exists exactly one path between every pair of network nodes. Basically, this means that the graph is connected and it does not contain cycles. A basic theorems of spanning trees states that in a network of n nodes, the tree contains exactly n-1 communication links. A spanning tree of a graph is in general not unique (even if the root node is fixed). Figure 17.3 shows an example of a network of five nodes and a spanning tree of the network [45]. 638

P1

P2

P4

P3

P1

P5

P2

P4

P3

P5

Figure 17.3: A network and its spanning tree A spanning tree in a network is often a prerequisite for more involved network protocols like routing or token circulation. It generally increases the efficiency of network protocols. For example, consider the problem of broadcasting messages in the network. There are algorithms which flood the network, i.e., the broadcast message is recursively sent to all neighbors. Consequently, the message crosses all communication links before the protocol terminates. However, if a spanning tree of the network is available, the message only needs to be sent along all the edges of the spanning tree. Instead of crossing all E links, the message just crosses n-1 links. Since |E| is usually significantly larger than n-1, a spanning tree can considerably reduce the message complexity of the broadcast algorithm. Two kinds of spanning trees may be distinguished: breadth-first search (BFS) trees result from a breadth-first traversal of the underlying network topology. Similarly, depth-first search (DFS) trees are obtained from a depth-first traversal. A notion underlying DFS and BFS trees is that of a rooted tree. A rooted spanning tree is a spanning tree of the network where the tree edges are consistently directed with respect to a particular node (the root). Edges can be directed towards the root or “away from” the root. Rooted spanning trees have a notion of “parent” and naturally result from the execution of semi-uniform algorithms. In fact, since almost all algorithms use a single pointer (to a neighbor, the parent) to store the structures of the tree, all these algorithms implicitly construct a rooted spanning tree. In spanning-tree construction, it is impossible to deterministically construct a spanning tree in uniform networks. Intuitively, this is caused by problems of symmetry, and so at least a semiuniform setting (e.g., a distinguished root processor) or a source of randomization is needed. Time-complexity of self-stabilizing algorithms is often measured by the number of rounds. In self-stabilizing spanning-tree construction, an arbitrary initial state may make it necessary to propagate information through the entire network. Therefore, a general lower bound of (d) rounds can be assumed for self-stabilizing spanning-tree algorithms. By combining the algorithm with a hierarchical structure and sacrificing true distribution, this bound can be lowered. Space complexity measures the the amount of state necessary to perform self-stabilizing spanning tree construction. Dolev, Gouda, and Schneider [40] derived the following result on lower bounds regarding the space complexity: self-stabilizing spanning tree construction needs at least (log n) bits per processor if the algorithm is silent (i.e., if the contents of the communication registers eventually stop changing). If the algorithm is not required to be silent, Johnen [68] showed that 639

it is possible to construct an algorithm using only O(1) bits per edge in a uniform rooted network with a central daemon.

17.8 Self-Stabilizing Algorithms for Spanning-tree Construction In this section, we discuss a set of representative self-stabilizing algorithms for constructing spanningtrees [45].

17.8.1 Dolev, Israeli, and Moran Algorithm Dolev, Israeli and Moran [42] developed a self-stabilizing BFS spanning-tree construction algorithm for semi-uniform systems with a central daemon under read/write atomicity. In the algorithm, every node maintains two variables: (1) a pointer to one if its incoming edges (this information is kept in a bit associated with each communication register), and (2) an integer measuring the distance in hops to the root of the tree. The distinguished node in the network acts as the root. The algorithm works as follows: The network nodes periodically exchange their distance value (current distance from the root node) with each other. After reading the distance values of all neighbors, a network node chooses the neighbor with minimum distance dist as its new parent. It then writes its own distance into its output registers, which is dist + 1. The distinguished root node does not read the distance values of its neighbors and always sends a value of 0. The algorithm stabilizes starting from the root process. After sufficient activations of the root, it has written 0 values into all of its output variables. These values will not change anymore. Note that without a distinguished root process the distance values in all nodes would grow without bound. More specifically, after reading all neighbors values for k times, the distance value of a process is at least k + 1. This means, that after the root has written its output registers, the direct neighbors of the root- after inspecting their input variables - will see that the root node has the minimum distance of all other nodes (the other nodes have distance at least 1). Hence, all direct neighbors of the root will select the root as their parent and update their distance correctly to 1. This line of reasoning can be continued incrementally for all other distances from the root. That is, after all nodes at distance d from the root have computed their distance from the root correctly and written it in their registers, their registers no longer change and nodes at distance d+1 from root are ready to compute their distance from the root. After O(δ) update cycles, the entire tree will have stabilized. Dolev et al.’s self-stabilizing algorithm for constructing spanning trees is shown in Algorithm 2. Two neighbors Pi and Pj communicate with each other by reading from and writing to two shared registers, rij and rji . To communicate, Pi writes to rij and reads from rji and Pj writes to rji and reads from rij . The root node repeatedly writes values in the registers of all of its neighbors. All other processors repeatedly perform the following steps: in each iteration, the processor reads the reg640

isters of all of its neighbors and computes the a value for variable dist as follows: it chooses the minimum distance of their neighbors, sets it dist variable to the minimum distance plus 1, and updates the registers of its neighbors. The internal vriable corresponding to register rij is denoted by lrij . It stores the last value of rji that is read by Pi . A snapshot of the system state in Dolev et al.’s self-stabilizing algorithm is given in Figure 17.4. Variables: no_neighbors = Number of processor’s neighbors i = the writing processor m = for whom the data is written lrji (local register rji) the last value of rji read by Pi Root Node: {do forever} while TRUE do for m := 1 to no_neighbors do write lrim := end end Other Nodes: {do forever} while TRUE do for m := 1 to no_neighbors do lrmi := read(lrmi ) FirstFound := false dist := 1 + min(lrmi .dist) ∀ m: 1≤m≤ no_neighbors for m := 1 to no_neighbors do if not FirstFound and lrmi .dis = dist -1 write rim := FirstFound := true else write rim := end end end Algorithm 2: Dolev et al.’s Spanning-tree Construction Algorithm for Pi Dolev et al.’s self-stabilizing algorithm has been used as the basis for a topology update algorithm in dynamic networks. Based on the similar idea, Collin and Dolev [32] present a semiuniform spanning-tree algorithm under a central daemon and read/write atomicity that constructs a DFS tree (instead of a BFS tree). A similar algorithm which also constructs a DFS tree but uses composite atomicity was developed by Herman. In this algorithm, the outgoing links at every process are ordered, and the DFS tree is defined as the tree resulting from a DFS graph traversal always selecting the smallest outgoing edge. Instead of writing its current level into the output 641

The distributed system − computation step r12 : m 1

r12 : x

r12 : m 1

P1 writes m1

P1

P2 reads P1

P2

P2

P1

P2

m1

Spanning−tree, system and code r21 :

1

parent = 1 dist = 1 2

5 4

r58 :

7

parent = 8

r73 :

dist = 3 6

parent = 2 dist = 3

8 3

Figure 17.4: An example of Dolev et al.’s algorithm registers, it writes a representation of its current estimate of the path (the sequence of outgoing link identifiers) to the root. The root repeatedly writes the “empty path” (⊥) to its output registers. If a node has k neighbors, there are k alternative paths to choose from. From these, the node chooses the path which is minimal according to a lexicographic order which prefers smaller link identifiers. For example, (⊥) < (⊥, 1) < (⊥, 1, 1) < (⊥, 2) < (1). Thus, a node does not choose the shortest path to the root but a path along the smallest link identifiers. The memory requirement for the DFS algorithm is O(n logK) bits where K is an upper bound on the maximum degree of a node. The time complexity is O(δnK) rounds.

17.8.2 Afek, Kutten, and Yung Algorithm for Spanning-tree Construction The algorithm by Afek, Kutten and Yung [3] constructs a BFS spanning-tree in the read/write atomicity model. However, this algorithm does not make the assumption of a distinguished root process. Instead, it assumes that all nodes have globally unique identifiers which can be totally ordered. The node with the largest identifier will eventually become the root of the tree. The idea of the algorithm is as follows: Every node maintains a parent pointer and a distance variable like in the Dolev, Israeli, and Moran algorithm. In addition, it stores the identifier of the root of the tree in which it thinks it is present. Periodically, nodes exchange this information. If 642

a node notices that it has the maximum identifier in its neighborhood, it makes itself the root of its own tree. If a node learns that there is a tree with a larger root identifier nearby, it joins this tree by sending a “join request” to the root of that tree and receiving a “grant” back from the root. Local consistency checks ensure that cycles and fake root identifiers are eventually detected and removed. The algorithm stabilizes in O(n2 ) asynchronous rounds and needs O(log n) space per edge to store the process identifier. Afek et al. argued this is optimal since message communication buffers need to communicate “at least” the identifier.

17.8.3 Arora and Gouda Algorithm for Spanning-tree Construction Arora and Gouda [13] developed a self-stabilizing BFS spanning-tree algorithm for the composite atomicity model under the assumption of a central daemon. Like Afek, Kutten and Yung, they also assume unique identifiers and the node with maximum identifier eventually becomes the root of the system. However, the algorithm needs a bound N on the number n of nodes in the network to work correctly. The bound on the number of nodes is necessary because the algorithm uses a different technique to detect and remove cycles. Every node maintains variables for distance, parent and root identifier. Periodically, every node compares its own distance and root identifier values with the values stores in the node pointed to by the parent variable. In the final spanning tree, the root identifiers should be identical and the distance should be the distance of the parent plus one. If this is not the case, the root identifier is copied from the parent and the distance is set to the parent’s distance plus one. A node also continuously monitors the root identifier and distance settings of its neighbors. If a neighbor has a larger root identifier or the same identifier with smaller distance, the node adjusts its values accordingly. Cycles are detected in the following manner: If there is a cycle in the tree (or the graph to be precise), say, due to improper initialization, the distance values are incremented along this cycle without bound. Hence, a cycle is detected when the distance value exceedes the bound N. The first node to detect this makes itself the root of a new tree. A bound on the number of nodes in the network, N, allows the Arora and Gouda algorithm to be simpler than the one by Afek, Kutten and Yung. However, the stabilization time in Arora and Gouda algorithm is O(N2 ), which can be much larger than that of Afek, Kutten and Yung, O(n2 ). In dynamic networks where network nodes may go down, a stabilization time in the order of the actual number of nodes is preferable.

17.8.4 Huang et al. Algorithms for Spanning-tree Construction Chen, Yu and Huang [30] developed a self-stabilizing spanning tree algorithm for semi-uniform systems with composite atomicity. It is based on the same idea of cycle breaking (bumping up the distance counter). The fact that there is a distinguished root makes the algorithm even simpler than the one by Arora and Gouda [13]. However, the algorithm does not necessarily stabilize to a BFS 643

tree since the choice of a new parent after a cycle is broken is non-deterministic and is governed by the scheduler. This algorithm was later improved by Huang and Chen [65] to yield an algorithm which constructs a BFS tree using knowledge of the size n of the network.

17.8.5 Afek and Bremler Algorithm for Spanning-tree Construction Afek and Bremler [6] gave a self-stabilizing algorithm for constructing spanning trees for systems with unidirectional, bounded capacity communication links. They assumed node have unique identifiers and adopted the algorithm for the synchronous and the asynchronous networks. The network node with the smallest identifier eventually becomes the root of the spanning tree. The algorithm is based on a new idea called “power supply”. The power supply method exploits the fact that self-stabilizing algorithms must continuously check their own state. Nodes which are part of a spanning tree expect to receive “power” from the root of the tree. Power, like electric current, means a continuous flow of certain messages, one per round. The basic idea is that only legal roots may be the source of power and nodes attached to fake roots eventually fail to receive power and subsequently make themselves the root of a new tree. Whenever a node receives power from a neighbor with a smaller identifier, it attaches itself to its tree. In the asynchronous case, the power supply idea is implemented using different types of messages: weak messages are exchanged periodically between the nodes to synchronize their states, while strong messages carry power. The idea of called power supply imparts the algorithm several interesting features. For example, the algorithm stabilizes in O(n) rounds without processes to have the knowledge of n. Afek and Bremler gave a generic power supply algorithm which can be instantiated to a leader election algorithm, or an algorithm to construct DFS or BFS spanning trees. The spanning-tree algorithms discussed in this section have been applied in many different settings in practice. For example, a variant of the algorithm by Dolev, Israeli and Moran [42] was used to implement a reliable data storage subsystem for the self-stabilizing file system developed at the Ben Gurion University [70]. As another example [45], consider the protocol to eliminate redundant paths in switched Ethernets [31]. If a network segment becomes unreachable or network parameters are changed, the protocol automatically reconfigures the spanning-tree topology by activating a standby path. The protocol can be briefly described as follows: Initially, switches believe they are the root of the spanning tree but they do not forward any packets. Based by a timer, they regularly exchange status information. The status information contains (1) the identifier of the transmitting switch (usually a MAC address), (2) the identifier of the switch which is believed to be the root of the tree, and (3) the “cost” of the path towards the root. A switch uses this information to choose the shortest path towards the root. If there are multiple possible roots, it selects the root with the smallest identifier (lowest MAC address). Links that are not included in the spanning tree are placed in blocking mode and they do not forward packets, but still transport status information.

644

17.9 An anonymous self-stabilizing algorithm for 1-maximal independent set in trees In a distributed system, an independent set is defined as a large subset of nodes that are pairwise nonadjacent. Maximal independent set is a set of nodes such that every node not in the set is adjacent to a node in the set. Maximal independent sets are important in several distributed network applications and several parallel or distributed algorithms have been developed for this task [75]. This algorithm for finding a 1-maximal independent set in a tree uses constant space at each node. Also this algorithm is somewhat unusual in that it stabilizes on all graphs, but is only guaranteed to be correct on some graphs. A distributed system is modeled as a connected, undirected graph G with node set V and edge set E. Two nodes joined by an edge are said to be neighbors and N(i) is used to denote the set of neighbors of node i. A self-stabilizing algorithm is presented as a set of rules, each with a Boolean predicate and an action. A node is said to be privileged if the predicate is true. If a node becomes privileged, it may execute the corresponding action called a move. The assumption is that there exists a central daemon, which at each time-step selects one of the privileged nodes to move (and thus two nodes never move at the same time). When no further move is possible, the system is said to be in a stable configuration. While the definition of self-stabilizing is normally more general, since this is a graph algorithm we say that an algorithm is self-stabilizing if from any initial configuration it always terminates in a legitimate stable configuration after a finite number of moves no matter the selections of the daemon.

Description of algorithm In the algorithm citeShi04 for 1-maximal independent set, each node is in one of a finite number of distinct states. Those nodes in the state called 0 will end up being in the desired set: let us call this set M. A node with no neighbor in state 0 will change to state 0 and a node in state 0 with a neighbor in state 0 will change to something else. This idea readily produces a maximal independent set. To achieve 1-maximality, however, a node must be able to leave set M when that would allow two of its neighbors to enter M. Available neighbors are those which have no other neighbor in state 0: these will be in the state called 1. In order to allow this interchange, we implement a handshaking process: the node offers to leave M by changing to state 0′ , the relevant neighbors agree to enter M by changing to state 1′ , the node leaves by changing to state 2′ , and then the relevant neighbors go in by changing to state 0. Specifically, the set of states is 0, 0′ , 1, 1′ , 2, 2′ . The states with a prime are transition states, used in the hand-shaking process described above. These transition states will be absent when the algorithm terminates if the network satisfies certain properties. For the purpose of the algorithm, the nodes with state 0′ are also considered to be in M. The states 0, 1 and 2 are used to indicate that a node has zero, one, or at least two neighbors in M, 645

respectively. Actions of a node in the algorithm can be summarized as follows: • If not involved in a transition process, then set state to the number of neighbors in state 0 or state 0′ . (The value 2 is used to indicate two or more such neighbors.) • If in state 0 and adjacent to at least two 1s, change state to 0′ . • If in state 1 and adjacent to a 0′ , change state to 1′ . • If in state 0′ and adjacent to at least two 1′ s, change state to 2′ . • If in state 1′ and adjacent to no 0′ , change state to 0. • If in state 2′ and adjacent to no 1′ , change state to 2. The complexity of the actual algorithm arises from invalid initial states and from two interchanges affecting one another. For a state y, we use the notation S(y) to represent the set of nodes in state y. Furthermore, we use the notation S (y1/y2/y3/ ···) to denote S (y1) ∪ S (y2) ∪ S (y3) ···. For example, the notation S (0) denotes the nodes in state 0. The state of a node is stored in a local variable denoted by x. The states with a prime are transition states. We will also identify the prime with a virtual flag—we will say the flag is set when the node is in a transition state, and clearing the flag will mean changing from state i′ to state i. To define the rules of the algorithm, we define the following function f. Let i be a node and t a state. Then we define f i (t) = min {2, | N (i) ∩ S (t)|}. The function fi gives the number of neighbors of node i in a specified state. We further use the notation: f i (x/y/z/ ···) = min {2, f i(x) + f i(y) + f i (z) +···} When the node i is clear from the context, we will drop the subscript from fi . We also utilize the concept of bad edge, which is defined next. The rules will be such that a bad edge can only occur as a result of faulty initialization. A bad edge is an edge connecting two nodes in the following list of pairs of states: 0–0, 0–0′ , 0′ –0′ , 0′ –2′ , 1′ –1′ , and 2′ –2′ . The complete algorithm for finding a 1-maximal independent set is given in Algorithm 3. The algorithm converges in O(mn) time, where m is the number of edges and n is the number of nodes in the network. The algorithm stabilizes to a 1-maximal independent set in O(n2 ) steps in an arbitrary tree. Having seen two regular self-stabilizing algorithms, let us now discuss a probabilistic selfstabilizing algorithm.

17.10 A Probabilistic Self-Stabilizing Leader Election Algorithm We now discuss a probabilistic self-stabilizing leader election algorithm by Dolev et al. [37]. The distributed system consist of n stations (sites) and they need to choose a leader among themselves 646

{* All moves are tried in the listed order *} V1: if flag is set and node is incident on bad edge and after clearing flag node would not be incident on any bad edge then clear flag ′ ′ ′ ′ ′ V2: if flag is clear and x = f (0/ 0 ) and (f (0/ 0 ) ≥ 1 or f (1 / 2 ) = 0) ′ then set x = f (0/ 0 ) ′ ′ C1: if x = 0 and f (1) = 2 and f (0/ 0 / 2 ) = 0 then set x = 0′ ′ ′ ′ C2: if x = 0 and (f (1/ 1 ) ≤ 1 or f (0/ 0 ) ≥ 1) then set x = f (0/ 0′) ′ ′ ′ ′ C3: if x = 0 and f (1 ) = 2 and f (0 / 2 ) = 0 then set x = 2′ ′ ′ C4: if x = 2 and f (1 ) = 0 then set x = f (0/ 0′) ′ ′ ′ C5: if x = 1 and f (0 ) = 1 and f (0/ 1 / 2 ) = 0 ′ then set x = 1 ′ ′ ′ ′ C6: if x = 1 and (f (0 ) 6= 1 or f (0/ 1 / 2 ) ≥ 1) then set x = f (0/ 0′) Algorithm 3: An algorithm for finding a 1-maximal independent set by using a leader election algorithm. The following three possibilities arise: During a time unit, stations can detect either silence, success or collision. Silence in the system implies that no station tried to transmit a message. Success implies that only one station used the channel to transmit a message, and finally, a collision implies that at least two stations attempted to transmit messages. Leader election algorithm is shown in Algorithm 4. {Termination Condition.} If n = 1 then Stop. {Randomized Selection Process} If n ≥ 2 then randomly divide n into (n1 , n - n1 ). If n1 6= 0 then Apply d (n1 ). Else Apply d (n). Algorithm 4: Dolev et al.’s Leader Election Algorithm d (n) If the number of stations is greater than or equal to two, then in the first time unit, all the stations send their messages via the channel and as a result, a collision occurs. First time unit is nothing but the first instance of a time unit. If we consider a station S into consideration, in the next time unit, there are two possibilities: • Case-1) S tries to send the message again or 647

• Case-2) S does not try to send the message again. There are two possibilities for the first case (S tries to send the message again ): success or collision. If result is a success, then S is the leader, else (a collision occurs) S flips a coin (send/not send. For the second case (S does not participate), there are three possibilities: Silence, Success, or Collision. If Silence occurs, then the station S flips the coin (send/not send) again, if Success occurs, then station S detects the leader, and if a collision occurs, S is eliminated. Thus, the algorithm can be written as: n Stations, n≥2 — First time unit: All stations send their messages via the channel; Collision. → Each station flips a coin (send or not send) again. — Next time unit: Case 1− Station S tries again Success: S is the leader Collision: S flips a coin again Case 2− Station S isn’t participating Silence: S flips again the coin Success: S detects the leader Collision: S is eliminated

An Example We now illustrate the algorithm using the example shown in Figure 17.5. Collision

Collision

ABCD

AB

CD

Silence Φ

Sucess

AB

B

A

Figure 17.5: An example.

648

In Figure 17.5 initially (in the first time unit), ABCD try to send messages and a collision occurs. In the next time unit, A and B send message again, while C and D do not participate. Since both A and B try to send their messages, there is a collision again. As a result, C and D are eliminated. Now in the next time unit, both A and B do not participate and the result is a Silence. So both of them flip a coin and B decides to send a message again and A decides not to participate. Since B is the only one sending a message, the result is Success and B is the Leader and the algorithm terminates.

17.11 The role of compilers in self-stabilization A compiler converts a program written in a language into an equivalent program in another language. Typically, the latter is an object program that is to be run on a particular architecture. Formally, a compiler is a homomorphism f: A → B where A and B are two classes of architecture or systems. Then, for each m dA, f (M) mimics the actions of M in some well-defined fashion [81]. When a source program is self-stabilizing, we expect the compiler to produce an object program that is self-stabilizing on the target architecture. It would be highly desirable to have a "selfstabilizing compiler" that will convert a non-self-stabilizing source program into self-stabilizing object code. It is very important for the compiler to preserve the properties of the source program that are important to the designers. • In a sequential paradigm under termination it’s important that both the programs compute the same results (quantitative). • In a distributed or parallel paradigm, preservation of qualitative properties due to the need for control and coordination among the processes is important. Dijkstra [36], in his seminal work, implied that there doesn’t exist a compiler from asymmetric rings to symmetric rings that forces or preserves self-stabilization. However, if self-stabilization is not required, we can compile an asymmetric ring into a symmetric ring [81]. Gouda et al. [55] showed that self-stabilization across architectures is in principle unstable. They also demonstrated that the ability to force or preserve self stabilization is very much dependent on how certain properties, like termination, fairness and concurrency, are required to be preserved when compiling from one system to other. Next we discuss compilers that force self stabilization in sequesntial programs, asynchronous distributed systems, and shared memory systems [81].

17.11.1 Compilers for sequential programs The main focus of research on self-stabilization has been in the domains of concurrent and distributed systems, where the goals of algorithms are both qualitative and quantitative. Achieving 649

self stabilization in sequential programs becomes much more difficult due to the termination requirement. Browne et al. [20] and Schneider [79] suggested a rule-based program model. A rule based program consists of an initialization section and a finite set of rules. A rule is a multiple assignment statement with an enabling condition, called a guard, which is a predicate over the variables of the program. If the guard of a rule is true for a state, then the rule is said to be enabled. A computation is a sequence of rule firings, where at each step an enabled rule is non-deterministically selected for execution. A program is said to have terminated when a fixed point is reached. A fixed point is a state in which the values of the variables can no longer change. A partial fixed point is defined as a state from which the values of a subset of variables do not change. For a terminating program to be self stabilizing, the relation it computes should be verifiable in one step, else it might terminate in an unsafe state. Browne et al. [20] showed that a class of programs exists for which there is a compiler that forces self-stabilization while preserving termination. Object programs have runtime and size within a constant factor of the source program. However, it is assumed that inputs are incorruptible. These programs must satisfy the following properties [81]: • Data dependency graphs of these programs are acyclic. • Each rule in the program assigns only one variable. • For any pair of enabled rules with the same target variable, both rules will assign the same value to the variable. For arbitrary programs, one cannot obtain the same result as for acyclic programs. Consider the class of programs restricted to Boolean variables. Schneider [79] showed that if there exists a compiler that forces self-stabilization onto Boolean programs, while preserving termination, then PSPACE = NP, which is not a very likely result. Further, if we require that the source and target to have the same set of variables, then PSPACE = P, which is even less likely result. However, if we waive the requirement that the object program reach a fixed point (i.e., the condition of termination), life becomes simpler. Schneider [78, 79] introduced the notion of partial fixed-points (where termination not required) and showed that one can produce, in quadratic time, an equivalent self-stabilizing program with time complexity and size within a constant factor of the original.

17.11.2 Compilers for asynchronous message passing systems Such a compiler should convert a non self-stabilizing program into a self-stabilizing version in an asynchronous message passing system [71]. This is accomplished through a self-stabilizing platform, which when nterleaved with a non self-stabilizing program, yields a self-stabilizing program. The resulting program is called an extension of the original program. The algorithm consists of three components [?]: 650

• Self-stabilizing version of Chandy-Lamport’s global snapshot algorithm [29]. • Self-stabilizing reset algorithm that si superposed on it. • A non self-stabilizing program on which the former two are superposed to obtain a selfstabilizing program. The algorithm works as follows: A distinguished initiator repeatedly takes global snapshots. After the distinguished initiator has obtained a snapshot, it evaluates a predicate1 . on the collected state. If an illegitimate global state is detected, then the initiator initiates the execution of the reset algorithm which resets the global state of the source program to an initial state. In this methodology, the compiler takes the program and the predicate (specifying the set of safe states) as input and produces a self-stabilizing version of the program. An Extension: Informally, a program Q is an extension of program P if the subset of Q corresponding to P behaves exactly like P, except that the same state may repeat. If P terminates, its extension Q needs to repeat the final state of P forever, changing only the variables not present in P, in order to achieve self-stabilization. If Q terminates, it can not be a self-stabilizing extension of P because it could terminate in an illegal state. The Katz and Perry methodology [71] has the following two drawbacks: First, it might not be always possible to find a predicate that can distinguish between legitimate and illegitimate states. Legitimate states could be defined in terms of the reachable states. However, computing the reachable set might become intractable. Second, a global snapshot algorithm does not produce the current state. It captures a possible successor to the state it was initited in. If the original program stabilizes by itself, we might end up doing a reset from a legitimate state.

17.11.3 Compilers for asynchronous shared memory systems In shared-memory systems, we can write the snapshot and reset algorithms in a way very similar to message passing systems. A self-stabilizing synchronous shared memory system might be compiled into an asynchronous self-stabilizing shared memory system as follows [81]: Assume that a process can read and write in one atomic action and that each shared variable is written by only one process (called the owner of the variable). The steps of the synchronous system are simulated using self-stabilizing asynchronous unison algorithm. One step of the synchronous algorithm is executed each time the clock is incremented. For each shared variable, two copies are maintained: one to store the current value and another to store the previous value. This allows a process to access the previous value of a shared variable even if it has been updated by another process. When the local clock of a process ticks from i to i+1, it concurrently executes one step of the synchronous system and updates current and previous values of all variables that it owns. 1

It is assumed that there exists a decidable predicate that can detect whether a global state is legitimate

651

17.12 Self stabilization as a Solution to Fault Tolerance Self-Stabilization is the property of a system, component, process or object to correct itself no matter how severely it’s state variables, including memory, message buffers, and registers, are corrupted. Self-stabilization is most interesting for distributed and concurrent systems because local detection of a faulty condition is difficult. Self stabilization has risen beyond the theory and has served as a guiding principle in many network protocols (in fact, a number of Internet and LAN protocols are self-stabilizing or very close to it). Recent applied research has succeeded in demonstrated self-stabilizing file systems and in implementing protocols for routing, reprogramming, and synchronizing nodes in sensor networks. These examples show that the principles of self-stabilization can be used to implement lightweight solutions to the problems of fault tolerance in real-life systems.

Fault Tolerance Fault tolerance is defined as tolerance to transient failures, in which the state of a component changes spontaneously, but the component remains correct. Fault-tolerance or graceful degradation is the property that enables a system to continue operating properly in the event of the failure of some of its components. The quality of operation may decrease in proportion to the severity of the failure, while in a naively-designed system, even a small failure can cause the total system breakdown. In a system, fault tolerance is generally achieved by anticipating exceptional conditions and designing the system to cope with them. The concept of self-stabilization has emerged as a complementary paradigm to fault tolerance in distributed computing. A system is said to be selfstabilizing, if starting from any state, it automatically recovers to a specified set of legal states in finite time. The arbitrary state from which the system starts may be a faulty state due to a transient failure within the system. Such a fault could be the corruption of local memory, loss of a message, or reception of a corrupted message. During the recovery process, the user may experience a partial loss of services and performance, but guarantee is given that correct system operation will eventually resume. Self-stabilizing systems meet a stronger notion of correctness under failures. If a transient error pushes the system into an inconsistent or incorrect state, then regardless of the origin and type of the failure, the system eventually coverges to a correct state without any outside assistance. The fact that the type of fault is not specified further contains the striking power of the paradigm: The ability to mask the effect of faults is traded for the ability to tolerate any kind and any number of faults. Thus, self-stabilizing systems offer a degree of fault tolerance that goes beyond the shortcomings of traditional approaches for designing fault-tolerant systems. Robustness is one of the most important requirements of modern distributed systems and a practical distributed system should be able to recover from transient faults of the processors and communication links. Ideally, the recovery process should automatically start as soon as a fault is detected and must not rely on the assumption that it is possible to start the system from a well 652

defined state. It is not reasonable to assume that the code executed by every processor is not altered by transient faults. This code may be stored in a read-only memory or may be reloaded from a nonvolatile memory after a transient fault. A distributed self-stabilizing system is a system that can start from any possible initial state and reach a legitimate state in finite time. Self-stabilization is a different way of looking at distributed system fault tolerance; it provides a “built-in-safeguard" against “transient failures" that might corrupt the data in a distributed system; self-stabilization enables systems to recover from failures automatically without any intervention by any external agency. Stabilizing algorithms are optimistic in the sense that the distributed system may temporarily behave inconsistently but a return to correct system behavior is guaranteed in finite time while traditional robust distributed algorithms follow a pessimistic approach in that it protects against the worst possible scenario which demands an assumption of the upper bound on the number of faults. Self-stabilization provides a unified approach to transient failures by formally incorporating them into the design model. The following transient faults can be handled by a self-stabilizing system [81]: 1. Inconsistent initialization: Different processes in the program may be initialized to local states that are inconsistent with one another. 2. Mode of change: There can be different modes of execution of a system. In changing the mode of operation, it is impossible for all of the processes to effect the change at the same time. The program is bound to reach a global state in which some processes have changed while others have not. 3. Transmission errors: These errors include loss, corruption, or reordering of messages and can cause inconsistency between the states of the sender and receiver. 4. Process failure and recovery: If a process goes down and recovers later, its local state may be inconsistent with the rest of the system/program. 5. Memory crash: A memory crash may cause the loss of local state, making it inconsistent with the rest of the system/program. Traditional approaches to fault tolerance have addressed each of these issues separately. Selfstabilization provides a unified approach to fault tolerance by handling all these issues singlehandedly. Global initialization is not necessary; each component can be started separately in an arbitrary state. Self-stabilization does not rely on particular initial state as other distributed algorithms do. There is no need for proper and consistent initialization. A self-stabilizing distributed system eventually reaches a legitimate system state, regardless of its initial state. Because of this property, a self-stabilizing distributed system is extremely robust against failures; it tolerates any finite number of transient failures.

653

Self-stabilization can be applied to topology preservation/control. After a topological change the system converges to a new feasible configuration. The self-stabilization principle applies to any system built on significant number of components which are evolving independently from one another, but which are cooperating or competing to achieve some common goals. This applies, in particular to large distributed systems which tend to result from the integration of many subsystems and components developed separately earlier by different people. The investigation and use of self stabilization as an approach to fault-tolerance has been undergoing a renaissance. Dijkstra’s notion of self stabilization, which originally had a very narrow scope of application, is proving to encompass a formal and unified approach to fault tolerance under a model of transient failures for distributed systems. Self-stabilization has most obvious application to the network protocols area since communication protocols should be especially tolerant to temporary faults.

17.13 Factors Preventing Self-Stabilization In this section, we discuss some of the factors that prevent self-stabilization. The factors preventing self-stabilization include the following: 1. Symmetry 2. Termination 3. Isolation and 4. Look-alike configuration.

Symmetry Self-Stabilization requires that all processes should not be identical/symmetric because a selfstabilization solution generally relies on a distiguished process. Asymmetry must be maintained in systems where processes may synchronize with one another such as mutual exclusion, dining philosophers, drinking philosophers, and resource allocation systems under deterministic rules. A system can be asymmetric by state or asymmetric by identity. A system is asymmetric by state when all processes are identical, however, they start from different initial local states. A system is asymmetric by identity when not all of the processes are identical. In general, a system asymmetric only by state cannot be self stabilizing, while a system asymmetric by identity can be self stabilizing.

Termination Self stabilization is generally incompatible with termination. If any unsafe global state is a final state, then a system will not be able to stabilize. Though self-stabilization is generally incompatible 654

with termination, there is one exceptional case where self-stabilization can be achieved in the presence of termination. That is, in the case of finite-state sequential programs since the number of states is finite, a compiler can remove all the unsafe states [81]. While the property of termination is very natural when dealing with algorithms whose goal is to compute a function (i.e., quantitative), it is unnatural in the domain of distributed systems, where computations are non-terminating by design and have qualitative goals such as coordination and control. One form of termination that occurs within distributed systems is deadlock where one or more processes wait for an event that will never occur [71]. In a distributed message passing system, processes will be waiting for messages to come from other processes. A process sends a message and then waits for a response. By way of a malicious adversary, control of a local process could be placed at a point just after a send instruction without a message actually having been sent. Thus at any local process state that follows the sending of a message, it is impossible for that process to know whether a message has in fact been sent. This situation can lead to deadlock where one or more processes wait for messages that will never come. The problem of deadlock is not seen in a shared memory system. Because a process can test the value of shared memory when required, there is no waiting for messages and thus no deadlock.

Isolation Isolation occurs within a system when the local state and computation of each process is consistent with some safe global state and computation, however, the resulting global state and computation is not safe. In such a situation, the system is unable to stabilize due to inadequate communication and coordination between its processes [55, 81].

Look-alike configurations Look-alike configurations result when the same computation (sequence of actions) is enabled in two different states with no way to differentiate between them [55, 81]. If one of the two states is unsafe, then the system cannot guarantee convergence from the unsafe state.

17.14 Limitations of Self-Stabilization The problem in self-stabilizing systems is the time it takes for a system to correct itself when started in an illegal state or there is an error causing it to go in an illegal state. If a system cannot tolerate this initial unknown period, then self-stabilization does not help. Even if the initial unknown can be tolerated for a brief period of time, the system may not converge to a legal state quickly enough.

655

Need for an exceptional machine Almost all self-stabilization algorithms rely on the fact that is at least one exceptional machine in the system. This may be difficult to achieve in some systems, but itis not a major drawback in most distributed systems. Convergence - Response Tradeoffs Convergence span denotes the maximum number of critical transitions made before the system reaches a legal state and response span denotes the maximum number of transitions to get from some starting state to some goal state. Critical transitions are similar to errors occurring in the system due to a move. For example, in a mutual exclusion system, if one process is in its critical section and another process makes a move and enters its critical section, an error has occurred because more than one process has been allowed to enter its critical section. Several self-stabilizing termination detection algorithms, each having different properties, have been developed. For a ring of n processes, if one has comparative convergence and response spans, one has a fast convergent span and a slow response span, and one shows the relationship between the two spans. If the convergence span is decreased by a factor of k (1≤ k≤ n), the response span is increased by the same factor. So, the convergence span is of the order of n/k while the response span is n*k. This relationship exists in all the other classes of self-stabilizing systems. This relationship is reasonable because the more checks that are made, the longer it will take to converge, while there will be a fewer number of errors made. This relationship is very useful in the design of self-stabilizing systems because the system can be modified according to the goal of the system. Depending on the requirements of the system, one can have fast convergence with many errors or slower convergence with fewer errors or something in between. Pseudo stabilization It is sometimes expensive to design self-stabilizing systems. Lessening the requirements of the system can reduce some of the cost. A system is said to stabilize if and only if every computation has some state in it such that any computation starting from this state will be in the set of legal computations. On the other hand, in order for a system to pseudo stabilize, every computation only needs to have some state such that the suffix of the computation beginning at this state is in the set of legal computations [24]. The property of pseudo-stabilization is obviously weaker than the requirement of stabilization, however, it is less expensive to implement. Verification of Self-Stabilizing Systems When designing self-stabilizing systems, verifying the correctness of these algorithms may be difficult, but there has been some work in this area. A convergence stair method has been developed where the legal states are built up step by step. Proof that the algorithm stabilizes in each step, verifies the correctness of the entire algorithm. The interleaving assumptions can be relaxed to

656

make it easier to verify the correctness of the algorithm. Algorithms that are pseudo-stabilizing [24] are usually good enough for many systems, and these are easier to implement, easier to verify, and more efficient to run.

17.15 Chapter Summary Self-stabilization has been used in many areas and the areas of study continue to grow. Algorithms have been developed using central or distributed daemons and uniform and non-uniform networks. The algorithms that assume a central daemon can usually be easily extended to support distributed daemon, so these algorithms are still useful when applied to distributed systems. Extensions of communication protocols that are self-stabilizing have also been developed such as the sliding window protocol, the two-way handshake, and the alternating-bit protocol [2]. The major drawback of self-stabilizing systems is the initial illegal configurations. The system must converge quickly in order to make the illegal configurations less serious. Verification of the systems can be difficult, but there are ways to make it easier. Relaxing interleaving assumptions and usage of a convergence stair are two of the ways. Some of the assumptions made while designing the systems make it nearly impossible to implement the systems. For example, self-stabilizing protocols require a timeout action that needs to examine the contents of the communication link and also needs to know the values of some non-local variables. Global timeout actions are usually avoided which makes these algorithms not easy to implement. These requirements may not be necessary in some cases. The alternating-bit protocol [2], for example, does not need unbounded sequence numbers, nor does it need expensive global timeout actions. Therefore, this protocol can be implemented relatively easily, and even though the algorithm is pseudo stabilizing and not exactly stabilizing, this does not affect the usefulness of the algorithm in most situations.

17.16 Bibliographic Notes The idea of self-stabilization was first proposed by Dijkstra in a seminal paper [36] in 1974. Since then considerable volume of work has been done on this topic. The most extensive work in selfstabilization has been done in the area of mutual exclusion [66, 69, 76, 26]. The reason for this is mainly due to Dijkstra’s original self-stabilizing model there a legal state is defined to be a state in which only one privilege exists in the system. An excellent survey on the topic is due to by Schneider [81]. Gartner [45] presents a survey of algorithms for construction of self-stabilizing spanning-trees. Aggarwal [7] presents a time optimal self-stabilizing algorithms for spanning trees. More details on distributed reset can be found in [13]. Chang et al. [28] discuss the cost of self-stabilization. Self-stabilization has been used to design more robust distributed mechanisms like synchronization [14].

657

17.17 Exercise Problems 1. When self-stabilization claims to solve so many problems in fault tolerance in a unified manner, why are people still studying and investigating each of those problems individually? 2. Describe self-stabilizing alternating-bit protocol. 3. Give a psuedo-stabilization algorithm. Discuss how it reduces the cost compared to stabilization. 4. What is “superstabilization"? What type of guarantees do superstabilization provide? 5. What are the trade-offs in a self-stabilizing system/algorithm? 6. Fault containment is a problem with self-stablizing algorithms. What are fault-containing self-stablizing algorithms [50]? Describe how they solve the problem. 7. Describe a self-stabilizing mutual exclusion algorithm. 8. One weakness of self-stabilization is that it is a global property. A failure that is local to a machine may spread and lead to corrective actions across the entire system. Discuss how this problem can be addressed by local detection and correction of failures [4, 16].

658

Bibliography [1] Abadir, M.S., and Gouda, M. G. 1992. The stabilizing computer. In Proc. of the 1992 International Conference on Parallel and Distributed Systems, (Dec.). [2] Y. Afek, and G. Brown, Self-stabilization of the alternating-bit protocol. In Proceedings of the 8th Symposium on Reliable Distributed Systems, 80-83, 1989. [3] Y. Afek, S. Kutten and M. Yung, Memory efficient self-stabilizing protocols for general networks, Proc. of the 4th International Workshop on Distributed Algorithms, 1991, 15-28. [4] Y. Afek, S. Kutten and M. Yung, The Local Detection Paradigm and its Applications to Selfstabilization, Theoretical Computer Science, 186(1-2), 199-229, 1997. [5] Y. Afek, S. Kutten, and M. Yung, Memory-efficient self-stabilization on general networks. In Proc. of the 4th International Workshop on Distributed Algorithms (Bari, Italy, Sept.). In Lecture Notes in Computer Science, vol. 486. Springer-Ver-lag, New York, 15-28, 1990. [6] Yehuda Afek and Anat Bremler, Self-stabilizing unidirectional network algorithms by power supply, Chicago Journal of Theoretical Computer Science, 1998(3), December 1998. [7] S. Aggarwal, Time optimal self-stabilizing spanning tree algorithms, Technical Report MITLCS/ MIT/ LCS/ TR-632, Massachusetts Institute of Technology, Laboratory for Computer Science, August 1994. [8] S. Aggarwal and S. Kutten, Time optimal self–stabilizing spanning tree algorithms, In Rudrapatna K. Shyamasundar, editor, Proceedings of Foundations of Software Technology and Theoretical Computer Science, volume 761 of Lecture Notes in Computer Science, pages 400–410, Berlin, Germany, December 1993. Springer-Ver-lag. [9] Gheorghe Antonoiu and Pradip K. Srimani, A self-stabilizing distributed algorithm to construct an arbitrary spanning tree of a connected graph, Computers and Mathematics with Applications, 30:1–7, 1995. [10] Gheorghe Antonoiu and Pradip K. Srimani, Distributed self-stabilizing algorithm for minimum spanning tree construction, In Euro-Par ’97 Parallel Processing, Proceedings, number 1300 in Lecture Notes in Computer Science, pages 480–487. Springer-Verlag, 1997.

659

[11] Gheorghe Antonoiu and Pradip K. Srimani, A self-stabilizing distributed algorithm for minimal spanning tree problem in a symmetric graph, Computers and Mathematics with Applications, 35(10):15–23, 1998. [12] A. Arora And M. G. Gouda, Closure and convergence: A foundation for fault-tolerant computing, In Proc. of the 22nd International Conference on Fault-Tolerant Computing Systems, 1992. [13] Anish Arora and Mohamed G, Gouda. Distributed reset, IEEE Transactions on Computers, 43(9):1026–1038, September 1994. [14] Baruch Awerbuch, Shay Kutten, Yishay Mansour, Boaz Patt-Shamir and George Varghese, Time optimal self-stabilizing synchronization, In ACM, editor, Proc. of the twenty-fifth annual ACM Symposium on the Theory of Computing, San Diego, California, May 16–18, 1993, pages 652–661, New York, NY, USA, 1993. ACM Press. [15] Baruch Awerbuch and Rafail Ostrovsky, Memory-efficient and self-stabilizing network reset, In Symposium on Principles of Distributed Computing (PODC ’94), pages 254–263, New York, USA, August 1994, ACM Press. [16] Baruch Awerbuch, Boaz Patt-Shamir, and George Varghese, Self-stabilization by local checking and correction, In FOCS91 Proceedings of the 31st Annual IEEE Symposium on Foundations of Computer Science, pages 268–277, 1991. [17] B. Awerbuch and G Varghese, Distributed program checking: A paradigm for building selfstabilizing distributed protocols, In Proc. of the 32nd IEEE Symposium on Foundations of Computer Science (Oct.), 1991 [18] Awerbuch, Patt, and G. Varghese, Self-stabilization by local checking and correction, In Proc. of the 32nd IEEE Symposium on Foundations of Computer Science(Oct.), 1991. [19] Bastani, Yen, and Y. Zhao, On self stabilization, non-determinism, and inherent fault tolerance, In Proc. of the MCC Workshop on Self-Stabilizing Systems. MCC Tech. Rep. STP379-89, 1989 [20] J. C Browne, A. Emerson, M. Gouda, D. Miranker, A. Mok and L. Rosier, Bounded time fault-tolerant rule-based systems, Telematics Informat. 7, 3/4, 441–454, 1990. [21] Geoffrey M. Brown, Mohamed G. Gouda and Chuan-Lin Wu, Token Systems That Self Stabilize, IEEE Transactions on Computers, Vol. 38, No. 6, pp 845-852, June 1989. [22] J. E. Burns and Jan K. Pachl, Uniform self-stabilizing rings, ACM Transactions on Programming Languages and Systems(TOPLAS),v.11 n.2, p.330-344, April 1989. [23] J. E. Burns, Self-stabilizing rings without demons, Technical Report GITICS-87/36, Georgia Institute of Technology, 1987. 660

[24] J. E. Burns, M. G. Gouda And R. E Miller, Stabilization and pseudo-stabilization, Distributed Computing archive Volume 7, Issue 1, November 1993. (Special issue: Self-stabilization) [25] J. E Burns , M. G Gouda And R. E. Miller, On relaxing interleaving assumptions, In proc. of the MCC Workshop on Self-Stabilizing Systems MCC, Tech. Rep. STP-379-89, 1989. [26] R. W. Buskens and R. P. Bianchini, Jr., Self-Stabilizing Mutual Exclusion in the Presence of Faulty Nodes, 25th International Symposium on Fault Tolerant Computing Digest of Papers, 144–153, 1995. [27] Franck Butelle, Christian Lavault, and Marc Bui, A uniform self-stabilizing minimum diameter tree algorithm (extended abstract), In Jean-Michel H´elary and Michel Raynal, editors, Distributed Algorithms, 9th International workshop, WDAG ’95, volume 972 of Lecture Notes in Computer Science, pages 25–272, Le Mont-Saint-Michel, France, 13–15 September 1995. Springer-Verlag. [28] Ernest J. H. Chang, Gaston H. Gonnet and Doron Rotem, On the costs of self-stabilization, Information Processing Letters, Vol. 24, No. 5, pp 311-316, 16 March 1987. [29] K. M. Chandy and L. Lamport, Distributed snapshots: Determining global states of distributed systems, ACM Trans. Comput. Syst. (63-75), 1985. [30] Nian-Shing Chen, Hwey-Pyng Yu, and Shing-Tsaan Huang, A self-stabilizing algorithm for constructing spanning trees, Information Processing Letters, 39:147–151,1991.

[31] Cisco Systems Inc, Using Vlan Director system documentation, Internet: http://www.cisco.com/univercd/cc/td/doc/product/rtrmgmt/swntman/cwsimain/cwsi2/cwsiug2/vlan2/index.h 1998. [32] Zeev Collin and Shlomi Dolev, Self-stabilizing depth first search, Information Processing Letters, 49:297–301, 1994. [33] Couvreur, N. Francez and M. G. Gouda, Asynchronous unison, In Proc. of the 12th International Conference on Distributed Computing Systems, Yokohama, Japan, June 1992. [34] E. W. Dijkstra, A belated proof of self-stabilization, Distrib. Comput., 1, 5-6, 1986. [35] E. W. Dijkstra, Self-stabilization in spite of distributed control, In Selected Writings on Computing: A Personal Perspective. Springer-Verlag, Berlin, 1982, 41–46. Originally published in 1973. [36] Edsger W. Dijkstra, Self stabilizing systems in spite of distributed control, Communications of the ACM, 17(11):643–644, 1974. [37] S. Dolev, A. Israeli, and S. Moran, Uniform dynamic self-stabilizing leader election, IEEE Transactions on Parallel and Distributed Systems, 8(4):424–440, 1997. 661

[38] Shlomi Dolev, Optimal time self stabilization in dynamic systems (preliminary version), In Andr´e Schiper, editor, Proce. of the 7th International Workshop on Distributed Algorithms (WDAG93), volume 725 of Lecture Notes in Computer Science, pages 160–173, Lausanne, Switzerland, 27–29 September 1993. Springer-Verlag [39] Shlomi Dolev, Self-Stabilization, MIT Press, 2000. [40] Shlomi Dolev, Mohamed G. Gouda, and Marco Schneider, Memory requirements for silent stabilization, Acta Informatica, 36(6):447–462, 1999. [41] Shlomi Dolev, A. Israeli and S. Moran, Self stabilization of dynamic systems, In Proc. of the MCC Workshop on Self-Stabilizing Systems, MCC Technical Report No. STP-379-89, 1989. [42] Shlomo Dolev, Amos Israeli and Shlomo Moran, Self-stabilization of dynamic systems assuming only read/write atomicity, In Proc. of the ninth annual ACM symposium on Principles of distributed computing, Quebec City, Quebec, Canada, August 22-24, 1990, pp 103-117. (Also in Distributed Computing 7(1993), 3-16.) [43] M. Flatebo and A. Datta, Two-state self-stabilizing algorithms, In Proc. of the 6th International Parallel Processing Symposium, Beverly Hills. Calif. Mar 1992, 198-203. [44] M. Flatebo and S. Ghosh, Self-stabilization in distributed systems, IEEE Comput, 1991. (Also in Readings in Distr. Comp. Systems. T. L. Casavant and M. Singhal Eds. New York: IEEE Computer Society Press, 1994, p.100-114.) [45] Felix C. Gartner, A Survey of Self-Stabilizing SpanningTree Construction Algorithms, EPFL Technical Report, 2003. (icwww.epfl.ch/publications/documents/IC_TECH_REPORT_200338.pdf) [46] Felix C. Gartner and Henning Pagnia, Time-efficient self-stabilizing algorithms through hierarchical structures, In Proc. of the 6th Symposium on Self-Stabilizing Systems, Lecture Notes in Computer Science, San Francisco, June 2003. Springer-Verlag. [47] C. Genolini and S. Tixeuil, A lower bound on dynamic k-stabilization in asynchronous systems, In SRDS 2002 21st Symposium on Reliable Distributed Systems, IEEE Computer Society Press, pages 211–221, 2002. [48] Sukumar Ghosh, Binary self-stabilization in distributed systems, Information Processing Letters, v.40 n.3, p.153-159, Nov. 1991 [49] S. Ghosh, Self-stabilizing distributed systems with binary machines, In Proc. of the 28th Annual Allerton Conference, 1988-997, 1990. [50] S. Ghosh, A. Gupta, T. Herman and S. V. Pemmaraju, Fault-Containing Self-Stabilizing Algorithms, In Proc. of the fifteenth annual ACM symposium on Principles of distributed computing, Philadelphia, 1996. 662

[51] S. Ghosh, Understanding self-stabilization in distr, systems. Tech. Rep. TR-90-02, Dept. of Computer Science, Univ. of Iowa, 1990. [52] S. Ghosh, A. Gupta, and S. Pemmaraju, A fault-containing self-stabilizing algorithm for spanning trees, Journal of Computing and Information 2(1996), 322-338. [53] S. Ghosh, A. Gupta, M. Karaata, S. Pemmaraju, Self-stabilizing dynamic programming algorithms on trees, in: Proc. of the Second Workshop on Self-Stabilizing Systems, 1995, pp. 11.1-11.15. [54] M. G. Gouda, The stabilizing Philosopher: Asymmetry by Memory and by Action, Technical Report TR-87-12, Dept. of Computer Sciences, University of Texas at Austin, 1987. [55] M. G. Gouda and M. Evangelist, Convergence/response tradeoffs in concurrent systems, In Proc. of the 2nd IEEE Symposium on Parallel and Distributed Processing, Dec 1990. [56] M. G. Gouda and T. Herman, Stabilizing unison. Inf. Process. Lett. 35 (1990), 171–175., 1990. [57] M. G. Gouda and N. Multari, Stabilizing communication protocols, IEEE Trans. Comput. 40, 4 (Apr.), 448–458, 1991. [58] M. G. Gouda, R. R. Howell and L. E. Rosier, The instability of self-stabilization, Acts Inf. 27, (1990), 697-724, 1990. [59] F. F. Haddix, Stabilization of bounded token rings, Tech. Rep. ARL-TR-91-31, Applied Research Lab., Univ. of Texas at Austin, 1991. [60] S. M. Hedetniemi, S.T. Hedetniemi, D. P. Jacobs, P. K. Srimani, Self-stabilizing algorithms for minimal dominating sets and maximal independent sets, Comput. Math. Appl. 46 (5-6) (2003) 805-811. [61] T. Herman, Probabilistic self-stabilization, Information Processing Letters, v.35 n.2, p.63-67, June 1990. [62] T. Herman, Self-stabilization: Randomness to reduce space, Distributed Computing, vol. 6, p.95-98,1992. [63] Lisa Higham and Zhiying Liang, Self-stabilizing minimum spanning tree construction on message-passing networks, In Proc. of the 15th International Symposium on Distributed Computing (DISC), number 2180 in Lecture Notes in Computer Science, Lisbon, Portugal, and October 2001. Springer-Verlag. [64] Su-Chu Hsu and Shing-Tsaan Huang, A self-stabilizing algorithm for maximal matching, Information Processing Letters, Volume 43, Issue 2, pp 77-81, August 1992.

663

[65] S. Huang and N. Chen, A self-stabilizing algorithm for constructing breadth-first trees, Inform. Process. Lett. 41 (1992),109-117. [66] Amos Israeli and Marc Jaflon, Token management schemes and random walks yield selfstabilizing mutual exclusion, Proceedings of the ninth annual ACM symposium on Principles of distributed computing, p.119-131, August 22-24, 1990, Quebec City, Quebec, Canada. [67] G. Itkis and L. Levin, Fast and lean self-stabilizing asynchronous protocols, In Shafi Goldwasser, editor, Proc.: 35th Annual Symposium on Foundations of Computer Science, November 20–22, 1994, Santa Fe, New Mexico, pages 226–239, 1109 Spring Street, Suite 300, Silver Spring, MD 20910, USA, 1994. IEEE Computer Society Press. [68] Colette Johnen, Memory efficient, self-stabilizing algorithm to construct BFS spanning trees, In Proc. of the 16th Annual ACM Symposium on Principles of Distributed Computing (PODC ’97), pages 288–288, August 1997. [69] Hirotsugu Kakugawa, Masafumi Yamashita, A Universal Self-Stabilizing Mutual Exclusion Algorithm, Dagstuhl Seminor 00431: SelfStabilization, Dagstuhl, Germany, 2000. (url = "citeseer.ist.psu.edu/yamashita00universal.html" ) [70] Ronen Kat, Self-stabilizing replication file system, Internet: http://www.cs.bgu.ac.il/ srfs/, September 2002. [71] S. Katz and K. J. Perry, Self-stabilizing extensions for message -passing systems, In Proc. of the 9th Annual ACM Symposium on Principles of Distributed Computing, Quebec City, Canada, Aug 1990. [72] H. S. M. Kruijer, Self-stabilization (in spite of distributed control) in tree-structured systems, Inf. Process. Lett., 8, 2, 2– 79, 1979. [73] D. Lehman and M. Rabin, On the advantages of free choice: A symmetric and fully distributed solution of the dining philosopher’s problem, In Proc. of the 8th Annual ACM Symposium on Principles of Programming Languages, 1981. [74] X. Lin and S. Ghosh, Self-stabilizing maxima finding. In Proceedings of the 28th Annual Allerton Conf., pp. 662-671, 1991. [75] Michael Luby, A simple parallel algorithm for the maximal independent set problem, SIAM Journal on Computing, Volume 15 , Issue 4, pp 1036-1055, November, 1986. [76] Masaaki Mizuno and Mikhail Nesterenko and Hirotsugu Kakugawa, Lock Based SelfStabilizing Distributed Mutual Exclusion Algorithms, International Conference on Distributed Computing Systems, 708-716, 1996.

664

[77] Reuay-Ching Pan, Jone-Zen Wang, and Louis R. Chow. A self-stabilizing distributed spanning tree construction algorithm with a distributed demon. Tamsui Oxford Journal of Mathematical Sciences, 15:23–32, 1999. [78] M. Schneider, Self-Stabilization – A Unified Approach to Fault Tolerance in the Face Transient Errors, TechReport TR-91-18, Dept. of Computer Science, University of Texas at Austin, Austin, TX, 1991. [79] M. Schneider, Compiling Self-Stabilization into Sequential Programs, Dept. of Computer Science, University of Texas at Austin, Austin, TX, 1992. [80] M. Schneider, Lecture notes on Self-Stabilization, The University of Texas at Austin, http: //www.cs.utexas.edu/users/plaxton/c/395t/slides/Schneider.pdf [81] M. Schneider, Self Stabilization, ACM Computing Surveys, Vol. 25, No. 1, March 1993. [82] S. Shukla, D. Rosenkrantz, S. Ravi, Observations on self-stabilizing graph algorithms for anonymous networks, in: Proc. of the Second Workshop on Self-Stabilizing Systems, 1995, p. 7.17.15. [83] Zhengnan Shi, Wayne Goddard and Stephen T. Hedetniemi, An Anonymous Self-Stabilizing Algorithm for 1-Maximal Independent Set in Trees, Information Processing Letters, Volume 91 , Issue 2, pp 77-83, July 2004. [84] S. Sur and P. K. Srimani. A self-stabilizing distributed algorithm to construct BFS spanning trees of a symmetric graph. Parallel Processing Letters, 2(2-3):171–179, September 1992. [85] Ming-Shin Tsai and Shing-Tsaan Huang. A self-stabilizing algorithm for the shortest paths problem with a fully distributed demon. Parallel Processing Letters, 4(1-2):65–72, June 1994.

665

Chapter 18 Peer-to-Peer Computing and Overlay Graphs 18.1 Introduction Peer-to-peer (P2P) network systems use an application-level organization of the network overlay for flexibly sharing resources (e.g., files and multimedia documents) stored across network-wide computers. In contrast to the client-server model, any node in a P2P network can act as a server to others and at the same time, it can act as a client. Communication and exchange of information is performed directly between the participating peers and the relationships among the nodes in the network are equal. Thus, P2P networks differ from other Internet applications in that they tend to share data from a large number of end users rather than from the more central machines and Web servers. Several well known P2P networks that allow P2P file-sharing include Napster [22], Gnutella [14, 15], Freenet [9], Pastry [27], Chord [29]. and CAN [24]. Traditional distributed systems used DNS (Domain Name service) to provide a lookup from host names (logical names) to IP addresses. Special DNS servers are required, and manual configuration of the routing information is necessary to allow requesting client nodes to navigate the DNS hierarchy. Further, DNS is confined to locating hosts or services (not data objects that have to be a priori associated with specific computers), and host names need to be structured as per administrative boundary regulations. P2P networks overcome these drawbacks, and more importantly, allow the location of arbitrary data objects. An important characteristic of P2P networks is their ability to provide a large combined storage, CPU power, and other resources while imposing a low cost for scalability, and for entry into and exist from the network. The ongoing entry and exit of various nodes, as well as dynamic insertion and deletion of objects is termed as churn. The impact of churn should be as transparent as possible. P2P networks exhibit a high level of self-organization and are able to operate efficiently despite the lack of any prior infrastructure or authority. The philosophy of this model requires that if a node wants to enjoy the services which other nodes provide, that node should provide service to other nodes. Some desirable features of P2P systems are summarized in Table 18.1.

666

Features self-organizing distributed control role symmetry for nodes anonymity naming mechanism security, authentication, trust

Performance large combined storage, CPU power, and resources fast search for machines and data objects scalable efficient management of churn selection of geographically close servers redundancy in storage and paths

Table 18.1: Desirable characteristics and performance features of P2P systems.

18.1.1 Napster One of the earliest popular P2P systems, Napster, used a server-mediated central index architecture that is organized around clusters of servers that store direct indices of the files in the system. The central server maintains a table with the following information of each registered client: (i) the client’s address (IP) and port, and offered bandwidth, and (ii) information about the files that the client can allow to share. The basic steps of operation to search for content and to determine a node from which to download the content are the following. 1. A client connects to a meta-server that assigns a lightly-loaded server from one of the closeby clusters of servers to process the client’s query. 2. The client connects to the assigned server and forwards its query along with its own identity. 3. The server responds to the client with information about the users connected to it and the files they are sharing. 4. On receiving the response from the server, the client chooses one of the users from whom to download a desired file. The address to enable the P2P connection between the client and the selected user is provided by the server to the client. Users are generally anonymous to each other. The directory serves to provide the mapping from a particular host that contains the required content, to the IP address needed to download from it.

18.1.2 Application Layer Overlays A core mechanism in P2P networks is searching for data, and this mechanism depends on how (i) the data, and (ii) the network, are organized. Search algorithms for P2P networks tend to be data-centric, as opposed to the host-centric algorithms for traditional networks. P2P search uses the P2P overlay, which is a logical graph among the peers, that is used for the object search and object storage and management algorithms. Note that above the P2P overlay is the application layer overlay, where communication between peers is point-to-pont (representing a logical all-toall connectivity,) once a connection is established. The P2P overlay can be structured (e.g., hypercubes, meshes, butterfly networks, de Bruijn graphs) or unstructured, i.e., no particular graph structure is used. Structured overlays use some 667

rigid organizational principles based on the properties of the P2P overlay graph structure, for the object storage algorithms and the object search algorithms. Unstructured overlays use very loose guidelines for object storage. As there is no definite structure to the overlay graph, the search mechanisms are more “ad-hoc”, and typicaly use some forms of flooding or random walk strategies. Thus, object storage and search strategies are intricately linked to the overlay structure as well as to the data organization mechanisms.

18.2 Data Indexing and Overlays The data in a P2P network is identified by using indexing. Data indexing allows the physical data independence from the applications. Indexing mechanisms can be classified as being centralized, local, or distributed. Centralized indexing entails the use of one or a few central servers to store references (indexes) to the data on many peers. The DNS lookup as well as the lookup by some early P2P networks such as Napster used a central directory lookup. Distributed indexing involves the indexes to the objects at various peers being scattered across other peers throughout the P2P network. In order to access the indexes, a structure is used in the P2P overlay to access the indexes. Distributed indexing is the most challenging of the indexing schemes, and many novel mechanisms have been proposed, most notably the distributed hash table (DHT). Various DHT schemes differ in the hash mapping, search algorithms, diameter for loookup, search diameter, fault-tolerance, and resilience to churn. A typical DHT uses a flat key space to associate the mapping between network nodes and data objects/files/values. Specifically, the node address is mapped to a logical identifier in the key space using a consistent hash function. The data object/file/value is also mapped to the same key space using hashing. These mappings are illustrated in Figure 18.1. Local indexing requires each peer to index only the local data objects and remote objects need to be searched for. This form of indexing is typically used in unstructured overlays in conjunction with flooding search or random walk search. Gnutella uses local indexing. An alternate way to classify indexing mechanisms is as being a semantic index mechanism or a semantic-free index mechanism. A semantic index is human readable, for example, a document name, a keyword, or a database key. A semantic-free index is not human readable and typically corresponds to the index obtained by a hash mechanism, e.g., the DHT schemes. A semantic index mechanism supports keyword searches, range searches, and approximate searches, whereas these searches are not supported by semantic-free index mechanisms.

668

Native node identifier (address) space

Object/ file value space

Common key (identifier) space

Figure 18.1: The mappings from node address space and object space in a typical DHT scheme, e.g., Chord, CAN, Tapestry.

18.2.1 Distributed Indexing Structured overlays. The P2P network topology has a definite structure, and the placement of files or data in this network is highly deterministic as per some algorithmic mapping. (The placement of files can sometimes be “loose”, as in some earlier P2P systems like Freenet, where “hints” are used.) The objective of such a deterministic mapping is to allow a very fast and deterministic lookup to satisfy queries for the data. These systems are termed as lookup systems and typically use a hash table interface for the mapping. The hash function, which efficiently maps keys to values, in conjunction with the regular structure of the overlay, allows fast search for the location of the file. An implicit characteristic of such a deterministic mapping of a file to a location is that the mapping can be based on a single characteristic of the file (such as its name, its length, or more generally some predetermined function computed on the file). A disadvantage of such a mapping is that arbitrary queries, such as range queries, attribute queries and exact keyword queries cannot be handled directly. Another implicit effect of the tight coupling of the regular overlay structure and the rigid mapping function to enable fast access is that file insertions and deletions incur some overhead which may be nontrivial under churn. Unstructured overlays. The P2P network topology does not have any particular controlled structure, nor is there any control over where files/data is placed. Each peer typically indexes only its local data objects, hence, local indexing is used. Node joins and departures are easy - the local overlay is simply adjusted. File placement is not governed by the topology. Search for a file may entail high message overhead and high delays. However, complex queries are supported because the search criteria can be arbitrary. Although the P2P network topology does not have any controlled structure, some topologies naturally emerge. The following topologies are common and will be studied in later sections. 669

1. Power Law Random Graph (PLRG): This is a random graph where the node degrees follow the power law. Here, if the nodes are ranked in terms of their degree, then the ith node has c/iα neighbors, where c is a constant. 2. Normal Random Graph: This is a normal random graph where the nodes typically have a uniform degree. We study search in unstructured overlay networks in the next section.

18.3 Unstructured Overlays 18.3.1 Unstructured Overlays: Properties Unstructured overlays have the serious disadvantage that queries may take a long time to find a file or may even be unsuccessful even if the queried object exists. The message overhead of a query search may also be high. Advantages: The following are the main advantages of unstructured overlays such as the one used by Gnutella. 1. Exact keyword queries, range queries, attribute-based queries and other complex queries can be supported because the search query can capture the semantics of the data being sought; and the indexing of the files and data is not bound to any non-semantic structure. 2. Unstructured overlays can accommodate high churn, i.e., the rapid joining and departure of many nodes without affecting performance. The following are advantages of unstructured overlays if certain conditions are satisfied. 1. Unstructured overlays are efficient when there is some degree of data replication in the network. 2. Users are satisfied with a best-effort search. 3. The network is not so large as to lead to scalability problems during the search process.

18.3.2 Gnutella Gnutella uses a fully decentralized architecture. In Gnutella logical overlays, nodes index only their local content. The acutal overlay topology can be arbitrary as nodes join and leave randomly. A node joins the Gnutella network by forming a connection to some nodes found in standard Gnutella directory-like databases. (Note that the function of joining the network cannot be said to be fully decentralized.) Users communicate with each other, performing the role of both server and client, termed as servent. The following are the main message types used by Gnutella. • Ping messages are used to discover hosts, and allow a new host to announce itself. 670

• Pong messages are the responses to Pings. The Pong messages indicate the port and (IP) address of the responder, and some information about the amount of data (the number and size of files) that node can make available. • Query messages. The search strategy used is flooding. Query messages contain a search string and the minimum download speed required of the potential responder, and are flooded in the network. • QueryHit messages are sent as responses if a node receiving a Query detects a local match in response to a query. A QueryHit contains the port and address (IP), speed, the number of files found, and related information. The path traced by a Query is recorded in the message, so the QueryHit follows the same path in reverse.

18.3.3 Search in Gnutella and Unstructured Overlays Consider a system with n nodes and m objects. Let qi be the popularity of object i, as measured by the fraction of all queries that are queries for object i. All objects may be equally popular, or more realistically, a Zipf-like power law distribution of popularity exists. Thus, m X

qi = 1

(18.1)

i=1

Zipf-like: qi ∝ i−α

Uniform: qi = 1/m;

(18.2)

Let ri be the number of replicas of object i, and let pi be the fraction of all objects that are replicas of i. Three static replication strategies are: uniform, proportional, and square root. Thus, m X

ri = R;

pi = ri /R

(18.3)

i=1

Uniform: ri = R/m;

Proportional: ri ∝ qi ;

Square-root: ri ∝

√ qi

(18.4)

Under uniform replication, all objects have an equal number of replicas and hence the performance for all query rates is the same. With a uniform query rate, proportional and sqaure-root replication schemes reduce to the uniform replication scheme. For an object search, some of the more popular metrics of efficiency are: • probability of success of finding the queried object. • delay or the number of hops in finding an object. • the number of messages processed by each node in a search. • node coverage, the fraction of (distinct) nodes visited • message duplication, which is (#messages - #nodes visited)/#messages 671

• maximum number of messages at a node • recall, the number of objects found satisfying the desired search criteria. This metric is useful for keyword, inexact, and range queries. • message efficiency, which is the recall per message used Guided versus Unguided Search. In unguided or blind search, there is no history of earlier searches, andf hence, each search is inherently independent. In guided search, nodes store some history of past searches to aid future searches. Various mechanisms for caching hints to guide and narrow down future searches are used. In this chapter, we focus on unguided searches in the context of unstructured overlays. Search strategies: Flooding • In order to curtail the high message overhead that flooding introduces, the initial strategy was to use checking. Here, a node checks back with the query originator before forwarding a query. Unfortunately, this cause heavy load on the originator, in addition to excessive delays, and hence is not practical. • The next approach is to use the time to live (TTL) field or the hop count. However, this does not guarantee that a match can be found for the query even if the object exists in the network, and requires a high value of TTL to have a high degree of success. • A refinement that allows more control is the expanding ring strategy. A node first floods with a small TTL. If the search is not successful, it starts another flood with a larger TTL, and so on. This strategy is more successful when objects are replicated. The expanding ring approach is significantly more successful than the TTL approach, for all replication strategies, and all query distributions, and the cost is only a relatively small increase in delay. Although expanding ring is superior to TTL, both are flooding-based strategies and suffer from message duplication. Search strategies: Random walk. Another strategy to use is that of random walking. Here, a query is randomly forwarded by a node when it is received. Random walk greatly reduces the message overhead but it increases the search latency. Hence, k random walkers can be used. To terminate the k random walkers, a “checking-cum-TTL” strategy is effective. Here, each walker periodically (after a certain number of hops) checks with the query originator whether to terminate; the TTL is used to prevent looping, and is usually set to a large value. Search strategies: Performance. The performance of searches in unstructured overlays has been studied via simulations and by experiments. The following are some of the relationships of interest, for both flooding and for k-random walk (for various values of k) for various graph topologies such as the random graph and the PLRG. • The success rate as a function of the number of message hops, or TTL. 672

n m qi ri ρ R pi

number of nodes in the system number of objects in the system P normalized query rate, where m i=1 qi = 1 number of replicas of object i capacity P (measured as number of objects) per node nρ = m i=1 ri , the total capacity in the system ri /R, the population fraction of object i replicas

Table 18.2: Parameters to study replication.

• The number of messages as a function of the number of message hops, or TTL. • The above metrics as the replication ratio and the replication strategy changes. • The node coverage, recall, and message efficiency, as a function of the number of hops, or TTL; and of various replication ratios and replication strategies. Search strategies: Guidelines • Adaptively determining the termination condition is important. Checking is adaptive whereas TTL is not. • Message duplication must be minimized, for it represents wasted resources. • At each step in the search, the number of messages (or number of nodes visited) should not increase by a large ammount. Overall, k-random walk performs much better than flooding and is more scalable, for various replication and query distributions, and various graph topologies.

18.3.4 Replication Strategies Scott and Shenker [11] studied the degree of replication for blind or unguided search in random overlay graphs. The various parameters used to study replication are defined in Table 18.2. Random search is modeled by the following process. A node is repeatedly drawn at random from a bin, examined for a match with the copy of the object, and replaced in ther bin, until the object is found. The metric then is the number of nodes drawn (or equivalently, the number of hops of a random walker) until success. The probability that the object is found on the kth drawing is: P ri (k) = rni (1 − rni )k−1 . The average search size for i, denoted as Ai , is: Ai = Eover all k (P ri (k)) =

n X ri ri n [k (1 − )k−1] ∼ , for large n n n ri k=1

(18.5)

Across the system, the average search size A is: Average search size A =

m X i=1

673

qi Ai = n

X qi i

ri

(18.6)

Setting ri to n maximizes A, but requires full replication. As resources are constrained, assume that average number of replicas per node is ρ = R/n < m. (It is easy to see that R ≥ m ≥ ρ.) Substituting for n with R/ρ in the equation above, we have 1 X qi R X qi = (18.7) Average search size A = ρ i ri ρ i pi

The utilization rate ui of a replica of object i is the average rate of requests serviced by a replica of i. With random search, ui = pqii = R rqii . Over all all replicas of object i, the utilization is simply Pm P qi ui = Rqi . The average utilization rate over (all copies of) all objects is u = m i=1 pi pi i=1 ri R = = 1. This average is a constant, and independent of the replication scheme. It is desirable to have a low maximum utilization rate in order to distribute the load more uniformly. The replication problem is formulated as the optimization solution for Equation 18.7. We assume that all objects are of uniform size. To simplify analysis, we also assume that each object that is queried exists in the system and a search continues until the object is found, i.e., all searches are eventually successful. (In practice, there is a parameter L – such as TTL – that control the maximum search size. Search on insoluble queries continues until this parameter is exceeded. The cost of such queries is fs A + (1 − fs )L, where fs is the fraction of queries that are soluble.) Two natural replication strategies are Uniform and Proportional. R which implies pi = rRi = m1 . Uniform: ri = m R = mρ . This is the same Average search size for object i is Ai = rni . This equals ρrRi = ρR/m i for all objects. P P From Equation (18.7), average search size Aunif orm = ρ1 i pqii = ρ1 i mqi = mρ . Utilization of a replica of i is ui = pqii which is proportional to the query rate as pi is same for all objects. Maximum utilization of a replica of i is maxi ui = maxi pqii = R rqii , which can vary significantly.

Proportional: ri = Rqi which implies pi = qi . n Average search size for object i is Ai = rni = Rp = Rqn i = ρq1i , which is inversely propori tional to the query rate. P P m From Equation (18.7), average search size Aproportional = ρ1 i pqii = ρ1 m i=1 1 = ρ . Utilization of a replica of i is ui = pqii = 1, a constant for all replicas of all objects. Maximum utilization of a replica of i is maxi ui = maxi pqii = maxi qqii = 1 for all i. Both Uniform and Proportional replication have the same average search size which is independent of the query distribution. However, objects whose query rates are below the average have lower overhead with uniform replication, while those with query rates larger than the average have lower overhead with proportional replication. Square root: The optimal replication strategy that minimizes the average search size is the Square√ P √ Root replication, which is defined as having pi = rRi ∝ qi / j qj , assuming that 1/R ≤ √ P √ qi / j qj ≤ n/R for all i. 674

Uniform Proportional Square-root

ri constant, R/m qi R √ P √ R qi / j qj

A m/ρ m/ρ P √ 2 ( i qi ) /ρ

Ai = n/ri m/ρ 1/(ρq i) P j

√ √ qj / qi ρ

ui = Rqi /ri qi m 1 √ P √ qi j qj

Table 18.3: Comparison of Uniform, Proportional, and Square-root replication.

The optimality of square-root replication can be seen as follows. Substituting 1 − for pm in the cost function of Equation 18.7, we have search size Asq−rt

Pm−1 i=1

pi

m−1 m−1 X 1 X 1X qi /pi = [ qi /pi + qm /(1 − pi )] = ρ i ρ i=1 i=1

By solving ds/dpi = 0, the value of pi that minimizes Asq−rt is seen to be pm

p qi /qm .

Analogous to Uniform and Proportional replications, the values of A, Ai , and ui for Squareroot replication can be dervied. Exercise 1 asks you to show the derivations. The results are √ P √ summarized in Table 18.3. It can be seen that to minimize A, ri = R qi / j qj . √ The Square-root replication rate (∝ qi ) is more than that of Uniform (∝ 1), but less than that of Proportional (∝ qi ). It has been shown that 1. any allocation rate “in between” that of Uniform and of Proportional has a lower average search size A than that of Uniform and Proportional, and 2. any allocation rate either less than that of Uniform, or greater than that of Proportional has a higher average search size A than that of Uniform and Proportional.

18.3.5 Implementing Replication Strategies. Proportional and Uniform can be trivally implemented. For Proportional, each query creates a copy; for Uniform, a fixed number of copies are made when an object is created. The simple “path replication” scheme, wherein the number of copies made is proportional to the length of the (successful) search path, implements Square-root replication. Here object i is replicated c rni times per query, where c is some constant. Then ri can be captured by the following i = qi c rni . equation: dr dt q dr i = cn( rj2 − rq2i ) = r1j dtj − r1i dr . Let a = ln( rrji ). Then da dt dt j i √ R Square-root replication, wherein ri = P √qi qi , is a fixed point solution of this equation. Therefore, path replication implements Square-root replication. The analysis implicitly assumes that replicas also get deleted, in a way that is independent of their object identity or query rate, and the lifetime of a replica is a non-decreasing function of its age. (Policies such as random and FIFO satisfy this condition, but LRU and LFU do not.) Then, during steady state, the creation rate can equal the deletion rate. 675

An alternate way of analyzing replication schemes is as follows. Let C be the number of replicas created on a successful query; C is its average. Then in steady state, qi Ci pi = . pj qj Cj

(18.8)

To implement distributed algorithms for various replication policies, it is necessary to determine Ci locally without knowing pi or qi . • For Proportional, C is the same for all objects. p √ • For Square-root, if Ci ∝ 1/ qi then pi /pj = qi /qj , by substituting in Equation (18.8). As Ai ∝

nR pi

and pi ∝ qi Ci , therefore Ai ∝

1 . qi Ci

With path replication, Ci ∝ Ai , hence Ci ∝ Ai ∝

1 . qi Ci

In steady state, Ai and Ci are equal. Solving Ci ∝ q 1C for the fixed point, Ci ∝ √1qi . As i i √ pi ∝ qi Ci when Ci is steady, this gives pi ∝ qi . In a practical implementation, it needs to be ensured that convergence occurs once steady state sets in.

18.4 Chord Distributed Hash Table 18.4.1 Overview The Chord protocol, proposed by Stoica et al. [29], uses a flat key space to associate the mapping between network nodes and data objects/files/values. The node address as well as the data object/file/value is mapped to a logical identifier in the common key space using a consistent hash function. These mappings are illustrated in Figure 18.1. Both these mappings should ensure that the keys are distributed roughly equally among the nodes. This also insures that with high probability, the overhead of key management when nodes join or leave the P2P network is low. Specifically, when a node joins or leaves the network having n nodes, only O(1/n) keys need to be moved from one location to another. The Chord key space is flat, thus giving applications flexibility in mapping their files/ data to keys. Chord supports a single operation, lookup(x), that maps a given key x to a network node. Specifically, Chord stores a file/object/value at the node to which the file/object/value’s key maps. Two steps are involved. 1. Map the object/file/value to its key in the common address space. 2. Map the key to the node in its native address space using lookup. The design of lookup is the main challenge. In Chord, a node’s IP address is hashed to a m-bit identifier which servers as the node identifier in the common key (identifier) space. Similarly, the file/data key is hashed to a m-bit identifier that 676

K121 N119

N5

N115

K8 K15 N18

N104 K87

N23 N28

N99

K28

lookup(K8)

N73 N63 K53

Figure 18.2: An example Chord ring with m = 7, showing mappings to the Chord address space, and a query lookup using a simple scheme. serves as the key identifier. m is sufficiently large so that the probability of collisions during the hash is negligible. The Chord overlay uses a logical ring of size 2m . The identifier space is ordered on the logical ring modulo 2m . Henceforth in this section, we will assume modulo m arithmetic. A key k gets assigned to the first node such that its node identifier equals or follows the key identifier of k in the common identifier space. The node is the successor of k, denoted succ(k). A Chord ring for m = 7 is depicted in Figure 18.2. Nodes N5, N18, N23, N28, N63, N73, N99, N104, N115 and N119 are shown. Six keys: K8, K15, K28, K53, K87, K121 are stored among these nodes as follows. succ(8) = 18, succ(15) = 18, succ(28) = 28, succ(53) = 63, succ(87) = 99, and succ(121) = 5.

18.4.2 Simple lookup A simple key lookup algorithm that requires each node to store only 1 entry in its routing table works as follows. Each node tracks its successor on the ring, in the variable succ; a query for key x is forwarded to the successors of nodes until it reaches the first node such that that node’s identifier y is greater than the key x, modulo 2m . The result, which includes the IP address of the node with key y, is returned to the querying node along the reverse of the path that was followed by the query. This mechanism requires O(1) local space but O(n) hops, where n is the number of nodes in the P2P network. The pseudo-code for this simple lookup is given in Figure 18.3. The following convention is assumed. Notation (x, y] represents the left-open right-closed segment of the Chord logical ring modulo m. Notation x.P roc(·) is a RPC to execute P roc on node x while x.var is a RPC to read the variable var at process x. Example: The steps for the query: lookup(K8) initiated at node 28, are shown in Figure 18.2 using arrows. 677

(variables) integer: successor ←− initial value; (1) i.Locate_Successor(key), where key 6= i: (1a) if key ∈ (i, successor] then (1b) return(successor) (1c) else return successor.Locate_Successor(key).

Figure 18.3: A simple object location algorithm in Chord at node i.

18.4.3 Scalable Lookup A scalable lookup algorithm that uses O(log n) message hops at the cost of O(log n) space in the local routing tables, uses the following idea. Each node i maintains a routing table, called the finger table, with O(log n) entries, such that the xth entry (1 ≤ x ≤ m) is the node identifier of the node succ(i + 2x−1). This is denoted by i.f inger[x] = succ(i + 2x−1 ). This is the first node whose key is greater than the key of node i by at least 2x−1 mod m. Note that each finger table entry would have to contain the IP address and port number in addition to the node identifier, in order that i can communicate with i.f inger[x]; henceforth we will assume this implicitly without showing these entries. The size of the finger table is bounded by m entries. Due to the logarithmic structure, the finger table has more information about nodes closer ahead of it in the Chord overlay, than about nodes further away. Given any key whose node is to be located, the highly scalable logarithmic search shown in Figure 18.4 is used. For a query on key key at node i, if key lies between i and its successor, the key would reside at the successor and the successor’s address is returned. If key lies beyond the successor, then node i searches through the m entries in its finger table to identify the node j such that j most immediately precedes key, among all the entries in the finger table. As j is the closest known node that precedes key, j is most likely to have the most information on locating key, i.e., locating the immediate successor node to which key has been mapped. Example: The use of the finger tables in answering the query lookup(K8) at node N28 is illustrated in Figure 18.5. The finger tables of N28, N99, and N5 that are used are shown.

18.4.4 Managing Churn The code to manage dynamic node joins, departures, and failures is given in Figure 18.6. Node Joins. To create a new ring, a node i executes Create_New_Ring which creates a ring with the singleton node. To join a ring that contains some node j, node i invokes Join_Ring(j). Node j locates i’s successor on the logical ring and informs i of its successor. Before i can participate in the P2P exchanges, several actions need to happen: i’s successor needs to update its predecessor entry to i, i’s predecessor needs to revise its successor field to i, i needs to identify its predecessor, the finger table at i needs to be built, and the finger tables of all nodes need to be 678

(variables) integer: successor ←− initial value; integer: predecessor ←− initial value; array of integer f inger[1 . . . log n]; (1) i.Locate_Successor(key), where key 6= i: (1a) if key ∈ (i, successor] then (1b) return(successor) (1c) else (1d) j ←− Closest_P receding_N ode(key); (1e) return j.Locate_Successor(key). (2) i.Closest_P receding_N ode(key), where key 6= i: (2a) for count = m down to 1 do (2b) if f inger[count] ∈ (i, key] then (2c) break(); (2d) return(f inger[count]).

Figure 18.4: A scalable object location algorithm in Chord at node i. updated to account for i’s presence. This is achieved by procedures Stabilize(), F ix_F ingers(), and Check_P redecessor() that are periodically invoked by each node. Figure 18.7 illustrates the main steps of the joining process. A recent joiner node i that has executed Join_Ring(·) gets integrated into the ring by the following sequence. (a). The configuration after a recent joiner node i has executed Join_Ring(·). (b). Node i executes Stabilize(), which allows its successor j to adjust j’s variable predecessor to i. Specifically, when node i invokes Stabilize(), it identifies the successor’s predecessor k. If k ∈ (i, successor), then i updates its successor to k. In either case, i notifies its successor of itself via successor.Notif y(i), so the successor has a chance to adjust its predecessor variable to i. (c). The earlier predecessor k of j (i.e., the predecessor in Step (a)) executes Stabilize() and adjusts its successor pointer from j to i. (d). Node i executes F ix_F ingers() to build its finger table, and other nodes also execute the procedure to update their finger tables if necessary. Once all the successor variables and finger tables have stabilized, a call by any node to Locate_Successor(·) will reflect the new joiner i. Until then, a call by to Locate_Successor(·) may result in the Locate_Successor(·) call performing a conservative scan. The loop in Closest_P receding_Node that scans the finger table will result in a search traversal using smaller hops rather than truly logarithmic hops, resulting in some inefficiency. Still, the node i will be located although via more hops. 679

finger table for N5

N119 N115

N5 K8 N18 N23

N104

N28

N99 finger table for N99 99+1 99+2 99+4 99+8 99+16 99+32 99+64

N104 N104 N104 N115 N115 N5 N63

5+1 5+2 5+4 5+8 5+16 5+32 5+64

N18 N18 N18 N18 N23 N63 N73

lookup(K8) 28+1 28+2 28+4 28+8 28+16 28+32 28+64

N63 N63 N63 N63 N63 N63 N99

finger table for N28

N73 N63

Figure 18.5: An example showing a query lookup using the logarithmically-structured finger tables. Showing the correctness of the Chord protocol in the face of concurrent join operations and stablize operations in which pointers are being rewired is nontrivial. It can be shown that for any set of concurrent join operations, at some point after the last join operation completes, all the pointers and finger tables will be correct. However, in the transient period before the Chord ring stabilizes, an object search can result in three outcomes. • The finger tables used in a search are up to date and the correct successor of the key is sought in O(log n) hops. • The finger tables are not up to date but the successor pointers are correct. The sought key will be located but may take more steps as the full advantage of a logarithmic search space pruning cannot be used. • If the successor pointers are incorrect, or the key transfer to the new joiners in procedure Notif y has not completed, the search may fail. This is during a transient duration, and the source has the choice of reissuing the query. Node Failures and Departures. When a node j fails abruptly, its successor i on the ring will discover the failure when the successor i executes Check_P redecessor() periodically. Process i gets a chance to update its predecessor field when another node k causes i to execute Notif y(k). But that can happen only if k’s successor variable is i. This requires the predecessor of the failed node to recognize that its successor has failed, and get a new functioning successor! In fact, the successor pointers are required for object search; the predecessor variables are required only to accommodate new joiners. Note from Figure 18.4 that knowing that the successor is functional, and that the nodes pointed to by the finger pointers are functional, is essential. Example: In Figure 18.5, assume that node N63 fails. The closest successor that node N28 can 680

(variables) integer: successor ←− initial value; integer: predecessor ←− initial value; array of integer f inger[1 . . . log m]; integer: next_f inger ←− 1; (1) i.Create_N ew_Ring(): (1a) predecessor ←−⊥; (1b) successor ←− i. (2) i.Join_Ring(j), where j is any node on the ring to be joined: (2a) predecessor ←−⊥; (2b) successor ←− j.Locate_Successor(i). (3) i.Stabilize(): // executed periodically to verify and inform successor (3a) x ←− successor.predecessor; (3b) if x ∈ (i, successor) then (3c) successor ←− x; (3d) successor.N otif y(i). (4) i.N otif y(j): // j believes it is predecessor of i (4a) if predecessor =⊥ or j ∈ (predecessor, i)) then (4b) transfer keys in the range [j, i) to j; (4c) predecessor ←− j. (5) i.F ix_F ingers(): // executed periodically to update the finger table (5a) next_f inger ←− next_f inger + 1; (5b) if next_f inger > m then (5c) next_f inger ←− 1; (5d) f inger[next_f inger] ←− Locate_Successor(i + 2next_f inger−1 ). (6) i.Check_P redecessor(): // executed periodically to verify whether predecessor still exists (6a) if predecessor has failed then (6b) predecessor ←−⊥.

Figure 18.6: Managing churn in Chord. Code shown is for node i. find via the finger table is N99. N73 cannot be detected, and keys K64 through K73 will effectively be lost. A solution such as introducing a Check_Successor() procedure analogous to Check_P redecessor procedure will not solve the problem because it does not help to identify the functional successor. The Chord protocol proposes that rather than maintain a single successor, each node maintains a list of α successors, which are the node’s first α successors. If the first successor does not respond, the node can try the next successor from the list, and so on. Only the simultaneous failure of all the α successors can then cause the protocol to fail. Maintaining a list of successors requires some 681

successor=j

k

successor=j

i

predecessor=

(a) after i executes Join_Ring(.) predecessor=i

successor=i

k

j

k

successor= successor=j

i

predecessor=

successor

(b) after i executes Stabilize() and j executes Notify(i) predecessor=i

predecessor

successor=i

k

j

i

i predecessor=

T

successor=j

successor= successor=j

j T

j

predecessor= predecessor=i

T

predecessor= k

(c) after k executes Stabilize(), that triggers step (d)

successor=j

predecessor=k

(d) after i executes Notify(k)

Figure 18.7: Steps in the integration of node i in the ring, where j > i > k. changes to the code in Figure 18.6. Exercise 2 asks you to adapt this code to the changes required for maintaining successor lists. The provision for a successor list at each node provides a natural mechanism for the application to manage replicated objects. The replicas get placed at the node corresponding to the object key, as well as at the nodes in the successor list of that node. As Chord is able to update its successor list as the successor list changes, Chord can also interface with the application to let it track the locations of the replicas. A voluntary departure from the ring can be treated as a failure. However, a failed node causes all the data (keys) stored at that node to be lost until corrective action is taken. When a node departs voluntarily, it should first transfer all the keys it is responsible for to its successor. The departing node should also inform its successor and predecessor. This will enable the successor to update its predecessor to the predecessor of the departing node. The predecessor will also be able to update its successor list by deleting the departing node and adding the last successor of the departing node’s successor list to its own successor list.

18.4.5 Complexity The following results on the complexity have a nontrivial correctness proof and interested readers should consult the Chord papers for the proofs. 1. For a Chord network with n nodes, each node is responsible for at most (1 + ǫ)K/n keys, with “high probability”, where K is the total number of keys. Using consistent hashing, ǫ can be shown to be bounded by O(log n). The “high probability” clause is required because the validity of the result depends on the randomness and conflictfree mappings of the hash function used. 682

2. The search for a successor in Locate_Successor in a Chord network with n nodes requires time complexity O(log n) with high probability. This result is based on the observation that assuming completely random distributions of the key mappings and node mappings, after 2 log n hops, the distance between the key being searched for and the present node that the query has reached is at most 1/n. 3. The size of the finger table is log(n) ≤ m. 4. The average lookup time is 1/2 log(n). Exercises 2 and 3, based on the Chord papers, ask you to prove further results about the complexity under churn conditions.

18.5 Content Addressible Networks: CAN 18.5.1 Overview A content-addressible network (CAN) is essentially an indexing mechanism that maps objects to their locations in the network. The CAN project originated from the observation that the bottleneck to designing a scalable P2P network is this indexing mechanism. An efficient and scalable CAN is useful not only for object location in P2P networks, but also for large-scale storage management systems and wide-area name resolution services that decouple name resolution and the naming scheme. All these applications inherently require efficient and scalable addition of and location of objects using arbitrary location-independent names or keys for the objects. A CAN supports three basic operations: insertion, search, and deletion of (key, value) tuples. (A “value” is an object in the context of a CAN.) A good CAN design is distributed, fault-tolerant, scalable, independent of the naming structure, implementable at the application layer, and autonomic, i.e., self-organizing and self-healing. Although CAN is a generic phrase, it also specifically denotes the particular design of a CAN proposed by Ratnasamy et al. [24]. We now study this particular CAN design. CAN is a logical d-dimensional Cartesian coordinate space organized as a d-torus logical topology, i.e., a virtual overlay d-dimensional mesh with wrap-around. A two-dimensional torus was shown in Figure ??, Chapter 1. The entire space is partitioned dynamically among all the nodes present, so that each node i is assigned a disjoint region r(i) of the space. As nodes arrive, depart, or fail, the set of participating nodes, as well as the assignment of regions to nodes, change. For any object v, its key k(v) is mapped using a deterministic hash function to a point ~p in the Cartesian coordinate space. The (k, v) pair is stored at the node that is presently assigned the region that contains the point ~p. In other words, the (k, v) pair is stored at node i if presently the point ~p corresponding to (k, v) lies in region r(i). Analogously, to retrieve object v, the same hash function is used to map its key k to the same point p~. The node that is presently assigned the region that contains p~ is accessed (using a CAN routing algorithm) to retrieve v. The three core components of a CAN design are the following. 683

1. Setting up the CAN virtual coordinate space, and partitioning it among the nodes as they join the CAN. 2. Routing in the virtual coordinate space to locate the node that is assigned the region containing p~. 3. Maintaining the CAN due to node departures and failures.

18.5.2 CAN Initialization 1. Each CAN is assumed to have a unique DNS name that maps to the IP address of one or a few bootstrap nodes of that CAN. A bootstrap node is responsible for tracking a partial list of the nodes that it believes are currently participating in the CAN. These are reasonable assumptions, and perhaps the most “non-distributed” portions of the CAN design. 2. To join a CAN, the joiner node queries a bootstrap node via a DNS lookup, and the bootstrap node replies with the IP addresses of some randomly chosen nodes that it believes are participating in the CAN. 3. The joiner chooses a random point p~ in the coordinate space. The joiner sends a request to one of the nodes in the CAN, of which it learnt in Step 2, asking to be assigned a region containing ~p. The recipient of the request routes the request to the owner old_owner(~p) of the region containing p~, using the CAN routing algorithm. 4. The old_owner(~p) node splits its region in half and assigns one half to the joiner. The region splitting is done using an a priori ordering of all the dimensions, so as to decide which dimension to split along. This also helps to methodically merge regions, if necessary. The (k, v) tuples for which the key k now maps to the zone to be transferred to the joiner, are also transferred to the joiner. 5. The joiner learns the IP addresses of its neighbours from old_owner(~p). The neighbors are old_owner(~p) and a subset of the neighbours of old_owner(~p). old_owner(~p) also updates its set of neighbours. The new joiner as well as old_owner(~p) inform their neighbours of the changes to the space allocation, so that that they have correct information about their neighbourhood and can route correctly. In fact, each node has to send an immediate update of its assigned region, followed by periodic HEARTBEAT refresh messages, to all its neighbours. When a node joins a CAN, only the neighbouring nodes in the coordinate space are required to participate in the joining process. The overhead is thus of the order of the number of neighbours, which is O(d) and independent of n, the number of nodes in the CAN.

684

18.5.3 CAN Routing CAN routing uses the straight-line path from the source to the destination in the logical Euclidean space. This routing is realized as follows. Each node maintains a routing table that tracks its neighbour nodes in the logical coordinate space. In d-dimensional space, nodes x and y are neighbours if the coordinate ranges of their regions overlap in d − 1 dimensions, and abut in one dimension. All the regions are convex and can be characterized as follows. Let 1 1 d d region(x) = [[x1min , x1max ], . . . [xdmin , xdmax ]]. Let region(y) = [[ymin , ymax ], . . . [ymin , ymax ]]. j j Nodes x and y are neighbours if there is some dimension j such that xmax = ymin and for all i i other dimensions i, [ximin , ximax ] and [ymin , ymax ] overlap. An example of neighbouring nodes in 2-dimensional space is shown in Figure 18.8. (0,100) (100,100) [[0,25], [[25,50], [50,100]] [50,100]] [[50,50],[100,100]] 4 5 3

[[75,100], [[50,75], [25,50]] [0,50]] 6 1

2

[[0,0],[50,50]] (0,0)

[[75,100], [0,25]] 7 (100,0)

Figure 18.8: Two-dimensional CAN space. Seven regions are shown. The dashed arrows show the routing from node 2 to the coordinate p~ shown by the shaded circle. The routing table at each node tracks the IP address and the virtual coordinate region of each neighbour. To locate value v, its key k(v) is mapped to a point ~p whose coordinates are used in the message header. Knowing the neighbours’ region coordinates, each node follows simple greedy routing by forwarding the message to that neighbour having coordinates that are closest to the destination’s coordinates. To implement greedy routing to a destination node x, the present node routes a message to that neighbour among the neighbours k ∈ Neighbours, given by argmink∈N eighbours[min |~x − ~k|] Here, ~x and ~k are the coordinates of nodes x and k. Assuming equal-sized zones in d-dimensional space, the average number of neighbours for a node is O(d). The average path length is d4 · n1/d . The implication on scaling is that each node has about the same number of neighbours and needs to maintain about the same amount of state information, irrespective of the total number of nodes participating in the CAN. In this respect, 685

the CAN structure is superior to that of Chord. Also note that unlike in Chord, there are typically many paths for any given source-destination pair. This greatly helps for fault-tolerance. Average path length in CAN scales as O(n1/d ) as opposed to log n for Chord.

18.5.4 CAN Maintainence When a node voluntarily departs from CAN, it hands over its region and the associated database of (key, value) tuples to one of its neighbours. The neighbour is chosen as follows. If the node’s region can be merged with that of one of its neighbours to form a valid convex region, then such a neighbour is chosen. Otherwise the node’s region is handed over to the neighbour whose region has the smallest volume or load – the regions are not merged and the neighbour handles both zones temporarily until a periodic background region reassignment process runs to integrate the regions and prevent further fragmentation. CAN requires each node to periodically send a HEARTBEAT update message to each neighbour, giving its assigned region coordinates, the list of its neighbours, and their assigned region coordinates. When a node dies, the neighbours suspect its death and initiate a TAKEOVER protocol to decide who will take over the crashed node’s region. Despite this TAKEOVER protocol, the (key, value) tuples in the crashed node’s database remain lost until the primary sources of those tuples refresh the tuples. Requiring the primary sources to periodically issue such refreshes also serves the dual purpose of updating stale (dirty) objects in the CAN. The TAKEOVER protocol is as follows. When a node suspects that a neighbour has died, it starts a timer in proportion to its region’s volume. On timeout, it sends a TAKEOVER message, with its region volume piggybacked on the message, to all the neighbours of the suspected failed node. When a TAKEOVER message is received, a node cancels its bid to take over the failed node’s region if the received TAKEOVER message contains a smaller region volume than that of the recipient’s region. This protocol thus helps in load balancing by choosing the neighbour whose region volume is the smallest, to take over the failed node’s region. As all nodes initiate the TAKEOVER protocol, the node taking over also discovers its neighbours and vica versa. In the case of multiple concurrent node failures in one vicinity of the Cartesian space (this is rare), a more complex protocol using a expanding ring search for the TAKEOVER messages can be used. A graceful departure as well as a failure can result in a neighbour holding more than one region if its region cannot be merged with that of the departed or failed node. To prevent the resulting fragmentation and restore the 1 → 1 node to region assignment, there is a background reassignment algorithm that is run periodically. Conceptually, consider a binary tree whose root represents the entire space. An internal node represents a region that existed earlier but is now split into regions represented by its children nodes. A leaf represents a currently existing region, and (to overloading the semantics and the notation), also the node that represents that region. When a leaf node x fails or departs, there are two cases. 1. If its sibling node y is also a leaf, then the regions of x and y are merged and assigned to y. The region corresponding to the parent of x and y becomes a leaf and it is assigned to node 686

y. 2. If the sibling node y is not a leaf, run a depth-first search in the subtree rooted at y until a pair of sibling leaves (say, z1 and z2) is found. Merge the regions of z1 and z2, making their parent z a leaf node, assign the merged region to node z2, and the region of x is assigned to node z1. Figure 18.9 illustrates this reassignment. If node 2 fails, its region is assigned to node 3. If node 7 fails, regions 5 and 6 get merged and assigned to node 5 whereas node 6 is assigned the region of the failed node 7.

(entire coordinate space)

root

4 1 5

6 1 7

2 7 2

3

3

4 5

6

Figure 18.9: Example showing region reassignment in a CAN. A distributed version of the above depth-first centralized tree traversal can be performed by the neighbours of a departed node. The distributed traversal leverages the fact that when a region is split, it is done in accordance to a particular ordering on the dimensions. Node i performs its part of the depth-first traversal (initiated by the node to which the region of the departed node x is assigned in the TAKEOVER protocol) as follows. dima a 1. Identify the highest ordered dimension dima that has the shortest coordinate range [idim min , imax ]. Node i’s region was last halved along dimension dima .

2. Identify neighbour j such that j is assigned the region that was split off from i’s region in the last partition along dimension dima . Node j’s region abuts i’s region along dimension dima . 3. If j’s region volume equals i’s region volume, the two nodes are siblings and the regions can be combined. This is the terminating case of the depth-first tree search for siblings. Node j is assigned the combined region, and node i takes over the region of the departed node x. This takeover by node i is done by returning the recursive search request to the originator node, and communicating i’s identity on the replies. 4. Otherwise, j’s region volume must be smaller than i’s region volume. Node i forwards a recursive depth-first search request to j. 687

18.5.5 CAN Optimizations The following design techniques aim to improve one or more of the performance factors: the perhop latency, the path length, fault tolerance, availability, and load balancing. These techniques typically demonstrate a trade-off. 1. Multiple dimensions. As the path length is O(d·n1/d), increasing the number of dimensions decreases the path length and increases routing fault tolerance at the expense of larger state space per node. 2. Multiple realities. A coordinate space is termed as a reality. The use of multiple independent realities assigns to each node a different region in each different reality. This implies that in each reality, the same node will store different (k, v) tuples belonging to the region assigned to it in that reality, and will also have a different neighbour set. The data contents (k, v) get replicated in each reality, leading to higher data availability. Furthermore, the multiple copies of each (k, v) tuple, one in each reality, offer a choice – the closest copy can be accessed. Routing fault tolerance also improves because each reality offers a set of different paths to the same (k, v) tuple. All these advantages come at the cost of more storage – for state information for the neighbours in each reality, as well as for the (k, v) tuples mapped to the region allocated to a node in each reality. 3. Delay latency. Rather than using just the Cartesian distance as a metric to make routing decisions, the delay latency (measured using round-trip time RTT) on each of the candidate logical links can also be used in making the routing decision. 4. Overloading coordinate regions. Each region can be shared by multiple nodes, up to some upper limit. This offers several advantages. First, the path length and path latency get reduced because overloading is equivalent to having fewer nodes in the CAN. Second, the fault tolerance improves because a region becomes empty only if all the nodes assigned to it depart or fail concurrently. Third, the per-hop latency decreases because a node can select the closest node from the neighbouring region to forward a message towards the destination. The cost of gaining these advantages is that many of the aspects of the basic CAN protocol need to be reengineered to accommodate overloading of coordinate regions (See Exercise 5). 5. Multiple hash functions. The use of multiple hash functions maps each key to different points in the coordinate space. This replicates each (k, v) pair for each hash function used. The effect is similar to that of using multiple realities. 6. Topologically sensitive overlay. The CAN overlay described so far has no correlation to the physical proximity or to the IP addresses of domains. Logical neighbours in the overlay may be geographically far apart, and logically distant nodes may be physical neighbours. By constructing an overlay that accounts for physical proximity in determining logical neighbours, the average query latency can be significantly reduced. 688

18.5.6 CAN Complexity The time overhead for a new joiner is O(d) for updating the new neighbours in the CAN, and O(d/4 · log(n)) for routing to the appropriate location in the coordinate space. This is also the overhead in terms of the number of messages. The time overhead and the overhead in terms of the number of messages for a node departure is O(d2), because the TAKEOVER protocol uses a message exchange between each pair of neighbours of the departed node. Exercise 4 asks you to compute the complexity of the distributed region reassignment protocol.

18.6 Tapestry 18.6.1 Overview The Tapestry P2P overlay network provides efficient scalable location-independent routing to locate objects distributed across the Tapestry nodes. Much of the design is adapted from an earlier design of Plaxton trees. The notable enhancements of Tapestry include dealing with node churn as well as dynamic addition and deletion of objects. As in Chord, nodes as well as objects are assigned identifiers obtained by mapping from their native name spaces to a common large identifier space using a uniformly distributed hash function such as SHA-1. The hashed node identifiers are termed VIDs and the hashed object identifiers are termed as GUIDs (acronym for globally unique ids). For brevity, a specific node v’s identifier is denoted vid and a specific object O’s GUID is denoted OG .

18.6.2 Overlay and Routing 18.6.2.0.1 Root and Surrogate Root. Tapestry uses a common identifier space specified using m bit values. This identifier is typically expressed in hexadecimal notation, i.e., base b = 16 and presently Tapestry recommends m = 160. Each identifier OG in this common overlay space is mapped to a set of unique nodes that exists in the network, termed as the identifier’ s root set denoted OGR . Typically, |OGR | is a small constant, and the main purpose of having |OGR | > 1 is to increase fault-tolerance. In our discussion, we assume |OGR | = 1, and refer to a root node of OG as OGR . If there exists a node v such that vid = OGR , then v is the root of identifier OG . If such a node does not exist, then a globally known deterministic rule is used to identify another unique node sharing the largest common prefix with OG , that acts as the surrogate root. To access object O, the goal is to reach the root OGR (whether real or surrogate). Routing to OGR is done using distributed routing tables that are constructed using prefix routing information. Prefix routing in Tapestry is somewhat analogous to prefix routing within the telephone network, or to address allocation in the Internet using Classless InterDomain Routing (CIDR). Unlike the telephone numbers or CIDR-assigned IP addresses, Tapestry’s VIDs are in a virtual space without correlation to topology, however, topological information can be used to select nodes that are “close” as per some metric.

689

7B28 2

7DD0 2 1

4 7C25 7C2B 3 2 7C4A 1 3 7114 9833 7C13

7C27

0672 4 1

AA21

3 4

7CFF

7C21

Figure 18.10: Some example links of the Tapestry routing mesh at node with identifier “7C25”. Three links from each level 1 through 4 are labeled by the level. 18.6.2.0.2 Prefix Routing. Prefix routing at any node to select the next hop is done by increasing the prefix match of the next hop’s VID with the destination OGR . Thus, a message destined for OGR = 62C35 could be routed along nodes with VIDs 6****, then 62***, then 62C**, then 62C3*, and then to 62C35. Let M = 2m . The routing table at node vid contains b · logb M entries, organized in logb M levels i = 1 . . . logb M. Each entry is of the form hwid , IP addressi. In level i, there are b entries with the following property. • Each entry denotes some “neighbour” node VIDs with a (i − 1)-digit prefix match with vid – thus, the entry’s wid matches vid in the (i − 1)-digit prefix. Further, in level i, for each digit j in the chosen base (e.g., 0, 1, . . . E, F when b = 16), there is an entry for which the ith digit position is j. Specifically, the j th entry (counting from 0) in level i has value j for digit position i. Let an i digit prefix of vid be denoted as pref ix(vid , i). Then the j th entry (counting from 0) in level i begins with an i-digit prefix pref ix(vid , i − 1) ◦ j. For example, the fifth entry in level 2 at node 9F248 will be 94***, thus having a 2-digit prefix “94”. 18.6.2.0.3 Router Table. The nodes in the router table at vid are the neighbours in the overlay, and these are exactly the nodes with which vid communicates. A part of the routing mesh at one node is shown in Figure 18.10. For each forward pointer from node v to v ′ , there is a backward pointer from v ′ to v. Observe the following regarding the router table construction. • There is a choice of which entry to add in the router table. For example, the j th entry in level i can be the VID of any node whose i-digit prefix is determined; the (m − i)-digit suffix can vary. The flexibility is useful to select a node that is “close”, as defined by some metric space (e.g., round-trip time). In fact, this choice also allows a more fault-tolerant strategy for routing. Multiple VIDs can be stored in the routing table, as follows. For each prefix β of a node v’s identifier and for each digit j ∈ {0 . . . b − 1} in the alphabet, define the v neighbour set Nβ,j as the set of all nodes whose identifiers share prefix β ◦ j. The nodes in this neighbour set are also referred to as (β, j) neighbours of v. The b sets, one for each value 690

(variables) array of array of integer T able[1 . . . logb 2m , 1 . . . b];

// routing table

(1) N EXT _HOP (i, OG = d1 ◦ d2 . . . ◦ dlogb M ) executed at node vid to route to OG : // i is (1 + length of longest common prefix), also level of the table (1a) while T able[i, di ] =⊥ do // dj is ith digit of destination (1b) di ←− (di + 1) mod b; (1c) if T able[i, di ] = v then // node v also acts as next hop (special case) (1d) return N EXT _HOP (i + 1, OG ) // locally examine next digit of destination (1e) else return(T able[i, di ]). // node T able[i, di ] is next hop

Figure 18.11: Routing in Tapestry. The logic for determining the next hop at a node with node identifier v, 1 ≤ v ≤ n, based on the ith digit of OG , i.e., based on the digit in the ith most significant position in OG . v of j, form the routing table of level |β| + 1. |Nβ,j | grows exponentially as |β| decreases, so the size of this set can be limited by a predetermined parameter c. The closest node in each set is the primary neighbour. Thus the size of the routing table is: c · b · logβ M.

0 The route from vid (source) to destination j1 ◦j2 . . .◦jlog m , is via nodes v 1 , v 2 . . . v log m , where 0 1 2 v v 1 ∈ N⊥,j (first hop), v 2 ∈ Njv1 ,j2 (second hop), v 1 ∈ Njv1 ◦j2 ,j3 (third hop), and so on. The 1 primary neighbour is chosen at each hop. Observe that this provides location-independent routing, i.e., irrespective of the source, the same unique root node is reached.

• The j th entry in level i may not exist because no node meets the criterion. This is a hole in v the routing table. Stated more generally, |Nβ,j | may be 0, signifying a hole for digit j at level |β| + 1.

Surrogate routing can be used to route around holes. If the j th entry in level i should be chosen but is missing, route to the next non-empty entry in level i, using wraparound if needed. All the levels from 1 to logb 2m need to be considered in routing, thus requiring logb 2m hops. The code for determining the next hop using NEXT _HOP (i, OG) is shown in Figure 18.11. This is invoked as NEXT _HOP (1, OG) at the source node. To determine hop i of the route, the node v that executes the function has a prefix at least i − 1 digits in common with OG . Example: An example of routing is shown in Figure 18.12. P1. Surrogate routing leads to a unique root. If the routing were to lead to different nodes A and B, let the most significant position in which the digits of A and B differ be i. This implies level i routing caused the routing at some nodes X and Y along different digits. However, the first i digits do not change henceforth, and assuming synchronized routing tables, the holes would be consistent in the tables at X and Y . Hence both should route to the same ith digit, which is a contradiction. It can now be seen that: 691

62CFF 4

62C01

5

62C3A 4 62C24 3

2 4

62C31 4 5

62655

65011

62C79 4

5 62C3A 62C35

62006 3 62CAB 4 62C11

2

62409 3

6C144 1 FAB11

2 64000

Figure 18.12: An example of routing from FAB11 to 62C35. The numbers on the arrows show the level of the routing table used. The dashed arrows show some unused links. P2. The routing algorithm identifies for each identifier vid , a unique spanning tree rooted at vid .

18.6.3 Object Publication and Object Search The unique spanning tree used to route to vid is used to publish and locate an object whose unique root identifier OGR is vid . A server S that stores object O having GUID OG and root OGR periodically publishes the object by routing a publish message from S towards OGR . At each hop and including the root node OGR , the publish message creates a pointer to the object. Ideally, “each node between O and OGR must maintain a pointer to O despite churn”. (Note that the publishing is done by each server at which a replica of the object resides, as well as for each GUID of the object. Recall that an object can be assigned multiple GUIDs, each mapping to a different root node, and giving rise to the set of root nodes OGR .) If a node lies on the path from two or more servers storing replicas, that node will store a pointer to each replica, sorted in terms of a distance metric (such as latency from itself). This is the directory information for objects, and is maintained as a soft-state, i.e., it requires periodic updates from the server, to deal with changes and to provide fault-tolerance. Example: An example showing publishing of an object with OG = 72EA1 by two replicas, at 1F329 and C2B40 is shown in Figure 18.13. To search for an object O with GUID OG , a client sends a query destined for the root OGR . Along the logb 2m hops, if a node finds a pointer to the object residing on server S, the node redirects the query directly to S. Otherwise, it forwards the query towards the root OGR which is guaranteed to have the pointer for the location mapping. A query gets redirected directly to the object as soon as the query path overlaps the publish path towards the same root. Each hop towards the root reduces the choice of the selection of its next node by a factor of b; hence, the more likely by a factor of b that a query path and a publish path will meet. Furthermore, as the next hop is chosen based on the network distance metric whenever there is a choice, we also observe that the 692

72EA1 72EA8 7826C 72E33

72E34

72F11

729CC

720B4 7FAB1 75BB1

70666

7D4FF

094ED C2B40

25011

BCF35

17202 1F329

server

publish path

server

routing pointer

object pointer

Figure 18.13: An example showing publishing of object with identifier 72EA1 at two replicas 1F329 and C2B40. closer the client is to the server in terms of the distance metric, the more likely that their paths to the object root will meet sooner, and the faster the query will be redirected to the object. Example: Consider the object OG which has identifier 72EA1 and two replicas at 1F329 and C2B40, as shown in Figure 18.13. A query for the object from 094ED will find the object pointer at 7FAB1. A query from 7826C will find the object pointer at 72F11. A query from BCF35 will find the object pointer at 729CC.

18.6.4 Node Insertion When nodes join the network, the result should be the same as though the network and the routing tables had been initialized with the nodes as part of the network. The procedure for the insertion of node X should maintain the following property of Tapestry: P3. For any node Y on the path between a publisher of object O and the root GOR , node Y should have a pointer to O. More generally, the insertion should satisfy the following properties. • Nodes which have a hole in their routing table should be notified if the insertion of node X can fill that hole. • If X becomes the new root of existing objects, references to those objects should now lead to X. • The routing table for node X must be constructed. • The nodes near X should include X in their routing tables to perform more efficient routing. The main steps in node insertion are outlined here. 693

• Node X uses some gateway node into the Tapestry network to route a message to itself. This leads to its “surrogate”, i.e., the root node with identifier closest to that of itself (which is Xid ). The surrogate Z identifies the length α of the longest common prefix that Zid shares with Xid . • Node Z initiates a MULTICAST-CONVERGECAST on behalf of X by essentially creating a logical spanning tree as follows. Acting as a root, Z contacts all the (α, j) nodes, for all j ∈ {0, 1 . . . b−1} (tree level 1). These are the nodes with prefix α followed by digit j. Each such (level 1) node Z1 contacts all the (pref ix(Z1, |α| + 1), j) nodes, for all j ∈ {0, 1 . . . b − 1} (tree level 2). This continues up to level logb 2m − |α| and completes the MULTICAST. The nodes at this level are the leaves of the tree, and initiate the CONVERGECAST, which also helps to detect the termination of this phase. All the nodes contacted fill in any holes in their routing table and, if necessary, transfer any references of pointers that are rooted locally. All these nodes also contact X with their information, so that X can build its routing table from level |α| + 1 up to logb 2m . All these nodes that contact X have a common prefix of α. To construct the rest of its routing table from levels 1 through |α|, node X procures similar lists for successively smaller prefixes until it gets closest b nodes matching the empty prefix. Node X begins with the list of nodes for level α, corresponding to the level l of its routing table which is already filled. To construct the level l − 1 list, node X contacts all the nodes in the level l list to find out all the level l − 1 nodes they know about by asking for both forward pointers and backward pointers. Level l − 1 of the routing table is filled in using the K closest nodes from the level l − 1 list, for each of the digits 0 . . . b − 1. In this manner, X completes its routing table, and all the nodes contacted in the process can optimize their routing tables by using X if it helps. The insertion protocols are fairly complex and deal with concurrent insertions.

18.6.5 Node Deletion When a node A leaves the Tapestry overlay, the following actions are performed. • Node A informs the nodes to which it has (routing) backpointers. It also provides them with replacement entries for each level from its routing table. This is to prevent holes in their routing tables. (The notified neighbours can periodically run the nearest neighbour algorithm to fine-tune their tables.) • The servers to which A has object pointers are also notified. The notified servers send object republish messages. • During the above steps, node A routes messages to objects rooted at itself to their new roots. On completion of the above steps, node A informs the nodes reachable via its backpointers and forward pointers that it is leaving, and then leaves. 694

Nodes failures are handled by using the redundancy that is built in to the routing tables and object location pointers. For example, each routing table entry has up to c neighbours in the v neighbour set Nβ,j . A node X detects a failure of another node A by using soft-state beacons or when a node sends a reply but does not get a response. Node X updates its routing table entry for A with a suitable substitute node, running the nearest neighbour algorithm if necessary. If A’s failure leaves a hole in the routing table of X, then X contacts the suggorate of A in an effort to identify a node to fill the hole. The details of the protocol can be found in the Tapestry papers. In addition to repairing the routing mesh, the object location pointers also have to be adjusted. Objects rooted at the failed node may be inaccessible until the object is republished. The protocols for doing so essentially have to (i) maintain path availability, and (ii) optionally collect garbage/dangling pointers which would otherwise persist until the next soft-state refresh and timeout. Overall, experiments have shown that Tapestry continues to perform well with high probability despite dynamic node insertions and failures. Complexity: • A search for an object is expected to take (logb 2m ) hops. However, the routing tables are optimized to identify nearest neighbour hops (as per the space metric). Thus, the latency for each hop is expected to be small, compared to that for CAN and Chord protocols. • The size of the routing table at each node is c · b · logb 2m , where c is the constant that limits the size of the neighbour set that is maintained for fault-tolerance. The larger the Tapestry network, the more efficient is the performance. Hence, it is better that different applications share the same overlay.

18.7 Some Other Challenges in P2P System Design 18.7.1 Fairness: A Game Theory Application P2P systems depend on all the nodes cooperating to store objects and allowing other nodes to download from them. However, nodes tend to be selfish in nature; thus there is a tendancy to download files without reciprocating by allowing others to download the locally available files. This behavior, termed as leaching or free-riding, leads to a degradation of the overall P2P system performance. Hence, penalties and incentives should be built in the system to encourage sharing and maximize the benefit to all nodes. We now examine at the classical problem, termed the Prisoners’ Dilemma, from game theory, that has some useful lessons on how selfish agents might cooperate. This problem is an example of a non-zero-sum-game. “Two suspects, A and B, are arrested by the police. There is not enough evidence for a conviction. The police separate the two prisoners, and separately, offer each the same deal: if the prisoner testifies against (betrays) the other prisoner and the other prsioner remains silent, the betrayer gets freed and the silent accomplice get a 10 year sentence. If both testify against the other (betray), 695

they each receive a 2 year sentence. If both remain silent, the police can only sentence both to a small 6-month term on a minor offence.” Rational selfish behavior dictates that both A and B would betray the other. This is a not a Pareto optimal solution, where a Pareto-optimal solution is one in which the overall good of all the participants is maximized. In the above example, both A and B staying silent results in a Pareto optimal solution. The dilemma is that this is not considered the rational behaviour of choice. In the Iterative Prisoners’ Dilemma, the game is played multiple times, until an “equilibrium” is reached. Each player retains emmory of the last move of both players (In more general versions, the memory extends to several past moves.) After trying out various strategies, both players should converge to the ideal optimal solution of staying silent. This is Pareto-optimal. The commonly accepted view is that the tit-for-tat strategy, described next, is the best for winning such a game. In the first step, a prisoner cooperates, and in each subsequent step, he reciprocates the action taken by the other party in the immediately preceding step. The BitTorrent P2P system has adopted the tit-for-tat strategy in deciding whether to allow a download of a file in solving the leaching problem. Here, cooperation is analogous to allowing others to upload local files, and betrayal is analogous to not allowing others to upload. The term chocking refers to the refusal to allow uploads. As the interactions in a P2P system are longlived, as opposed to a one-time decision to cooperate or not, optimistic unchocking is periodically done to unchoke peers that have been chocked. This optimistic action roughly corresponds to the re-initiation of the game with the previously chocked peer after some time epoch has elapsed.

18.7.2 Trust or Reputation Management Various incentive-based economic mechanisms to ensure maximum cooperation among the selfish peers inherently depend on the notion of trust. In a P2P environment where the peer population is highly transient, there is also a need to have trust in the quality of data being downloaded. This requirements have lead to the area of trust and trust management in P2P systems. As no node has a complete view of the other downloads in the P2P system, it may have to contact other nodes to evaluate the trust in particular offerers from which it could download some file. These communication protocol messages for trust management may be susceptible to various forms of malicious attack (such as man-in-the-middle attacks and Sybil attacks), thereby requiring strong security guarantees. The many challenges to tracking trust in a distributed setting include: quantifying trust and using different metrics for trust, how to maintain trust about other peers in the face of collusion, how to minimize the cost of the trust management protocols.

18.8 Tradeoffs between Table Storage and Route Lengths 18.8.1 Unifying DHT Protocols Chord, CAN, and Tapestry are three well-known representative protocols for managing structured P2P overlays. Despite their seeming differences, Xu, Kumar, and Yu [32] showed that the routing 696

function they perform can be expressed in a uniform way by generalizing the function of Classless Interdomain Domain Routing (CIDR) used by the IP protocol. We assume that all identifiers are in the common address space. We also assume modulo arithmetic (modulo n for Chord, modulo n−d for CAN, modulo b for Tapestry). Routing Rule: The next-hop routing to node with identifier dest from the current node with identifier id is as follows. Let the k entries in a routing table at a node with identifier id be the tuples hSid,i , Jid,i i, for 1 ≤ i ≤ k. If |dest − id| ∈ the range Sid,i then route to R(id + Jid,i ), where R(x) is the node responsible for key R(x). Clearly, we must have that for distinct i and j, Sid,i ∩ Sid,j = ∅ and Jid,i 6= Jid,j . Further, ∪1≤i≤s Sid,i contains all the keys not stored by node id. When Sid,i and Jid,i are independent of id, as is the case for CAN, CHord, and Tapestry, the subscript id can be deleted. Chord: if dest − id ∈ Si = [2i−1 , 2i) then node id routes to node id + Ji , where Ji = 2i−1 . This corresponds to looking up the ith entry in the finger table, as described in Section 18.4.3. CAN: The greedy routing function for CAN was given in Section 18.5.3. Here we assume a simple uniform distribution of nodes in the address space, xd = n, and that nodes are numbered by an integer in base x, where x is the number of nodes in each dimension. Routing is assumed to be done dimension by dimension (rather than using greedy routing). Wraparound routing is assumed in each dimension. Then, for each dimension i, the following holds: If dest and id differ in dimension i, route to i’s neighbour in that dimension. Formally, If dest − id ∈ (Si =)[xi−1 , xi ) then route to id + Ji where Ji + id is a neighbour node in dimension in i − 1 and Ji = kxi−1 for some k ≤ x. Tapestry: Let x = logb n, lvl = 1 . . . x and j ∈ 0 . . . b − 1. After deleting the longest common prefix between id and dest, pref ix(dest, lvl − 1), from dest, we have suf f ix(dest, x − lvl + 1). The routing function was described in Section 18.6.2. If suf f ix(dest, x − lvl + 1) ∈ S(lvl−1)·b+j = [j · bx−lvl+1 , (j + 1) · bx−lvl+1 ) then node id routes to node pref ix(id, lvl − 1) ◦ suf f ix(J(lvl−1)·b+j , x − lvl + 1), where J(lvl−1)·b+j ∈ [j · bx−lvl+1 , (j + 1) · bx−lvl+1 ). These routing relationships are summarized in Table 18.4.

18.8.2 Bounds on DHT Storage and Routing Distance Based on Table 18.4, the router table size and network diameter are represented in Figure 18.14. A fundamental question is whether the asymptotic bounds on (routing table size, network diameter as determined by the maximum number of hops) are (log2 n, Ω(log2 n)) as for Chord and Tapestry, and (d, Ω(n−d )) as for CAN. Xu et al. [32] uased the following definitions to answer this.

697

Protocol Routing table size Worst case distance n, common name space Si Ji

Chord k = O(log2 n) O(log2 n) 2k [2i−1 , 2i ) 2i−1

CAN k = O(d) O(n1/d ) xd [xi−1 , xi ) kxi−1

Tapestry k = O(logb n) O((b − 1) · logb n) bx [j · bx−lvl+1 , (j + 1) · bx−lvl+1 ) suf f ix(J(lvl−1)·b+j , x − lvl + 1)

Table 18.4: Comparison of representative P2P overlays. d is the number of dimensions in CAN. b is the base in Tapestry.

Routing table size n

maintain full state asymptotic tradeoff curve Chord, Tapestry CAN

log n

maintain no state

x visitors)

LOG

LOG

(a) Power law (linear scale)

# visitors

PDF

# visitors

LOG

slope a

# visitors

CDF

slope b = a−1

LOG

(b) Power Law (log−log scale)

# visitors

LOG

(c) Pareto law (log−log scale)

slope c = 1/b

rank of site LOG (i.t.o. > y visitors)

(d) Zipf’s Law (log−log scale)

Figure 18.15: The popularity of web sites. (a) Power law showing the PDF using a linear scale. (b) Power Law showing the PDF using a log-log scale. (c) Pareto Law showing the CDF using a log-log scale. (d) Zipf’s Law using a log-log scale. Power Law: P [X = x] ∼ x−a This law is stated as a Probability Distribution Function (PDF). It says that the number of occurrences of events that equal x is an inverse power of x. Figures 18.15(a) and (b) show the typical Power Law PDF plots on a nomral scale and on a log-log scale, respectively. In the log-log plot, the slope is a. In our example, this corresponds to the number of sites which have exactly x visitors. Pareto Law: P [X ≥ x] ∼ x−b = x−(a−1) 701

This law is stated as a Cumulative Distribution Function (CDF). The number of occurrences larger than x is an inverse power of x. The CDF can be obtained by integrating the PDF. The exponents a and b of the Pareto (CDF) and Power Laws (PDF) are related as b + 1 = a. Figure 18.15(c) shows the Pareto Law CDF plot on a log-log scale. In the log-log plot, the slope is b = a − 1. In our example, this corresponds to the number of sites which have at least x visitors. Zipf’s Law: n ∼ r −c . This law states the count n (i.e., the number) of the occurrences of an event, as a function of the event’s rank r. It says that the count of the rth largest occurrence is an inverse power of the rank r. Figure 18.15(d) shows the Zipf plot on a log-log scale. In the log-log plot, the 1 . slope is c, which we see below is 1b = a−1 The context initally used by Zipf was the frequency of occurrence of words in English, where the most frequently occurring word had rank 1. Zipf’s law is widely occurring, e.g., magnitude of earthquakes, and populations of cities follow this law. In our example, this corresponds to the number of visits to the rth most popular site. Clearly, that the Pareto Law (CDF) and Power law (PDF) are related. The Zipf Law n ∼ r −c , stating “the r-ranked object has n = r c occurrences, can be equivalently expressed as: “r objects (X-axis) have n = r −c (Y-axis) of more occurrences”. This becomes the same as the Pareto Law’s CDF after transposing the X and Y axes, i.e., by restating as: “the number of occurrences larger than n = r −c (Y-axis) is r (X-axis)”. 1 From Zipf’s Law, n = r −c , hence, r = n− c . Hence, the Pareto exponent b is 1c . As b = (a − 1), where a is the Power Law exponent, we see that a = 1 + 1c . Hence, the Zipf Law distribution also satisfies a Power Law PDF.

18.10.2 Properties of the Internet The Internet is a prime example of a complex entity that exhibits power-law behaviour. Based on extensive empirical measurements, Siganos at el. [28] showed the following results. Rank exponent/ Zipf’s law: The nodes in the Internet graph are ranked in decreasing order of their degree. When the degree di is plotted as a function of the rank ri on a log-log scale, the graph is like Figure 18.15(d). The slope is termed the rank exponent R, and di ∝ riR . If the minimum degree dn = m is known, then m = dn = CnR , implying that the proportionality constant C is m/nR . Exercise 6 asks you to estimate the number of edges as a function of the rank exponent and the number of nodes. Degree exponent/ PDF and CDF: Let the CDF fd of the node degree d be the fraction of nodes with degree greater than d. Then fd ∝ dD , where D is the degree exponent that is the slope of the log-log plot of fd as a function of d. 702



Analogously, let the PDF be gd . Then gd ∝ dD , where D ′ is the degree exponent that is the slope of the log-log plot of gd as a function of d. Empirically, D ′ ∼ D + 1, as theoretically predicted. Further, Rsim D1 , also as theoretically predicted. The imperfect match is attributed to imperfect measurements and approximations in curve-fitting. In practice, the CDF is preferred as it can be estimated with greater accuracy. Eigen exponent E: For the adjacency matrix A of a graph, its eigenvalue λ is the solution to AX = λX, where X is a vector of real numbers. The eigenvalues are related to the graph’s number of edges, number of connected components, the number of spanning trees, the diameter, and other important topological properties. Let the various eigenvalues be λi , where i is the order and between 1 and n. Then the graph of λi as a function of i is a straight line, with a slope of E, the eigen-exponent. Thus, λi ∝ iE . More intriguingly, when the eigenvalues and √ the degree are sorted in descending order, it is found that λi = di, implying that E = D2 . The following additional hypotheses have not been very vigorously tested and verified. Nevertheless, they offer very insightful looks into the prevalance and use of power laws in complex uncontrolled entities such as the Internet. Two definitions are useful at this stage. • P N(h) is the number of pairs of nodes within h hops, counting self-pairs, and counting all other pairs twice due to the dual edge incidence. • NH(h), the neighbourhood, is the expected number of nodes within h hops. Hop-plot exponent, H: Experimental measurements have shown that P N(h) follows a power law regime more closely, rather than the exponential regime as previously estimated. Thus, P N(h) ∝ hH , where H is the slope of the log-log plot of P N(h) as a function of h for h ≪ dia. From the definition of P N(h), observe that P N(1) = n + 2l. Hence,  (n + 2l)hH , if h ≪ dia P N(h) = (18.9) n2 , if h ≥ dia The hop-plot exponent is useful to estimate the effective diameter diaef f of the network. Informally, any two nodes in the network are within diaef f hops of each other, with “high probability”. When some destination node whose location is unknown needs to be reached, the use of hopconstrained broadcast is the standard solution. A large hop count takes too long, whereas a small hop count may not reach the entire network. If the hop count, is set to diaef f , then with high probability, the destination can be reached with just the right amount of overhead. Using n, H, and the number of edges l (see Exercise 6), the effective diameter is defined as: diaef f = (

n2 1/H ) n + 2l

This effective diameter is estimated as the abscissa of the intersection of the log-log hop-plot with slope H and the n2 coverage that is expected within diameter hops. 703

Observe that the average size of the neighbourhood NN(h) = P Nn(h) − 1. Hence NN(h) = (n+2l)hH − 1. The NN(h) is seen to be a more accurate estimate of the neighbourhood than the n average-degree estimate, NNd (h) = d(d − 1)h−1 The NNd (h) estimate assumes that the degree distributionis mor uniform, and that each hop adds d − 1 new nodes per node at the boundary of the examined network. As the degree distribution is highly skewed, the traditional NN ′ (h) metric is not accurate. For all the cases above, the power law regime has so far been mpirically valiated. The exponent itself has been observed to change gradually over time as the networks evolve. The power law regime provides a good handle on predicting the future growth of the Internet, and building accurate graphs for simulations. 18.10.2.0.4 Classification of Scale-free Networks. Scale-free networks of different types – WWW, INTNET, AS, ACT, AUTH, SUBSTRAE, PROT, PHON, in-degree for CITE, WORDSYN - have different degree exponents, typically ranging from 2 to 3. The quest to seek a more universal and common factor resulted in the analysis of another metric, called the “betweenness centrality”. For any graph, let its geodesics, i.e., set of shortest paths, between any pair of nodes i and j be denoted S(i, j). Let Sk (i, j) be a subset of S(i, j) such that all the geodesics in Sk (i, j) pass P P k (i,j)| . through node k. The betweenness centrality BC of node k, bk , is i6=j gk (i, j) = i6=j |S|S(i,j)| The bk denotes the importance of node k in shortest-path connections between all pairs of nodes in the network. The metric BC follows the power law PBC (g) ∼ g −β , where β is the BC-exponent. Unlike the degree exponent which varies across different network types, the BC-exponent has been empirically found to take on values of only 2 or 2.2 for these varied network types. This very interesting observation is under further study.

18.10.3 Error and Attack Tolerance of Complex Networks Based on the node degree distribution P (k), two broad classes of small world networks are the exponential networks and the scale-free networks. In exponential networks, such as the ER random graph model and the Watts Strogatz small world model, P (k) reaches a maximum at a k value and then P (k) decreases exponentially per a Poisson distribution as k increases. In scale-free networks, such as the web and the Internet, P (k) decreases as per a power law, P (k) ∼ k −γ . The following are two key differences that leads to different behaviour of exponential networks and of scale-free networks, under errors and attakcs. (1) Nodes with a very high degree are statistically significant in scale-free networks, whereas they are close to an impossibility in exponential networks. (2) In an exponential network, all nodes have about the same number of links, whereas in a scale-free network, some nodes have many links and the majority of the nodes have a small number of links. Errors are simulated by removing nodes at random. Attacks are simulated by removing the nodes with highest degree. Their impact is measured on network diameter and network partitioning. 704

scale−free (under attack) network diameter exponential (attack & errors) scale−free (under errors) 0

0.05 0.1 f, the fraction of nodes removed

Figure 18.16: Impact of attacks and failures on the diameter of exponential networks and scale-free networks, from Albert, Jeong, and Barabasi. 18.10.3.0.5 Impact on network diameter. Figure ?? is used to descibe the impact on the diameter. The graph shows only the relative trends, as empirically verified by simulations for many large networks, including the Web and Internet. Any numbers simply in the graph convey an approximate order of magnitude for the particular networks studied by Albert, Jeong, and Barabasi [5]. Errors. As all nodes have about the same degree, the removal of any node has approximately the same amount of small impact in terms of decreases connectivity. The network diameter increases gradually. The diameter of scale-free networks remains almost same under errors, as nodes that are removed have small degree with very high probability and are very unlikely to alter the lengths of the paths among other nodes. Attacks. As nodes in an exponential network have about the same degree, the network behaves similarly under attack as under errors. Under attack, the diameter of scale-free networks increases dramatically, as the few nodes with highest connectivity are removed, thereby greatly reducing the connectivity of the entire network. 18.10.3.0.6 Impact on network partitioning. The impact of removal of nodes on partitioning is measured using two metrics: Smax , the ratio of the size of the pargest cluster to the system size, and Sothers , the average size of all clusters except the largest. Exponential networks. See Figure 18.17. As f , the fraction of nodes removed is increased, Sothers increases from 1 to around 2 for some threshold fraction fthreshold. This implies that for very small f , where Sothers ∼ 1, single nodes break off. As f increases, several small but larger partitions set in, leading to a peak of Sothers at fthreshold. For f > fthreshold, Sothers reduces back to 1, as the isolated clusters (fragments) in the network further disintegrate. In terms of Smax , as f is varied from 0 to fthreshold, Smax decreases from 1 to a low value as small (mostly single-node) partitions break off. As fthreshold is approached, the main cluster disintegrates, leading to Smax tending to 0. As f is increased beyond fthreshold, Smax remains near 0. 705

S and S max others

2

S

others

under attack and under errors

1

S max under attack and under errors fthreshold 0 0.5 f. the fraction of nodes removed

partitions at very low f

partitions at f threshold

partitions at f>f

Figure 18.17: Impact on cluster size of exponential networks, from Albert, Jeong, and Barabasi. (a) Graphical trend. (b) Pictoral cluster sizes for low f , i.e., f ≪ fthreshold . (c) Pictoral cluster sizes for f ∼ fthreshold . (d) Pictoral cluster sizes for f > fthreshold. The pictoral trend in (b)-(d) is also exhibited by scale-free networks under attack, but for a lower value of fthreshold. The impact of attacks on network partitioning is the same as the impact of errors, for the same reasoning given for the analysis on the diameter. Scale-free networks. See Figure 18.18. When nodes are randomly removed, Smax decreases from 1 very gradually. Also, Sothers remains steady at 1, indicating that singleton nodes get removed from the main network. There is no threshold fthreshold observed, even for high values of f , such as 0.5 error rate.

S and S max others

However, under attack, when the most connected nodes are removed, the behaviour is similar to (but more acute than) that of the exponential network. Thus, the threshold fthreshold sets in at a lower value. This is because the impact of removing the highly connected nodes first causes disintegration to set in quickly.

2

S others under attack S others under errors 1 S under errors max S under attack max fthreshold 0 0.4 f. the fraction of nodes removed

partitions under errors (very low f)

partitions under errors (moderate f)

partitions under errors (higher f)

Figure 18.18: Impact on cluster size of scale-free networks, from Albert, Jeong, and Barabasi. The pictoral impact of attacks on cluster sizes are similar to those in Figure 18.17. (a) Graphical trend. (b) Pictoral cluster sizes for low f under failure. (c) Pictoral cluster sizes for moderate f under failure. (d) Pictoral cluster sizes for high f under failure.

706

18.11 Random Graphs Some interesting features of random graphs are as follows.

18.11.1 Graph Model The probability of obtaining a particular random graph G = (V, L), where |V | = n and |L| = l, is P (G) = pl (1 − p)n(n−1)/2−l . Alternately, G can be viewed as a random graph obtained from a l space of Cn(n−1)/2 equiprobable graphs. Gp is used to denote that the probability of a conneciton between any pair of edges in G is p. 18.11.1.1 Graph Properties A graph has a property Q if limitn→∞ p(Q) = 1. A main focus of random graph theory is to determine the connection probability p at which a particular property Q, such as the occurence of a specific subgraph like tree or ring, can most likely arise. A significant result by Erdos and Renyi is that many properties in random networks appear or disappear “suddenly”, at a particular value of probability pc (n), called the critical probability. The connection probability is itself modeled as a function of n. If p(n) grows faster than pc (n), then almost any graph will exhibit the property Q. But if p(n) grows slower than pc (n), then almost no graph will exhibit the property Q. ( →0 0 if pp(n) c (n) (18.10) limitn→∞ p(Q) = p(n) 1 if pc (n) → ∞ An interesting property of random graphs is the appearance of subgraphs. For example, is there a critical probability at which arbitrary subgraphs consisting of k nodes and l edges appear in the graph? Given a graph Gp , the number of such subgraphs F was shown by Bollobas [8] to be: E(G′ ) = Cnk

k! l nk pl p ∼ a a

(18.11)

Here, a is the number of number of subgraphs that are isomorphic to one another. If p(n) = cn−k/l , the mean number of subgraphs F is λ = cl /a. Using the distribution of subgraph numbers Pp (X = r), Bollobas showed that λr limitn→∞ Pp (X = r) = e−λ (18.12) r! The probability that G contains at least one subgraph F is: ∞ X i=1

Pp (X = r) = 1 − e−λ

(18.13)

This probability converges to 1 with increasing c. Hence the critical probability for which each graph contains a subgraph F (k, l) is pc (n) = cn−k/l . Instantiating this, 707

1. for a tree of k nodes, pc (n) = cn−k/(k−1) 2. for a complete subgraph of k nodes, pc (n) = cn−2/(k−1) 3. for a cycle of size k, pc (n) = cn−1

18.11.2 Graph Degree Distribution The degree distribution ki of node i is a binomial distribution, given by Bollobas [7] as: k P (ki = k) = Cn−1 pk (1 − p)n−k−1

(18.14)

For the entire graph Gp , the expected number of nodes with degree k is simply nP (ki = k), which we denote as λ. The degree distribution of the graph gives Xk , the number of nodes with degree k, which then becomes the Poisson distribution with mean λk : P (Xk = r) = e−λ

λrk k!

(18.15)

For large n, (pn)k (k)k = e−k (18.16) k! k! Much work studied the existence and uniqueness of the maximum and minimum degrees of a random graph. Among the many interesting results is the result that for a sufficiently high p, if pn/ln(n) → ∞, then the average degree and the maximum degree of almost all random graphs is of the same order of magnitude. A typical random graph has the same number of edges for the majority of the nodes. P (k) = e−pn

18.11.3 Graph Diameter Further, the diameter of a random graph is typically small. With a high probability, the number of l l . This gives: nodes l hops away is close to k . As k ∼ Coverage, then l = ln(Coverage) ln(k) d=

ln(n) n = pn k

In particular, if k ≥ ln(n), the diameter of all graph with the same n and k is very close to

(18.17) ln(n) . k

18.11.4 Graph Clustering Coefficient In a random graph, the probability that two of the neighbours of any node are connected is the clustering coefficient, which is simply: nk . However, real networks have a clustering coefficient that is independent of n or another parameter.

708

18.11.5 Generalized Random Graph Networks Random graphs cannot capture the scale-free nature of real networks, which states that the node degree distribution follows a power law. The generalized random graph model uses the degree distribution as an input, but is random in all other respects. Thus, the constraint that the degree distribution must obey a power law is superimposed on an otherwise random selection of nodes to be connected by edges. These semi-random graphs can be analyzed for various properties of interest. Although a simple formal model for the clustering coefficient is not known, it has been observed that generalized random graphs have a random distribution of edges similar to the ER model, and hence the clustering coefficient will likely tend to zero as N increases.

18.12 Small-world Networks Real-world networks are small worlds, having small diameter, like random graphs, but they have relatively large clustering coefficients that tend to be independent of the network size. Ordered lattices tend to satisfy this property that clustering coefficients are independent of the network size. Figure 18.19(a) shows a one-dimensional lattice in which each node is connected to 3(k−2) . k = 4 closest nodes. The clustering coefficient C = 4(k−1)

1 0

1 0

1 0

1 0

1 0 1 0

1 0

1 0

1 0 1 0

1 0

(a)

1 0

1 0

1 0

1 0

1 0

0 1 11 00 00 11

1 0

0 1

11 00 00 11 1 0

1 0 1 0

1 0

(b)

1 0

1 0

1 0

1 0

1 0

1 0

1 0

1 0 1 0

1 0

1 0

(c)

Figure 18.19: The Watts-Strogatz random rewiring procedure. (a) Regular. (b) Small-world. (c) Random. The rewiring shown maintains the degree of each node. The first model for small world graphs with high clustering coefficients and low path length is the Watts Strogatz (WS) model. 1. Define a ring lattice with n nodes and each node connected to K closest neighbours (k/2 on either side). Let n ≫ k ≫ ln(n) ≫ 1. 2. Rewire each edge randomly with probability p. When p = 0, there is a perfect structure, as in Figure 18.19(b). When p = 1, complete randomness, as in Figure 18.19(c). A characteristic of small-world graphs is the small average path length. When p is small, len scales linearly with n but when p is large, len scales logarithmically. Through analytical arguments 709

and simulations, it is now believed that the characteristic path length varies as: len(n, p) ∼

n1/d f (pkn) k

where the function f behaves as follows.  constant if u ≪ 1 f (u) = ln(u) if u ≫ 1 u

(18.18)

(18.19)

The variable u ∝ pknd has the intuitive interpretation that it depends on the average number of random links that provide “jumps” across the graph, and f (u) is the average factor by which the distance between a pair of nodes gets reduced by the “jumps”.

18.13 Scale-free Networks Many real networks are scale-free, and even for those that are not scale-free, the degree distribution follows an exponential tail that is significantly different from that of the Poisson distribution. Semirandom graphs that are constrained to obey a power law for the degree distributions and constrained to have large clustering coefficients yield scale-free networks, but do not shed any insight into the mechanisms that give birth to scale-free networks. Rather than modeling the network topology, it is better to model the network assembly and evolution process. Specifically, • Rather than begin with a constant number of nodes n that are then randomly connected or rewired, real networks (e.g., WWW, INTERNET) exhibit growth by the addition of nodes and edges. • Rather than assume that the probability of adding (or rewiring) an edge between two nodes is a constant, real networks exhibit the property of preferential attachment, where the probability of connecting to a node depends on the node degree. The simple Barabasi-Albert model which captures growth and preferential attachment is described in Figure 18.20. Numerically, it is verified that the degree distribution follows a power law with degree=3, that is independent of the parameter m. Two techniques to analyze the degree distribution of models are now exemplified in the context of the BA model.

18.13.1 Master-equation approach Let p(k, ti , t) denote the probability that at time t, a node i that was added at time ti has degree k. When a new node with m edges is added to the graph, the degree of node i increases by one with Q probability m · (k) = k/2t. Hence, we have: p(k, ti , t + 1) =

k k−1 · p(k − 1, ti , t) − [1 − ] · p(k, ti , t) 2t 2t 710

(18.21)

Initially, there are m0 isolated nodes. At each sequential step, perform one of the following operations. With probability p, add m, where m ≤ m0 , new edges. For each new edge, one end is randomly selected, the other end with probability Y ki + 1 (ki ) = P j (kj + 1)

(18.20)

With probability q, rewire m edges. To rewire an edge, randomly Q select node i, delete some edge (i, w), add edge (i, x) to node x that is chosen with probability (kx ) as per Equation (18.20). With probability Q 1 − p − q, insert a new node. Add m new edges to the new node, such that with probability (ki ), an edge connects to a node i already present before this step.

Figure 18.20: The simple Barabasi-Albert model.

The first term is the probability that a node with k − 1 degree gets a new edge; the second term is the probability that a node with degree k does not get a new edge. Based on this formulation, the degree distribution can be expressed as X P (k) = limitt→∞ p(k, ti , t)/t (18.22) ti

From Equation (18.21), it can be shown that  k−1 P (k − 1) if k ≥ m + 1 k+2 P (k) = 2 if k = m m+2 This solves as: P (k) =

2m(m + 1) k(k + 1)(k + 2)

(18.23)

(18.24)

18.13.2 Rate-equation approach Let Nk (t) be the average number of nodes having k edges at time t. When a new node is added, Nk (t) changes as follows. New edges are added to some nodes with degree k − 1, new edges are added to some nodes with degree k, and new nodes with m edges are added. These three changes affect Nk (t) in the following manner. (k − 1) · Nk−1 (t) k · Nk (t) dNk =m·[ P −P ] + δk,m (18.25) dt k kNk (t) k kNk (t) P By taking the asymptotic limit, Nk (t) = t · P (k), and k kNk (t) = 2mt. This yields the same recursive Equation (??) obtained using the Master-equation approach.

711

18.14 Evolving Networks The BA algorithm in Figure 18.20 represents a basic model that cannot fully capture real network properties. For example, the BA model has a fixed exponent of 3 for the power law, independent of the parameter m. Real networks have an exponent that varies, typically between 1 and 3. Some real networks sometimes have exponential cutoffs that are not within the power law regime. The study of more general and flexible models that can accurately capture real networks has lead to several notable directions of investigation. Q Preferential attachment: The BA model assumed that the probability (k) that a node connects Q to a node of i is proportional to the degree ki . This implied that (k) is linearly proportional to k. It has been shown analytically that for sublinear preferential attachment as well as for superlinear preferential attachment, the scale-free nature of the network cannot be preserved. In real networks, there is a finite probability that a node attaches to a isolated node, i.e., Q Q (0) 6= 0 and (k) = C + k α . Here, C denotes the intial attractiveness. It can be seen that initial attractiveness changes the degree exponent but preserves the scale-free nature of the degree distribution.

Growth: The BA model assumed that the rate of addition of nodes and edges was uniform. Many real networks like INTNET, AS, WEB, SUBSTRATE, WORDOCC, have the property that the number of edges increases faster than the number of nodes, implying an increase in the average degree as the number of nodes increases. It has been analytically shown that accelerated growth does not affect the power law nature although the exponent degree is altered. Local events: Real networks undergo local (microscopic) changes to the topology, such as node addition and node deletion, edge addition and edge deletion. A popular model that explores the properties of such local events is the extended Barabasi-Albert model, shown in Figure 18.21. Growth constraints: Real networks often have bounded capacity for the number of edges (e.g., connections at a router) or a finite lifetime for the nodes (as in social networks). In the electrical power distribution network which exhibits an exponential distribution, there are practical reasons why the node degree is bounded. In the actors network which exhibits a power law with an exponential cutoff for large k, ageing limits the accrual of new edges. Thus, ageing and finite capacity need to explicitly captured in a good model for such networks. Competition: Real-world networks exhibit competition, wherein some nodes can attract more edges (e.g., via advertising) at the cost of other nodes. This feature can be modeled by a fitness parameter. Similarly, a new node may inherit edges belonging to some other node or nodes (e.g., modifying a replica of a web page). This needs to be explicitly modeled. 712

Initially, there are m0 isolated nodes. At each sequential step, perform one of the following operations. With probability p, add m, where m ≤ m0 , new edges. For each new edge, one end is randomly selected, the other end with probability Y ki + 1 (ki ) = P j (kj + 1)

(18.26)

With probability q, rewire m edges. To rewire an edge, randomly Q select node i, delete some edge (i, w), add edge (i, x) to node x that is chosen with probability (kx ) as per Equation 18.26. With probability Q 1 − p − q, insert a new node. Add m new edges to the new node, such that with probability (ki ), an edge connects to a node i already present before this step.

Figure 18.21: The extended Barabasi-Albert model.

Preferential attachment: Various local-level mechanisms such as: copying mechanism (copy edges of another node as in web pages), and tracing selected walks (as in recursively following the citation trail in a citation network), need to be modeled.

18.14.1 Extended Barabasi-Albert Model The extended BA model is an example model for evolving networks. Continuum theory analysis: In continuum theory, it is assumed that ki changes continuously and Q (ki ) then represents the rate at which ki changes. Each of the three possible events in a sequential step can affect the rate at which ki changes as follows. 1. With probability p, m new links are added. For each link, one end is randomly chosen, leading to a change in ki of pm/N . For each link, the second end attaches preferentially, . Hence, leading to a change in ki of pm · P(k(ki +1) j +1) j

1 ki + 1 dki = pm + pm P dt N j (kj + 1)

(18.27)

2. With probability p, m existing links are rewired. For each rewired link, a randomly chosen node loses one incident edge which then attaches preferentially. Thus, the impact on ki is: dki 1 ki + 1 = −qm + qm P dt N j (kj + 1)

(18.28)

3. With probability (1 − p − q), a new node is added with m links. Each of the m links connects preferentially, thus: ki + 1 dki = (1 − p − q)C P (18.29) dt j (kj + 1) 713

Summing the three effects, we have: dki 1 ki + 1 = (p − q)m + m P dt N j (kj + 1)

As the system size and topology varies with time, we have: X N(t) = m0 + (1 − p − q)t; kj = 2mt(1 − q) − m

(18.30)

(18.31)

j

As t increases, the constants m and m0 can be deleted. Further, for a node added at ti , we have that k(ti ) = m (the initialization step). Exercise 9 asks you to show that the solution to Equation (18.30) has the form t ki (t) = [A(p, q, m) + m + 1]( )1/B(p,q,m) , ti 2m(1 − q) 2m(1 − q) + 1 − p − q + 1), B(p, q, m) = 1−p−q m Based on further algebraic derivations, Albert and Barabasi showed that

A(p, q, m) = (p − q)(

(18.32) (18.33)

P (k)α[k+κ(p, q, m)]−γ(p,q,m), where κ(p, q, m) = A(p, q, m)+1 and γ(p, q, m) = B(p, q, m)+1 (18.34) Equation (18.34) is valid if, for a fixed p and m, q < qmax = min(1 − p, (1 − p + m)/(1 + 2m)) There are now two cases. q < qmax : Equation (18.34) is valid and the degree distribution is a power law. q < qmax : Equation (18.34) is invalid, and P (k) can be shown to behave like an exponential distribution. The model now behaves like the ER and WS models. This is similar to the behaviour seen in real networks – some networks show a power law while others show an exponential tail – and a single model can capture both behaviors by tuning the parameter q. The scale-free regime and the exponential regime are marked in the graph in Figure 18.22. The boundary between the two regimes depends on the value of m and has slope −m/(1 + 2m). The area enclosed by thick lines shows the scale-free regime; the dashed line is its boundary when m → ∞ and the dotted line is its boundary when m → 0.

18.15 Chapter Summary Peer-to-peer (P2P) networks allow equal participation and resource sharing among the users. This chapter first analyzed the different types of P2P networks. Unstructured P2P networks are like 714

1.0

E q

SF 0 0

p

1.0

Figure 18.22: Phase diagram for the Extended Barabasi-Albert model. SF denotes the scale-free regime, which is enclosed by the thick border. E denotes the exponential regime which exists in the remainder of the lower diagonal region of the graph. The plain line shows the boundary for m = 1, having a Y-axis intercept at 0.67. Gnutella and BitTorrent. We studied different search mechanisms - flooding, constrained flooding, and blind search - for such unstructured networks. We also examined some data replication strategies, and their impact on the search performance. The chapter then studied 3 classical structured P2P networks - Chord, CAN, Tapestry - all of which use the distributed hash table concept in their implementations. Although all the three mechanisms differ, they are similar in that they represent different trade-offs in search efficiency, i.e., path length, and the amount of local storage for implementing the hash tables. The spectrum of P2P networks from unstructured to structured offer a wide range of trade-offs for user requirements. The chapter also examined issues such as fairness and trust management. These issues are important because in the P2P environment where there is no control authority, the system must be able to autonomously alllow for fairness. The internet, AS-AS level internets, and the web (WWW) overlays exhibit some interesting properties about how they grow and evolve. Many network overlays outside of computer science also exhibit the same properties. The chapter studied several properties of the Internet and the web graphs. Then, in a more general setting, the chapter examined random networks, smallworld networks, node degree distributions, scale-free networks, and the impact of error and attack tolerance for such networks. Networks grow in an uncontrolled fashion, yet, there seems to be some underlying basis for such growth. Of the several proposals to model the growth of networks, we study the Barabasi-Albert model which appears to be promising in its applicability to not just the computer science networks, but also to networks in other disciplines and natural phenomena. are being widely used as the

715

18.16 Exercise Problems 1. (Replication.) Derive the values of average search size A, Ai , and utilization ui for Squareroot replication. The derived answers should match the entries in Table 18.3. 2. (Fault-tolerance in Chord.) Adapt the code in Figure 18.6 so that the nodes manage a successor list or α successors, rather than a single successor. 3. (Chord.) In the Chord protocol, assume that the successor list at each node has α = Ω(log n) nodes. Show the following. (a) If a Chord ring is initially stable, and if the probability of subsequent failure of each node is 0.5, then Locate_Successor returns the closest functional successor node to the key being searched with high probability. (b) If a Chord ring is initially stable, and if the probability of subsequent failure of each node is 0.5, it takes O(log n) average-case time for Locate_Successor to complete. 4. (CAN.) Compute the time and message complexity of the distributed region reassignment protocol that is run periodically by the CAN protocol. 5. (CAN.) Identify all the changes to the base CAN protocol to accommodate the optimization of overloading coordinate regions, discussed in Section 18.5.5. 6. (Power Law in the Internet. [28]) Show that the number of edges in the Internet graph that obeys the power law for the rank exponent is given as follows. Let the graph have n nodes 1 1 (1 − nR+1 )n and rank exponent R. Then: l ∼ 2(R+1) 7. Show that Equation (18.23) using the Master-equation approach for the degree distribution in the extended BA model can be solved as Equation (18.24). 8. Show that the Master-equation approach used to solve for the degree distribution in the extended BA model in Section 18.13.2 gives the solution expressed by Equation (??). 9. Show that the solution to Equation (18.30) for the degree distribution in the Extended BA model using continuum theory analysis is given by Equation (18.33).

18.17 Bibliographic Notes The introduction is based on the survey by Risson and Moors [26] and Androutsellis-Theotokis and Spinellis [6]. The discussion on replication and search in unstructured networks is based on Cohen and Shenker [11], and on Lv, Cao, Cohen, Li, Shenker [20], respectively. Gnutella [14, 15], Napster [22], and Freenet [9] are widely implemented commercial P2P protocols. The Chord protocol was proposed by Stoica et al. [29]. The Content Addressible Network (CAN) was proposed by Ratnasamy et al. [24]. The design of Tapestry [18, 19, 33, 34] and the related 716

Pastry [27] overlay was based on the ideas of Plaxton trees proposed by Plaxton, Rajaraman, and Richa [23]. Tapestry built on the Plaxton trees by providing better fault-tolerance and resilience in the face of node joins and departures. The discussion on fundamental tradeoffs between routing table size and network diameter is based on Xu, Kumar, and Yu [32] and Ratnasamy, Stoica, and Shenker [25]. The BitTorrent system was initially proposed by Brahm Cohen [10]. The discussion of trust management is based on Gupta et al. [16]. The discussion on the graph structures of complex networks is structured and based on the excellent survey by Albert and Barabasi [4]. The discussion on power laws and Zipf’s law is taken from the tutorial by Adamic [2]. The power laws for the Internet were discovered by Siganos and the Faloutsos brothers [28]. The discussion on the betweenness centrality metric for graphs is based on the work by Goh et al. [13]. The random graphs model was proposed and analyzed by Erdos and Renyi [12]. Further results on the properties on random graphs were given by Bollobas [7, 8]. The small worlds model was proposed by Watts and Strogatz [30]. The Extended BarabasiAlbert model for graph evolution was given by Barabasi and Albert [3]. The analysis of error and attack tolerance on exponential networks and on scale-free networks in based was done by Albert, Jeong, and Barabasi [5].

717

Bibliography [1] K. Aberer and Z. Despotovic, Managing Trust in a Peer-To-Peer Information System, In Proceedings of the 10th International Conference on Information and Knowledge Management, Atlanta, Georgia, USA, November 2001, pp. 310-317. [2] L. Adamic, Zipf, Power-Laws, and Pareto - A http://www.hpl.hp.com/research/idl/papers/ranking/ranking.html

Ranking

Tutorial,

[3] R. Albert, A.-L. Barabasi, Topology of Evolving Networks: Local Events and Universality, Physical Review Letters, Volume 85(24): 5234-5237, December 2000. [4] R. Albert, A.-L. Barabasi, Statistical Mechanics of Complex Networks, Review of Modern Physics, Volume 74(1): 47-97, January 2002. [5] R. Albert, H. Jeong, A. Barabasi, Error and Attack Tolerance of Complex Networks, Nature, Vol. 406, 378-381, July 2000. [6] S. Androutsellis-Theotokis, D. Spinellis, A Survey of Peer-to-Peer Content Distribution Technologies, ACM Computing Surveys, Vol. 36(4): 335-371, December 2004. [7] B. Bollobas, Discrete Math, Vol. 33, pp. 1-, 1981. [8] B. Bollobas, Random Graphs, Academic Press, London, 1985. [9] I. Clarke, O. Sandberg, B. Wiley, T. W. Hong, Freenet: A Distributed Anonymous Information Storage and Retrieval System. In Workshop on Design Issues in Anonymity and Unobservability, Berkeley, California, USA, July 2000, pp. 46-66. [10] B. Cohen, Incentives Build http://www.bittorrent.com/bittorrentecon.pdf

Robustness

in

BitTorrent,

[11] E. Cohen, S. Shenker, Replication Strategies in Unstructured Peer-to-Peer Networks, ACM SIGCOMM, 177-190, 2002. [12] P. Erdos, A. Renyi, Random Graphs. Publ. math. (Debrecen), Vol 6, p. 290-, 1959 [13] K. Goh, E. Oh, H. Jeong, B. Kahng, D. Kim, Classification of Scale-Free Networks, Proceedings of the National Academy of Sciences, 2002. 718

[14] Gnutella, http://www.gnutella.com/. [15] The Gnutella protocol specification, http://www9.limewire.com/ developer/gnutella_protocol_0.4.pdf. [16] M. Gupta, P. Judge and M. Ammar, A Reputation System for Peer-to-Peer Networks. In Proceedings of the 13th International Workshop on Network and Operating Systems Support for Digital Audio and Video, (ACM Press), Monterey, California, USA, June 2003, pp. 144152. [17] M. Gupta, M. H. Ammar, M. Ahamad: Trade-offs between Reliability and Overheads in Peer-to-peer Reputation Tracking, Computer Networks 50(4): 501-522 (2006) [18] K. Hildrum, J. Kubiatowicz, S. Rao, B. Y. Zhao, Distributed Object Location in a Dynamic Network, ACM SPAA 2002: 41-52 [19] K. Hildrum, J. Kubiatowicz, S. Rao and B. Y. Zhao, Distributed Object Location in a Dynamic Network, Theory of Computing Systems, March 2004, No. 37, Pgs. 405-440, Springer-Verlag [20] Q. Lv, P. Cao, E. Cohen, K. Li, S. Shenker, Search and Replication in Unstructured Peer-topeer Networks, International Conference on Supercomputing, 2002: 84-95 [21] S. Milgram, The Small World Problem. Psychology Today, 2, page 60-67, 1967. [22] Napster, http://www.napster.com/. [23] C. G. Plaxton, R. Rajaraman, A. W. Richa, Accessing Nearby Copies of Replicated Objects in a Distributed Environment. ACM SPAA 1997: 311-320 [24] S. Ratnasamy, P. Francis, M. Handley, R.M. Karp, S. Shenker: A scalable content-addressable network. SIGCOMM 2001: 161-172. [25] S. Ratnasamy, I. Stoica, S. Shenker: Routing Algorithms for DHTs: Some Open Questions. IPTPS 2002: 45-52 [26] J. Risson, T. Moors, Survey of research towards robust peer-to-peer networks: Search Methods. Computer networks, 2006 (to appear). [27] A. Rowstron and P. Druschel, Pastry: Scalable, Distributed Object Location and Routing for Large-Scale Peer-to-Peer Systems, In Proceedings of the IFIP/ACM Middleware 2001, Heidelberg, Germany, November 2001, pp. 329-350. [28] G. Siganos, M. Faloutsos, P. Faloutsos, C. Faloutsos, Power Laws and the AS-level Internet Topology, IEEE/ACM Transactions on Networking, 11(4): 514-524, 2003.

719

[29] I. Stoica, R. Morris, D. Liben-Nowell, D. Karger, M.F. Kaashoek, F. Dabek, H. Balakrishnan, Chord: A Scalable Peer-to-peer Lookup Service for Internet Applications, IEEE Transactions on Networking 11(1): 17-31, February 2003. [30] D.J. Watts, S.H. Strogatz. Nature, 393, page 440, 1998. [31] O. Wolfson, S. Jajodia, Y. Huang, An Adaptive Data Replication Algorithm, ACM Transactions on Database Systems, 22(2): 255-314, 1997. [32] J. Xu, A. Kumar, X. Yu, On the Fundamental Tradeoffs between Routing Table Size and Network Diameter in Peer-to-Peer Networks, IEEE Journal on Selected Areas in Communications, 22(1): 151-163, Jan 2004. [33] B. Y. Zhao, L. Huang, J. Stribling, S. Rhea, A. Joseph, J. Kubiatowicz, Tapestry: A Resilient Global-Scale Overlay for Service Deployment, IEEE Journal on Selected Areas inCommunications, 22(1): 41-53, Jan 2004. [34] B. Y. Zhao, J. D. Kubiatowicz and A. D. Joseph, Tapestry: An Infrastructure for FaultResilient Wide-Area Location and Routing, Technical Report UC Berkeley, CSD-01-1141, U. C. Berkeley, Berkeley, California, USA, April 2001.

720

Index Compare&Swap, 536 F etch&Increment, 536 Swap, 421 T est&Set, 421 Definitely, 373 Possibly, 373 Read-Modify-Write, 539

Baldoni, R, 494 Barabasi-Albert model extended Barabasi-Albert model, 713 Bellovin, SM, 609 Bhargava, B, 494 Bremler, A, 644 Briatico, D, 458 broadcast, 137 Burrows, M, 613 Byzantine agreement, 502 exponential tree algorithm, 509 upper bound, 507

Abadi, M, 589, 613 accuracy properties, 558 Acharya, 99 adaptive algorithms, 122 Afek, Y, 642, 644 Agarwal, D, 320 Agrawala, AK, 300 agreement failure-free system, 505 Alagar, 100 Alvisi, L, 494 anonymous algorithms, 121 antimessages, 68 Arora, A, 622, 637, 643 asynchronous execution, 18 asynchronous system, 124 atomic broadcast, 570 atomic registers, 424 authentication, 586 authentication protocol failures, 611 authentication protocols with asymmetric cryptosystem, 602 with symmetric cryptosystem, 590 authentication server, 593

Cao, G, 456 causal delivery, 98 causal order, 196 optimal algorithm, 198 Raynal-Sciper-Toueg algorithm, 197 causal ordering, 40 causal path, 104 causal precedence relation, 39 Chakrabarti, S, 613 Chandra, TD, 556, 557, 561, 566, 570, 571 Chandrasekaran, S, 242 Chandy, 87, 352, 353 Chandy, KM, 365 channel state recording, 99 checkpoint, 102, 447 global, 448 local, 447 checkpointing communication-induced, 456 coordinated, 454

Badrinath, 99 721

uncoordinated, 452 checkpointing algorithm Helary-Mostefaoui-Netzer-Raynal protocol, 486 Juang and Venkatesan algorithm, 466 Koo-Toueg, 463 Manivannan-Singhal algorithm, 470 Peterson-Kearns algorithm, 479 Chord, 676 churn, 678 clock inaccuracies, 73 clock offset, 73 clock skew, 73 clocks matrix, 63 physical, 72 scalar, 49 vector, 51 closure, 622 clustering, 700 common clock primitives, 636 common knowledge, 280 concurrent common knowledge, 281 Epsilon common knowledge, 280 eventual common knowledge, 280 protocols for concurrent common knowledge, 282 timestamped common knowledge, 280 communication asynchronous, 44 synchronous, 44 communication primitives, 14 asynchronous, 14 blocking, 14 nonblocking, 14 synchronous, 14 Compare&Swap, 537 completeness properties, 558 complex networks Barabasi-Albert model, 710

error and attack tolerance, 704 graph structures, 699 Internet, 702 Internet graph, 701 complexity metrics, 127 concurrency, 12 concurrency measure, 47 conistent cut, 86 conjunctive predicate detection interval-based piggybacking algorithm, 390 interval based algorithm, 379 interval-based token algorithm, 386 state-based token algorithm, 384 conjunctive predicate detetion state-based algorithm, 381 consensus, 503 k-set consensus, 521 approximate agreement, 522 impossibility in shared memory asynchronous systems, 533 impossibility result for asynchronous systems, 518 phase king algorithm, 516 reliable broadcast, 532 renaming problem, 527 transaction commit, 521 wait-free renaming using splitters, 547 wait-free shared memory renaming, 545 consensus hierarchy, 536 consensus problem, 565 solution using eventually strong FD, 568 solution using strong FD, 566 consensus under crash failures, 507 Consistent clobal gnapshots, 105 consistent global snapshots necessary and bufficient conditions, 102 consistent global state, 85 consistent global states, 41 consnesus shared memory k-set consensus, 545

722

terminating reliable broadcast, 520 content-addressible networks (CAN), 683 convergecast, 137 convergence, 622 crown, 186 cryptographic protocols design principles, 589 cut, 86 cut in a ditributed computation, 42

design issues, 21 Dolev, S, 646 Dolve, S, 640 duplicate messages, 450 dynamic termination detection, 249 El Abbadi, A, 320 Elnozahy, EN, 494 emulations, 20 message-passing, 13 shared memory, 13 synchronous system, 19 Encrypted Key Exchange (EKE) protocol, 609 enumerating consistent snapshots, 109 event counting, 51 eventual accuracy properties, 559 evolving networks, 712 executions realizable with synchronous communication, 185 timestamps, 188

data indexing, 668 deadlock avoidance, 343 Chandy-Misra-Haas algorithm, 352, 353 detection, 344 Kshemkalyani-Singhal algorithm, 355 Mitchell-Merritt algorithm, 349 phantom, 344 prevention, 343 resolution, 344 deadlock detection, 344 deadlocks, 318 diffusing computations based algorithms, 348 edge-chasing algorithm, 348 global state detection based algorithms, 349 path-pushing algorithms, 348 degree distributions, 700 delayed messages, 450 Delporte-Gallet, C, 573 deterministic execution, 122 dictionary attack, 609 diffusion computation, 348 Dijkstra, E, 619, 624, 649 distributed deadlock, 342 distributed discrete event simulations, 71 Distributed Program, 37 distributed reset, 637 distributed systems characteristics, 1

failure detector adaptive, 578 implementation, 576 realistic, 574 weakest, 575, 576 failure detectors, 557 reducibility, 560 types, 560 failure pattern, 557 failure recovery, 450 Fowler, 58 free-riding, 695 Fuchs, WK, 457 future cone of an event, 43 Garg, V, 582 Gartner, F, 640 generalized deadlocks, 355 generalized random graph networks, 709 Gligor, V, 365 global state, 40, 86 723

consistent, 85 global virtual time, 70 Gnutella, 670 Gouda, M, 622, 637, 638, 643, 649 graph algorithms, 129 maximal independent set (MIS), 158 all sources shortest paths, 142 compact routing tables, 161 connected dominating set (CDS), 160 constrained flooding, 145 delay bounded Steiner trees, 221 distance vector rouitng, 141 leader election, 163 minimum weight spanning tree, 146, 152 reverse path forwarding, 219 single source shortest path, 140, 141 spanning tree, 130, 131, 134, 137 Steiner trees, 220 group communication, 195 fault-tolerant, 216 multicast, 209 Guerraoui, R, 582

Kaminsky, M, 613 Kasami, 325 Katz, S, 651 Kearns, 91, 100 Kearns, P, 479 Kerberos authentication service, 597 authenticator, 600 Kerberos authentication service, 597, 599 Kim, KH, 494 Knapp, 348 Knapp, E, 365 knowledge agreement, 279 asynchronous system, 278 logic, 272 multi-dimensional clocks, 287 operators, 272 properties, 277 transfer, 283 Koo, R, 463 Kripke structures, 274 Kshemkalyani, 56, 309, 355 Kshemkalyani, AD, 365 Kutten, S, 642

Haas, L, 352, 353 Helary, 93 Helary, JM, 486 Herman, T, 365 Huang, ST, 643

Lai, 95 Lam, S, 613 Lamport, 87, 297 Lamport’s happens before relation, 39 layering, 635 lazy failure detection protocol, 579 legitimate state, 622 Lodha, 309 log-based rollback recovery, 458 logging causal, 462 optimistic, 461 pessimistic, 459 logical clocks, 48 lost messages, 450

illegitimate state, 622 impersonation attack, 604 incarnation number, 473 incremental snapshot, 92 inhibition, 123 interactive consistency, 503 interconnection networks, 6, 667 Israeli, A, 640 Jard, 60 Johnson, D, 494 Jourdan, 60 Juang, 466 724

propagation trees, 210 Muntz, R, 365 mutual exclusion Agarwal-El Abbadi algorithm, 320 fast mutual exclusion, 418 hardware-assisted, 421 Lamport’s algorithm, 297 Lamport’s bakery algorithm, 416 Lodha-Kshemkalyani algorithm, 309 Maekawa’s algorithm, 317 quorum-based algorithms, 316 Raymond’s algorithm, 327 Ricart-Agrawala algorithm, 300 Singhal’s dynamic algorithm, 303 Suzuki-Kasami algorithm, 325 token-based algorithms, 324

Maekawa, M, 317 Manivannan, 109, 470 Marzullo, K, 494 matrix clocks, 63 matrix time, 63 Mattern, 97 Mattern, F, 251 memory consistency, 402 atomic consistency, 403 causal consistency, 409 hierarchy, 413 linearizability, 403 pipelined RAM (PRAM), 411 processor consistency, 411 sequential consistency, 406 slow memory, 412 Menasce, D, 365 Merritt, 349 Merritt, M, 609 message ordering, 180 asynchronous executions, 180 hierarchy, 188 synchronous executions, 184 message ordering paradigms causal order, 181 FIFO executions, 180 Misra, 352, 353 Mitchell, 349 modularization, 635 monitoring global gtate, 100 Moran, S, 640 Mostefaoui, 486 muddy children puzzle, 271 multicast, 209 core-based trees, 222 destination agreement based, 216 fixed sequencer based, 216 history based, 214 moving sequencer based, 215 privilege based, 215

Napster, 667 Needham and Schroeder protocol, 603 Needham, R, 589, 603, 613 Network Time Protocol (NTP), 74 Netzer, 105, 109 Netzer, R, 486 nonblocking universal algorithm, 541 nonce, 591 nondeterministic execution, 122 object replication, 165 one-time password, 594 orphan messages, 450 Otway-Rees protocol, 596 overlays, 118, 668 structured, 669 unstrucuted, 669 parallel system, 5 coupling, 10 Flynn’s taxonomy, 10 interconnection networks, 6 multiprocessor, 5 paralelism, 11 Pareto law, 701 725

partial synchrony, 576 partially synchronous models, 576 past cone of an event, 43 path causal, 104 zigzag, 103 peer-to-peer flooding, 672 proportional replication, 674 random walk, 672 replication, 674 square-root replication, 674 uniform replication, 674 Peterson, SL, 479 physical clock cynchronization, 72 physical clocks, 72 power law, 701 Prakah, R, 494 predicates, 371 conjunctive, 378 disjunctive, 393 modalities, 373 observer-independent, 393 relational, 374 stable, 371 unstable, 372 Prisoners’ dilemma, 695 probabilistic self-stabilization, 623 probe message, 353 program structure, 128 progress, 344 pseudo stabilization, 656 pseudo-stabilizing system, 623 public key certificate, 603

Reducing Weak FD to a Strong FD, 561 register hierarchy, 423 regular registers, 424 relational predicate detection, 374 rendezvous, 191 reputation management, 696 Ricart, 300 Richard, G, 494 safe registers, 423 safety, 344 scalar time, 49 scale-free networks, 704, 706, 710 Schiper, A, 582 Schneider, M, 650 Schroeder, MD, 603 Secure Remote Password (SRP) protocol, 610 secure sockets layer, 605 self-stabilization, 622 cost, 634 for fault folerance, 652 role of compilers, 649 self-stabilizing algorithm for 1-maximal independent set, 645 self-stabilizing distributed spanning trees, 638 self-stabilizing token ring, 624 shared memory, 399 shared memory mutual exclusion, 416 simultaneous regions, 100 Singhal, 56, 109, 303, 355, 365, 456, 470 Sistla, P, 494 small-world networks, 699, 709 snapshots, 262 solution to atomic broadcast, 571 spanning tree, 236 Spezialetti, 91, 100 splitters, 547 SSL Protocol, 605 stable property, 90 stable storage, 445–447 starvation, 334

R-graph, 110 Ramamoorthy, CV, 365 random graphs, 707 randomized self-stabilization, 623 Raymond, K, 327 Raynal, M, 486, 582 726

total ordering, 50 Toueg, S, 463, 556, 557, 561, 566, 570, 571 transient failure, 623 tree-structured quorum, 320 Tseng, 260

state lattice, 374 static termination detection, 247 Strom, 494 surface of the future cone, 44 surface of the past cone, 44 Suzuki, 325 symmetry, 654 synchronizers, 153 α synchronizer, 155 β synchronizer, 156 gamma synchronizer, 156 simple synchronizer, 154 synchronous execution, 19 synchronous order, 192 synchronous system, 124

uniform algorithms, 122 uniform consensus, 573 universality of consensus objects, 540 useless checkpoints, 453, 457 vector clocks, 51 efficient implementations, 56 size, 54 vector clocks size, 54 vector time, 51, 479 Venkatesan, 92 Venkatesan, S, 242, 466 Venkatesan, S., 100 virtual time, 64

Tapestry, 689 termination detection, 231, 358 atomic computation model, 251 channel counting method, 258 distributed snapshots, 232 faulty distributed system, 260 four counter method, 253 message-optimal, 242 spanning-sreetbased, 236 vector counters method, 256 very general model, 245 weight throwing, 234 time matrix, 63 physical, 72 scalar, 49 vector, 51 virtual, 64 time warp mechanism, 67 time-space diagram, 38 topology based primitives, 636 total order, 205 centralized algorithm, 205 three-phase distributed algorithm, 206 total order property, 571

wait-for-graph (WFG), 343 wait-free algorithms, 126 wait-free atomic snapshot, 435 wait-free consensus Compare&Swap, 538 wait-free register simulations, 426–429, 432, 433 wait-free simulations, 423 wait-free universal algorithm, 544 wait-freedom, 422 Wang, Y-M, 106 Watts Strogatz model, 709 weight-throwing scheme, 260 Welch, J, 494 wide-mouth frog protocol, 592 Woo, T, 613 Wu, TD, 610 Xu, 105 Yang, 95 Yemini, 494 727

Yung, M, 642 zigzag cycle, 104 zigzag path, 103 Zipf’s law, 702 Zwaenepoel, 58

728