dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/client/pubsub/pubsub.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * 20 * Copyright 2021 gRPC authors. 21 * 22 */ 23 24 // Package pubsub implements a utility type to maintain resource watchers and 25 // the updates. 26 // 27 // This package is designed to work with the xds resources. It could be made a 28 // general system that works with all types. 29 package pubsub 30 31 import ( 32 "sync" 33 "time" 34 ) 35 36 import ( 37 dubbogoLogger "github.com/dubbogo/gost/log/logger" 38 ) 39 40 import ( 41 "dubbo.apache.org/dubbo-go/v3/xds/client/resource" 42 "dubbo.apache.org/dubbo-go/v3/xds/utils/buffer" 43 "dubbo.apache.org/dubbo-go/v3/xds/utils/grpcsync" 44 ) 45 46 // Pubsub maintains resource watchers and resource updates. 47 // 48 // There can be multiple watchers for the same resource. An update to a resource 49 // triggers updates to all the existing watchers. Watchers can be canceled at 50 // any time. 51 type Pubsub struct { 52 done *grpcsync.Event 53 logger dubbogoLogger.Logger 54 watchExpiryTimeout time.Duration 55 56 updateCh *buffer.Unbounded // chan *watcherInfoWithUpdate 57 // All the following maps are to keep the updates/metadata in a cache. 58 mu sync.Mutex 59 ldsWatchers map[string]map[*watchInfo]bool 60 ldsCache map[string]resource.ListenerUpdate 61 ldsMD map[string]resource.UpdateMetadata 62 rdsWatchers map[string]map[*watchInfo]bool 63 rdsCache map[string]resource.RouteConfigUpdate 64 rdsMD map[string]resource.UpdateMetadata 65 cdsWatchers map[string]map[*watchInfo]bool 66 cdsCache map[string]resource.ClusterUpdate 67 cdsMD map[string]resource.UpdateMetadata 68 edsWatchers map[string]map[*watchInfo]bool 69 edsCache map[string]resource.EndpointsUpdate 70 edsMD map[string]resource.UpdateMetadata 71 } 72 73 // New creates a new Pubsub. 74 func New(watchExpiryTimeout time.Duration, logger dubbogoLogger.Logger) *Pubsub { 75 pb := &Pubsub{ 76 done: grpcsync.NewEvent(), 77 logger: logger, 78 watchExpiryTimeout: watchExpiryTimeout, 79 80 updateCh: buffer.NewUnbounded(), 81 ldsWatchers: make(map[string]map[*watchInfo]bool), 82 ldsCache: make(map[string]resource.ListenerUpdate), 83 ldsMD: make(map[string]resource.UpdateMetadata), 84 rdsWatchers: make(map[string]map[*watchInfo]bool), 85 rdsCache: make(map[string]resource.RouteConfigUpdate), 86 rdsMD: make(map[string]resource.UpdateMetadata), 87 cdsWatchers: make(map[string]map[*watchInfo]bool), 88 cdsCache: make(map[string]resource.ClusterUpdate), 89 cdsMD: make(map[string]resource.UpdateMetadata), 90 edsWatchers: make(map[string]map[*watchInfo]bool), 91 edsCache: make(map[string]resource.EndpointsUpdate), 92 edsMD: make(map[string]resource.UpdateMetadata), 93 } 94 go pb.run() 95 return pb 96 } 97 98 // WatchListener registers a watcher for the LDS resource. 99 // 100 // It also returns whether this is the first watch for this resource. 101 func (pb *Pubsub) WatchListener(serviceName string, cb func(resource.ListenerUpdate, error)) (first bool, cancel func() bool) { 102 wi := &watchInfo{ 103 c: pb, 104 rType: resource.ListenerResource, 105 target: serviceName, 106 ldsCallback: cb, 107 } 108 109 wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() { 110 wi.timeout() 111 }) 112 return pb.watch(wi) 113 } 114 115 // WatchRouteConfig register a watcher for the RDS resource. 116 // 117 // It also returns whether this is the first watch for this resource. 118 func (pb *Pubsub) WatchRouteConfig(routeName string, cb func(resource.RouteConfigUpdate, error)) (first bool, cancel func() bool) { 119 wi := &watchInfo{ 120 c: pb, 121 rType: resource.RouteConfigResource, 122 target: routeName, 123 rdsCallback: cb, 124 } 125 126 wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() { 127 wi.timeout() 128 }) 129 return pb.watch(wi) 130 } 131 132 // WatchCluster register a watcher for the CDS resource. 133 // 134 // It also returns whether this is the first watch for this resource. 135 func (pb *Pubsub) WatchCluster(clusterName string, cb func(resource.ClusterUpdate, error)) (first bool, cancel func() bool) { 136 wi := &watchInfo{ 137 c: pb, 138 rType: resource.ClusterResource, 139 target: clusterName, 140 cdsCallback: cb, 141 } 142 143 wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() { 144 wi.timeout() 145 }) 146 return pb.watch(wi) 147 } 148 149 // WatchEndpoints registers a watcher for the EDS resource. 150 // 151 // It also returns whether this is the first watch for this resource. 152 func (pb *Pubsub) WatchEndpoints(clusterName string, cb func(resource.EndpointsUpdate, error)) (first bool, cancel func() bool) { 153 wi := &watchInfo{ 154 c: pb, 155 rType: resource.EndpointsResource, 156 target: clusterName, 157 edsCallback: cb, 158 } 159 160 wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() { 161 wi.timeout() 162 }) 163 return pb.watch(wi) 164 } 165 166 // Close closes the pubsub. 167 func (pb *Pubsub) Close() { 168 if pb.done.HasFired() { 169 return 170 } 171 pb.done.Fire() 172 } 173 174 // run is a goroutine for all the callbacks. 175 // 176 // Callback can be called in watch(), if an item is found in cache. Without this 177 // goroutine, the callback will be called inline, which might cause a deadlock 178 // in user's code. Callbacks also cannot be simple `go callback()` because the 179 // order matters. 180 func (pb *Pubsub) run() { 181 for { 182 select { 183 case t := <-pb.updateCh.Get(): 184 pb.updateCh.Load() 185 if pb.done.HasFired() { 186 return 187 } 188 pb.callCallback(t.(*watcherInfoWithUpdate)) 189 case <-pb.done.Done(): 190 return 191 } 192 } 193 }