github.com/tsuna/gohbase@v0.0.0-20250731002811-4ffcadfba63e/region/multi.go (about)

     1  // Copyright (C) 2017  The GoHBase Authors.  All rights reserved.
     2  // This file is part of GoHBase.
     3  // Use of this source code is governed by the Apache License 2.0
     4  // that can be found in the COPYING file.
     5  
     6  package region
     7  
     8  import (
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"sync"
    13  
    14  	"github.com/tsuna/gohbase/hrpc"
    15  	"github.com/tsuna/gohbase/pb"
    16  	"google.golang.org/protobuf/proto"
    17  )
    18  
    19  var multiPool = sync.Pool{
    20  	New: func() interface{} {
    21  		return &multi{}
    22  	},
    23  }
    24  
    25  func freeMulti(m *multi) {
    26  	// Set pointers to nil to allow the objects to be garbage
    27  	// collected
    28  	for i := range m.calls {
    29  		m.calls[i] = nil
    30  	}
    31  	m.calls = m.calls[:0]
    32  	// set m.regions to nil because the slice is not reused.
    33  	m.regions = nil
    34  	m.size = 0
    35  	multiPool.Put(m)
    36  }
    37  
    38  type multi struct {
    39  	size  int
    40  	calls []hrpc.Call
    41  	// regions preserves the order of regions to match against RegionActionResults
    42  	regions []hrpc.RegionInfo
    43  }
    44  
    45  func newMulti(queueSize int) *multi {
    46  	m := multiPool.Get().(*multi)
    47  	m.size = queueSize
    48  	return m
    49  }
    50  
    51  // Name returns the name of this RPC call.
    52  func (m *multi) Name() string {
    53  	return "Multi"
    54  }
    55  
    56  // Description returns the description of this RPC call.
    57  func (m *multi) Description() string {
    58  	return m.Name()
    59  }
    60  
    61  // ToProto converts all request in multi batch to a protobuf message.
    62  func (m *multi) ToProto() proto.Message {
    63  	msg, _, _ := m.toProto(false, nil)
    64  	return msg
    65  }
    66  
    67  type actions struct {
    68  	pbs        []*pb.Action
    69  	cellblocks [][]byte
    70  }
    71  
    72  func (m *multi) toProto(isCellblocks bool, cbs [][]byte) (proto.Message, [][]byte, uint32) {
    73  	// aggregate calls per region
    74  	actionsPerReg := map[hrpc.RegionInfo]*actions{}
    75  	var size uint32
    76  
    77  	pbActions := make([]pb.Action, len(m.calls))
    78  	indices := make([]uint32, len(m.calls))
    79  	for i, c := range m.calls {
    80  		if c.Context().Err() != nil {
    81  			// context has expired, don't bother sending it
    82  			m.calls[i] = nil
    83  			continue
    84  		}
    85  
    86  		as, ok := actionsPerReg[c.Region()]
    87  		if !ok {
    88  			as = &actions{}
    89  			actionsPerReg[c.Region()] = as
    90  		}
    91  
    92  		var msg proto.Message
    93  		if s, ok := c.(canSerializeCellBlocks); isCellblocks && ok && s.CellBlocksEnabled() {
    94  			var sz uint32
    95  			msg, as.cellblocks, sz = s.SerializeCellBlocks(as.cellblocks)
    96  			size += sz
    97  		} else {
    98  			msg = c.ToProto()
    99  		}
   100  
   101  		a := &pbActions[i]
   102  		indices[i] = uint32(i) + 1 // +1 because 0 index means there's no index
   103  		a.Index = &indices[i]
   104  
   105  		switch r := msg.(type) {
   106  		case *pb.GetRequest:
   107  			a.Get = r.Get
   108  		case *pb.MutateRequest:
   109  			a.Mutation = r.Mutation
   110  		default:
   111  			panic(fmt.Sprintf("unsupported call type for Multi: %T", c))
   112  		}
   113  
   114  		as.pbs = append(as.pbs, a)
   115  	}
   116  
   117  	// construct the multi proto
   118  	ra := make([]*pb.RegionAction, len(actionsPerReg))
   119  	m.regions = make([]hrpc.RegionInfo, len(actionsPerReg))
   120  
   121  	// grow cbs
   122  	var cbCount int
   123  	for _, as := range actionsPerReg {
   124  		cbCount += len(as.cellblocks)
   125  	}
   126  	cbs = append(cbs, make([][]byte, cbCount)...)[:len(cbs)]
   127  
   128  	i := 0
   129  	for r, as := range actionsPerReg {
   130  		ra[i] = &pb.RegionAction{
   131  			Region: &pb.RegionSpecifier{
   132  				Type:  hrpc.RegionSpecifierRegionName,
   133  				Value: r.Name(),
   134  			},
   135  			Action: as.pbs,
   136  		}
   137  		cbs = append(cbs, as.cellblocks...)
   138  		// Track the order of RegionActions,
   139  		// so that we can handle whole region exceptions.
   140  		m.regions[i] = r
   141  		i++
   142  	}
   143  	return &pb.MultiRequest{RegionAction: ra}, cbs, size
   144  }
   145  
   146  func (m *multi) SerializeCellBlocks(cbs [][]byte) (proto.Message, [][]byte, uint32) {
   147  	return m.toProto(true, cbs)
   148  }
   149  
   150  func (m *multi) CellBlocksEnabled() bool {
   151  	// TODO: maybe have some global client option
   152  	return true
   153  }
   154  
   155  // NewResponse creates an empty protobuf message to read the response of this RPC.
   156  func (m *multi) NewResponse() proto.Message {
   157  	return &pb.MultiResponse{}
   158  }
   159  
   160  // DeserializeCellBlocks deserializes action results from cell blocks.
   161  func (m *multi) DeserializeCellBlocks(msg proto.Message, b []byte) (uint32, error) {
   162  	mr := msg.(*pb.MultiResponse)
   163  
   164  	var nread uint32
   165  	for _, rar := range mr.GetRegionActionResult() {
   166  		if e := rar.GetException(); e != nil {
   167  			if l := len(rar.GetResultOrException()); l != 0 {
   168  				return 0, fmt.Errorf(
   169  					"got exception for region, but still have %d result(s) returned from it", l)
   170  			}
   171  			continue
   172  		}
   173  
   174  		for _, roe := range rar.GetResultOrException() {
   175  			e := roe.GetException()
   176  			r := roe.GetResult()
   177  			i := roe.GetIndex()
   178  
   179  			if i == 0 {
   180  				return 0, errors.New("no index for result in multi response")
   181  			} else if r == nil && e == nil {
   182  				return 0, errors.New("no result or exception for action in multi response")
   183  			} else if r != nil && e != nil {
   184  				return 0, errors.New("got result and exception for action in multi response")
   185  			} else if e != nil {
   186  				continue
   187  			}
   188  
   189  			c := m.get(i)                     // TODO: maybe return error if it's out-of-bounds
   190  			d := c.(canDeserializeCellBlocks) // let it panic, because then it's our bug
   191  
   192  			response := c.NewResponse()
   193  			switch rsp := response.(type) {
   194  			case *pb.GetResponse:
   195  				rsp.Result = r
   196  			case *pb.MutateResponse:
   197  				rsp.Result = r
   198  			default:
   199  				panic(fmt.Sprintf("unsupported response type for Multi: %T", response))
   200  			}
   201  
   202  			// TODO: don't bother deserializing if the call's context has already expired
   203  			n, err := d.DeserializeCellBlocks(response, b[nread:])
   204  			if err != nil {
   205  				return 0, fmt.Errorf(
   206  					"error deserializing cellblocks for %q call as part of MultiResponse: %v",
   207  					c.Name(), err)
   208  			}
   209  			nread += n
   210  		}
   211  	}
   212  	return nread, nil
   213  }
   214  
   215  func (m *multi) returnResults(msg proto.Message, err error) {
   216  	defer freeMulti(m)
   217  
   218  	if err != nil {
   219  		for _, c := range m.calls {
   220  			if c == nil {
   221  				continue
   222  			}
   223  			c.ResultChan() <- hrpc.RPCResult{Error: err}
   224  		}
   225  		return
   226  	}
   227  
   228  	mr := msg.(*pb.MultiResponse)
   229  
   230  	// Here we can assume that everything has been deserialized correctly.
   231  	// Dispatch results to appropriate calls.
   232  	for i, rar := range mr.GetRegionActionResult() {
   233  		if e := rar.GetException(); e != nil {
   234  			// Got an exception for the whole region,
   235  			// fail all the calls for that region.
   236  			reg := m.regions[i]
   237  
   238  			err := exceptionToError(*e.Name, string(e.Value))
   239  			for _, c := range m.calls {
   240  				if c == nil {
   241  					continue
   242  				}
   243  				if c.Region() == reg {
   244  					c.ResultChan() <- hrpc.RPCResult{Error: err}
   245  				}
   246  			}
   247  			continue
   248  		}
   249  
   250  		for _, roe := range rar.GetResultOrException() {
   251  			i := roe.GetIndex()
   252  			e := roe.GetException()
   253  			r := roe.GetResult()
   254  
   255  			c := m.get(i)
   256  
   257  			// TODO: don't bother if the call's context has already expired
   258  
   259  			if e != nil {
   260  				c.ResultChan() <- hrpc.RPCResult{
   261  					Error: exceptionToError(*e.Name, string(e.Value)),
   262  				}
   263  				continue
   264  			}
   265  
   266  			response := c.NewResponse()
   267  			switch rsp := response.(type) {
   268  			case *pb.GetResponse:
   269  				rsp.Result = r
   270  			case *pb.MutateResponse:
   271  				rsp.Result = r
   272  			default:
   273  				panic(fmt.Sprintf("unsupported response type for Multi: %T", response))
   274  			}
   275  
   276  			c.ResultChan() <- hrpc.RPCResult{Msg: response}
   277  		}
   278  	}
   279  }
   280  
   281  // add adds the call and returns wether the batch is full.
   282  func (m *multi) add(calls []hrpc.Call) bool {
   283  	m.calls = append(m.calls, calls...)
   284  	return len(m.calls) >= m.size
   285  }
   286  
   287  // len returns number of batched calls.
   288  func (m *multi) len() int {
   289  	return len(m.calls)
   290  }
   291  
   292  // get retruns an rpc at index. Indicies start from 1 since 0 means that
   293  // region server didn't set an index for the action result.
   294  func (m *multi) get(i uint32) hrpc.Call {
   295  	if i == 0 {
   296  		panic("index cannot be 0")
   297  	}
   298  	return m.calls[i-1]
   299  }
   300  
   301  // Table is not supported for Multi.
   302  func (m *multi) Table() []byte {
   303  	panic("'Table' is not supported for 'Multi'")
   304  }
   305  
   306  // Reqion is not supported for Multi.
   307  func (m *multi) Region() hrpc.RegionInfo {
   308  	panic("'Region' is not supported for 'Multi'")
   309  }
   310  
   311  // SetRegion is not supported for Multi.
   312  func (m *multi) SetRegion(r hrpc.RegionInfo) {
   313  	panic("'SetRegion' is not supported for 'Multi'")
   314  }
   315  
   316  // ResultChan is not supported for Multi.
   317  func (m *multi) ResultChan() chan hrpc.RPCResult {
   318  	panic("'ResultChan' is not supported for 'Multi'")
   319  }
   320  
   321  // Context is not supported for Multi.
   322  func (m *multi) Context() context.Context {
   323  	return context.Background()
   324  }
   325  
   326  // String returns a description of this call
   327  func (m *multi) String() string {
   328  	return "MULTI"
   329  }
   330  
   331  // Key is not supported for Multi RPC.
   332  func (m *multi) Key() []byte {
   333  	panic("'Key' is not supported for 'Multi'")
   334  }
   335  
   336  // callCount returns how many calls are represented
   337  func (m *multi) callCount() int {
   338  	return len(m.calls)
   339  }