reign

package module
v0.9.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2018 License: MIT Imports: 26 Imported by: 0

README

reign 0.9.1

Build Status

Reign, "Rewrite Erlang In Go Nicely", is designed to make it easy to port Erlang or Erlang-style programs into Go by providing a framework for Erlang-style message passing, including the creation of clusters.

That is, this is not intended to be a "hey, look, if I wrap a mutex around a slice I get something that looks like a PID" four hours of screwing around in Go... this is intended to be a real, production-quality replacement for Erlang-style message passing functionality, suitable for porting existing Erlang programs out of Erlang without significant architecture overhauls.

This module is fully covered with godoc, but test coverage is not yet 100%. But it isn't bad.

If you do not need exactly Erlang-style semantics, note there are other libraries out there that may be better starting points. But I don't know of anything else for Go explicitly written with this goal in mind.

Current status: This is used in a production system in a 4-node cluster, and has been through some trial-by-fire. But as is the nature of this code, I'm sure there are more bugs we haven't tickled yet. I think if you want to convert an existing Erlang code base to Go, this is likely to be very, very helpful to you, but I don't promise it'll do everything you need perfectly or that you won't need to work with me to make some things work.

Getting Started

In order to address security concerns, reign uses TLS certificates signed by a central CA, and all nodes mutually verify the CA's signature with each other. To run reign at all requires creating a CA and certificates for each node.

Since it's a bit of a pain to do this in openssl, especially if you don't have it installed (Windows), a pure-go program to create some certificates is provided, as well as a sample program. If you have where ever "go get" puts binaries for you in your path, you can run:

# go get github.com/thejerf/reign/cmd/reign_init
# go get github.com/thejerf/reign/cmd/reign_sample
# reign_init
Signing certificate created
Constructed certificate for node 1
Constructed certificate for node 2

Note this will plop some certificates into your current directory. You may want to start in a temporary directory or something.

Now, in two separate shells, you can run:

# reign_sample 1
# reign_sample 2

This starts up a simple chat server, as documented in the sample.go program. I have chosen to create an example that focuses very specifically on the message-passing; consequently it is terribly, terribly ugly on the console. I assume you will prefer this to some slick presentation that obscures the message-passing under the pile of code it takes to make a halfway decent interface of any kind.

To get started, I suggest simply copying and pasting that example into a new workspace and start working with the code. That way you start with a working system the whole time.

Reign can use any certificates and certificate authorities that the Go TLS library can handle. The certificates created by this script may not entirely meet your needs. But they can get you started.

What Is "Erlang-like clustering"?

There are some common misconceptions about what "Erlang clustering" is. Erlang gives you two things:

  • A PID which can be sent messages from anywhere within a cluster. The message itself may include PIDs, which remain live and useful through the transfer.
  • Mnesia, a shared dabatase, which can contain PIDs.

The misconception is that Erlang gives you some sort of magic automatic clustering. It does not. It is still possible and even a bit easy to build applications that are locked to a single OS process. It is your job to take the Erlang primitives and build clusterable code.

What Is Reign?

Reign is a library to allow you to Rewrite Erlang In Go Nicely... that is, by allowing you to do fairly direct ports of your existing Erlang code without having to rearchitect it, or give up on clustering support.

This package provides a PID-like data structure and the code to use them across a cluster of Go processes, that may live on other machines. It is expected that even code porting from Erlang will use other database solutions, so there is no Mnesia equivalent, nor any desire to implement such a thing.

The goal is NOT a precise translation; for one thing, that's just plain impossible as both languages can easily do things the other can not. In particular, Erlang's native concurrency is asynchronous (that is, an Erlang message can be sent to a process whether or not it is receiving), and Go can use user-defined types, both of which have major impacts on properly structuring your program. The goal of this library is to give you a scaffolding that you can use to rewrite your Erlang code without having to entirely restructure it. In some sense, ideally over time you should use less and less of this library, however the goal is that this is production-quality and you should not be forced to do so, especially as there aren't necessarily pure-Go implementations of some of this functionality.

If you're using this to start a new Go project, you're probably doing it wrong. Probably. Maybe. To be honest while I have not yet pulled the trigger, I often find myself eyeing this library for my own projects even so.

Mailboxes And Addresses

