Skip to content

triuzzi/distributed-snapshot-library

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

129 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Distributed Snapshot Library

A Java library implementing an adapted Chandy-Lamport distributed snapshot algorithm that enables recording consistent global states and restoring them after node failures in distributed systems communicating over Java RMI.

Created by Emanuele Triuzzi, Giancarlo Sorrentino, Vincenzo Riccio

Politecnico di Milano — Distributed Systems, A.Y. 2020-2021

Professors: Gianpaolo Cugola, Alessandro Margara


Table of Contents


Overview

This library provides an abstract class Snapshottable<S, M> that allows any node in a distributed system to:

  1. Record consistent snapshots of the global state, including local process states and in-transit messages across communication channels
  2. Restore the system to a previously saved consistent state after one or more node failures

The snapshot and restore procedures are both based on an adapted version of the Chandy-Lamport algorithm, extended with crash detection, Lamport clock-based snapshot identification, and a coordinated restore protocol. The library is implemented in Java and communicates over RMI (Remote Method Invocation).

The library is message and state agnostic: it is built on two generic types — M (messages) and S (state) — whose only requirement is being Serializable. The application itself defines the structure of its state and exchangeable messages.


Goals

Goal Description
G1 Record a consistent global state of a distributed system
G2 Let the system recover a consistent state after one (or more) node failures

Library Assumptions

The library operates under the following assumptions:

  1. No mid-snapshot crashes — Nodes do not crash in the middle of a snapshot
  2. Reliable FIFO channels — Communication channels are reliable, unidirectional, and FIFO ordered
  3. Full connectivity — There is a communication path between any two processes in the system
  4. Non-interference — The snapshot algorithm does not interfere with the normal execution of the processes
  5. Network awareness — The library is aware of all the network changes
  6. Topology stability — The network topology does not change when a snapshot or a restore is running
  7. Message independence — The outcome of the elaboration of received messages is independent from the moment in which they are elaborated
  8. Connection awareness — Each node must be aware of its incoming and outgoing connections
  9. Bounded re-join — A failed node re-joins the network in a limited amount of time
  10. Graceful leave — When a node wants to leave the network, it informs all the nodes it is connected to
  11. Unique identification — Each node is uniquely identified by its IP address
  12. Snapshot integrity — The library's saved snapshots can only be modified or deleted by the library itself

Architecture

Component Overview

The library is composed of four core components that work together to provide transparent snapshot and restore capabilities.

Library Architecture

Component Type Role
Snapshottable<S, M> Abstract Class Core class that nodes extend. Manages Lamport clocks, snapshot coordination, marker propagation, crash detection, and state persistence. Extends UnicastRemoteObject for RMI.
Snapshot<S, M> Class Represents a single snapshot instance. Stores the process state S and a map of channel states (queues of in-transit messages M keyed by sender).
SnapInt Remote Interface Defines the RMI contract: startSnapshot(id), restore(id), and getClock(). Enables remote snapshot/restore coordination.
ConnInt Interface Abstracts a network connection with getHost(), getPort(), and getName(). Applications implement this to define their connection model.

Class Diagram

The following diagram shows the complete class structure, including both the library (snaplib) and the example application (snaptest) packages.

Detailed Class Diagram

Generic Type Design

The library achieves application independence through two generic type parameters:

Snapshottable<S extends Serializable, M extends Serializable>
  • S — The state type. Represents the full internal state of a node. The application defines what constitutes its state (e.g., a ledger, a counter, a game board).
  • M — The message type. Represents the messages exchanged between nodes. The application defines the message structure (e.g., a transfer command, a chat message).

Both types must be Serializable so that the library can persist snapshots to disk and transmit markers over RMI.


The Snapshot Algorithm

The snapshot algorithm is based on the Chandy-Lamport marker-based approach, adapted to work with Java RMI and extended with Lamport clocks for snapshot identification.

The Snapshot Algorithm

Snapshot ID Structure

