github.com/datastax/go-cassandra-native-protocol@v0.0.0-20220706104457-5e8aad05cf90/client/client.go (about) 1 // Copyright 2020 DataStax 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package client 16 17 import ( 18 "bytes" 19 "context" 20 "crypto/tls" 21 "errors" 22 "fmt" 23 "io" 24 "math" 25 "net" 26 "sync" 27 "sync/atomic" 28 "time" 29 30 "github.com/rs/zerolog/log" 31 32 "github.com/datastax/go-cassandra-native-protocol/frame" 33 "github.com/datastax/go-cassandra-native-protocol/message" 34 "github.com/datastax/go-cassandra-native-protocol/primitive" 35 "github.com/datastax/go-cassandra-native-protocol/segment" 36 ) 37 38 const ( 39 DefaultConnectTimeout = time.Second * 5 40 DefaultReadTimeout = time.Second * 12 41 ) 42 43 const ( 44 DefaultMaxInFlight = 1024 45 DefaultMaxPending = 10 46 ) 47 48 const ManagedStreamId int16 = 0 49 50 // EventHandler An event handler is a callback function that gets invoked whenever a CqlClientConnection receives an incoming 51 // event. 52 type EventHandler func(event *frame.Frame, conn *CqlClientConnection) 53 54 // CqlClient is a client for Cassandra-compatible backends. It is preferable to create CqlClient instances using the 55 // constructor function NewCqlClient. Once the client is created and properly configured, use Connect or ConnectAndInit 56 // to establish new connections to the server. 57 type CqlClient struct { 58 // The remote contact point address to connect to. 59 RemoteAddress string 60 // The AuthCredentials for authenticated servers. If nil, no authentication will be used. 61 Credentials *AuthCredentials 62 // The compression to use; if unspecified, no compression will be used. 63 Compression primitive.Compression 64 // The maximum number of in-flight requests to apply for each connection created with Connect. Must be strictly 65 // positive. 66 MaxInFlight int 67 // The maximum number of pending responses awaiting delivery to store per request. Must be strictly positive. 68 // This is only useful when using continuous paging, a feature specific to DataStax Enterprise. 69 MaxPending int 70 // The timeout to apply when establishing new connections. 71 ConnectTimeout time.Duration 72 // The timeout to apply when waiting for incoming responses. 73 ReadTimeout time.Duration 74 // An optional list of handlers to handle incoming events. 75 EventHandlers []EventHandler 76 // TLSConfig is the TLS configuration to use. 77 TLSConfig *tls.Config 78 } 79 80 // NewCqlClient Creates a new CqlClient with default options. Leave credentials nil to opt out from authentication. 81 func NewCqlClient(remoteAddress string, credentials *AuthCredentials) *CqlClient { 82 return &CqlClient{ 83 RemoteAddress: remoteAddress, 84 Credentials: credentials, 85 MaxInFlight: DefaultMaxInFlight, 86 MaxPending: DefaultMaxPending, 87 ConnectTimeout: DefaultConnectTimeout, 88 ReadTimeout: DefaultReadTimeout, 89 } 90 } 91 92 func (client *CqlClient) String() string { 93 return fmt.Sprintf("CQL client [%v]", client.RemoteAddress) 94 } 95 96 // Connect establishes a new TCP connection to the client's remote address. 97 // Set ctx to context.Background if no parent context exists. 98 // The returned CqlClientConnection is ready to use, but one must initialize it manually, for example by calling 99 // CqlClientConnection.InitiateHandshake. Alternatively, use ConnectAndInit to get a fully-initialized connection. 100 func (client *CqlClient) Connect(ctx context.Context) (*CqlClientConnection, error) { 101 log.Debug().Msgf("%v: connecting", client) 102 var conn net.Conn 103 var err error 104 connectCtx, connectCancel := context.WithTimeout(ctx, client.ConnectTimeout) 105 defer connectCancel() 106 if client.TLSConfig != nil { 107 dialer := tls.Dialer{Config: client.TLSConfig} 108 conn, err = dialer.DialContext(connectCtx, "tcp", client.RemoteAddress) 109 } else { 110 dialer := net.Dialer{} 111 conn, err = dialer.DialContext(connectCtx, "tcp", client.RemoteAddress) 112 } 113 if err != nil { 114 return nil, fmt.Errorf("%v: cannot establish TCP connection: %w", client, err) 115 } else { 116 log.Debug().Msgf("%v: new TCP connection established", client) 117 if connection, err := newCqlClientConnection( 118 conn, 119 ctx, 120 client.Credentials, 121 client.Compression, 122 client.MaxInFlight, 123 client.MaxPending, 124 client.ReadTimeout, 125 client.EventHandlers, 126 ); err != nil { 127 log.Err(err).Msgf("%v: cannot establish CQL connection", client) 128 _ = conn.Close() 129 return nil, err 130 } else { 131 log.Info().Msgf("%v: new CQL connection established: %v", client, connection) 132 return connection, nil 133 } 134 } 135 } 136 137 // ConnectAndInit establishes a new TCP connection to the server, then initiates a handshake procedure using the 138 // specified protocol version. The CqlClientConnection connection will be fully initialized when this method returns. 139 // Use stream id zero to activate automatic stream id management. 140 // Set ctx to context.Background if no parent context exists. 141 func (client *CqlClient) ConnectAndInit( 142 ctx context.Context, 143 version primitive.ProtocolVersion, 144 streamId int16, 145 ) (*CqlClientConnection, error) { 146 if connection, err := client.Connect(ctx); err != nil { 147 return nil, err 148 } else { 149 return connection, connection.InitiateHandshake(version, streamId) 150 } 151 } 152 153 // CqlClientConnection encapsulates a TCP client connection to a remote Cassandra-compatible backend. 154 // CqlClientConnection instances should be created by calling CqlClient.Connect or CqlClient.ConnectAndInit. 155 type CqlClientConnection struct { 156 conn net.Conn 157 frameCodec frame.Codec 158 segmentCodec segment.Codec 159 compression primitive.Compression 160 modernLayout bool 161 readTimeout time.Duration 162 credentials *AuthCredentials 163 handlers []EventHandler 164 inFlightHandler *inFlightRequestsHandler 165 outgoing chan *frame.Frame 166 events chan *frame.Frame 167 waitGroup *sync.WaitGroup 168 closed int32 169 ctx context.Context 170 cancel context.CancelFunc 171 payloadAccumulator *payloadAccumulator 172 } 173 174 func newCqlClientConnection( 175 conn net.Conn, 176 ctx context.Context, 177 credentials *AuthCredentials, 178 compression primitive.Compression, 179 maxInFlight int, 180 maxPending int, 181 readTimeout time.Duration, 182 handlers []EventHandler, 183 ) (*CqlClientConnection, error) { 184 if conn == nil { 185 return nil, fmt.Errorf("TCP connection cannot be nil") 186 } 187 if ctx == nil { 188 return nil, fmt.Errorf("context cannot be nil") 189 } 190 if maxInFlight < 1 { 191 return nil, fmt.Errorf("max in-flight: expecting positive, got: %v", maxInFlight) 192 } else if maxInFlight > math.MaxInt16 { 193 return nil, fmt.Errorf("max in-flight: expecting <= %v, got: %v", math.MaxInt16, maxInFlight) 194 } 195 if maxPending < 1 { 196 return nil, fmt.Errorf("max pending: expecting positive, got: %v", maxInFlight) 197 } 198 frameCodec := frame.NewCodecWithCompression(NewBodyCompressor(compression)) 199 segmentCodec := segment.NewCodecWithCompression(NewPayloadCompressor(compression)) 200 if compression == "" { 201 compression = primitive.CompressionNone 202 } 203 connection := &CqlClientConnection{ 204 conn: conn, 205 frameCodec: frameCodec, 206 segmentCodec: segmentCodec, 207 compression: compression, 208 readTimeout: readTimeout, 209 credentials: credentials, 210 handlers: handlers, 211 outgoing: make(chan *frame.Frame, maxInFlight), 212 events: make(chan *frame.Frame, maxInFlight), 213 waitGroup: &sync.WaitGroup{}, 214 payloadAccumulator: &payloadAccumulator{ 215 frameCodec: frame.NewRawCodec(), // without compression 216 }, 217 } 218 connection.ctx, connection.cancel = context.WithCancel(ctx) 219 connection.inFlightHandler = newInFlightRequestsHandler(connection.String(), connection.ctx, maxInFlight, maxPending, readTimeout) 220 connection.incomingLoop() 221 connection.outgoingLoop() 222 connection.awaitDone() 223 return connection, nil 224 } 225 226 func (c *CqlClientConnection) String() string { 227 return fmt.Sprintf("CQL client conn [L:%v <-> R:%v]", c.conn.LocalAddr(), c.conn.RemoteAddr()) 228 } 229 230 // LocalAddr returns the connection's local address (that is, the client address). 231 func (c *CqlClientConnection) LocalAddr() net.Addr { 232 return c.conn.LocalAddr() 233 } 234 235 // RemoteAddr returns the connection's remote address (that is, the server address). 236 func (c *CqlClientConnection) RemoteAddr() net.Addr { 237 return c.conn.RemoteAddr() 238 } 239 240 // Credentials returns a copy of the connection's AuthCredentials, if any, or nil if no authentication was configured. 241 func (c *CqlClientConnection) Credentials() *AuthCredentials { 242 if c.credentials == nil { 243 return nil 244 } 245 return c.credentials.Copy() 246 } 247 248 type payloadAccumulator struct { 249 targetLength int 250 accumulatedData []byte 251 frameCodec frame.RawCodec 252 } 253 254 func (a *payloadAccumulator) reset() { 255 a.targetLength = 0 256 a.accumulatedData = nil 257 } 258 259 func (c *CqlClientConnection) incomingLoop() { 260 log.Debug().Msgf("%v: listening for incoming frames...", c) 261 c.waitGroup.Add(1) 262 go func() { 263 abort := false 264 for !abort && !c.IsClosed() { 265 if source, err := c.waitForIncomingData(); err != nil { 266 abort = c.reportConnectionFailure(err, true) 267 } else if c.modernLayout { 268 abort = c.readSegment(source) 269 } else { 270 abort = c.readFrame(source) 271 } 272 } 273 c.waitGroup.Done() 274 if abort { 275 c.abort() 276 } 277 }() 278 } 279 280 func (c *CqlClientConnection) outgoingLoop() { 281 log.Debug().Msgf("%v: listening for outgoing frames...", c) 282 c.waitGroup.Add(1) 283 go func() { 284 abort := false 285 for !abort && !c.IsClosed() { 286 if outgoing, ok := <-c.outgoing; !ok { 287 if !c.IsClosed() { 288 log.Error().Msgf("%v: outgoing frame channel was closed unexpectedly, closing connection", c) 289 abort = true 290 } 291 break 292 } else { 293 log.Debug().Msgf("%v: sending outgoing frame: %v", c, outgoing) 294 if c.modernLayout { 295 // TODO write coalescer 296 abort = c.writeSegment(outgoing, c.conn) 297 } else { 298 abort = c.writeFrame(outgoing, c.conn) 299 } 300 } 301 } 302 c.waitGroup.Done() 303 if abort { 304 c.abort() 305 } 306 }() 307 } 308 309 func (c *CqlClientConnection) waitForIncomingData() (io.Reader, error) { 310 buf := make([]byte, 1) 311 if _, err := io.ReadFull(c.conn, buf); err != nil { 312 return nil, err 313 } else { 314 return io.MultiReader(bytes.NewReader(buf), c.conn), nil 315 } 316 } 317 318 func (c *CqlClientConnection) readSegment(source io.Reader) (abort bool) { 319 if incoming, err := c.segmentCodec.DecodeSegment(source); err != nil { 320 abort = c.reportConnectionFailure(err, true) 321 } else if incoming.Header.IsSelfContained { 322 log.Debug().Msgf("%v: received incoming self-contained segment: %v", c, incoming) 323 abort = c.readSelfContainedSegment(incoming, abort) 324 } else { 325 log.Debug().Msgf("%v: received incoming multi-segment part: %v", c, incoming) 326 abort = c.addMultiSegmentPayload(incoming.Payload) 327 } 328 return abort 329 } 330 331 func (c *CqlClientConnection) readSelfContainedSegment(incoming *segment.Segment, abort bool) bool { 332 payloadReader := bytes.NewReader(incoming.Payload.UncompressedData) 333 for payloadReader.Len() > 0 { 334 if abort = c.readFrame(payloadReader); abort { 335 break 336 } 337 } 338 return abort 339 } 340 341 func (c *CqlClientConnection) addMultiSegmentPayload(payload *segment.Payload) (abort bool) { 342 accumulator := c.payloadAccumulator 343 if accumulator.targetLength == 0 { 344 // First reader, read ahead to find the target length 345 if header, err := accumulator.frameCodec.DecodeHeader(bytes.NewReader(payload.UncompressedData)); err != nil { 346 log.Error().Err(err).Msgf("%v: error decoding first frame header in multi-segment payload, closing connection", c) 347 return true 348 } else { 349 accumulator.targetLength = int(primitive.FrameHeaderLengthV3AndHigher + header.BodyLength) 350 } 351 } 352 accumulator.accumulatedData = append(accumulator.accumulatedData, payload.UncompressedData...) 353 if accumulator.targetLength == len(accumulator.accumulatedData) { 354 // We've received enough data to reassemble the whole frame 355 encodedFrame := bytes.NewReader(accumulator.accumulatedData) 356 accumulator.reset() 357 return c.readFrame(encodedFrame) 358 } 359 return false 360 } 361 362 func (c *CqlClientConnection) writeSegment(outgoing *frame.Frame, dest io.Writer) (abort bool) { 363 // never compress frames individually when included in a segment 364 outgoing.Header.Flags = outgoing.Header.Flags.Remove(primitive.HeaderFlagCompressed) 365 encodedFrame := &bytes.Buffer{} 366 if abort = c.writeFrame(outgoing, encodedFrame); abort { 367 abort = true 368 } else { 369 seg := &segment.Segment{ 370 Header: &segment.Header{IsSelfContained: true}, 371 Payload: &segment.Payload{UncompressedData: encodedFrame.Bytes()}, 372 } 373 if err := c.segmentCodec.EncodeSegment(seg, dest); err != nil { 374 abort = c.reportConnectionFailure(err, false) 375 } else { 376 log.Debug().Msgf("%v: outgoing segment successfully written: %v (frame: %v)", c, seg, outgoing) 377 } 378 } 379 return abort 380 } 381 382 func (c *CqlClientConnection) readFrame(source io.Reader) (abort bool) { 383 if incoming, err := c.frameCodec.DecodeFrame(source); err != nil { 384 abort = c.reportConnectionFailure(err, true) 385 } else { 386 c.maybeSwitchToModernLayout(incoming) 387 abort = c.processIncomingFrame(incoming) 388 } 389 return abort 390 } 391 392 func (c *CqlClientConnection) maybeSwitchToModernLayout(incoming *frame.Frame) { 393 if !c.modernLayout && 394 incoming.Header.Version.SupportsModernFramingLayout() && 395 (isReady(incoming) || isAuthenticate(incoming)) { 396 // Changing this value could be racy if some outgoing frame is being processed; 397 // but in theory, this should never happen during handshake. 398 log.Debug().Msgf("%v: switching to modern framing layout", c) 399 c.modernLayout = true 400 } 401 } 402 403 func (c *CqlClientConnection) writeFrame(outgoing *frame.Frame, dest io.Writer) (abort bool) { 404 if err := c.frameCodec.EncodeFrame(outgoing, dest); err != nil { 405 abort = c.reportConnectionFailure(err, false) 406 } else { 407 log.Debug().Msgf("%v: outgoing frame successfully written: %v", c, outgoing) 408 } 409 return abort 410 } 411 412 func (c *CqlClientConnection) reportConnectionFailure(err error, read bool) (abort bool) { 413 if !c.IsClosed() { 414 if errors.Is(err, io.EOF) { 415 log.Info().Msgf("%v: connection reset by peer, closing", c) 416 } else { 417 if read { 418 log.Error().Err(err).Msgf("%v: error reading, closing connection", c) 419 } else { 420 log.Error().Err(err).Msgf("%v: error writing, closing connection", c) 421 } 422 } 423 abort = true 424 } 425 return abort 426 } 427 428 func (c *CqlClientConnection) processIncomingFrame(incoming *frame.Frame) (abort bool) { 429 log.Debug().Msgf("%v: received incoming frame: %v", c, incoming) 430 if incoming.Header.OpCode == primitive.OpCodeEvent { 431 for _, handler := range c.handlers { 432 handler(incoming, c) 433 } 434 select { 435 case c.events <- incoming: 436 log.Debug().Msgf("%v: incoming event frame successfully delivered: %v", c, incoming) 437 default: 438 log.Error().Msgf("%v: events queue is full, discarding event frame: %v", c, incoming) 439 } 440 } else { 441 if err := c.inFlightHandler.onIncomingFrameReceived(incoming); err != nil { 442 log.Error().Err(err).Msgf("%v: incoming frame delivery failed: %v", c, incoming) 443 } else { 444 log.Debug().Msgf("%v: incoming frame successfully delivered: %v", c, incoming) 445 } 446 if incoming.Header.OpCode == primitive.OpCodeError { 447 e := incoming.Body.Message.(message.Error) 448 if e.GetErrorCode().IsFatalError() { 449 log.Error().Msgf("%v: server replied with fatal error code %v, closing connection", c, e.GetErrorCode()) 450 abort = true 451 } 452 } 453 } 454 return 455 } 456 457 func (c *CqlClientConnection) awaitDone() { 458 c.waitGroup.Add(1) 459 go func() { 460 <-c.ctx.Done() 461 log.Debug().Err(c.ctx.Err()).Msgf("%v: context was closed", c) 462 c.waitGroup.Done() 463 c.abort() 464 }() 465 } 466 467 // NewStartupRequest is a convenience method to create a new STARTUP request frame. The compression option will be 468 // automatically set to the appropriate compression algorithm, depending on whether the connection was configured to 469 // use a compressor. Use stream id zero to activate automatic stream id management. 470 func (c *CqlClientConnection) NewStartupRequest(version primitive.ProtocolVersion, streamId int16) (*frame.Frame, error) { 471 startup := message.NewStartup() 472 if c.compression != primitive.CompressionNone { 473 if version.SupportsCompression(c.compression) { 474 startup.SetCompression(c.compression) 475 } else { 476 return nil, fmt.Errorf("%v does not support compression %v", version, c.compression) 477 } 478 } 479 startup.SetDriverName("DataStax Go client") 480 return frame.NewFrame(version, streamId, startup), nil 481 } 482 483 // InFlightRequest is an in-flight request sent through CqlClientConnection.Send. 484 type InFlightRequest interface { 485 486 // StreamId is the in-flight request stream id. 487 StreamId() int16 488 489 // Incoming returns a channel to receive incoming frames for this in-flight request. Typically the channel will 490 // only ever emit one single frame, except when using continuous paging (DataStax Enterprise only). 491 // The returned channel is never nil. It is closed after receiving the last frame, or if an error occurs 492 // (typically a timeout), whichever happens first; when the channel is closed, IsDone returns true. 493 // If the channel is closed because of an error, Err will return that error, otherwise it will return nil. 494 // Successive calls to Incoming return the same channel. 495 Incoming() <-chan *frame.Frame 496 497 // IsDone returns true if Incoming is closed, and false otherwise. 498 IsDone() bool 499 500 // Err returns nil if Incoming is not yet closed. 501 // If Incoming is closed, Err returns either nil if the channel was closed normally, or a non-nil error explaining 502 // why the channel was closed abnormally. 503 // After Err returns a non-nil error, successive calls to Err return the same error. 504 Err() error 505 } 506 507 // Send sends the given request frame and returns a receive channel that can be used to receive response frames and 508 // errors matching the request's stream id. The channel will be closed after receiving the last frame, or if the 509 // configured read timeout is triggered, or if the connection itself is closed, whichever happens first. 510 // Stream id management: if the frame's stream id is ManagedStreamId (0), it is assumed that the frame's stream id is 511 // to be automatically assigned by the connection upon write. Users are free to choose between managed stream ids or 512 // manually assigned ones, but it is not recommended mixing managed stream ids with non-managed ones on the same 513 // connection. 514 func (c *CqlClientConnection) Send(f *frame.Frame) (InFlightRequest, error) { 515 if f == nil { 516 return nil, fmt.Errorf("%v: frame cannot be nil", c) 517 } 518 if c.IsClosed() { 519 return nil, fmt.Errorf("%v: connection closed", c) 520 } 521 log.Debug().Msgf("%v: enqueuing outgoing frame: %v", c, f) 522 if inFlight, err := c.inFlightHandler.onOutgoingFrameEnqueued(f); err != nil { 523 return nil, fmt.Errorf("%v: failed to register in-flight handler for frame: %v: %w", c, f, err) 524 } else { 525 select { 526 case c.outgoing <- f: 527 log.Debug().Msgf("%v: outgoing frame successfully enqueued: %v", c, f) 528 return inFlight, nil 529 default: 530 return nil, fmt.Errorf("%v: failed to enqueue outgoing frame: %v", c, f) 531 } 532 } 533 } 534 535 // Receive is a convenience method that takes an InFlightRequest obtained through Send and waits until the next response 536 // frame is received, or an error occurs, whichever happens first. 537 // If the in-flight request is completed already without returning more frames, this method return a nil frame and a 538 // nil error. 539 func (c *CqlClientConnection) Receive(ch InFlightRequest) (*frame.Frame, error) { 540 if ch == nil { 541 return nil, fmt.Errorf("%v: response channel cannot be nil", c) 542 } 543 log.Debug().Msgf("%v: waiting for incoming frame", c) 544 if incoming, ok := <-ch.Incoming(); !ok { 545 if ch.Err() == nil { 546 log.Debug().Msgf("%v: in-flight request closed for stream id: %d", c, ch.StreamId()) 547 return nil, nil 548 } else { 549 return nil, fmt.Errorf("%v: failed to retrieve incoming frame: %w", c, ch.Err()) 550 } 551 } else { 552 log.Debug().Msgf("%v: incoming frame successfully received: %v", c, incoming) 553 return incoming, nil 554 } 555 } 556 557 // SendAndReceive is a convenience method chaining a call to Send to a call to Receive. 558 func (c *CqlClientConnection) SendAndReceive(f *frame.Frame) (*frame.Frame, error) { 559 if ch, err := c.Send(f); err != nil { 560 return nil, err 561 } else { 562 return c.Receive(ch) 563 } 564 } 565 566 // EventChannel is a receive-only channel for incoming events. A receive channel can be obtained through 567 // CqlClientConnection.EventChannel. 568 type EventChannel <-chan *frame.Frame 569 570 // EventChannel returns a channel for listening to incoming events received on this connection. This channel will be 571 // closed when the connection is closed. If this connection has already been closed, this method returns nil. 572 func (c *CqlClientConnection) EventChannel() EventChannel { 573 return c.events 574 } 575 576 // ReceiveEvent waits until an event frame is received, or the configured read timeout is triggered, or the connection 577 // is closed, whichever happens first. Returns the event frame, if any. 578 func (c *CqlClientConnection) ReceiveEvent() (*frame.Frame, error) { 579 if c.IsClosed() { 580 return nil, fmt.Errorf("%v: connection closed", c) 581 } 582 select { 583 case incoming, ok := <-c.events: 584 if !ok { 585 return nil, fmt.Errorf("%v: incoming events channel closed", c) 586 } 587 return incoming, nil 588 case <-time.After(c.readTimeout): 589 return nil, fmt.Errorf("%v: timed out waiting for incoming events", c) 590 } 591 } 592 593 func (c *CqlClientConnection) IsClosed() bool { 594 return atomic.LoadInt32(&c.closed) == 1 595 } 596 597 func (c *CqlClientConnection) setClosed() bool { 598 return atomic.CompareAndSwapInt32(&c.closed, 0, 1) 599 } 600 601 func (c *CqlClientConnection) Close() (err error) { 602 if c.setClosed() { 603 log.Debug().Msgf("%v: closing", c) 604 c.cancel() 605 err = c.conn.Close() 606 outgoing := c.outgoing 607 events := c.events 608 c.outgoing = nil 609 c.events = nil 610 close(outgoing) 611 close(events) 612 c.inFlightHandler.close() 613 c.waitGroup.Wait() 614 if err != nil { 615 err = fmt.Errorf("%v: error closing: %w", c, err) 616 } else { 617 log.Info().Msgf("%v: successfully closed", c) 618 } 619 } else { 620 log.Debug().Err(err).Msgf("%v: already closed", c) 621 } 622 return err 623 } 624 625 func (c *CqlClientConnection) abort() { 626 log.Debug().Msgf("%v: forcefully closing", c) 627 if err := c.Close(); err != nil { 628 log.Error().Err(err).Msgf("%v: error closing", c) 629 } 630 }