Raft Project 1 Review/Discussion (Winter 2020)

Lecture Notes for CS 190
Winter 2020
John Ousterhout

Class Design

  • Small project, so not many opportunities for deep classes.
    • Communication
    • Persistence
  • Most common problems:
    • Fuzzy division of responsibility
      • A class handles part of a problem, but not all of it.
      • Or, mutliple implementations of the same thing (e.g., for clients and servers)
    • Specialization: information about Raft leaks into the infrastructure classes
  • Thoughts for Project 2:
    • Don't solve just part of a problem such as RPC communication or persistence; solve the whole problem in one place
    • Think about solving big problems, not little ones
      • Design top-down?
    • Make classes general-purpose whenever possible
      • Delay specialization: push it up to the highest layers of the application
    • Just because you know something doesn't mean you should use that information
  • Asynchronous I/O vs. multiple threads?
  • Alternatives for connection topology:
    • Requester opens connections, responses sent back on the same connection as request.
    • Sender opens connections: all outgoing traffic (requests and responses uses sender's connection).
    • One connection between each pair of servers.
  • State machine
    • Separate code for each state
    • Separate code for each message type

Synchronization and Threads

  • At a low level, several projects had a scary number of mutexes
    • Almost never need more than one mutex in a given object (only if you have measured contention)
  • At a high level: what concurrency is needed?
  • Will the choice of synchronization mechanism have a big impact on performance?
  • What is the simplest synchronization/thread architecture that will meet needs?


  • Two ways to implement timers
    • Separate timer mechanism in its own thread(s)
    • Timeouts on I/O operations
  • Starting and stopping is awkward with separate thread
    • Can use condition variables with timeouts
  • Separate timers for elections and heartbeats can also be awkward
    • Must keep track of which is running
  • Awkward interfaces: relative vs. absolute time
  • Can't use second-granularity timers

Exception Handling

  • Common problems:
    • Not enough error checks
    • Not enough info in log messages
    • Exceptions not handled in the best way
  • Logging is essential:
    • Log as often as you can possibly afford
    • Include as much information in the log message as possible
    • Log at the scene of the crime, where the most information is available (or, incorporate the info into an exception). (Example Log1)
  • In general, unsafe to assume anything about information coming from outside the process
    • Contents of files holding persistent data (e.g. std::stoi).
    • Message formats
  • Must check results of every kernel call
  • What to do when an error occurs? First, think about how it is likely to be handled.
  • Don't exit in low-level methods
    • Limits generality
    • Bad for unit testing
    • Instead, throw exception
  • Define specific exception types: don't just use std::exception (consider likely usage)
  • All threads should have top-level exception handlers: catch, log, exit

Writing Obvious Code

  • C++ constructs to avoid:
    • std::pair, std::tuple
    • auto
  • Use closures judiciously (examples Obvious1-2)
  • C++ I/O APIs are clunky (example Obvious3)
  • Indentation affects readability (example Obvious4)


  • Many projects did a pretty good job.
  • A few projects didn't have enough:
    • Particularly: instance variables and parameters
  • Documentation duplicates code (example Dup)
  • Sometimes can have too much documentation (examples TMI1-3)


  • Interface comments:
    • In header file vs. code file
    • Private vs. public methods
    • Multiple headers (interface vs. implementation?)
  • Bad variable names (Example Misc1-2)
  • Using one variable for two different things (Misc3)