Each snapshot is uniquely identified by an ID composed of:

<Lamport Clock Value> . <Initiator Host Address>
  • The Lamport clock value of the initiator at the moment the snapshot starts
  • The unique identifier (IP address) of the initiator

The library increments its local Lamport clock only when starting a new snapshot instance. This structure enables:

  • Happens-before relationships between snapshots with different Lamport clocks, which is necessary for the restore procedure to lead the system to a consistent state
  • Multiple concurrent snapshots running in parallel, since each snapshot has a unique ID

Algorithm Steps

When a process initiates a snapshot or receives a snapshot marker for the first time:

  1. Records its internal state — The current state S is deep-copied (via serialization) and stored in a new Snapshot<S, M> object
  2. Sends a marker — A snapshot marker, embedding the snapshot ID, is sent on all outgoing channels to neighboring nodes
  3. Starts recording messages — The node begins recording all incoming messages on every incoming channel, associating them with the snapshot
  4. Processes subsequent markers — When a marker for the same snapshot arrives from an incoming node, the node stops recording messages from that channel
  5. Completes the local snapshot — When the node has received the marker from all incoming channels, the local snapshot is considered complete and is persisted to disk as a .snap file

If the node has already started a snapshot with the given ID (i.e., it receives a duplicate marker), it simply removes the sender from its pending set and checks for completion.

Concurrent Snapshots

The unique ID structure allows multiple snapshots to run concurrently. The library maintains separate state for each active snapshot:

  • runningSnapshots: Map<String, Snapshot<S, M>> — Maps snapshot IDs to their in-progress snapshot objects
  • incomingStatus: Map<String, Set<String>> — Maps snapshot IDs to the set of incoming nodes from which the marker has not yet been received

Each incoming message is checked against all active snapshots: if the sender has not yet sent a marker for a given snapshot, the message is recorded in that snapshot.


The Restore Algorithm

The restore algorithm allows the system to recover from node failures by rolling back to a previously saved consistent snapshot.

The Restore Algorithm

Restore Algorithm Steps

When a node recovers from a failure or receives a restore marker for the first time:

  1. Selects the snapshot — If it is the failed node, it selects the local snapshot with the greatest Lamport clock. If receiving a restore marker, it uses the specified snapshot ID. In case of ambiguity, a common rule is applied.
  2. Restores local state — The node restores its local state to the saved one, including resetting the Lamport clock value to the one associated with the selected snapshot.
  3. Processes saved messages — All messages included in the selected snapshot are replayed, ensuring the node reaches the exact state it was in when the snapshot was taken plus any in-transit messages.
  4. Cancels active snapshots — All currently running snapshots are discarded, and the incoming status maps are cleared.
  5. Sends restore markers — A restore marker, embedding the ID of the selected snapshot, is sent on each outgoing channel.

When a node receives the marker from all incoming channels, the local restore can be considered complete.

Message Handling During Restore

During the restore process, strict rules govern message handling:

  • Messages from nodes that have not already sent the restore marker are discarded, since those nodes may still be in an inconsistent state
  • Messages from nodes that have already sent the restore marker can be safely processed, since they are generated from a node in a consistent state
  • New snapshot requests from non-restored nodes are also ignored to prevent inconsistent snapshots from being created

Multiple Failures

The restore algorithm is designed to handle multiple node failures. If a second failure occurs during an ongoing restore, the algorithm detects this condition and restarts the restore process from scratch, selecting the appropriate snapshot for the new failure scenario.

Unlike snapshots, only one restore can be active at a time across the network. This is because, assuming a snapshot completes successfully, all nodes will have that snapshot saved. If a snapshot didn't complete, nodes that had already started it will discard it during restore, converging to the last fully completed snapshot.


Formal Correctness Proofs

This section provides a formal proof that the adapted Chandy-Lamport algorithm implemented by this library correctly records consistent global states and that the restore procedure recovers the system to a valid state.

System Model

Consider a distributed system modeled as a directed graph $G = (P, C)$ where:

  • $P = {p_1, p_2, \ldots, p_n}$ is a finite set of processes (nodes)
  • $C \subseteq P \times P$ is a set of unidirectional FIFO channels, where $c_{ij} \in C$ denotes the channel from $p_i$ to $p_j$

Each process $p_i$ generates an ordered sequence of events. An event $e$ is one of:

  • Internal event — a local state transition on $p_i$
  • Send event $\mathrm{send}(m, c_{ij})$ — process $p_i$ sends message $m$ on channel $c_{ij}$
  • Receive event $\mathrm{recv}(m, c_{ij})$ — process $p_j$ receives message $m$ from channel $c_{ij}$

We define a Lamport clock $L : P \to \mathbb{N}$ such that each process $p_i$ maintains a local counter $L_i$ that is incremented on snapshot initiation and synchronized via:

$$L_j \leftarrow \max(L_j, \ L_{\mathrm{marker}} + 1)$$

where $L_{\mathrm{marker}}$ is the clock value embedded in a received snapshot marker.

Definitions

Definition 1 (Happens-Before). The happens-before relation $\to$ is the smallest transitive relation on events satisfying:

(i)$e_1 \to e_2$   if $e_1$ and $e_2$ occur at the same process and $e_1$ precedes $e_2$

(ii)$\mathrm{send}(m, c_{ij}) \to \mathrm{recv}(m, c_{ij})$   for every message $m$

Definition 2 (Cut). A cut $\mathcal{C}$ of an execution is a set of events such that for each process $p_i$, the events of $p_i$ in $\mathcal{C}$ form a prefix of $p_i$'s event sequence. The frontier of $\mathcal{C}$ is the set of last events $\lbrace e_1^{k_1}, e_2^{k_2}, \ldots, e_n^{k_n}\rbrace$ where $e_i^{k_i}$ is the last event of $p_i$ in $\mathcal{C}$.

Definition 3 (Consistent Cut). A cut $\mathcal{C}$ is consistent if and only if:

$$\forall \ e, e' : (e \in \mathcal{C} \ \wedge \ e' \to e) \implies e' \in \mathcal{C}$$

Equivalently, no message is recorded as received without its corresponding send also being recorded.

Definition 4 (Global State). A global state $\mathcal{S}$ recorded by the snapshot algorithm consists of:

$$\mathcal{S} = \big(\lbrace s_1, s_2, \ldots, s_n\rbrace, \ \lbrace\sigma_{ij} \mid c_{ij} \in C\rbrace\big)$$

where $s_i$ is the local state of process $p_i$ at the moment of its snapshot, and $\sigma_{ij}$ is the sequence of messages recorded in channel $c_{ij}$ (messages sent before $p_i$'s snapshot and received after $p_j$'s snapshot).

Definition 5 (Channel State). For a given snapshot, the recorded state of channel $c_{ij}$ is:

$$\sigma_{ij} = \lbrace m \mid \mathrm{send}(m, c_{ij}) \prec \hat{s}_i \ \wedge \ \hat{s}_j \preceq \mathrm{recv}(m, c_{ij}) \rbrace$$

where $\hat{s}_i$ denotes the snapshot event at process $p_i$, and $\prec$ denotes local ordering.

Theorem 1 — Snapshot Consistency

Theorem. The global state $\mathcal{S}$ recorded by the adapted Chandy-Lamport algorithm corresponds to a consistent cut of the execution.

Proof. We prove this by contradiction.

Suppose the recorded global state $\mathcal{S}$ does not correspond to a consistent cut. Then, by Definition 3, there exist events $e_i$ at process $p_i$ and $e_j$ at process $p_j$ such that:

$$e_j \in \mathcal{C} \quad \wedge \quad e_i \to e_j \quad \wedge \quad e_i \notin \mathcal{C}$$

Since $e_i \to e_j$ with $e_i$ at $p_i$ and $e_j$ at $p_j$ (where $i \neq j$), there exists a message $m$ such that:

