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 }