Addresses are the equvialent of Erlang PIDs. Mailboxes are the equivalent of the implicit mailbox that an Erlang process has. Since goroutines can not carry implicit data, they are explicit in Reign. To avoid confusion about this unitary idea in Erlang being split in half in Reign, neither is called a "PID", especially since it isn't a "process ID" anyhow.

To get the equivalent of a PID in Erlang, once you have the cluster running, use NewMailbox from the connection service to receive a paired Address and Mailbox. The Address may be moved across the cluster transparently. The Mailbox is bound to the OS process that created it. Mailboxes provide both the ability to simply receive the next message and to pick a particular message out of the queue, with the same caveats in Go as there are in Erlang about the dangers of the queue backing up if you do not empty it.

In addition to asynchronous message queues and selective receive, reign implements an equivalent to "linking" in Erlang called via OnCloseNotify. When used like Erlang linking, this allows for some relatively safe RPC calling that will handle the mailbox (or the relevant node) being entirely unattached while you are trying to communicate with it. Exactly as with Erlang, this still isn't a guarantee of delivery, but it helps a lot.

Notes On Translation From Erlang

Go channels are synchronous. Erlang-style messages are asynchronous. In particular, this means that code written like this:

receive {msg1, Msg} -> {echo, Msg, self()} ! AnotherPID, receive {echoResponse, Msg2} -> ... end end

while idiomatic Erlang, is not generally safe in Go with the standard channels if you try to naively replace the receive statements with channel operations, because while you are processing a message, you are not able to receive any others. Should you end up sending a message that is a query to another such process, you'll deadlock on their finishing processing their message. The best case is weird latencies, the worst case is huge chunks of the system will end up deadlocked.

Of course, the correct Go answer is "don't do that", and when writing code from scratch it is a solvable problem. However, it is very easy for Erlang code to have accidentally embedded the asynchronous nature of messages quite deeply into its architecture, especially given the number of ways there are of using "receive" without realizing it. For instance, any gen_server that makes a call to another gen_server is exhibiting this pattern.

If you do not know for sure that the only possible next message for a mailbox is coming from the reply, you need to be sure to use .Receive() with a proper selection function in such cases.

Known Issues

In Erlang, a PID secretly contains more information that identifies something about when the node that originated the PID starts, or something like that, which prevents the "same" PID from being used from two different executions. This is easy to fix, but it isn't fixed yet.

I haven't yet benchmarked this very throughly, but generally speaking it seems to perform comparably to Erlang at worst, and better in a lot of common cases. There are obvious cases that are harder to compare, such as the impact of the Go GC on your runtime. This code does inevitably result in allocations.

Pre-1.0 Possible Changes

  • The NodeID type may be unexported. I'm not sure it's ever directly useful to external code.
  • I'd like to change the logging code to log with JSON objects that happen to render to strings, allowing transparent use with logrus.

Contribution Opportunities