$$\mathrm{send}(m, c_{ij}) \preceq e_i \quad \wedge \quad e_j \preceq \mathrm{recv}(m, c_{ij})$$

From $e_i \notin \mathcal{C}$, the send of $m$ occurs after $p_i$'s snapshot event:

$$\hat{s}_i \prec \mathrm{send}(m, c_{ij}) \qquad (1)$$

From $e_j \in \mathcal{C}$, the receive of $m$ occurs before or at $p_j$'s snapshot event:

$$\mathrm{recv}(m, c_{ij}) \preceq \hat{s}_j \qquad (2)$$

Now, in the adapted Chandy-Lamport algorithm, when $p_i$ performs its snapshot, it sends a marker $\mu_i$ on all outgoing channels, including $c_{ij}$. Since the snapshot event precedes the send of $m$ (from (1)):

$$\mathrm{send}(\mu_i, c_{ij}) \preceq \hat{s}_i \prec \mathrm{send}(m, c_{ij})$$

By the FIFO property of channel $c_{ij}$ (Assumption 2):

$$\mathrm{recv}(\mu_i, c_{ij}) \prec \mathrm{recv}(m, c_{ij}) \qquad (3)$$

From the algorithm's marker rule, upon receiving marker $\mu_i$, process $p_j$ performs its local snapshot if it has not already done so. In either case:

$$\hat{s}_j \preceq \mathrm{recv}(\mu_i, c_{ij}) \qquad (4)$$

Combining (3) and (4):

$$\hat{s}_j \preceq \mathrm{recv}(\mu_i, c_{ij}) \prec \mathrm{recv}(m, c_{ij})$$

This gives us:

$$\hat{s}_j \prec \mathrm{recv}(m, c_{ij}) \qquad (5)$$

But (5) directly contradicts (2), which states $\mathrm{recv}(m, c_{ij}) \preceq \hat{s}_j$.

$$\Longrightarrow \quad \mathrm{Contradiction.} \qquad \blacksquare$$

Theorem 2 — Termination

Theorem. If the network graph $G$ is strongly connected and no process fails during the snapshot, the algorithm terminates in finite time, and every process records a local snapshot.

Proof. We proceed by induction on the distance from the initiator.

Base case. The initiator $p_0$ records its state and sends markers on all outgoing channels immediately. $p_0$'s local snapshot terminates when it receives markers from all incoming channels.

Inductive step. Assume all processes at distance $\leq k$ from $p_0$ (in the directed graph $G$) have received a marker and initiated their local snapshots. Consider a process $p_j$ at distance $k + 1$. By the strongly connected property, there exists a directed path from $p_0$ to $p_j$ of length $k + 1$. Let $p_i$ be the predecessor of $p_j$ on this path. By the inductive hypothesis, $p_i$ has sent markers on all outgoing channels, including $c_{ij}$. By channel reliability (Assumption 2), the marker reaches $p_j$ in finite time.

Since $G$ is strongly connected and finite, every process is at finite distance from $p_0$. By induction, every process eventually receives at least one marker and initiates its local snapshot.

For each process $p_j$, the local snapshot terminates when markers arrive from all incoming channels. Since every process with an outgoing channel to $p_j$ eventually sends a marker (by the inductive argument), and channels are reliable, $\mathrm{recv}(\mu_i, c_{ij})$ occurs in finite time for all $p_j \in P$ and all $c_{ij} \in C$.

Therefore, every local snapshot completes, and the global snapshot terminates. $\blacksquare$

Theorem 3 — Restore Correctness

Theorem. After the restore procedure completes, the global state of the system is equivalent to the consistent global state $\mathcal{S}$ previously recorded by a successful snapshot, plus the deterministic replay of its channel states.

Proof. Let $\mathcal{S} = (\lbrace s_1, \ldots, s_n\rbrace, \lbrace\sigma_{ij}\rbrace)$ be the recorded consistent global state selected for restore (with the greatest Lamport clock). We show that after restore, the system state $\mathcal{S}'$ satisfies $\mathcal{S}' \equiv \mathcal{S}^{}$, where $\mathcal{S}^{}$ is the state obtained by applying all recorded channel messages to $\mathcal{S}$.

Part (a): State restoration. Each process $p_i$ resets its local state to $s_i$ and its Lamport clock to the value embedded in the snapshot ID. By Assumption 7 (message processing independence), the restored state is identical to the state at the moment of the original snapshot.

Part (b): Message replay. Each process $p_i$ replays the messages in $\bigcup_j \sigma_{ji}$ (all messages recorded in its incoming channels). By Assumption 7, the result of processing these messages is independent of when they are processed, so:

$$s_i^{*} = \mathrm{apply}\big(s_i, \ \bigcup\nolimits_j \sigma_{ji}\big) = s_i'$$

Part (c): Message filtering during restore. The algorithm discards messages from processes that have not yet sent the restore marker. Let $R \subseteq P$ be the set of processes that have completed their restore, and $\overline{R} = P \setminus R$ be those still pending. For any message $m$ from $p_k \in \overline{R}$ to $p_j \in R$, the state of $p_k$ may be inconsistent with $\mathcal{S}$, so $m$ is discarded. Conversely, for $p_k \in R$, its state is consistent with $\mathcal{S}^{*}$, so its messages are valid and can be processed. This ensures no causally invalid messages pollute the restored state.

Part (d): Convergence. By Theorem 2 (substituting restore markers for snapshot markers), the restore propagation terminates in finite time over the strongly connected graph. After termination, $R = P$, all processes are in states derived from $\mathcal{S}$, and the Lamport clocks are synchronized:

$$\forall \ p_i \in P : L_i = L_{\mathcal{S}} + 1$$

where $L_{\mathcal{S}}$ is the Lamport clock value of the restored snapshot.

The system has thus reached a globally consistent state equivalent to $\mathcal{S}^{*}$, from which normal execution can resume. $\blacksquare$


Network Management

Network Creation

The network topology is built incrementally while maintaining a strongly connected graph at every step. This is essential because the Chandy-Lamport algorithm requires a path between any two processes.

Network Creation Process

The network creation follows this mechanism:

  1. First node — The initiator starts alone in the network
  2. Joining the network — When a new node joins, it must create both an incoming and an outgoing connection with an existing node, guaranteeing the graph remains strongly connected. A snapshot marker is triggered immediately after joining to capture the new topology.
  3. Adding connections — Additional single point-to-point connections can be added between any two nodes at any time. Each connection change triggers a new snapshot to record the updated topology.

Network Changes

Network Topology Changes

Safe Disconnect

When a node wants to leave the network, it must verify that its removal would not break the strongly connected property of the graph.

In the diagram above:

  • Node G cannot leave safely because removing it would disconnect V and E (G is a bridge node)
  • Node E can leave safely because V and G would remain strongly connected through their direct bidirectional link

The disconnect procedure:

  1. The leaving node removes all its incoming connections, notifying each peer
  2. The leaving node removes all its outgoing connections, notifying each peer
  3. The last disconnected peer triggers a snapshot to capture the new topology
  4. The leaving node transfers its state (e.g., customer ledgers) to another node before exiting

Snapshot Scenario — Basic Workflow

This section walks through a complete snapshot scenario with three nodes — V (Vincenzo), G (Giancarlo), and E (Emanuele) — connected in a strongly connected graph.

Snapshot Scenario — Basic Workflow

Step 1: Initiator Starts Snapshot

G starts a snapshot. G records its current state S_G1, creates a new snapshot object, and sends snapshot markers (SM) on all its outgoing channels — to V and to E. G's Lamport clock is used in the snapshot ID.

Node Actual State Saved State Saved MSGs
V S_V1
G S_G1 S_G1
E S_E1

