dubbo.apache.org/dubbo-go/v3@v3.1.1/protocol/dubbo/dubbo_invoker.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  package dubbo
    19  
    20  import (
    21  	"context"
    22  	"strconv"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  )
    27  
    28  import (
    29  	"github.com/dubbogo/gost/log/logger"
    30  
    31  	"github.com/opentracing/opentracing-go"
    32  )
    33  
    34  import (
    35  	"dubbo.apache.org/dubbo-go/v3/common"
    36  	"dubbo.apache.org/dubbo-go/v3/common/constant"
    37  	"dubbo.apache.org/dubbo-go/v3/config"
    38  	"dubbo.apache.org/dubbo-go/v3/protocol"
    39  	"dubbo.apache.org/dubbo-go/v3/protocol/invocation"
    40  	"dubbo.apache.org/dubbo-go/v3/remoting"
    41  )
    42  
    43  var attachmentKey = []string{
    44  	constant.InterfaceKey, constant.GroupKey, constant.TokenKey, constant.TimeoutKey,
    45  	constant.VersionKey,
    46  }
    47  
    48  // DubboInvoker is implement of protocol.Invoker. A dubboInvoker refers to one service and ip.
    49  type DubboInvoker struct {
    50  	protocol.BaseInvoker
    51  	clientGuard *sync.RWMutex // the exchange layer, it is focus on network communication.
    52  	client      *remoting.ExchangeClient
    53  	quitOnce    sync.Once
    54  	timeout     time.Duration // timeout for service(interface) level.
    55  }
    56  
    57  // NewDubboInvoker constructor
    58  func NewDubboInvoker(url *common.URL, client *remoting.ExchangeClient) *DubboInvoker {
    59  	rt := config.GetConsumerConfig().RequestTimeout
    60  
    61  	timeout := url.GetParamDuration(constant.TimeoutKey, rt)
    62  	di := &DubboInvoker{
    63  		BaseInvoker: *protocol.NewBaseInvoker(url),
    64  		clientGuard: &sync.RWMutex{},
    65  		client:      client,
    66  		timeout:     timeout,
    67  	}
    68  
    69  	return di
    70  }
    71  
    72  func (di *DubboInvoker) setClient(client *remoting.ExchangeClient) {
    73  	di.clientGuard.Lock()
    74  	defer di.clientGuard.Unlock()
    75  
    76  	di.client = client
    77  }
    78  
    79  func (di *DubboInvoker) getClient() *remoting.ExchangeClient {
    80  	di.clientGuard.RLock()
    81  	defer di.clientGuard.RUnlock()
    82  
    83  	return di.client
    84  }
    85  
    86  // Invoke call remoting.
    87  func (di *DubboInvoker) Invoke(ctx context.Context, ivc protocol.Invocation) protocol.Result {
    88  	var (
    89  		err    error
    90  		result protocol.RPCResult
    91  	)
    92  	if !di.BaseInvoker.IsAvailable() {
    93  		// Generally, the case will not happen, because the invoker has been removed
    94  		// from the invoker list before destroy,so no new request will enter the destroyed invoker
    95  		logger.Warnf("this dubboInvoker is destroyed")
    96  		result.Err = protocol.ErrDestroyedInvoker
    97  		return &result
    98  	}
    99  
   100  	di.clientGuard.RLock()
   101  	defer di.clientGuard.RUnlock()
   102  
   103  	if di.client == nil {
   104  		result.Err = protocol.ErrClientClosed
   105  		logger.Debugf("result.Err: %v", result.Err)
   106  		return &result
   107  	}
   108  
   109  	if !di.BaseInvoker.IsAvailable() {
   110  		// Generally, the case will not happen, because the invoker has been removed
   111  		// from the invoker list before destroy,so no new request will enter the destroyed invoker
   112  		logger.Warnf("this dubboInvoker is destroying")
   113  		result.Err = protocol.ErrDestroyedInvoker
   114  		return &result
   115  	}
   116  
   117  	inv := ivc.(*invocation.RPCInvocation)
   118  	// init param
   119  	inv.SetAttachment(constant.PathKey, di.GetURL().GetParam(constant.InterfaceKey, ""))
   120  	for _, k := range attachmentKey {
   121  		if v := di.GetURL().GetParam(k, ""); len(v) > 0 {
   122  			inv.SetAttachment(k, v)
   123  		}
   124  	}
   125  
   126  	// put the ctx into attachment
   127  	di.appendCtx(ctx, inv)
   128  
   129  	url := di.GetURL()
   130  	// default hessian2 serialization, compatible
   131  	if url.GetParam(constant.SerializationKey, "") == "" {
   132  		url.SetParam(constant.SerializationKey, constant.Hessian2Serialization)
   133  	}
   134  	// async
   135  	async, err := strconv.ParseBool(inv.GetAttachmentWithDefaultValue(constant.AsyncKey, "false"))
   136  	if err != nil {
   137  		logger.Errorf("ParseBool - error: %v", err)
   138  		async = false
   139  	}
   140  	// response := NewResponse(inv.Reply(), nil)
   141  	rest := &protocol.RPCResult{}
   142  	timeout := di.getTimeout(inv)
   143  	if async {
   144  		if callBack, ok := inv.CallBack().(func(response common.CallbackResponse)); ok {
   145  			result.Err = di.client.AsyncRequest(&ivc, url, timeout, callBack, rest)
   146  		} else {
   147  			result.Err = di.client.Send(&ivc, url, timeout)
   148  		}
   149  	} else {
   150  		if inv.Reply() == nil {
   151  			result.Err = protocol.ErrNoReply
   152  		} else {
   153  			result.Err = di.client.Request(&ivc, url, timeout, rest)
   154  		}
   155  	}
   156  	if result.Err == nil {
   157  		result.Rest = inv.Reply()
   158  		result.Attrs = rest.Attrs
   159  	}
   160  
   161  	return &result
   162  }
   163  
   164  // get timeout including methodConfig
   165  func (di *DubboInvoker) getTimeout(ivc *invocation.RPCInvocation) time.Duration {
   166  	timeout := di.timeout                                                //default timeout
   167  	if attachTimeout, ok := ivc.GetAttachment(constant.TimeoutKey); ok { //check invocation timeout
   168  		timeout, _ = time.ParseDuration(attachTimeout)
   169  	} else { // check method timeout
   170  		methodName := ivc.MethodName()
   171  		if di.GetURL().GetParamBool(constant.GenericKey, false) {
   172  			methodName = ivc.Arguments()[0].(string)
   173  		}
   174  		mTimeout := di.GetURL().GetParam(strings.Join([]string{constant.MethodKeys, methodName, constant.TimeoutKey}, "."), "")
   175  		if len(mTimeout) != 0 {
   176  			timeout, _ = time.ParseDuration(mTimeout)
   177  		}
   178  	}
   179  	// set timeout into invocation
   180  	ivc.SetAttachment(constant.TimeoutKey, strconv.Itoa(int(timeout.Nanoseconds())))
   181  	return timeout
   182  }
   183  
   184  func (di *DubboInvoker) IsAvailable() bool {
   185  	client := di.getClient()
   186  	if client != nil {
   187  		return client.IsAvailable()
   188  	}
   189  
   190  	return false
   191  }
   192  
   193  // Destroy destroy dubbo client invoker.
   194  func (di *DubboInvoker) Destroy() {
   195  	di.quitOnce.Do(func() {
   196  		di.BaseInvoker.Destroy()
   197  		client := di.getClient()
   198  		if client != nil {
   199  			activeNumber := client.DecreaseActiveNumber()
   200  			di.setClient(nil)
   201  			if activeNumber == 0 {
   202  				exchangeClientMap.Delete(di.GetURL().Location)
   203  				client.Close()
   204  			}
   205  		}
   206  	})
   207  }
   208  
   209  // Finally, I made the decision that I don't provide a general way to transfer the whole context
   210  // because it could be misused. If the context contains to many key-value pairs, the performance will be much lower.
   211  func (di *DubboInvoker) appendCtx(ctx context.Context, ivc *invocation.RPCInvocation) {
   212  	// inject opentracing ctx
   213  	currentSpan := opentracing.SpanFromContext(ctx)
   214  	if currentSpan != nil {
   215  		err := injectTraceCtx(currentSpan, ivc)
   216  		if err != nil {
   217  			logger.Errorf("Could not inject the span context into attachments: %v", err)
   218  		}
   219  	}
   220  }