@@ -24,25 +24,32 @@ import (
24
24
// of PubSub's methods safe to access concurrently. PubSub should be
25
25
// constructed with New().
26
26
type PubSub struct {
27
- mu rlocker
28
- n * node.Node
29
- rand func (n int64 ) int64
27
+ mu rlocker
28
+ n * node.Node
29
+ rand func (n int64 ) int64
30
+ deterministicRoutingHasher func (interface {}) uint64
30
31
}
31
32
32
33
// New constructs a new PubSub.
33
34
func New (opts ... PubSubOption ) * PubSub {
34
- p := & PubSub {
35
+ s := & PubSub {
35
36
mu : & sync.RWMutex {},
36
37
rand : rand .Int63n ,
37
38
}
38
39
39
40
for _ , o := range opts {
40
- o .configure (p )
41
+ o .configure (s )
41
42
}
42
43
43
- p .n = node .New (p .rand )
44
+ if s .deterministicRoutingHasher == nil {
45
+ s .deterministicRoutingHasher = func (_ interface {}) uint64 {
46
+ return uint64 (s .rand (0x7FFFFFFFFFFFFFFF ))
47
+ }
48
+ }
44
49
45
- return p
50
+ s .n = node .New (s .rand )
51
+
52
+ return s
46
53
}
47
54
48
55
// PubSubOption is used to configure a PubSub.
@@ -52,25 +59,34 @@ type PubSubOption interface {
52
59
53
60
type pubsubConfigFunc func (* PubSub )
54
61
55
- func (f pubsubConfigFunc ) configure (p * PubSub ) {
56
- f (p )
62
+ func (f pubsubConfigFunc ) configure (s * PubSub ) {
63
+ f (s )
57
64
}
58
65
59
66
// WithNoMutex configures a PubSub that does not have any internal mutexes.
60
67
// This is useful if more complex or custom locking is required. For example,
61
68
// if a subscription needs to subscribe while being published to.
62
69
func WithNoMutex () PubSubOption {
63
- return pubsubConfigFunc (func (p * PubSub ) {
64
- p .mu = nopLock {}
70
+ return pubsubConfigFunc (func (s * PubSub ) {
71
+ s .mu = nopLock {}
65
72
})
66
73
}
67
74
68
75
// WithRand configures a PubSub that will use the given function to make
69
76
// sharding decisions. The given function has to match the symantics of
70
77
// math/rand.Int63n.
71
78
func WithRand (int63 func (max int64 ) int64 ) PubSubOption {
72
- return pubsubConfigFunc (func (p * PubSub ) {
73
- p .rand = int63
79
+ return pubsubConfigFunc (func (s * PubSub ) {
80
+ s .rand = int63
81
+ })
82
+ }
83
+
84
+ // WithDeterministicHashing configures a PubSub that will use the given
85
+ // function to hash each published data point. The hash is used only for a
86
+ // subscription that has set its deterministic routing name.
87
+ func WithDeterministicHashing (hashFunction func (interface {}) uint64 ) PubSubOption {
88
+ return pubsubConfigFunc (func (s * PubSub ) {
89
+ s .deterministicRoutingHasher = hashFunction
74
90
})
75
91
}
76
92
@@ -106,9 +122,19 @@ func WithPath(path []uint64) SubscribeOption {
106
122
})
107
123
}
108
124
125
+ // WithDeterministicRouting configures a subscription to have a deterministic
126
+ // routing name. A PubSub configured to use deterministic hashing will use
127
+ // this name and the subscription's shard ID to maintain consistent routing.
128
+ func WithDeterministicRouting (name string ) SubscribeOption {
129
+ return subscribeConfigFunc (func (c * subscribeConfig ) {
130
+ c .deterministicRoutingName = name
131
+ })
132
+ }
133
+
109
134
type subscribeConfig struct {
110
- shardID string
111
- path []uint64
135
+ shardID string
136
+ deterministicRoutingName string
137
+ path []uint64
112
138
}
113
139
114
140
type subscribeConfigFunc func (* subscribeConfig )
@@ -133,7 +159,7 @@ func (s *PubSub) Subscribe(sub Subscription, opts ...SubscribeOption) Unsubscrib
133
159
for _ , p := range c .path {
134
160
n = n .AddChild (p )
135
161
}
136
- id := n .AddSubscription (sub , c .shardID )
162
+ id := n .AddSubscription (sub , c .shardID , c . deterministicRoutingName )
137
163
138
164
return func () {
139
165
s .mu .Lock ()
@@ -261,15 +287,15 @@ func (s *PubSub) traversePublish(d, next interface{}, a TreeTraverser, n *node.N
261
287
if n == nil {
262
288
return
263
289
}
264
- n .ForEachSubscription (func (shardID string , ss []node.SubscriptionEnvelope ) {
290
+ n .ForEachSubscription (func (shardID string , isDeterministic bool , ss []node.SubscriptionEnvelope ) {
265
291
if shardID == "" {
266
292
for _ , x := range ss {
267
293
x .Subscription (d )
268
294
}
269
295
return
270
296
}
271
297
272
- idx := s .rand ( int64 ( len (ss )) )
298
+ idx := s .determineIdx ( d , len (ss ), isDeterministic )
273
299
ss [idx ].Subscription (d )
274
300
})
275
301
@@ -291,6 +317,13 @@ func (s *PubSub) traversePublish(d, next interface{}, a TreeTraverser, n *node.N
291
317
}
292
318
}
293
319
320
+ func (s * PubSub ) determineIdx (d interface {}, l int , isDeterministic bool ) int64 {
321
+ if isDeterministic {
322
+ return int64 (s .deterministicRoutingHasher (d ) % uint64 (l ))
323
+ }
324
+ return s .rand (int64 (l ))
325
+ }
326
+
294
327
// rlocker is used to hold either a real sync.RWMutex or a nop lock.
295
328
// This is used to turn off locking.
296
329
type rlocker interface {
0 commit comments