Step 2: In-Transit Message Recording

Meanwhile, G receives messages from V and E, which arrive before their respective snapshot markers. These messages (M_E1, M_V1) are saved in G's local snapshot since G has not yet received the snapshot marker from those senders.

Node Actual State Saved State Saved MSGs
V S_V1
G S_G3 S_G1 M_E1, M_V1
E S_E1

Step 3: Marker Propagation

V and E receive the snapshot marker from G. Upon receiving the marker for the first time, each node:

  • Records its current state (S_V1 for V, S_E1 for E)
  • Forwards the snapshot marker on its outgoing channels
  • Starts recording incoming messages for this snapshot

Since E has no pending messages from any channel before receiving the marker, its saved messages are None.

Node Actual State Saved State Saved MSGs
V S_V1 S_V1
G S_G3 S_G1 M_E1, M_V1
E S_E1 S_E1 None

Step 4: Snapshot Completion

The snapshot ends for each node. Once a node receives the marker from all its incoming channels, its local snapshot is complete. No further messages are saved for this snapshot. The snapshot is persisted to disk.

Node Actual State Saved State Saved MSGs
V S_V2 S_V1 None
G S_G4 S_G1 M_E1, M_V1
E S_E2 S_E1 None

The saved states (S_V1, S_G1, S_E1) together with the recorded channel messages form a consistent global snapshot of the distributed system at a logical point in time.


Example Application — Distributed Bank Transfers

The repository includes a complete example application that demonstrates the library in a realistic scenario: a distributed system of banks performing money transfers.

Example Application — Distributed Bank Transfers

System Design

Each node in the system represents a bank, holding customer ledgers (a map of customer names to balances). Banks are connected via RMI and can:

  • Transfer money — A bank debits a local customer and sends an RMI call to the recipient bank, which credits the recipient customer
  • Transfer ledgers — When a bank is shutting down, it can transfer all its customer accounts to another bank
  • Connect/disconnect — Banks can dynamically join or leave the network

The global state to preserve is the total amount of money spread across all banks in the network. The Chandy-Lamport snapshot ensures that this invariant is captured consistently, even while transfers are in-flight.

The example application implements:

Class Role
Node Extends Snapshottable<State, Message>. Represents a bank node with transfer operations, network management, and snapshot restoration via reflection-based message replay.
State The serializable state containing the ledger (customer balances) and the sets of incoming/outgoing connections.
Message A serializable message that stores a method name, parameter types, and parameters — enabling reflective replay during restore.
Connection Implements ConnInt. Represents a connection to another bank identified by host, port, and name.
PublicInt Remote interface exposing bank operations: transfer, transferLedger, addConn, removeConn.
Main Entry point with an interactive CLI for performing transfers, managing connections, and triggering snapshots.

Running the Example

Each bank node requires two XML configuration files in its working directory:

config.xml — Node configuration:

<config>
    <host>192.168.1.10</host>
    <port>1099</port>
    <name>Hype</name>
    <defaultCustomer>Emanuele</defaultCustomer>
    <newNetwork>true</newNetwork>           <!-- true only for the first node -->
    <incoming>                               <!-- required when joining an existing network -->
        <host>192.168.1.20</host>
        <port>1099</port>
        <name>Intesa</name>
    </incoming>
    <outgoing>
        <host>192.168.1.20</host>
        <port>1099</port>
        <name>Intesa</name>
    </outgoing>
    <canLeaveNetworkSafely>true</canLeaveNetworkSafely>
</config>

ledgers.xml — Initial customer balances:

<ledgers>
    <user>
        <name>Emanuele</name>
        <balance>150</balance>
    </user>
    <user>
        <name>Lia</name>
        <balance>50</balance>
    </user>
    <user>
        <name>Lorenzo</name>
        <balance>100</balance>
    </user>
</ledgers>

To run a node:

java -jar Node.jar

