-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create integration for using a atomic broadcast
backed by etcd. Using this approach we should have a primitive that is consistent and totally orders all messages. Along with this changes, added a generic interface to be possible to use different atomic broadcast protocols.
- Loading branch information
Showing
15 changed files
with
631 additions
and
298 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package internal | ||
|
||
import "sync/atomic" | ||
|
||
const ( | ||
// Constant to represent the `active` state on the Flag. | ||
active = 0x0 | ||
|
||
// Constant to represent the `inactive` state on the Flag. | ||
inactive = 0x1 | ||
) | ||
|
||
// An atomic boolean implementation, to act specifically as a flag. | ||
type Flag struct { | ||
flag int32 | ||
} | ||
|
||
// Verify if the flag still on `active` state. | ||
func (f *Flag) IsActive() bool { | ||
return atomic.LoadInt32(&f.flag) == active | ||
} | ||
|
||
// Verify if the flag is on `inactive` state. | ||
func (f *Flag) IsInactive() bool { | ||
return atomic.LoadInt32(&f.flag) == inactive | ||
} | ||
|
||
// Transition the flag from `active` to `inactive`. | ||
func (f *Flag) Inactivate() bool { | ||
return atomic.CompareAndSwapInt32(&f.flag, active, inactive) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package internal | ||
|
||
import ( | ||
"context" | ||
"github.com/coreos/etcd/clientv3" | ||
"io" | ||
"time" | ||
) | ||
|
||
// Configuration for the coordinator. | ||
type CoordinatorConfiguration struct { | ||
// Each Coordinator will handle only a single partition. | ||
// This will avoid peers with overlapping partitions. | ||
Partition string | ||
|
||
// Address for etcd server. | ||
Server string | ||
|
||
// Context that the Coordinator will work. | ||
Ctx context.Context | ||
|
||
// Handler for managing goroutines. | ||
Handler *GoRoutineHandler | ||
} | ||
|
||
// Coordinator interface that should be implemented by the | ||
// atomic broadcast handler. | ||
// Commands should be issued through the coordinator to be delivered | ||
// to other peers | ||
type Coordinator interface { | ||
io.Closer | ||
|
||
// Watch for changes on the partition. | ||
// After called, this method will start a new goroutine that only | ||
// returns when the Coordinator context is done. | ||
Watch(received chan<- Event) error | ||
|
||
// Issues an Event. | ||
Write(event Event) error | ||
} | ||
|
||
// Create a new Coordinator using the given configuration. | ||
// The current implementation is the EtcdCoordinator, backed by etcd. | ||
func NewCoordinator(configuration CoordinatorConfiguration) (Coordinator, error) { | ||
cli, err := clientv3.New(clientv3.Config{ | ||
DialTimeout: 30 * time.Second, | ||
Endpoints: []string{configuration.Server}, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
kv := clientv3.NewKV(cli) | ||
coord := &EtcdCoordinator{ | ||
configuration: configuration, | ||
cli: cli, | ||
kv: kv, | ||
} | ||
return coord, nil | ||
} | ||
|
||
// EtcdCoordinator will use etcd for atomic broadcast. | ||
type EtcdCoordinator struct { | ||
// Configuration parameters. | ||
configuration CoordinatorConfiguration | ||
|
||
// A client for the etcd server. | ||
cli *clientv3.Client | ||
|
||
// The key-value entry point for issuing requests. | ||
kv clientv3.KV | ||
} | ||
|
||
// Starts a new coroutine for watching the Coordinator partition. | ||
// All received information will be published back through the channel | ||
// received as parameter. | ||
// | ||
// After calling a routine will run bounded to the application lifetime. | ||
func (e *EtcdCoordinator) Watch(received chan<- Event) error { | ||
watchChan := e.cli.Watch(e.configuration.Ctx, e.configuration.Partition) | ||
watchChanges := func() { | ||
for { | ||
select { | ||
case <-e.configuration.Ctx.Done(): | ||
return | ||
case response := <-watchChan: | ||
e.handleResponse(response, received) | ||
} | ||
} | ||
} | ||
e.configuration.Handler.Spawn(watchChanges) | ||
return nil | ||
} | ||
|
||
// Write the given event using the KV interface. | ||
func (e *EtcdCoordinator) Write(event Event) error { | ||
_, err := e.kv.Put(e.configuration.Ctx, event.Key, string(event.Value)) | ||
return err | ||
} | ||
|
||
// Stop the etcd client connection. | ||
func (e *EtcdCoordinator) Close() error { | ||
return e.cli.Close() | ||
} | ||
|
||
// This method is responsible for handling events from the etcd client. | ||
// | ||
// This method will transform each received event into Event object and | ||
// publish it back using the given channel. This method can block the whole | ||
// event loop if the channel is not consumed. | ||
// TODO: asynchronously publish events. | ||
func (e *EtcdCoordinator) handleResponse(response clientv3.WatchResponse, received chan<- Event) { | ||
for _, event := range response.Events { | ||
received <- Event{ | ||
Key: string(event.Kv.Key), | ||
Value: event.Kv.Value, | ||
Error: nil, | ||
} | ||
} | ||
} |
Oops, something went wrong.