From 1c9ded6cebdaea0ec28383e285b9cd66fa0bd36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20=C8=98tefan?= Date: Wed, 10 Apr 2024 20:14:59 +0300 Subject: [PATCH] Update readme --- README.md | 145 +++++++++++++++++++++++------------- _examples/http/bmmc_test.go | 16 ++-- _examples/http/main.go | 2 +- pkg/bmmc/config.go | 12 +-- pkg/bmmc/gossiper_test.go | 2 +- pkg/internal/peer/peer.go | 2 +- 6 files changed, 112 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index a574918..2d05ef2 100644 --- a/README.md +++ b/README.md @@ -8,106 +8,149 @@ This is an implementation of the Bimodal Multicast Protocol written in GO. You can synchronize all types of messages: bool, string, int, complex structs, etc. +--- + ## Overview The Bimodal Multicast Protocol runs in a series of rounds. + At the beginning of each round, every node randomly chooses another node and sends it a digest of its message histories. The message is called gossip message. + The node that receive the gossip message compares the given digest with the messages in its own message buffer. + If the digest differs from its message histories, then it send a message back to the original sender to request the missing messages. This message is called solicitation. +--- + ## Usage -* Imports +- ### Step 1: Imports -```golang -import ( - "github.com/rstefan1/bimodal-multicast/pkg/bmmc" -) +```go +import "github.com/rstefan1/bimodal-multicast/pkg/bmmc" ``` -* Configure the protocol - -```golang - cfg := bmmc.Config{ - Addr: "localhost", - Port: "14999", - Callbacks: map[string]func (interface{}, *log.Logger) error { - "awesome-callback": - func (msg interface{}, logger *log.Logger) error { - fmt.Println("The message is:", msg) - return nil - }, - }, - BufferSize: 2048, - } -``` +- ### Step 2: Configure the host -* Create an instance for protocol +The host must implement [Peer interface](https://github.com/rstefan1/bimodal-multicast/blob/f98c69dbc8ac22decdb438a1d6b5abc4b5db2db0/pkg/internal/peer/peer.go#L20): -```golang - p, err := bmmc.New(cfg) +```go +type Peer interface { + String() string + Send(msg []byte, route string, peerToSend string) error +} ``` -* Start the protocol +- ### Step 3: Configure the bimodal-multicast protocol + +```go +cfg := bmmc.Config{ + Host: host, + Callbacks: map[string]func (interface{}, *log.Logger) error { + "custom-callback": + func (msg interface{}, logger *log.Logger) error { + fmt.Println("The message is:", msg) -```golang - err := p.Start() + return nil + }, + }, + Beta: float64, + Logger: logger, + RoundDuration: time.Second * 5, + BufferSize: 2048, +} ``` -* Stop the protocol +| Config | Required | Description | +|---------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Host | Yes | Host of Bimodal Multicast server.
Must implement [Peer interface](https://github.com/rstefan1/bimodal-multicast/blob/f98c69dbc8ac22decdb438a1d6b5abc4b5db2db0/pkg/internal/peer/peer.go#L20). Check the previous step. | +| Callback | No | You can define a list of callbacks.
A callback is a function that is called every time a message on the server is synchronized. | +| Beta | No | The beta factor is used to control the ratio of unicast to multicast traffic that the protocol allows. | +| Logger | No | You can define a [structured logger](https://pkg.go.dev/log/slog). | +| RoundDuration | No | The duration of a gossip round. | +| BufferSize | Yes | The size of messages buffer.
The buffer will also include internal messages (e.g. synchronization of the peer list).
***When the buffer is full, the oldest message will be removed.*** | -```golang - p.Stop() -``` -* Add a new message in buffer (When the buffer is full, the oldest message will be removed.) +- ### Step 4. Create a bimodal multicast server -```golang - err := p.AddMessage("awesome message", "awesome-callback") - - err := p.AddMessage(12345, "awesome-callback") - - err := p.AddMessage(true, "awesome-callback") +```go +bmmcServer, err := bmmc.New(cfg) ``` -For messages without callback, you can use `bmmc.NOCALLBACK` as callback type. +- ### Step 5. Create the host server (e.g. a HTTP server) + +The server must handle a list of predefined requests. +Each of these handlers must read the message body and call a predefined function. -* Get all messages from the buffer +| Handler route | Function to be called | +|-------------------|-------------------------------------------| +| `bmmc.GossipRoute` | `bmmcServer.GossipHandler(body)` | +| `bmmc.SolicitationRoute` | `bmmcServer.SolicitationHandler(body)` | +| `bmmc.SynchronizationRoute` | `bmmcServer.SynchronizationHandler(body)` | -```golang - messages := p.GetMessages() +For more details, check the [exemples](#examples). + +- ### Step 6. Start the host server and the bimodal multicast server + +```go +# Start the host server +hostServer.Start() + +# Start the bimodal multicast server +bmmcServer.Start() +``` + + +- ### Step 7. Add a message to broadcast + +```go +bmmcServer.AddMessage("new-message", "my-callback") +bmmcServer.AddMessage(12345, "another-callback") +bmmcServer.AddMessage(true, bmmc.NOCALLBACK) ``` -* Add a new peer in peers buffer. +- ### Step 8. Retrieve all messages from buffer -```golang - err := p.AddPeer("localhost", "18999") +```go +bmcServer.GetMessages() ``` -* Remove a peer from peers buffer +- ### Step 9. Add/Remove peers -```golang - err := p.RemovePeer("localhost", "18999") +```go +bmmcServer.AddPeer(peerToAdd) +bmmcServer.RemovePeer(peerToRemove) ``` -* Get all peers +- ### Step 10. Stop the bimodal multicast server -```golang - peers := GetPeers() +```go +bmmcServer.Stop() ``` +--- + +## Examples + + +1. using a [http server](_examples/http) +2. using a [maelstrom server](_examples/maelstrom) + +--- ## Contributing I welcome all contributions in the form of new issues for feature requests, bugs or even pull requests. +--- + ## License This project is licensed under Apache 2.0 license. Read the [LICENSE](LICENSE) file diff --git a/_examples/http/bmmc_test.go b/_examples/http/bmmc_test.go index e20c38a..491a360 100644 --- a/_examples/http/bmmc_test.go +++ b/_examples/http/bmmc_test.go @@ -172,33 +172,33 @@ var _ = Describe("BMMC with HTTP Server", func() { It("sync buffers", func() { // Add a message in first node. // Both nodes must have this message. - Expect(bmmc1.AddMessage("awesome-first-message", "my-callback")).To(Succeed()) + Expect(bmmc1.AddMessage("my-first-message", "my-callback")).To(Succeed()) Eventually(getBufferFn(bmmc1)).Should(ConsistOf( []string{ - "awesome-first-message", + "my-first-message", }, )) Eventually(getBufferFn(bmmc2)).Should(ConsistOf( []string{ - "awesome-first-message", + "my-first-message", }, )) // Add a message in second node. // Both nodes must have this message. - Expect(bmmc2.AddMessage("awesome-second-message", "my-callback")).To(Succeed()) + Expect(bmmc2.AddMessage("my-second-message", "my-callback")).To(Succeed()) Eventually(getBufferFn(bmmc1)).Should(ConsistOf( []string{ - "awesome-first-message", - "awesome-second-message", + "my-first-message", + "my-second-message", }, )) Eventually(getBufferFn(bmmc2)).Should(ConsistOf( []string{ - "awesome-first-message", - "awesome-second-message", + "my-first-message", + "my-second-message", }, )) }) diff --git a/_examples/http/main.go b/_examples/http/main.go index e8849f3..7d3fff3 100644 --- a/_examples/http/main.go +++ b/_examples/http/main.go @@ -153,7 +153,7 @@ func main() { //nolint: funlen, cyclop, gocyclo, gocognit case "add-message": if len(args) != 3 { //nolint: gomnd fmt.Println("Invalid command. The `add-message` command must be in form: " + - "add-message awesome-message first-callback") + "add-message my-message first-callback") break } diff --git a/pkg/bmmc/config.go b/pkg/bmmc/config.go index 1fd54ef..881e84e 100644 --- a/pkg/bmmc/config.go +++ b/pkg/bmmc/config.go @@ -36,20 +36,22 @@ var errInvalidBufSize = errors.New("invalid buffer size") // Config is the config for the protocol. type Config struct { // Host is the host peer. + // Required. Host peer.Peer - // Beta is the expected fanout for gossip rounds + // Beta is the expected fanout for gossip rounds. // Optional Beta float64 - // Logger + // Logger. // Optional Logger *slog.Logger - // Callbacks functions + // Callbacks functions. // Optional Callbacks map[string]func(any, *slog.Logger) error - // Gossip round duration + // Gossip round duration. // Optional RoundDuration time.Duration - // Buffer size + // Buffer size. + // When the buffer is full, the oldest message will be removed. // Required BufferSize int } diff --git a/pkg/bmmc/gossiper_test.go b/pkg/bmmc/gossiper_test.go index 934bba7..d5a3362 100644 --- a/pkg/bmmc/gossiper_test.go +++ b/pkg/bmmc/gossiper_test.go @@ -34,7 +34,7 @@ var _ = Describe("Gossiper", func() { msgBuf := buffer.NewBuffer(25) - msg, err := buffer.NewElement("awesome message", "awesome-callback", false) + msg, err := buffer.NewElement("my message", "my-callback", false) Expect(err).ToNot(HaveOccurred()) Expect(msgBuf.Add(msg)).To(Succeed()) diff --git a/pkg/internal/peer/peer.go b/pkg/internal/peer/peer.go index 29a9c9f..9f7e78c 100644 --- a/pkg/internal/peer/peer.go +++ b/pkg/internal/peer/peer.go @@ -16,7 +16,7 @@ limitations under the License. package peer -// Peer ... +// Peer is the interface of Host Peer. type Peer interface { String() string Send(msg []byte, route string, peerToSend string) error