Of course, it's open source. Contribute whatever you like. But in addition to resolving the known issues or the pre-1.0 possible changes, I would certainly be interested in:

  • More test coverage.

  • Adding an element to the Address that includes the node's start-up time or some other element that will be different between each run, so an address can't be obtained from one OS process, then that process dies and another starts in its place, and then someone re-uses that ID. Erlang has this.

  • Under the hood, we use channels for communication. The initial design did not. If a user is willing to commit to saying "the users of this mailbox only ever process the messages in order, so we do not need ReceiveNext", would it be possible to have what is probably a separate type of mailbox that can safely expose a channel?

    (Note it will never be possible to send messages directly to a channel. Go channels do not work over networks. It would be no different than you starting up a goroutine on your own that accepts messages and sends them somewhere. The previous paragraph proposes something that should not need a new goroutine spun up by Reign. (I want reign to spin up a relatively constant number of goroutines, O(nodes) in the worst case. O(mailboxes) is unacceptably expensive and impactful.)

  • Dynamically modifying the cluster at run time. Conceptually it's not too hard, "just work", but there's some i's to dot and t's to cross. I have some questions around the UI for this.

  • Is it possible to efficiently avoid the need to register message types? I'm vaguely nervous about looking at the gob error message and trying to infer whether I should call "register", or similar things, but it would make this easier to use.

  • Pluggable serialization types. Again, conceptually not that difficult, "just work".

  • Any additional similar Erlang-like services that are not covered by other packages, that are useful for Erlang code porting. e.g., I'm not really that interested in an Mnesia-like service for reign, because there are so many good database choices nowadays. (If you want, you can create such a thing, but make it a separate project. I'll happily link it.)

  • A coworker was doing some experimentation on having the mailboxes use channels instead of sync.Cond. It is not clear to either of whether this really clarified the code, or whether it would offer any performance advantages. (Unfortunately, it still wouldn't allow you to put a mailbox in a select statement. Mailbox semantics are fundamentally incompatible with select, as they are fundamentally asynchronous.) It certainly added a lot of lines of code.

  • Additional debugging capabilities. It would be interesting to split the private mailbox stuff off into a "reign/mailbox" package that made a lot more of the internals public for debugging, and then have the current mailbox.go offer the same external interface, but nicely "sealed". Using reign/mailbox would be something you might not want to do in production. But I've found it very useful over the years in Erlang to be able to peek into the mailboxes for diagnostics.

    And there's other capabilities that may need to be exposed, or need to better documented, or something. Another interesting idea would be to add an optional interface like type TracedMsg { ReignSent(); ReignReceived() } that can be called for an implementing type whenever a message is sent, received, transferred across a cluster, etc.

    Erlang's got certain advantages that barring the adoption of a standardized dynamic embedded language for Go, we can't really offer. But we can offer what we can!

  • On that note, any sort of integration with any REPL for Go would be accepted. I would hope it would take the form of implementing certain interfaces or something and not deep integration into the data types, though.

Release Notes

reign uses semantic versioning. As reign has not yet hit 1.0, note that API changes may still occur without a major version release.

  • 0.9.2
    • A somewhat experimental re-write of the internals to use channels instead of Conditions under the hood. This has a few API changes, to include the use of Contexts now.
  • 0.9.1
    • Initial release.

Documentation

Overview

Package reign implements the Erlang-like clustering support.

If you are coming in directly via the godoc, please see the README.md for this package for more information about what this is. The godoc is being used here only for API-type documentation.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrIllegalAddressFormat is returned when something attempts to
	// unmarshal an illegal text or binary string into an Address.
	ErrIllegalAddressFormat = errors.New("illegally-formatted address")

	// ErrIllegalNilSlice is returned when UnmarshalText is called with a nil byte slice.
	ErrIllegalNilSlice = errors.New("cannot unmarshal nil slice into an address")

	// ErrMailboxClosed is returned when the target mailbox has (already) been closed.
	ErrMailboxClosed = errors.New("mailbox has been closed")

	// ErrNotLocalMailbox is returned when a remote mailbox's MailboxID is passed
	// into a function that only works on local mailboxes.
	ErrNotLocalMailbox = errors.New("function required a local mailbox ID but this is a remote MailboxID")
)
View Source
var (
	// DefaultPingInterval determines the minimum interval between PING messages.
	DefaultPingInterval = time.Second * 30

	// MaxSequentialPingFailures is the maximum number of sequential ping failures
	// tolerable before the pinger panics, triggering a service restart if running
	// under suture.
	MaxSequentialPingFailures uint8 = 5
)
View Source
var DeadlineInterval = time.Minute * 5

DeadlineInterval determine how long to keep the network connection open after a successful read. The net.Conn deadline value will be reset to time.Now().Add(DeadlineInterval) upon each successful message read over the network. Defaults to 5 minutes.

View Source
var ErrCantGloballyRegister = errors.New("can't globally register this address")

ErrCantGloballyRegister is returned when you are trying to register an address with the registry that can not be so registered. Only local mailboxes created with New() can be registered with the registry.

View Source
var ErrFailOnClusterHandshake = errors.New("Failing on ssl handshake, as instructed")

ErrFailOnClusterHandshake is the error returned when a node connector's failOnClusterHandshake property is true.

View Source
var ErrNoAddressRegistered = errors.New("no address is registered with that name")

ErrNoAddressRegistered is returned when there are no addresses at the given name.

View Source
var NullLogger = nullLogger{}

NullLogger implements ClusterLogger, and throws all logging messages away.

View Source
var StdLogger = stdLogger{}

StdLogger is a ClusterLogger that will use the log.Output function from the standard logging package.

Functions

func CreateFromReader

func CreateFromReader(r io.Reader, thisNode NodeID, log ClusterLogger) (ConnectionService, Names, error)

CreateFromReader creates a cluster based on the io.Reader of your choice.

func CreateFromSpec

func CreateFromSpec(spec *ClusterSpec, thisNode NodeID, log ClusterLogger) (ConnectionService, Names, error)

CreateFromSpec creates a cluster directly from a *ClusterSpec, the ultimate in control.

func CreateFromSpecFile

func CreateFromSpecFile(clusterSpecLocation string, thisNode NodeID, log ClusterLogger) (ConnectionService, Names, error)

CreateFromSpecFile is the most automated way of creating a cluster, using the command-line parameter "clusterspec" to specify the location of the cluster specification .json file, and creating a cluster from there.

Once created, you still need to call .Serve()

Note *Cluster conforms to the suture.Service interface.

nil may be passed as the ClusterLogger, in which case the standard log.Printf will be used.

func NoClustering

func NoClustering(log ClusterLogger) (ConnectionService, Names)

NoClustering is called to say you have no interest in clustering.

This configures reign to work in a no-clustering state. You can use all mailbox functionality, and there will be no network activity or configuration required.

func RegisterType

func RegisterType(value interface{})

RegisterType registers a type to be sent across the cluster.

This wraps gob.Register, in case we ever change the encoding method.

Types

type Address

type Address struct {
	// contains filtered or unexported fields
}

An Address is the public face of the Mailbox. It is fine to pass this by value.

WARNING: It is not safe to use either Address or *Address for equality testing or as a key in maps! Use .GetID() to obtain a MailboxID, which is. (Both Address and *Address are fine to store as values.)

func (*Address) GetID

func (a *Address) GetID() MailboxID

GetID returns the MailboxID of the Address

func (*Address) MarshalBinary

func (a *Address) MarshalBinary() ([]byte, error)

MarshalBinary implements binary marshalling for Addresses.

A marshalled Address only carries its identifier. When unmarshalled on the same node, the unmarshalled address will be reconnected to the original Mailbox. If unmarshalled on a different node, a reference to the remote mailbox will be unmarshaled.

func (*Address) MarshalJSON added in v0.9.1

func (a *Address) MarshalJSON() ([]byte, error)

MarshalJSON implements JSON marshalling for Addresses.

func (*Address) MarshalText

func (a *Address) MarshalText() ([]byte, error)

MarshalText implements text marshalling for Addresses.

See MarshalBinary.

func (*Address) OnCloseNotify added in v0.9.1

func (a *Address) OnCloseNotify(addr *Address)

OnCloseNotify requests that the target address receive a close notice when the target address is closed.

This is like linking in Erlang, and is intended to provide the same guarantees.

While addresses and goroutines are not technically bound together, it is convenient to think of an address "belonging" to a goroutine. From that point of view, note the caller is the *argument*, not the object. Calls look like:

otherAddress.OnCloseNotify(myAddress)

Read that as something like, "You over there, upon your closing notify (me)."

Calling this more than once with the same address may or may not cause multiple notifications to occur.

func (*Address) RemoveNotify added in v0.9.1

func (a *Address) RemoveNotify(addr *Address)

RemoveNotify will remove the notification request from the Address you call this on.

This does not guarantee that you will not receive a closed notification from the Address, due to (inherent) race conditions.

func (*Address) Send

func (a *Address) Send(m interface{}) error

Send something to the mailbox corresponding to this address.

All concrete types that you wish to send across the cluster must have .Register called on them. See the documentation on gob.Register for the reason why. (The local .RegisterType abstracts our dependency on gob. If you don't register through reign's .RegisterType, future versions of this package may require you to fix that.)

The error is primarily for internal purposes. If the mailbox is local, and has been closed, ErrMailboxClosed will be returned.

An error guarantees failure, but lack of error does not guarantee success! Arguably, "ErrMailboxClosed" should be seen as a purely internal detail, and just like in Erlang, if you want a guarantee you must implement an acknowledgement. However, just like in Erlang, we leak this internal detail a bit. I don't know if that's a good idea; use with caution. (See: erlang:is_process_alive, which similarly leaks out whether the process is local or not.)

func (*Address) String

func (a *Address) String() string

func (*Address) UnmarshalBinary

func (a *Address) UnmarshalBinary(b []byte) error

UnmarshalBinary implements binary unmarshalling for Addresses.

func (*Address) UnmarshalFromID

func (a *Address) UnmarshalFromID(mID MailboxID)

UnmarshalFromID allows you to obtain a legal address from an AddressID. Use as:

var addr reign.Address
err := addr.UnmarshalFromID(addressID)
if err == nil {
    addr.Send(...)
}

func (*Address) UnmarshalJSON added in v0.9.1

func (a *Address) UnmarshalJSON(b []byte) error

UnmarshalJSON implements JSON unmarshalling for Addresses.

func (*Address) UnmarshalText

func (a *Address) UnmarshalText(b []byte) error

UnmarshalText implements text unmarshalling for Addresses.

type Cluster

type Cluster struct {
	Nodes map[NodeID]*NodeDefinition

	ThisNode *NodeDefinition

	// Populate this with the desired protocols you may want from
	// the crypto/tls constants list. By default, this library uses
	// TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 if you don't specify.
	PermittedProtocols []uint16

	// The root signing certificate used by the entire cluster.
	ClusterCertificate *x509.Certificate

	// The CertPool containing that certificate
	RootCAs *x509.CertPool

	// This node's certificate
	Certificate tls.Certificate

	ClusterLogger
	// contains filtered or unexported fields
}

A Cluster describes a cluster.

func (*Cluster) AddConnectionStatusCallback

func (c *Cluster) AddConnectionStatusCallback(f func(NodeID, bool))

AddConnectionStatusCallback allows you to register a callback to be called when a node becomes connected or disconnected from this node. Note that while the connection callback is reliable, the "disconnected" callback is not, simply due to the nature of networks. The boolean parameter is true if this is a connection event, and false if this is a disconnection event.

This function should not block, or should be guaranteed to run very quickly, as you'll be blocking the reconnection attempt itself if the function is slow. This function is also guaranteed to be called within the same process for a given node, thus, it is safe on a per-node basis to send a message via a mailbox; they're guaranteed to be in order on a per-node basis.

While there is certain advanced functionality that can only be implemented via this functionality, bear in mind that the clustering system is already able to tell you when you become disconnected from a remote Address, via the general NotifyAddressOnTerminate. In general, if you can get away with using NotifyAddressOnTerminate on an address, rather than this, you'll probably be happier.

For example, it is not worthwhile to try to maintain a local dictionary of "connectedness" to Nodes with this callback, then try to check if a Node is connected before doing something. This is a classic error. Better to just do it and try to pick up the pieces.

(This is, internally, the mechanism used to implement that functionality, and it seems reasonable to expose this.)

There is no way to remove a callback once added.

Future note: The true semantic meaning of this callback is "we have given up on this node's connection for now and are dropping messages on the floor". At the moment, this fires the instant the TCP connection drops. Should reign ever change that behavior, this callback will only be fired when we finally give up. "Giving up" will be defined as ceasing to buffer messages to be sent, and dropping all future messages on the floor.

type ClusterLogger

type ClusterLogger interface {
	Error(...interface{})
	Errorf(format string, args ...interface{})
	Warn(...interface{})
	Warnf(format string, args ...interface{})
	Info(...interface{})
	Infof(format string, args ...interface{})
	Trace(...interface{})
	Tracef(format string, args ...interface{})
}

A ClusterLogger is the logging interface used by the Cluster system.

The clustering system uses Info for situations that are not problems. This includes:

  • Address resolution progress of remote cluster nodes. (Common DNS problems or misconfigurations can cause excessive times for resolution. This should give enough visibility into the resolution process to rapidly identify the problem.)

The clustering system uses Warn for situations that are problematic and you need to know about them, but are generally "expected" and may resolve themselves without any direction action. (That is, in general, losing network connections is "bad", but also perfectly normal and expected.) The clustering system uses Warn for:

  • Connections established and lost to the other nodes
  • Attempts to update the cluster configuration that fail due to invalid configuration

The clustering system uses Error for situations that prevent connection to some target node, and will most likely not resolve themselves without active human intervention. The clustering system will user Error for:

  • Handshake with foreign node failed due to:
  • Remote said they had a different NodeID than I expected.
  • Incompatible clustering version.
  • Failed SSL handshake.

The goal is that all Errors are things that should fire alarming systems, and all things that should fire alarming systems are Errors.