The interactive CLI provides the following operations:

  • A — Trigger a snapshot
  • B — Perform a generic bank transfer
  • E / G / V — Quick transfers to predefined recipients
  • C — Connect to a new outgoing node
  • CC — Print all connections
  • S — Print all customer balances
  • X — Shut down the bank (transfer customers to another bank and leave the network)

Project Structure

.
├── README.md
├── .gitignore
├── docs/
│   ├── generate_diagrams.py          # Python script to regenerate all diagrams
│   └── images/
│       ├── architecture.png          # Library component overview
│       ├── class_diagram.png         # Full class diagram (library + example)
│       ├── snapshot_algorithm.png    # Snapshot algorithm flow
│       ├── restore_algorithm.png     # Restore algorithm flow
│       ├── network_creation.png      # Network creation steps
│       ├── network_changes.png       # Safe vs unsafe disconnect
│       ├── snapshot_scenario.png     # Step-by-step snapshot walkthrough
│       └── bank_example.png          # Bank transfer example architecture
├── SnapLib/                          # The library
│   ├── src/it/polimi/ds/ricciosorrentinotriuzzi/snaplib/
│   │   ├── Snapshottable.java        # Core abstract class
│   │   ├── Snapshot.java             # Snapshot data structure
│   │   ├── SnapInt.java              # Remote interface
│   │   └── ConnInt.java              # Connection interface
│   └── out/artifacts/SnapLib/
│       └── SnapLib.jar               # Compiled library
└── Node/                             # Example application
    ├── src/it/polimi/ds/ricciosorrentinotriuzzi/snaptest/
    │   ├── Node.java                  # Bank node implementation
    │   ├── State.java                 # Bank state (ledger + connections)
    │   ├── Message.java               # Reflective message for replay
    │   ├── Connection.java            # ConnInt implementation
    │   ├── PublicInt.java             # Remote bank interface
    │   └── Main.java                  # Interactive CLI entry point
    ├── lib/                           # Dependencies
    │   ├── SnapLib.jar
    │   ├── commons-configuration-1.10.jar
    │   ├── commons-collections-3.2.2.jar
    │   ├── commons-lang-2.6.jar
    │   └── commons-logging-1.2.jar
    └── out/artifacts/Node/
        └── Node.jar                   # Compiled example application

Getting Started

Integrating the Library

Add SnapLib.jar to your project's classpath. The library has no external dependencies beyond the Java standard library (specifically java.rmi).

Implementing a Snapshottable Node

To use the library, create a class that extends Snapshottable<S, M> with your application-specific state and message types:

public class MyNode extends Snapshottable<MyState, MyMessage> {

    private MyState state;

    public MyNode(int port) throws RemoteException, AlreadyBoundException {
        super(port);  // Registers RMI, checks for crashes, starts restore if needed
        state = new MyState();
    }

    @Override
    public MyState getState() {
        return state;
    }

    @Override
    public String getHost() {
        return "192.168.1.10";  // This node's unique identifier
    }

    @Override
    public Set<ConnInt> getInConn() {
        return state.getIncomingConnections();
    }

    @Override
    public Set<ConnInt> getOutConn() {
        return state.getOutgoingConnections();
    }

    @Override
    public void restoreSnapshot(Snapshot<MyState, MyMessage> snapshot) {
        this.state = snapshot.getState();
        for (MyMessage msg : snapshot.getMessages()) {
            // Replay each message to reach the consistent state
            processMessage(msg);
        }
    }
}

Key points:

  • Both MyState and MyMessage must implement Serializable
  • Call addMessage(sender, message) when receiving a message from another node, so the library can record in-transit messages during snapshots
  • Call shouldDiscard(sender) before processing messages to check if they should be discarded during a restore
  • Call initiateSnapshot() to start a new snapshot
  • Call applyNetworkChange() after any topology change to trigger a snapshot
  • Call safeExit() before graceful shutdown to prevent false crash detection on next startup
  • Call joinNetwork(host, port) to synchronize Lamport clocks when joining an existing network

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages