Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update readme #179

Merged
merged 1 commit into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 94 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. <br/>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.<br/>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.<br/>The buffer will also include internal messages (e.g. synchronization of the peer list).<br/>***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()
```

<a name="custom_anchor_name"></a>
- ### 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

<a name="examples"></a>

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
Expand Down
16 changes: 8 additions & 8 deletions _examples/http/bmmc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
))
})
Expand Down
2 changes: 1 addition & 1 deletion _examples/http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
12 changes: 7 additions & 5 deletions pkg/bmmc/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/bmmc/gossiper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion pkg/internal/peer/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading