func (d *Discovery) Join() (int, error) {
	if d.serviceDiscovery != nil {
		peers, err := d.serviceDiscovery.DiscoverPeers()
		if err != nil {
			return 0, err
		}
		return d.memberlist.Join(peers)
	}
	return d.memberlist.Join(d.config.Peers)
}
Join is used to take an existing Memberlist and attempt to Join a cluster
by contacting all the given hosts and performing a state sync. Initially,
the Memberlist only contains our own state, so doing this will cause remote
nodes to become aware of the existence of this node, effectively joining the cluster.
package discovery
import (
	"context"
	"errors"
	"fmt"
	"net"
	"plugin"
	"sort"
	"strconv"
	"sync"
	"time"
	"github.com/buraksezer/olric/config"
	"github.com/buraksezer/olric/internal/stats"
	"github.com/buraksezer/olric/pkg/flog"
	"github.com/buraksezer/olric/pkg/service_discovery"
	"github.com/hashicorp/memberlist"
)
const eventChanCapacity = 256
var UptimeSeconds = stats.NewInt64Counter()
var ErrMemberNotFound = errors.New("member not found")
type ClusterEvent struct {
	Event    memberlist.NodeEventType
	NodeName string
	NodeAddr net.IP
	NodePort uint16
	NodeMeta []byte  
}
func (c *ClusterEvent) MemberAddr() string {
	port := strconv.Itoa(int(c.NodePort))
	return net.JoinHostPort(c.NodeAddr.String(), port)
}
type Discovery struct {
	log        *flog.Logger
	member     *Member
	memberlist *memberlist.Memberlist
	config     *config.Config
	clusterEventsMtx sync.RWMutex
	ClusterEvents    chan *ClusterEvent
	eventSubscribers []chan *ClusterEvent
	serviceDiscovery service_discovery.ServiceDiscovery
	wg     sync.WaitGroup
	ctx    context.Context
	cancel context.CancelFunc
}
func New(log *flog.Logger, c *config.Config) *Discovery {
	member := NewMember(c)
	ctx, cancel := context.WithCancel(context.Background())
	d := &Discovery{
		member: &member,
		config: c,
		log:    log,
		ctx:    ctx,
		cancel: cancel,
	}
	return d
}
func (d *Discovery) loadServiceDiscoveryPlugin() error {
	var sd service_discovery.ServiceDiscovery
	if val, ok := d.config.ServiceDiscovery["plugin"]; ok {
		if sd, ok = val.(service_discovery.ServiceDiscovery); !ok {
			return fmt.Errorf("plugin type %T is not a ServiceDiscovery interface", val)
		}
	} else {
		pluginPath, ok := d.config.ServiceDiscovery["path"]
		if !ok {
			return fmt.Errorf("plugin path could not be found")
		}
		plug, err := plugin.Open(pluginPath.(string))
		if err != nil {
			return fmt.Errorf("failed to open plugin: %w", err)
		}
		symDiscovery, err := plug.Lookup("ServiceDiscovery")
		if err != nil {
			return fmt.Errorf("failed to lookup serviceDiscovery symbol: %w", err)
		}
		if sd, ok = symDiscovery.(service_discovery.ServiceDiscovery); !ok {
			return fmt.Errorf("unable to assert type to serviceDiscovery")
		}
	}
	if err := sd.SetConfig(d.config.ServiceDiscovery); err != nil {
		return err
	}
	sd.SetLogger(d.config.Logger)
	if err := sd.Initialize(); err != nil {
		return err
	}
	d.serviceDiscovery = sd
	return nil
}
func (d *Discovery) increaseUptimeSeconds() {
	defer d.wg.Done()
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			UptimeSeconds.Increase(1)
		case <-d.ctx.Done():
			return
		}
	}
}
func (d *Discovery) Start() error {
	if d.config.ServiceDiscovery != nil {
		if err := d.loadServiceDiscoveryPlugin(); err != nil {
			return err
		}
	}
	d.ClusterEvents = d.SubscribeNodeEvents()
	dl, err := d.newDelegate()
	if err != nil {
		return err
	}
	eventsCh := make(chan memberlist.NodeEvent, eventChanCapacity)
	d.config.MemberlistConfig.Delegate = dl
	d.config.MemberlistConfig.Logger = d.config.Logger
	d.config.MemberlistConfig.Events = &memberlist.ChannelEventDelegate{
		Ch: eventsCh,
	}
	list, err := memberlist.Create(d.config.MemberlistConfig)
	if err != nil {
		return err
	}
	d.memberlist = list
	if d.serviceDiscovery != nil {
		if err := d.serviceDiscovery.Register(); err != nil {
			return err
		}
	}
	d.wg.Add(1)
	go d.eventLoop(eventsCh)
	d.wg.Add(1)
	go d.increaseUptimeSeconds()
	return nil
}