You can wrap a standard *log.Logger with the provided WrapLogger.

func WrapLogger

func WrapLogger(l *log.Logger) ClusterLogger

WrapLogger takes as standard *log.Logger and returns a ClusterLogger that uses that logger.

type ClusterSpec

type ClusterSpec struct {
	Nodes []*NodeDefinition `json:"nodes"`

	PermittedProtocols []string `json:"permitted_protocols,omit_empty"`

	// To specify the path for the node's cert, set either both of
	// NodeKeyPath and NodeCertPath to load from disk, or
	// NodeKeyPEM and NodeCertPEM to load the certs from some other source.
	//
	// The paths may use %d as a placeholder, to fill in the node ID.
	NodeKeyPath  string `json:"node_key_path,omitempty"`
	NodeCertPath string `json:"node_cert_path,omitempty"`
	NodeKeyPEM   string `json:"node_key_pem,omitempty"`
	NodeCertPEM  string `json:"node_cert_pem,omitempty"`

	// And to specify the path for the cluster's cert, set either
	// ClusterCertPath to load it from disk, or ClusterKeyPEM to load
	// it from source.
	//
	// Note you SHOULD NOT distribute the cluster's private keys to all
	// the nodes.
	ClusterCertPath string `json:"cluster_cert_path,omitempty"`
	ClusterCertPEM  string `json:"cluster_cert_pem,omitempty"`
}

ClusterSpec defines how to create a cluster. The primary purpose of this data type is to define the JSON serialization via the standard Go encoding/json serialization.

Note that Nodes should use string representations of the numbers 0-255 to specify the NodeID as the key to "nodes". (encoding/json does not permit anything except strings as keys for the map.)

type ConnectionService

type ConnectionService interface {
	NewMailbox() (*Address, *Mailbox)
	Terminate()

	// Inherited from suture.Service
	Serve()
	Stop()

	// Inherited from reign.Cluster
	AddConnectionStatusCallback(f func(NodeID, bool))
}

ConnectionService provides an interface to the reign connectionServer and registry objects. It inherits from suture.Service and reign.Cluster.

type Mailbox

type Mailbox struct {
	// contains filtered or unexported fields
}

A Mailbox is what you receive messages from via Receive or ReceiveNext.

func (*Mailbox) Close added in v0.9.1

func (m *Mailbox) Close()

Close shuts down a given mailbox. Once closed, a mailbox will reject messages without even looking at them, and can no longer have any Receive used on them.

Further, it will notify any registered Addresses that it has been closed.

This facility is used analogously to Erlang's "link" functionality. Of course in Go you can't be notified when a goroutine terminates, but if you defer mailbox.Close() in the proper place for your mailbox user, you can get most of the way there.

It is not an error to Close an already-Closed mailbox.

func (*Mailbox) MessageCount added in v0.9.1

func (m *Mailbox) MessageCount() int

MessageCount returns the number of messages in the mailbox.

0 is always returned if the mailbox is terminated.

func (*Mailbox) Receive

func (m *Mailbox) Receive(ctx context.Context) (interface{}, error)

Receive will receive the next message sent to this mailbox. It blocks until the next message comes in, which may be forever. If the mailbox is closed, it will receive a MailboxClosed reply.

If you've got multiple receivers on a single mailbox, be sure to check for MailboxClosed.

func (*Mailbox) ReceiveAsync added in v0.9.1

func (m *Mailbox) ReceiveAsync() (interface{}, bool)

ReceiveAsync will return immediately with (obj, true) if, and only if, there was a message in the inbox, or else (nil, false). Works the same way as Receive otherwise.

func (*Mailbox) ReceiveMatch added in v0.9.1

func (m *Mailbox) ReceiveMatch(ctx context.Context, matcher func(interface{}) bool) (interface{}, error)

ReceiveMatch will receive the next message sent to this mailbox that matches according to the passed-in function.

ReceiveMatch assumes that it is the only function running against the Mailbox. If you ReceiveMatch from multiple goroutines, or ReceiveMatch in one and ReceiveNext in another, you *will* miss messages in the routine calling ReceiveMatch.

I recommend that your matcher function be:

func (i) bool {
    _, ok = i.(SomeType)

    return ok
}

If the mailbox gets closed, this will return a MailboxClosed, regardless of the behavior of the matcher.

