github.com/lirm/aeron-go@v0.0.0-20230415210743-920325491dc4/cluster/client/aeron_cluster.go (about)

     1  // Copyright 2022 Steven Stern
     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  	"errors"
    20  	"fmt"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/lirm/aeron-go/aeron"
    26  	"github.com/lirm/aeron-go/aeron/atomic"
    27  	"github.com/lirm/aeron-go/aeron/logbuffer"
    28  	"github.com/lirm/aeron-go/aeron/logging"
    29  	"github.com/lirm/aeron-go/aeron/util"
    30  	"github.com/lirm/aeron-go/cluster"
    31  	"github.com/lirm/aeron-go/cluster/codecs"
    32  )
    33  
    34  var logger = logging.MustGetLogger("cluster-client")
    35  var marshaller = codecs.NewSbeGoMarshaller()
    36  
    37  var TemporaryError = errors.New("temporary error")
    38  
    39  type AeronCluster struct {
    40  	opts                 *Options
    41  	aeronClient          *aeron.Aeron
    42  	egressSub            *aeron.Subscription
    43  	ingressChannel       *aeron.ChannelUri
    44  	ingressPub           *aeron.Publication
    45  	clusterSessionId     int64
    46  	leadershipTermId     int64
    47  	leaderMemberId       int32
    48  	memberByIdMap        map[int32]*memberIngress
    49  	fragmentAssembler    *aeron.FragmentAssembler
    50  	egressListener       EgressListener
    51  	sessionMsgHdrBuffer  *atomic.Buffer
    52  	keepAliveBuffer      *atomic.Buffer
    53  	state                clientState
    54  	correlationId        int64
    55  	nextRetryConnectTime int64
    56  	awaitTimeoutTime     int64
    57  }
    58  
    59  type memberIngress struct {
    60  	memberId    int32
    61  	endpoint    string
    62  	publication *aeron.Publication
    63  }
    64  
    65  type clientState int8
    66  
    67  const (
    68  	clientDisconnected clientState = iota
    69  	clientCreatePublications
    70  	clientAwaitPublicationConnected
    71  	clientAwaitConnectReply
    72  	clientConnected
    73  	clientClosed
    74  )
    75  
    76  const (
    77  	protocolMajorVersion = 0
    78  	protocolMinorVersion = 2
    79  	protocolPatchVersion = 0
    80  )
    81  
    82  var protocolSemanticVersion = util.SemanticVersionCompose(
    83  	protocolMajorVersion, protocolMinorVersion, protocolPatchVersion)
    84  
    85  func NewAeronCluster(
    86  	aeronCtx *aeron.Context,
    87  	options *Options,
    88  	egressListener EgressListener,
    89  ) (*AeronCluster, error) {
    90  	if egressListener == nil {
    91  		return nil, fmt.Errorf("egressListener is nil")
    92  	}
    93  	ingressChannel, err := aeron.ParseChannelUri(options.IngressChannel)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	if ingressChannel.IsIpc() && options.IngressEndpoints != "" {
    98  		return nil, fmt.Errorf("IngressEndpoints must be empty when using IPC ingress")
    99  	}
   100  
   101  	aeronClient, err := aeron.Connect(aeronCtx)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	egressSub, err := aeronClient.AddSubscription(options.EgressChannel, options.EgressStreamId)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	sessionMsgHdrBuf := codecs.MakeClusterMessageBuffer(cluster.SessionMessageHeaderTemplateId, cluster.SessionMessageHdrBlockLength)
   112  
   113  	client := &AeronCluster{
   114  		opts:                options,
   115  		aeronClient:         aeronClient,
   116  		egressSub:           egressSub,
   117  		ingressChannel:      &ingressChannel,
   118  		clusterSessionId:    cluster.NullValue,
   119  		leadershipTermId:    cluster.NullValue,
   120  		leaderMemberId:      cluster.NullValue,
   121  		memberByIdMap:       make(map[int32]*memberIngress),
   122  		egressListener:      egressListener,
   123  		state:               clientDisconnected,
   124  		sessionMsgHdrBuffer: sessionMsgHdrBuf,
   125  		keepAliveBuffer:     codecs.MakeClusterMessageBuffer(cluster.SessionKeepAliveTemplateId, 16),
   126  	}
   127  	client.fragmentAssembler = aeron.NewFragmentAssembler(client.onFragment, 0)
   128  	client.updateMemberEndpoints(options.IngressEndpoints)
   129  
   130  	return client, nil
   131  }
   132  
   133  func (ac *AeronCluster) ClusterSessionId() int64 {
   134  	return ac.clusterSessionId
   135  }
   136  
   137  func (ac *AeronCluster) LeadershipTermId() int64 {
   138  	return ac.leadershipTermId
   139  }
   140  
   141  func (ac *AeronCluster) LeaderMemberId() int32 {
   142  	return ac.leaderMemberId
   143  }
   144  
   145  func (ac *AeronCluster) IsConnected() bool {
   146  	return ac.state == clientConnected
   147  }
   148  
   149  func (ac *AeronCluster) IsClosed() bool {
   150  	return ac.state == clientClosed
   151  }
   152  
   153  func (ac *AeronCluster) Poll() int {
   154  	switch ac.state {
   155  	case clientDisconnected:
   156  		if time.Now().UnixMilli() > ac.nextRetryConnectTime {
   157  			ac.state = clientCreatePublications
   158  		}
   159  	case clientCreatePublications:
   160  		ret, err := ac.createPublications()
   161  		if err != nil {
   162  			logger.Warningf("error from createPublications %w", err)
   163  		}
   164  		return ret
   165  	case clientAwaitPublicationConnected:
   166  		ret, err := ac.awaitPublicationConnected()
   167  		if err != nil {
   168  			logger.Warningf("error from awaitPublicationConnected %w", err)
   169  		}
   170  		return ret
   171  	case clientAwaitConnectReply:
   172  		now := time.Now().UnixMilli()
   173  		if ac.ingressPub.IsConnected() && now < ac.awaitTimeoutTime {
   174  			return ac.pollEgress(1)
   175  		} else {
   176  			logger.Warningf("timed out waiting for session connect reply")
   177  			ac.state = clientDisconnected
   178  			ac.nextRetryConnectTime = now + (30 * time.Second).Milliseconds()
   179  		}
   180  	case clientConnected:
   181  		if ac.ingressPub.IsConnected() {
   182  			return ac.pollEgress(10)
   183  			// TODO: check if state == closed
   184  		} else {
   185  			ac.egressListener.OnDisconnect(ac, "ingress publication disconnected")
   186  			ac.state = clientCreatePublications
   187  		}
   188  	}
   189  	return 0
   190  }
   191  
   192  func (ac *AeronCluster) Offer(buffer *atomic.Buffer, offset, length int32) int64 {
   193  	if ac.state != clientConnected {
   194  		return aeron.NotConnected
   195  	} else {
   196  		hdrBuf := ac.sessionMsgHdrBuffer
   197  		return ac.ingressPub.Offer2(hdrBuf, 0, hdrBuf.Capacity(), buffer, offset, length, nil)
   198  	}
   199  }
   200  
   201  func (ac *AeronCluster) SendKeepAlive() bool {
   202  	if !ac.IsConnected() {
   203  		return false
   204  	}
   205  	buf := ac.keepAliveBuffer
   206  	for i := 0; i < 3; i++ {
   207  		if result := ac.ingressPub.Offer(buf, 0, buf.Capacity(), nil); result >= 0 {
   208  			return true
   209  		}
   210  		ac.opts.IdleStrategy.Idle(0)
   211  	}
   212  	return false
   213  }
   214  
   215  func (ac *AeronCluster) Close() {
   216  	if ac.IsConnected() && ac.ingressPub.IsConnected() {
   217  		ac.sendCloseSession()
   218  	}
   219  	if ac.ingressPub != nil {
   220  		if err := ac.ingressPub.Close(); err != nil {
   221  			logger.Debugf("error closing ingress publication: %v", err)
   222  		}
   223  		ac.ingressPub = nil
   224  	}
   225  	if ac.egressSub != nil {
   226  		if err := ac.egressSub.Close(); err != nil {
   227  			logger.Debugf("error closing egress subscription: %v", err)
   228  		}
   229  		ac.egressSub = nil
   230  	}
   231  	if err := ac.aeronClient.Close(); err != nil {
   232  		logger.Debugf("error closing aeron client: %v", err)
   233  	}
   234  	ac.state = clientClosed
   235  }
   236  
   237  func (ac *AeronCluster) updateMemberEndpoints(endpoints string) {
   238  	if endpoints == "" {
   239  		return
   240  	}
   241  	logger.Debugf("updateMemberEndpoints: %s", endpoints)
   242  	for idx, endpoint := range strings.Split(endpoints, ",") {
   243  		if delim := strings.IndexByte(endpoint, '='); delim > 0 {
   244  			memberId, err := strconv.Atoi(endpoint[:delim])
   245  			if err != nil {
   246  				logger.Warningf("invalid endpoint at idx=%d: %s", idx, endpoint)
   247  				continue
   248  			}
   249  			address := endpoint[delim+1:]
   250  			member := ac.memberByIdMap[int32(memberId)]
   251  			if member == nil {
   252  				member = &memberIngress{
   253  					memberId: int32(memberId),
   254  					endpoint: address,
   255  				}
   256  				ac.memberByIdMap[member.memberId] = member
   257  			} else if address != member.endpoint {
   258  				member.endpoint = address
   259  				if member.publication != nil {
   260  					member.close()
   261  				}
   262  			}
   263  			if member.memberId == ac.leaderMemberId {
   264  				if member.publication == nil {
   265  					pub, err := ac.createIngressPublication(address)
   266  					if err == nil {
   267  						member.publication = pub
   268  					} else {
   269  						logger.Warning(err)
   270  					}
   271  				}
   272  				ac.ingressPub = member.publication
   273  				ac.fragmentAssembler.Clear()
   274  			}
   275  		} else {
   276  			logger.Warningf("endpoint at idx=%d missing '=' separator: %s", idx, endpoint)
   277  		}
   278  	}
   279  }
   280  
   281  func (ac *AeronCluster) createPublications() (int, error) {
   282  	if len(ac.memberByIdMap) > 0 {
   283  		for _, member := range ac.memberByIdMap {
   284  			if member.publication == nil {
   285  				pub, err := ac.createIngressPublication(member.endpoint)
   286  				if err != nil {
   287  					return 0, err
   288  				}
   289  				member.publication = pub
   290  			}
   291  		}
   292  	} else if ac.ingressPub == nil {
   293  		pub, err := ac.createIngressPublication(ac.opts.IngressChannel)
   294  		if err != nil {
   295  			return 0, err
   296  		}
   297  		ac.ingressPub = pub
   298  	}
   299  	ac.state = clientAwaitPublicationConnected
   300  	ac.awaitTimeoutTime = time.Now().UnixMilli() + (5 * time.Second).Milliseconds()
   301  	return 1, nil
   302  }
   303  
   304  func (ac *AeronCluster) createIngressPublication(endpoint string) (*aeron.Publication, error) {
   305  	if ac.ingressChannel.IsUdp() {
   306  		ac.ingressChannel.Set("endpoint", endpoint)
   307  	}
   308  	channel := ac.ingressChannel.String()
   309  	logger.Debugf("createIngressPublication - endpoint=%s isUdp=%v isExclusive=%v",
   310  		endpoint, ac.ingressChannel.IsUdp(), ac.opts.IsIngressExclusive)
   311  	if ac.opts.IsIngressExclusive {
   312  		return ac.aeronClient.AddExclusivePublication(channel, ac.opts.IngressStreamId)
   313  	} else {
   314  		return ac.aeronClient.AddPublication(channel, ac.opts.IngressStreamId)
   315  	}
   316  }
   317  
   318  func (ac *AeronCluster) awaitPublicationConnected() (int, error) {
   319  	responseChannel := ac.egressSub.TryResolveChannelEndpointPort()
   320  	if responseChannel == "" {
   321  		// TODO: Is this an error or success condition?
   322  		return 0, nil
   323  	}
   324  	now := time.Now().UnixMilli()
   325  	if now > ac.awaitTimeoutTime {
   326  		ac.state = clientDisconnected
   327  		// close publications? shouldn't be necessary unless we've hit some bug
   328  		ac.nextRetryConnectTime = now + (30 * time.Second).Milliseconds()
   329  		return 0, errors.New("timed out waiting for connected publication")
   330  	}
   331  	if len(ac.memberByIdMap) > 0 {
   332  		for _, member := range ac.memberByIdMap {
   333  			if member.publication != nil && member.publication.IsConnected() {
   334  				ac.ingressPub = member.publication
   335  				ac.fragmentAssembler.Clear()
   336  				err := ac.sendConnectRequest(responseChannel)
   337  				if err == nil {
   338  					logger.Debugf("sent connect request to memberId=%d correlationId=%d channel=%s",
   339  						member.memberId, ac.correlationId, member.publication.Channel())
   340  					ac.state = clientAwaitConnectReply
   341  					ac.awaitTimeoutTime = now + (3 * time.Second).Milliseconds()
   342  					break
   343  				}
   344  				if !errors.Is(err, TemporaryError) {
   345  					return 0, err
   346  				}
   347  			}
   348  		}
   349  	} else if ac.ingressPub.IsConnected() && ac.sendConnectRequest(responseChannel) == nil {
   350  		ac.state = clientAwaitConnectReply
   351  		ac.awaitTimeoutTime = now + (3 * time.Second).Milliseconds()
   352  	}
   353  	return 0, nil
   354  }
   355  
   356  // Returns nil on success, TemporaryError, or any other error is a permanent error.
   357  func (ac *AeronCluster) sendConnectRequest(responseChannel string) error {
   358  	ac.correlationId = ac.aeronClient.NextCorrelationID()
   359  	req := codecs.SessionConnectRequest{
   360  		CorrelationId:    ac.correlationId,
   361  		ResponseStreamId: ac.opts.EgressStreamId,
   362  		Version:          int32(protocolSemanticVersion),
   363  		ResponseChannel:  []byte(responseChannel),
   364  	}
   365  	header := codecs.MessageHeader{
   366  		BlockLength: req.SbeBlockLength(),
   367  		TemplateId:  req.SbeTemplateId(),
   368  		SchemaId:    req.SbeSchemaId(),
   369  		Version:     req.SbeSchemaVersion(),
   370  	}
   371  	writer := new(bytes.Buffer)
   372  	if err := header.Encode(marshaller, writer); err != nil {
   373  		return err
   374  	}
   375  	if err := req.Encode(marshaller, writer, true); err != nil {
   376  		return err
   377  	}
   378  	buffer := atomic.MakeBuffer(writer.Bytes())
   379  	result := ac.ingressPub.Offer(buffer, 0, buffer.Capacity(), nil)
   380  	if result >= 0 {
   381  		return nil
   382  	} else {
   383  		return fmt.Errorf("%w, failed to send connect request, channel=%s result=%d",
   384  			TemporaryError, ac.ingressPub.Channel(), result)
   385  	}
   386  }
   387  
   388  func (ac *AeronCluster) pollEgress(fragmentLimit int) int {
   389  	return ac.egressSub.Poll(ac.fragmentAssembler.OnFragment, fragmentLimit)
   390  }
   391  
   392  func (ac *AeronCluster) onFragment(buffer *atomic.Buffer, offset, length int32, header *logbuffer.Header) {
   393  	if length < cluster.SBEHeaderLength {
   394  		return
   395  	}
   396  	blockLength := buffer.GetUInt16(offset)
   397  	templateId := buffer.GetUInt16(offset + 2)
   398  	schemaId := buffer.GetUInt16(offset + 4)
   399  	version := buffer.GetUInt16(offset + 6)
   400  	if schemaId != cluster.ClusterSchemaId {
   401  		logger.Errorf("unexpected schemaId=%d templateId=%d blockLen=%d version=%d",
   402  			schemaId, templateId, blockLength, version)
   403  		return
   404  	}
   405  	offset += cluster.SBEHeaderLength
   406  	length -= cluster.SBEHeaderLength
   407  
   408  	switch templateId {
   409  	case cluster.SessionMessageHeaderTemplateId:
   410  		ac.onSessionMessage(buffer, offset, length, header)
   411  	case cluster.SessionEventTemplateId:
   412  		ac.onSessionEvent(buffer, offset, length, version, blockLength)
   413  	case cluster.NewLeaderEventTemlateId:
   414  		ac.onNewLeaderEvent(buffer, offset, length, version, blockLength)
   415  	case cluster.ChallengeTemplateId:
   416  		e := codecs.Challenge{}
   417  		buf := bytes.Buffer{}
   418  		buffer.WriteBytes(&buf, offset, length)
   419  		if err := e.Decode(marshaller, &buf, version, blockLength, true); err != nil {
   420  			logger.Errorf("new leader event decode error: %v", err)
   421  		} else {
   422  			logger.Warningf("received challenge, corrId=%d clusterSessionId=%d", e.CorrelationId, e.ClusterSessionId)
   423  		}
   424  	}
   425  }
   426  
   427  func (ac *AeronCluster) onNewLeaderEvent(buffer *atomic.Buffer, offset, length int32, version, blockLength uint16) {
   428  	e := codecs.NewLeaderEvent{}
   429  	buf := bytes.Buffer{}
   430  	buffer.WriteBytes(&buf, offset, length)
   431  	if err := e.Decode(marshaller, &buf, version, blockLength, true); err != nil {
   432  		logger.Errorf("new leader event decode error: %v", err)
   433  	} else if ac.state == clientConnected && e.ClusterSessionId == ac.clusterSessionId {
   434  		ac.leadershipTermId = e.LeadershipTermId
   435  		ac.leaderMemberId = e.LeaderMemberId
   436  		ac.sessionMsgHdrBuffer.PutInt64(cluster.SBEHeaderLength, e.LeadershipTermId)
   437  		ac.keepAliveBuffer.PutInt64(cluster.SBEHeaderLength, e.LeadershipTermId)
   438  		if ac.opts.IngressEndpoints != "" {
   439  			if err := ac.ingressPub.Close(); err != nil {
   440  				logger.Warningf("error closing ingress publication: %v", err)
   441  			}
   442  			ac.ingressPub = nil
   443  			ac.updateMemberEndpoints(string(e.IngressEndpoints))
   444  		}
   445  		ac.fragmentAssembler.Clear()
   446  		ac.egressListener.OnNewLeader(ac, e.LeadershipTermId, e.LeaderMemberId)
   447  	} else {
   448  		logger.Debugf("ignored new leader event - state=%v thisSessionId=%d targetSessionId=%d leaderMemberId=%d leaderTermId=%d",
   449  			ac.state, ac.clusterSessionId, e.ClusterSessionId, e.LeaderMemberId, e.LeadershipTermId)
   450  	}
   451  }
   452  
   453  func (ac *AeronCluster) onSessionEvent(buffer *atomic.Buffer, offset, length int32, version, blockLength uint16) {
   454  	e := codecs.SessionEvent{}
   455  	buf := bytes.Buffer{}
   456  	buffer.WriteBytes(&buf, offset, length)
   457  	if err := e.Decode(marshaller, &buf, version, blockLength, true); err != nil {
   458  		logger.Errorf("session event decode error: %v", err)
   459  	} else if ac.state == clientAwaitConnectReply && e.CorrelationId == ac.correlationId {
   460  		switch e.Code {
   461  		case codecs.EventCode.OK:
   462  			ac.leadershipTermId = e.LeadershipTermId
   463  			ac.leaderMemberId = e.LeaderMemberId
   464  			ac.clusterSessionId = e.ClusterSessionId
   465  			ac.sessionMsgHdrBuffer.PutInt64(cluster.SBEHeaderLength, e.LeadershipTermId)
   466  			ac.sessionMsgHdrBuffer.PutInt64(cluster.SBEHeaderLength+8, e.ClusterSessionId)
   467  			ac.keepAliveBuffer.PutInt64(cluster.SBEHeaderLength, e.LeadershipTermId)
   468  			ac.keepAliveBuffer.PutInt64(cluster.SBEHeaderLength+8, e.ClusterSessionId)
   469  			ac.state = clientConnected
   470  			ac.closeNonLeaderPublications()
   471  			ac.egressListener.OnConnect(ac)
   472  		case codecs.EventCode.REDIRECT:
   473  			logger.Infof("got redirect - leaderTermId=%d leaderMemberId=%d", e.LeadershipTermId, e.LeaderMemberId)
   474  			ac.leaderMemberId = e.LeaderMemberId
   475  			ac.updateMemberEndpoints(string(e.Detail))
   476  			ac.closeNonLeaderPublications()
   477  			ac.state = clientAwaitPublicationConnected
   478  		case codecs.EventCode.ERROR:
   479  			ac.egressListener.OnError(ac, string(e.Detail))
   480  			ac.state = clientDisconnected
   481  			ac.nextRetryConnectTime = time.Now().UnixMilli() + (5 * time.Second).Milliseconds()
   482  		case codecs.EventCode.AUTHENTICATION_REJECTED:
   483  			ac.egressListener.OnError(ac, fmt.Sprintf("authentication rejected (%s)", string(e.Detail)))
   484  			ac.state = clientDisconnected
   485  			ac.nextRetryConnectTime = time.Now().UnixMilli() + time.Minute.Milliseconds()
   486  		}
   487  	} else if ac.state == clientConnected && e.ClusterSessionId == ac.clusterSessionId {
   488  		if e.Code == codecs.EventCode.CLOSED {
   489  			ac.egressListener.OnDisconnect(ac, string(e.Detail))
   490  			ac.state = clientClosed
   491  		} else if e.Code == codecs.EventCode.ERROR {
   492  			ac.egressListener.OnError(ac, string(e.Detail))
   493  		} else {
   494  			logger.Infof("onSessionEvent - code=%v (%s)", e.Code, string(e.Detail))
   495  		}
   496  	} else {
   497  		logger.Debugf("ignored session event - state=%v thisSessionId=%d targetSessionId=%d code=%d (%s)",
   498  			ac.state, ac.clusterSessionId, e.ClusterSessionId, e.Code, string(e.Detail))
   499  	}
   500  }
   501  
   502  func (ac *AeronCluster) closeNonLeaderPublications() {
   503  	for _, member := range ac.memberByIdMap {
   504  		if member.memberId != ac.leaderMemberId {
   505  			member.close()
   506  		}
   507  	}
   508  }
   509  
   510  func (ac *AeronCluster) onSessionMessage(buffer *atomic.Buffer, offset, length int32, header *logbuffer.Header) {
   511  	if length < cluster.SessionMessageHeaderLength {
   512  		logger.Errorf("received invalid session message - length: %d", length)
   513  		return
   514  	}
   515  	leadershipTermId := buffer.GetInt64(offset)
   516  	clusterSessionId := buffer.GetInt64(offset + 8)
   517  	timestamp := buffer.GetInt64(offset + 16)
   518  	if ac.state != clientConnected {
   519  		logger.Debugf("received unexpected session message - leadershipTermId=%d clusterSessionId=%d state=%v",
   520  			leadershipTermId, clusterSessionId, ac.state)
   521  	} else if clusterSessionId == ac.clusterSessionId {
   522  		ac.egressListener.OnMessage(ac, timestamp, buffer, offset+cluster.SessionMessageHeaderLength,
   523  			length-cluster.SessionMessageHeaderLength, header)
   524  	} else {
   525  		logger.Debugf("received unexpected session msg - leaderTermId=%d targetSessionId=%d thisSessionId=%d",
   526  			leadershipTermId, clusterSessionId, ac.clusterSessionId)
   527  	}
   528  }
   529  
   530  func (ac *AeronCluster) sendCloseSession() {
   531  	buf := codecs.MakeClusterMessageBuffer(cluster.SessionCloseRequestTemplateId, 16)
   532  	buf.PutInt64(cluster.SBEHeaderLength, ac.leadershipTermId)
   533  	buf.PutInt64(cluster.SBEHeaderLength+8, ac.clusterSessionId)
   534  	for i := 0; i < 3; i++ {
   535  		if result := ac.ingressPub.Offer(buf, 0, buf.Capacity(), nil); result >= 0 {
   536  			return
   537  		}
   538  		ac.opts.IdleStrategy.Idle(0)
   539  	}
   540  }
   541  
   542  func (member *memberIngress) close() {
   543  	if member.publication != nil {
   544  		if err := member.publication.Close(); err != nil {
   545  			logger.Warningf("error closing member publication, memberId=%d endpoint=%s: %v",
   546  				member.memberId, member.endpoint, err)
   547  		}
   548  		member.publication = nil
   549  	}
   550  }