@@ -17,15 +17,18 @@ package olric
1717import (
1818 "context"
1919 "encoding/json"
20+ "errors"
2021 "fmt"
21- "github.com/buraksezer/olric/hasher"
2222 "log"
23+ "net"
2324 "os"
2425 "sync"
2526 "sync/atomic"
27+ "syscall"
2628 "time"
2729
2830 "github.com/buraksezer/olric/config"
31+ "github.com/buraksezer/olric/hasher"
2932 "github.com/buraksezer/olric/internal/bufpool"
3033 "github.com/buraksezer/olric/internal/cluster/partitions"
3134 "github.com/buraksezer/olric/internal/discovery"
@@ -69,6 +72,10 @@ func processProtocolError(err error) error {
6972 if err == redis .Nil {
7073 return ErrKeyNotFound
7174 }
75+ if errors .Is (err , syscall .ECONNREFUSED ) {
76+ opErr := err .(* net.OpError )
77+ return fmt .Errorf ("%s %s %s: %w" , opErr .Op , opErr .Net , opErr .Addr , ErrConnRefused )
78+ }
7279 return convertDMapError (protocol .ConvertError (err ))
7380}
7481
@@ -95,10 +102,7 @@ func (dm *ClusterDMap) writePutCommand(c *dmap.PutConfig, key string, value []by
95102 return cmd
96103}
97104
98- func (cl * ClusterClient ) smartPick (dmap , key string ) (* redis.Client , error ) {
99- hkey := partitions .HKey (dmap , key )
100- partID := hkey % cl .partitionCount
101-
105+ func (cl * ClusterClient ) clientByPartID (partID uint64 ) (* redis.Client , error ) {
102106 raw := cl .routingTable .Load ()
103107 if raw == nil {
104108 return nil , fmt .Errorf ("routing table is empty" )
@@ -118,6 +122,12 @@ func (cl *ClusterClient) smartPick(dmap, key string) (*redis.Client, error) {
118122 return cl .client .Get (primaryOwner ), nil
119123}
120124
125+ func (cl * ClusterClient ) smartPick (dmap , key string ) (* redis.Client , error ) {
126+ hkey := partitions .HKey (dmap , key )
127+ partID := hkey % cl .partitionCount
128+ return cl .clientByPartID (partID )
129+ }
130+
121131// Put sets the value for the given key. It overwrites any previous value for
122132// that key, and it's thread-safe. The key has to be a string. value type is arbitrary.
123133// It is safe to modify the contents of the arguments after Put returns but not before.
@@ -150,31 +160,33 @@ func (dm *ClusterDMap) Put(ctx context.Context, key string, value interface{}, o
150160 return processProtocolError (cmd .Err ())
151161}
152162
163+ func (dm * ClusterDMap ) makeGetResponse (cmd * redis.StringCmd ) (* GetResponse , error ) {
164+ raw , err := cmd .Bytes ()
165+ if err != nil {
166+ return nil , processProtocolError (err )
167+ }
168+
169+ e := dm .newEntry ()
170+ e .Decode (raw )
171+ return & GetResponse {
172+ entry : e ,
173+ }, nil
174+ }
175+
153176// Get gets the value for the given key. It returns ErrKeyNotFound if the DB
154177// does not contain the key. It's thread-safe. It is safe to modify the contents
155178// of the returned value. See GetResponse for the details.
156179func (dm * ClusterDMap ) Get (ctx context.Context , key string ) (* GetResponse , error ) {
180+ cmd := protocol .NewGet (dm .name , key ).SetRaw ().Command (ctx )
157181 rc , err := dm .clusterClient .smartPick (dm .name , key )
158182 if err != nil {
159183 return nil , err
160184 }
161-
162- cmd := protocol .NewGet (dm .name , key ).SetRaw ().Command (ctx )
163185 err = rc .Process (ctx , cmd )
164186 if err != nil {
165187 return nil , processProtocolError (err )
166188 }
167-
168- raw , err := cmd .Bytes ()
169- if err != nil {
170- return nil , processProtocolError (err )
171- }
172-
173- e := dm .newEntry ()
174- e .Decode (raw )
175- return & GetResponse {
176- entry : e ,
177- }, nil
189+ return dm .makeGetResponse (cmd )
178190}
179191
180192// Delete deletes values for the given keys. Delete will not return error
@@ -241,7 +253,8 @@ func (dm *ClusterDMap) Decr(ctx context.Context, key string, delta int) (int, er
241253 return int (res ), nil
242254}
243255
244- // GetPut atomically sets the key to value and returns the old value stored at key.
256+ // GetPut atomically sets the key to value and returns the old value stored at key. It returns nil if there is no
257+ // previous value.
245258func (dm * ClusterDMap ) GetPut (ctx context.Context , key string , value interface {}) (* GetResponse , error ) {
246259 rc , err := dm .clusterClient .smartPick (dm .name , key )
247260 if err != nil {
@@ -604,6 +617,43 @@ func (cl *ClusterClient) Members(ctx context.Context) ([]Member, error) {
604617 return members , nil
605618}
606619
620+ // RefreshMetadata fetches a list of available members and the latest routing
621+ // table version. It also closes stale clients, if there are any.
622+ func (cl * ClusterClient ) RefreshMetadata (ctx context.Context ) error {
623+ // Fetch a list of currently available cluster members.
624+ var members []Member
625+ var err error
626+ for {
627+ members , err = cl .Members (ctx )
628+ if errors .Is (err , ErrConnRefused ) {
629+ err = nil
630+ continue
631+ }
632+ if err != nil {
633+ return err
634+ }
635+ break
636+ }
637+ // Use a map for fast access.
638+ addresses := make (map [string ]struct {})
639+ for _ , member := range members {
640+ addresses [member .Name ] = struct {}{}
641+ }
642+
643+ // Clean stale client connections
644+ for addr := range cl .client .Addresses () {
645+ if _ , ok := addresses [addr ]; ! ok {
646+ // Gone
647+ if err := cl .client .Close (addr ); err != nil {
648+ return err
649+ }
650+ }
651+ }
652+
653+ // Re-fetch the routing table, we should use the latest routing table version.
654+ return cl .fetchRoutingTable ()
655+ }
656+
607657// Close stops background routines and frees allocated resources.
608658func (cl * ClusterClient ) Close (ctx context.Context ) error {
609659 select {
@@ -705,7 +755,7 @@ func (cl *ClusterClient) fetchRoutingTablePeriodically() {
705755 case <- ticker .C :
706756 err := cl .fetchRoutingTable ()
707757 if err != nil {
708- cl .logger .Printf ("[ERROR] Failed to fetch the latest routing table: %s" , err )
758+ cl .logger .Printf ("[ERROR] Failed to fetch the latest version of the routing table: %s" , err )
709759 }
710760 }
711761 }
@@ -754,13 +804,24 @@ func NewClusterClient(addresses []string, options ...ClusterClientOption) (*Clus
754804 cl .client .Get (address )
755805 }
756806
807+ // Discover all cluster members
808+ members , err := cl .Members (ctx )
809+ if err != nil {
810+ return nil , fmt .Errorf ("error while discovering the cluster members: %w" , err )
811+ }
812+ for _ , member := range members {
813+ cl .client .Get (member .Name )
814+ }
815+
816+ // Hash function is required to target primary owners instead of random cluster members.
757817 partitions .SetHashFunc (cc .hasher )
758818
759- // Initial fetch.
819+ // Initial fetch. ClusterClient targets the primary owners for a smooth and quick operation.
760820 if err := cl .fetchRoutingTable (); err != nil {
761821 return nil , err
762822 }
763823
824+ // Refresh the routing table in every 15 seconds.
764825 cl .wg .Add (1 )
765826 go cl .fetchRoutingTablePeriodically ()
766827
0 commit comments