type MailboxClosed added in v0.9.1

type MailboxClosed MailboxID

MailboxClosed is sent to Addresses that request notification of when a Mailbox is being closed, with OnCloseNotify. If you request close notification of multiple mailboxes, this can be converted to an MailboxID which can be used to distinguish them.

type MailboxID

type MailboxID uint64

MailboxID is an identifier corresponding to a mailbox.

func (MailboxID) NodeID

func (mID MailboxID) NodeID() NodeID

NodeID returns the node ID corresponding to the current mailbox ID.

type MultipleClaim

type MultipleClaim struct {
	Name string
}

MultipleClaim messages are send to name claimers in the event that there are multiple name claims to a given name. Note that the claimants array is a static snapshot of the claimants at the time of conflict, and that the "current" situation (to the extent that is definable) may change at any time.

type Names

type Names interface {
	GetDebugger() NamesDebugger
	Lookup(string) *Address
	LookupAll(string) []*Address
	MessageCount() int
	MultipleClaimCount() int32
	Register(string, *Address) error
	SeenNames(...string) []bool
	Sync()
	Unregister(string, *Address)
}

Names exposes some functionality of registry

Lookup looks up a given name and returns a mailbox that can be used to send messages and request termination notifications.

Be sure to consult the documentation about the Registry in the documentation section above; use of this address could in some circumstances result in the message being delivered to more than one Mailbox.

In particular, this function does no checking as to whether the address exists, as that information is intrinsically racy anyhow. If you want to take extra care about it, use NotifyAddressOnTerminate, just as with local addresses.

Register claims the given global name in the registry. It can then be accessed and manipulated via Lookup.

This does not happen synchronously, as there seems to be no reason for the caller to synchronously wait for this.

A registered mailbox should stand ready to receive MultipleClaim messages from the cluster.

The passed-in Address must be something that directly came from a New() call. Addressed obtained from the network or from Lookup itself will return an error instead of registering.

Unregister removes the given claim from a given global name. Unregistration will only occur if the current registrant matches the address passed in. It is not an error for it not to match; the call will simply be ignored.

On a given node, only one Address can have a claim on a name. If you wish to supercede a claim with a new address, you can simply register the new claim, and it will overwrite the previous one.

If the address passed in is not the current registrant, the call is ignored, thus it is safe to call this.

type NamesDebugger

type NamesDebugger interface {
	AddressCount() uint
	AllNames() []string
	DumpClaims() map[string][]MailboxID
	DumpJSON() string
	MessageCount() int
	MultipleClaimCount() int32
	SeenNames(...string) []bool
}

NamesDebugger is an interface over the registry struct. These functions acquire locks and are not supposed to be called in a production setting.

type NodeDefinition

type NodeDefinition struct {
	ID            NodeID `json:"id"`
	Address       string `json:"address"`
	ListenAddress string `json:"listen_address,omit_empty"`
	LocalAddress  string `json:"local_address,omit_empty"`
	// contains filtered or unexported fields
}

A NodeDefinition gives information about the node in question. This is primarily used to create a static JSON file that represents the node, using the standard encoding/json to produce this structure.

The Address is the IP address and port (separated by colon) that the other nodes will use to talk to this cluster node. This is the only required field.

The ListenAddress is what the cluster will actually bind to. If this is the same as the address, you may leave it unspecified. This is for cases where due to network routing, load balancers, proxies, etc. the address the rest of the cluster uses to connect is not the same as the internal bind address. In simple cases, leave this blank.

The LocalAddress is the address to use for the outgoing connections to the cluster. If blank, net.DialTCP will be passed nil for the laddr. In simple cases, leave this blank.

type NodeID

type NodeID byte

NodeID is used to identify the current node's ID.

This type may be unexported in later versions of reign.

Directories

Path Synopsis
cmd
reign_init
Executable reign_init can be used to quickly set up a certificate authority and usable certificates for reign.
Executable reign_init can be used to quickly set up a certificate authority and usable certificates for reign.
reign_sample
Executable reign_sample contains a simple demonstration of using the reign library to run a cluster of nodes.
Executable reign_sample contains a simple demonstration of using the reign library to run a cluster of nodes.
Package internal segments off things that must be public for serialization but have no place in the main documentation.
Package internal segments off things that must be public for serialization but have no place in the main documentation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL