You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

conn_pool.go 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package couchbase
  2. import (
  3. "crypto/tls"
  4. "errors"
  5. "sync/atomic"
  6. "time"
  7. "github.com/couchbase/gomemcached"
  8. "github.com/couchbase/gomemcached/client"
  9. "github.com/couchbase/goutils/logging"
  10. )
  11. // GenericMcdAuthHandler is a kind of AuthHandler that performs
  12. // special auth exchange (like non-standard auth, possibly followed by
  13. // select-bucket).
  14. type GenericMcdAuthHandler interface {
  15. AuthHandler
  16. AuthenticateMemcachedConn(host string, conn *memcached.Client) error
  17. }
  18. // Error raised when a connection can't be retrieved from a pool.
  19. var TimeoutError = errors.New("timeout waiting to build connection")
  20. var errClosedPool = errors.New("the connection pool is closed")
  21. var errNoPool = errors.New("no connection pool")
  22. // Default timeout for retrieving a connection from the pool.
  23. var ConnPoolTimeout = time.Hour * 24 * 30
  24. // overflow connection closer cycle time
  25. var ConnCloserInterval = time.Second * 30
  26. // ConnPoolAvailWaitTime is the amount of time to wait for an existing
  27. // connection from the pool before considering the creation of a new
  28. // one.
  29. var ConnPoolAvailWaitTime = time.Millisecond
  30. type connectionPool struct {
  31. host string
  32. mkConn func(host string, ah AuthHandler, tlsConfig *tls.Config, bucketName string) (*memcached.Client, error)
  33. auth AuthHandler
  34. connections chan *memcached.Client
  35. createsem chan bool
  36. bailOut chan bool
  37. poolSize int
  38. connCount uint64
  39. inUse bool
  40. tlsConfig *tls.Config
  41. bucket string
  42. }
  43. func newConnectionPool(host string, ah AuthHandler, closer bool, poolSize, poolOverflow int, tlsConfig *tls.Config, bucket string) *connectionPool {
  44. connSize := poolSize
  45. if closer {
  46. connSize += poolOverflow
  47. }
  48. rv := &connectionPool{
  49. host: host,
  50. connections: make(chan *memcached.Client, connSize),
  51. createsem: make(chan bool, poolSize+poolOverflow),
  52. mkConn: defaultMkConn,
  53. auth: ah,
  54. poolSize: poolSize,
  55. tlsConfig: tlsConfig,
  56. bucket: bucket,
  57. }
  58. if closer {
  59. rv.bailOut = make(chan bool, 1)
  60. go rv.connCloser()
  61. }
  62. return rv
  63. }
  64. // ConnPoolTimeout is notified whenever connections are acquired from a pool.
  65. var ConnPoolCallback func(host string, source string, start time.Time, err error)
  66. // Use regular in-the-clear connection if tlsConfig is nil.
  67. // Use secure connection (TLS) if tlsConfig is set.
  68. func defaultMkConn(host string, ah AuthHandler, tlsConfig *tls.Config, bucketName string) (*memcached.Client, error) {
  69. var features memcached.Features
  70. var conn *memcached.Client
  71. var err error
  72. if tlsConfig == nil {
  73. conn, err = memcached.Connect("tcp", host)
  74. } else {
  75. conn, err = memcached.ConnectTLS("tcp", host, tlsConfig)
  76. }
  77. if err != nil {
  78. return nil, err
  79. }
  80. if TCPKeepalive == true {
  81. conn.SetKeepAliveOptions(time.Duration(TCPKeepaliveInterval) * time.Second)
  82. }
  83. if EnableMutationToken == true {
  84. features = append(features, memcached.FeatureMutationToken)
  85. }
  86. if EnableDataType == true {
  87. features = append(features, memcached.FeatureDataType)
  88. }
  89. if EnableXattr == true {
  90. features = append(features, memcached.FeatureXattr)
  91. }
  92. if EnableCollections {
  93. features = append(features, memcached.FeatureCollections)
  94. }
  95. if len(features) > 0 {
  96. if DefaultTimeout > 0 {
  97. conn.SetDeadline(getDeadline(noDeadline, DefaultTimeout))
  98. }
  99. res, err := conn.EnableFeatures(features)
  100. if DefaultTimeout > 0 {
  101. conn.SetDeadline(noDeadline)
  102. }
  103. if err != nil && isTimeoutError(err) {
  104. conn.Close()
  105. return nil, err
  106. }
  107. if err != nil || res.Status != gomemcached.SUCCESS {
  108. logging.Warnf("Unable to enable features %v", err)
  109. }
  110. }
  111. if gah, ok := ah.(GenericMcdAuthHandler); ok {
  112. err = gah.AuthenticateMemcachedConn(host, conn)
  113. if err != nil {
  114. conn.Close()
  115. return nil, err
  116. }
  117. return conn, nil
  118. }
  119. name, pass, bucket := ah.GetCredentials()
  120. if bucket == "" {
  121. // Authenticator does not know specific bucket.
  122. bucket = bucketName
  123. }
  124. if name != "default" {
  125. _, err = conn.Auth(name, pass)
  126. if err != nil {
  127. conn.Close()
  128. return nil, err
  129. }
  130. // Select bucket (Required for cb_auth creds)
  131. // Required when doing auth with _admin credentials
  132. if bucket != "" && bucket != name {
  133. _, err = conn.SelectBucket(bucket)
  134. if err != nil {
  135. conn.Close()
  136. return nil, err
  137. }
  138. }
  139. }
  140. return conn, nil
  141. }
  142. func (cp *connectionPool) Close() (err error) {
  143. defer func() {
  144. if recover() != nil {
  145. err = errors.New("connectionPool.Close error")
  146. }
  147. }()
  148. if cp.bailOut != nil {
  149. // defensively, we won't wait if the channel is full
  150. select {
  151. case cp.bailOut <- false:
  152. default:
  153. }
  154. }
  155. close(cp.connections)
  156. for c := range cp.connections {
  157. c.Close()
  158. }
  159. return
  160. }
  161. func (cp *connectionPool) Node() string {
  162. return cp.host
  163. }
  164. func (cp *connectionPool) GetWithTimeout(d time.Duration) (rv *memcached.Client, err error) {
  165. if cp == nil {
  166. return nil, errNoPool
  167. }
  168. path := ""
  169. if ConnPoolCallback != nil {
  170. defer func(path *string, start time.Time) {
  171. ConnPoolCallback(cp.host, *path, start, err)
  172. }(&path, time.Now())
  173. }
  174. path = "short-circuit"
  175. // short-circuit available connetions.
  176. select {
  177. case rv, isopen := <-cp.connections:
  178. if !isopen {
  179. return nil, errClosedPool
  180. }
  181. atomic.AddUint64(&cp.connCount, 1)
  182. return rv, nil
  183. default:
  184. }
  185. t := time.NewTimer(ConnPoolAvailWaitTime)
  186. defer t.Stop()
  187. // Try to grab an available connection within 1ms
  188. select {
  189. case rv, isopen := <-cp.connections:
  190. path = "avail1"
  191. if !isopen {
  192. return nil, errClosedPool
  193. }
  194. atomic.AddUint64(&cp.connCount, 1)
  195. return rv, nil
  196. case <-t.C:
  197. // No connection came around in time, let's see
  198. // whether we can get one or build a new one first.
  199. t.Reset(d) // Reuse the timer for the full timeout.
  200. select {
  201. case rv, isopen := <-cp.connections:
  202. path = "avail2"
  203. if !isopen {
  204. return nil, errClosedPool
  205. }
  206. atomic.AddUint64(&cp.connCount, 1)
  207. return rv, nil
  208. case cp.createsem <- true:
  209. path = "create"
  210. // Build a connection if we can't get a real one.
  211. // This can potentially be an overflow connection, or
  212. // a pooled connection.
  213. rv, err := cp.mkConn(cp.host, cp.auth, cp.tlsConfig, cp.bucket)
  214. if err != nil {
  215. // On error, release our create hold
  216. <-cp.createsem
  217. } else {
  218. atomic.AddUint64(&cp.connCount, 1)
  219. }
  220. return rv, err
  221. case <-t.C:
  222. return nil, ErrTimeout
  223. }
  224. }
  225. }
  226. func (cp *connectionPool) Get() (*memcached.Client, error) {
  227. return cp.GetWithTimeout(ConnPoolTimeout)
  228. }
  229. func (cp *connectionPool) Return(c *memcached.Client) {
  230. if c == nil {
  231. return
  232. }
  233. if cp == nil {
  234. c.Close()
  235. }
  236. if c.IsHealthy() {
  237. defer func() {
  238. if recover() != nil {
  239. // This happens when the pool has already been
  240. // closed and we're trying to return a
  241. // connection to it anyway. Just close the
  242. // connection.
  243. c.Close()
  244. }
  245. }()
  246. select {
  247. case cp.connections <- c:
  248. default:
  249. <-cp.createsem
  250. c.Close()
  251. }
  252. } else {
  253. <-cp.createsem
  254. c.Close()
  255. }
  256. }
  257. // give the ability to discard a connection from a pool
  258. // useful for ditching connections to the wrong node after a rebalance
  259. func (cp *connectionPool) Discard(c *memcached.Client) {
  260. <-cp.createsem
  261. c.Close()
  262. }
  263. // asynchronous connection closer
  264. func (cp *connectionPool) connCloser() {
  265. var connCount uint64
  266. t := time.NewTimer(ConnCloserInterval)
  267. defer t.Stop()
  268. for {
  269. connCount = cp.connCount
  270. // we don't exist anymore! bail out!
  271. select {
  272. case <-cp.bailOut:
  273. return
  274. case <-t.C:
  275. }
  276. t.Reset(ConnCloserInterval)
  277. // no overflow connections open or sustained requests for connections
  278. // nothing to do until the next cycle
  279. if len(cp.connections) <= cp.poolSize ||
  280. ConnCloserInterval/ConnPoolAvailWaitTime < time.Duration(cp.connCount-connCount) {
  281. continue
  282. }
  283. // close overflow connections now that they are not needed
  284. for c := range cp.connections {
  285. select {
  286. case <-cp.bailOut:
  287. return
  288. default:
  289. }
  290. // bail out if close did not work out
  291. if !cp.connCleanup(c) {
  292. return
  293. }
  294. if len(cp.connections) <= cp.poolSize {
  295. break
  296. }
  297. }
  298. }
  299. }
  300. // close connection with recovery on error
  301. func (cp *connectionPool) connCleanup(c *memcached.Client) (rv bool) {
  302. // just in case we are closing a connection after
  303. // bailOut has been sent but we haven't yet read it
  304. defer func() {
  305. if recover() != nil {
  306. rv = false
  307. }
  308. }()
  309. rv = true
  310. c.Close()
  311. <-cp.createsem
  312. return
  313. }
  314. func (cp *connectionPool) StartTapFeed(args *memcached.TapArguments) (*memcached.TapFeed, error) {
  315. if cp == nil {
  316. return nil, errNoPool
  317. }
  318. mc, err := cp.Get()
  319. if err != nil {
  320. return nil, err
  321. }
  322. // A connection can't be used after TAP; Dont' count it against the
  323. // connection pool capacity
  324. <-cp.createsem
  325. return mc.StartTapFeed(*args)
  326. }
  327. const DEFAULT_WINDOW_SIZE = 20 * 1024 * 1024 // 20 Mb
  328. func (cp *connectionPool) StartUprFeed(name string, sequence uint32, dcp_buffer_size uint32, data_chan_size int) (*memcached.UprFeed, error) {
  329. if cp == nil {
  330. return nil, errNoPool
  331. }
  332. mc, err := cp.Get()
  333. if err != nil {
  334. return nil, err
  335. }
  336. // A connection can't be used after it has been allocated to UPR;
  337. // Dont' count it against the connection pool capacity
  338. <-cp.createsem
  339. uf, err := mc.NewUprFeed()
  340. if err != nil {
  341. return nil, err
  342. }
  343. if err := uf.UprOpen(name, sequence, dcp_buffer_size); err != nil {
  344. return nil, err
  345. }
  346. if err := uf.StartFeedWithConfig(data_chan_size); err != nil {
  347. return nil, err
  348. }
  349. return uf, nil
  350. }