/* divine-cflags: -std=c++11 */ /* * Leader election in an Uni-circle (Basic algorithm) * ================================================== * * A simulation of a "Basic" O(n*log(n)) unidirectional distributed algorithm for * extrema finding in a circle. * * *tags*: leader election, C++11 * * Description * ----------- * * Given `n` processes in a ring, communicating only with message passing to its right * neighbour, the unidirectional circular extrema-finding problem (more likely known as a leader * election problem) is to select a maximum (or minimum) process. * Each process has a unique value, called ID, in a set with a total order. These values * may be transmitted and compared. All processes are identical (except for their IDs) and `n`, * the number of processes, is not initially known. * * The algorithm presented here is due to Danny Dolev and Maria M. Klawe and Michael Rodeh [1] * as well as Gary L. Peterson [2] who had submitted their papers at around the same time. * Even thought the algorithm is quite simple, it has some good properties, especially the * upper bound on message passes, which is only: 2*n*log(n) + O(n). * * The idea of the algorithm is to find all local maximas (among active IDs) and keep them active * also for the next iteration (= phase), while the rest is dismisshed. This way the number of * active processes lowers after each phase by a half at least. But while doing so, each maximum * moves to the next active process on the right side, as it is the only reasonable method as to * compare 3 adjacent IDs in unidirectional circle. And so after each phase the active process * becomes either passive or it starts to represent the maximum previously owned by its nearest active * process to the left. But when compiled with `-DBUG`, local maximas don't make the move and * therefore get dismisshed. * * ### References: ### * * 1. An O(n log n) Unidirectional Distributed Algorithm for Extrema Finding in a Circle * * @article{ dolev:an, * author = "Danny Dolev and Maria M. Klawe and Michael Rodeh", * title = "An O(n log n) Unidirectional Distributed Algorithm for Extrema * Finding in a Circle", * journal = "J. Algorithms", * pages = {245-260}, * year = {1982}, * } * * 2. An O(nlog n) Unidirectional Algorithm for the Circular Extrema Problem. * * @article{ Peterson:1982:ONL:69622.357194, * author = "Peterson, Gary L.", * title = "An O(nlog n) Unidirectional Algorithm for the Circular Extrema Problem", * journal = "ACM Trans. Program. Lang. Syst.", * year = {1982}, * issn = {0164-0925}, * pages = {758--762}, * publisher = "ACM", * address = "New York, NY, USA", * } * * Parameters * ---------- * * - `BUG`: if defined than the algorithm is incorrect and violates the safety property * - `NUM_OF_PROCESSES`: a number of processes in the ring * - `PIDS`: a distribution of identification numbers among processes to verify; * use the syntax for static array declaration in C/C++, i.e. `{ x, y, z, ...}`; * length of the array must match `NUM_OF_PROCESSES` * - `MSG_BUFFER_SIZE`: how many messages can be buffered at each node * * LTL Properties * -------------- * * - `progress`: leader will be eventually elected (but does not cover uniqueness) * * Verification * ------------ * * - all available properties with the default values of parameters: * * $ divine compile --llvm --cflags="-std=c++11" leader-unicircle_basic.cpp * $ divine verify -p assert leader-unicircle_basic.bc -d * $ divine verify -p safety leader-unicircle_basic.bc -d * $ divine verify -p progress leader-unicircle_basic.bc --fair -d * * - introducing a bug: * * $ divine compile --llvm --cflags="-std=c++11 -DBUG" leader-unicircle_basic.cpp -o leader-unicircle_basic-bug.bc * $ divine verify -p assert leader-unicircle_basic-bug.bc -d * * - customizing the number of processes and the distribution of IDs: * * $ divine compile --llvm --cflags="-std=c++11 -DNUM_OF_PROCESSES=3 -DPIDS=\"{2, 1, 3}\"" leader-unicircle_basic.cpp * $ divine verify -p progress leader-unicircle_basic.bc --fair -d * $ divine verify -p assert leader-unicircle_basic.bc -d * * Execution * --------- * * $ clang++ -std=c++11 -lpthread -o leader-unicircle_basic.exe leader-unicircle_basic.cpp * $ ./leader-unicircle_basic.exe */ // A number of processes in the ring. #ifndef NUM_OF_PROCESSES #define NUM_OF_PROCESSES 5 #endif // Distribution of identification numbers among processes to verify. It would be too expensive // to try them all -- n! outgoing edges for given set of ID numbers. // The size of the array should match value of macro NUM_OF_PROCESSES. #ifndef PIDS #define PIDS { 5, 6, 4, 7, 8 } #endif // How many messages can be buffered at each node. When buffer is full, the send operation becomes // blocking for that particular node. Larger buffer gives higher level of concurrency. #ifndef MSG_BUFFER_SIZE #define MSG_BUFFER_SIZE 2 #endif // Protocol constants - do not change! #define M1 1 #define M2 2 #define ELECT 3 #include #include #include #include #ifdef __divine__ // verification #include "divine.h" LTL(progress, F(elected)); #else // native execution #include #define AP( x ) pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; template void _info(const T& value) { std::cout << value << std::endl; } template void _info(const U& head, const T&... tail) { std::cout << head; _info( tail... ); } #endif template void info( const T&... args) { #ifndef __divine__ pthread_mutex_lock( &mutex ); _info( args... ); pthread_mutex_unlock( &mutex ); #endif } enum APs { elected }; volatile int chef = -1; void leader( int who ) { // Elected process should run this once. assert( chef == -1 ); assert( who > -1 ); chef = who; AP( elected ); } template< typename T, int size > struct Queue { // A lock-free inter-thread ring buffer. T q[ size ]; volatile int front; volatile int rear; void enqueue( const T &x ) { while ( (front + 1) % size == rear ); // full; need to wait q[ front ] = x; front = (front + 1) % size; } T dequeue() { while ( rear == front ); // empty; nothing to read T x = q[ rear ]; rear = (rear + 1) % size; return x; } bool empty() { return rear == front; } Queue() : front( 0 ), rear( 0 ) {} }; struct Message { int type; int pid; Message( int t, int p ) : type( t ), pid( p ) {} Message() : type( 0 ), pid( 0 ) {} }; struct Process { pthread_t thread; Queue< Message, MSG_BUFFER_SIZE > msg_buffer; int id; int elected; Process* next; void send( const Message &msg, int sender ) { #ifndef __divine__ usleep( 100000 ); #endif info( "Process ", sender, " sent a message (", msg.type, ",", msg.pid, ") to process ", id, "." ); msg_buffer.enqueue( msg ); } static void *run( void *_self ) { Process *self = reinterpret_cast< Process* >( _self ); int max = self->id, left, phase = 0; bool active = true; Message msg; // Initiate leader election process. self->next->send( Message( M1, max ), self->id ); while (1) { msg = self->msg_buffer.dequeue(); // Receive. if ( msg.type == ELECT ) { // Some process was elected. self->elected = msg.pid; self->next->send( msg, self->id ); if ( self->elected == self->id ) leader( self->id ); // I'am the leader! break; } if ( active ) { if ( msg.type == M1 ) { assert( phase == 0 ); if ( msg.pid != max ) { self->next->send( Message( M2, msg.pid ), self->id ); left = msg.pid; } else { // We have a leader! // When local maximum makes one complete turn, we know that it is // the global maximum. self->next->send( Message( ELECT, max ), self->id ); } } if ( msg.type == M2 ) { assert( phase == 1 ); if ( left > msg.pid && left > max ) { // My nearest active process to the left owns current local maxima. #ifndef BUG max = left; // Local maxima makes the move. #endif self->next->send( Message( M1, max ), self->id ); } else { active = false; info( "Process ", self->id, " became Passive." ); } } phase = 1 - phase; } else { // Passive = all messages are just forwarded. self->next->send( msg, self->id ); } } return NULL; } void join() { pthread_join( thread, NULL ); } void start() { pthread_create( &thread, NULL, Process::run, reinterpret_cast< void* >( this ) ); } // TODO: When operators _new_ and _delete_ are provided, the following method should be // implemented as a constructor. void init( int _id, Process *_next) { elected = -1; id = _id; next = _next; } }; int main() { assert( NUM_OF_PROCESSES > 0 ); Process process[NUM_OF_PROCESSES]; int pids[] = PIDS; int i, max = -1; // Let's find out who should be the leader. for ( i = 0; i < NUM_OF_PROCESSES; i++ ) if ( pids[i] > max ) max = pids[i]; // Initiate and start the election. for ( i = 0; i < NUM_OF_PROCESSES; i++ ) { process[i].init( pids[i], process + ( (i + 1) % NUM_OF_PROCESSES ) ); process[i].start(); } for ( i = 0; i < NUM_OF_PROCESSES; i++ ) process[i].join(); assert( chef > -1 ); // Someone should be elected.. assert( chef == max ); // .. and we know who should it be. for ( i = 0; i < NUM_OF_PROCESSES; i++ ) // Everyone should agree on the same leader. assert( process[i].elected == chef ); info ( "Process ", chef, " was elected to be the leader." ); return 0; }