@@ -23,16 +23,19 @@ type VshardMode string
23
23
const (
24
24
ReadMode VshardMode = "read"
25
25
WriteMode VshardMode = "write"
26
+
27
+ // callTimeoutDefault is a default timeout when no timeout is provided
28
+ callTimeoutDefault = 500 * time .Millisecond
26
29
)
27
30
28
31
func (c VshardMode ) String () string {
29
32
return string (c )
30
33
}
31
34
32
35
type vshardStorageCallResponseProto struct {
33
- assertError * assertError // not nil if there is assert error
34
- vshardError * StorageCallVShardError // not nil if there is vshard response
35
- data [] interface {} // raw response data
36
+ AssertError * assertError // not nil if there is assert error
37
+ VshardError * StorageCallVShardError // not nil if there is vshard response
38
+ CallResp VshardCallResp
36
39
}
37
40
38
41
func (r * vshardStorageCallResponseProto ) DecodeMsgpack (d * msgpack.Decoder ) error {
@@ -88,7 +91,7 @@ func (r *vshardStorageCallResponseProto) DecodeMsgpack(d *msgpack.Decoder) error
88
91
return fmt .Errorf ("failed to decode storage vshard error: %w" , err )
89
92
}
90
93
91
- r .vshardError = & vshardError
94
+ r .VshardError = & vshardError
92
95
93
96
return nil
94
97
}
@@ -110,20 +113,19 @@ func (r *vshardStorageCallResponseProto) DecodeMsgpack(d *msgpack.Decoder) error
110
113
return fmt .Errorf ("failed to decode storage assert error: %w" , err )
111
114
}
112
115
113
- r .assertError = & assertError
116
+ r .AssertError = & assertError
114
117
115
118
return nil
116
119
}
117
120
118
121
// isVShardRespOk is true
119
- r .data = make ([]interface {}, 0 , respArrayLen - 1 )
120
-
122
+ r .CallResp .data = make ([]msgpack.RawMessage , 0 , respArrayLen - 1 )
121
123
for i := 1 ; i < respArrayLen ; i ++ {
122
- elem , err := d .DecodeInterface ()
124
+ elem , err := d .DecodeRaw ()
123
125
if err != nil {
124
- return fmt .Errorf ("failed to decode into interface element #%d of response array" , i + 1 )
126
+ return fmt .Errorf ("failed to decode into msgpack.RawMessage element #%d of response array" , i - 1 )
125
127
}
126
- r .data = append (r .data , elem )
128
+ r .CallResp . data = append (r . CallResp .data , elem )
127
129
}
128
130
129
131
return nil
@@ -176,54 +178,113 @@ type CallOpts struct {
176
178
Timeout time.Duration
177
179
}
178
180
179
- // revive warns us: time-naming: var CallTimeoutMin is of type time.Duration; don't use unit-specific suffix "Min".
180
- // But the original lua vshard implementation uses this naming, so we use it too.
181
- //
182
- //nolint:revive
183
- const CallTimeoutMin = time .Second / 2
181
+ type VshardCallMode int
184
182
185
- // RouterCallImpl Perform shard operation function will restart operation
186
- // after wrong bucket response until timeout is reached
187
- func (r * Router ) RouterCallImpl (ctx context.Context ,
188
- bucketID uint64 ,
189
- opts CallOpts ,
190
- fnc string ,
191
- args interface {}) (interface {}, StorageResultTypedFunc , error ) {
183
+ const (
184
+ VshardCallModeRO VshardCallMode = iota
185
+ VshardCallModeRW
186
+ VshardCallModeRE
187
+ VshardCallModeBRO
188
+ VshardCallModeBRE
189
+ )
190
+
191
+ type VshardCallOptions struct {
192
+ Timeout time.Duration
193
+ }
194
+
195
+ type VshardCallResp struct {
196
+ data []msgpack.RawMessage
197
+ }
198
+
199
+ func (r VshardCallResp ) Get () ([]interface {}, error ) {
200
+ resp := make ([]interface {}, 0 , len (r .data ))
201
+
202
+ for i , rawMessage := range r .data {
203
+ var v interface {}
204
+ if err := msgpack .Unmarshal (rawMessage , & v ); err != nil {
205
+ return nil , fmt .Errorf ("failed to decode into interface element #%d of response array: %w" , i , err )
206
+ }
207
+ resp = append (resp , v )
208
+ }
192
209
210
+ return resp , nil
211
+ }
212
+
213
+ func (r VshardCallResp ) GetTyped (result []interface {}) error {
214
+ minLen := len (result )
215
+ if dataLen := len (r .data ); dataLen < minLen {
216
+ minLen = dataLen
217
+ }
218
+
219
+ for i := 0 ; i < minLen ; i ++ {
220
+ if err := msgpack .Unmarshal (r .data [i ], result [i ]); err != nil {
221
+ return fmt .Errorf ("failed to decode into result[%d] element #%d of response array: %w" , i , i , err )
222
+ }
223
+ }
224
+
225
+ return nil
226
+ }
227
+
228
+ func (r * Router ) Call (ctx context.Context , bucketID uint64 , mode VshardCallMode ,
229
+ fnc string , args interface {}, opts VshardCallOptions ) (VshardCallResp , error ) {
193
230
const vshardStorageClientCall = "vshard.storage.call"
194
231
195
232
if bucketID < 1 || r .cfg .TotalBucketCount < bucketID {
196
- return nil , nil , fmt .Errorf ("bucket id is out of range: %d (total %d)" , bucketID , r .cfg .TotalBucketCount )
233
+ return VshardCallResp {} , fmt .Errorf ("bucket id is out of range: %d (total %d)" , bucketID , r .cfg .TotalBucketCount )
197
234
}
198
235
199
- if opts .Timeout == 0 {
200
- opts .Timeout = CallTimeoutMin
236
+ var poolMode pool.Mode
237
+ var vshardMode VshardMode
238
+
239
+ switch mode {
240
+ case VshardCallModeRO :
241
+ poolMode , vshardMode = pool .RO , ReadMode
242
+ case VshardCallModeRW :
243
+ poolMode , vshardMode = pool .RW , WriteMode
244
+ case VshardCallModeRE :
245
+ // poolMode, vshardMode = pool.PreferRO, ReadMode
246
+ // since go-tarantool always use balance=true politic,
247
+ // we can't support this case until: https://github.com/tarantool/go-tarantool/issues/400
248
+ return VshardCallResp {}, fmt .Errorf ("mode VshardCallModeRE is not supported yet" )
249
+ case VshardCallModeBRO :
250
+ poolMode , vshardMode = pool .ANY , ReadMode
251
+ case VshardCallModeBRE :
252
+ poolMode , vshardMode = pool .PreferRO , ReadMode
253
+ default :
254
+ return VshardCallResp {}, fmt .Errorf ("unknown VshardCallMode(%d)" , mode )
201
255
}
202
256
203
- timeout := opts .Timeout
204
- timeStart := time .Now ()
257
+ timeout := callTimeoutDefault
258
+ if opts .Timeout > 0 {
259
+ timeout = opts .Timeout
260
+ }
205
261
206
- req := tarantool .NewCallRequest (vshardStorageClientCall )
207
- req = req .Context (ctx )
208
- req = req .Args ([]interface {}{
209
- bucketID ,
210
- opts .VshardMode .String (),
211
- fnc ,
212
- args ,
213
- })
262
+ ctx , cancel := context .WithTimeout (ctx , timeout )
263
+ defer cancel ()
264
+
265
+ tntReq := tarantool .NewCallRequest (vshardStorageClientCall ).
266
+ Context (ctx ).
267
+ Args ([]interface {}{
268
+ bucketID ,
269
+ vshardMode ,
270
+ fnc ,
271
+ args ,
272
+ })
273
+
274
+ requestStartTime := time .Now ()
214
275
215
276
var err error
216
277
217
278
for {
218
- if since := time .Since (timeStart ); since > timeout {
219
- r .metrics ().RequestDuration (since , false , false )
279
+ if spent := time .Since (requestStartTime ); spent > timeout {
280
+ r .metrics ().RequestDuration (spent , false , false )
220
281
221
- r .log ().Debugf (ctx , "Return result on timeout; since %s of timeout %s" , since , timeout )
282
+ r .log ().Debugf (ctx , "Return result on timeout; spent %s of timeout %s" , spent , timeout )
222
283
if err == nil {
223
284
err = fmt .Errorf ("cant get call cause call impl timeout" )
224
285
}
225
286
226
- return nil , nil , err
287
+ return VshardCallResp {} , err
227
288
}
228
289
229
290
var rs * Replicaset
@@ -243,18 +304,20 @@ func (r *Router) RouterCallImpl(ctx context.Context,
243
304
244
305
r .log ().Infof (ctx , "Try call %s on replicaset %s for bucket %d" , fnc , rs .info .Name , bucketID )
245
306
246
- future := rs .conn .Do (req , opts .PoolMode )
247
-
248
307
var storageCallResponse vshardStorageCallResponseProto
249
- err = future .GetTyped (& storageCallResponse )
308
+ err = rs . conn . Do ( tntReq , poolMode ) .GetTyped (& storageCallResponse )
250
309
if err != nil {
251
- return nil , nil , fmt .Errorf ("got error on future.Get (): %w" , err )
310
+ return VshardCallResp {}, fmt .Errorf ("got error on future.GetTyped (): %w" , err )
252
311
}
253
312
254
- r .log ().Debugf (ctx , "Got call result response data %v" , storageCallResponse .data )
313
+ r .log ().Debugf (ctx , "Got call result response data %+v" , storageCallResponse )
314
+
315
+ if storageCallResponse .AssertError != nil {
316
+ return VshardCallResp {}, fmt .Errorf ("%s: %s failed: %+v" , vshardStorageClientCall , fnc , storageCallResponse .AssertError )
317
+ }
255
318
256
- if storageCallResponse .vshardError != nil {
257
- vshardError := storageCallResponse .vshardError
319
+ if storageCallResponse .VshardError != nil {
320
+ vshardError := storageCallResponse .VshardError
258
321
259
322
switch vshardError .Name {
260
323
case VShardErrNameWrongBucket , VShardErrNameBucketIsLocked :
@@ -264,7 +327,7 @@ func (r *Router) RouterCallImpl(ctx context.Context,
264
327
if vshardError .Destination != "" {
265
328
destinationUUID , err := uuid .Parse (vshardError .Destination )
266
329
if err != nil {
267
- return nil , nil , fmt .Errorf ("protocol violation %s: malformed destination %w: %w" ,
330
+ return VshardCallResp {} , fmt .Errorf ("protocol violation %s: malformed destination %w: %w" ,
268
331
vshardStorageClientCall , vshardError , err )
269
332
}
270
333
@@ -288,8 +351,8 @@ func (r *Router) RouterCallImpl(ctx context.Context,
288
351
const defaultPoolingPause = 50 * time .Millisecond
289
352
time .Sleep (defaultPoolingPause )
290
353
291
- if time .Since (timeStart ) > timeout {
292
- return nil , nil , vshardError
354
+ if spent := time .Since (requestStartTime ); spent > timeout {
355
+ return VshardCallResp {} , vshardError
293
356
}
294
357
}
295
358
}
@@ -308,34 +371,98 @@ func (r *Router) RouterCallImpl(ctx context.Context,
308
371
// There is a comment why lua vshard router doesn't retry:
309
372
// https://github.com/tarantool/vshard/blob/b6fdbe950a2e4557f05b83bd8b846b126ec3724e/vshard/router/init.lua#L697
310
373
r .BucketReset (bucketID )
311
- return nil , nil , vshardError
374
+ return VshardCallResp {} , vshardError
312
375
case VShardErrNameNonMaster :
313
376
// vshard.storage has returned NON_MASTER error, lua vshard router updates info about master in this case:
314
377
// See: https://github.com/tarantool/vshard/blob/b6fdbe950a2e4557f05b83bd8b846b126ec3724e/vshard/router/init.lua#L704.
315
378
// Since we use go-tarantool library, and go-tarantool library doesn't provide API to update info about current master,
316
379
// we just return this error as is.
317
- return nil , nil , vshardError
380
+ return VshardCallResp {} , vshardError
318
381
default :
319
- return nil , nil , vshardError
382
+ return VshardCallResp {} , vshardError
320
383
}
321
384
}
322
385
323
- if storageCallResponse .assertError != nil {
324
- return nil , nil , fmt .Errorf ("%s: %s failed: %+v" , vshardStorageClientCall , fnc , storageCallResponse .assertError )
325
- }
386
+ r .metrics ().RequestDuration (time .Since (requestStartTime ), true , false )
326
387
327
- r .metrics ().RequestDuration (time .Since (timeStart ), true , false )
388
+ return storageCallResponse .CallResp , nil
389
+ }
390
+ }
328
391
329
- return storageCallResponse .data , func (result interface {}) error {
330
- if len (storageCallResponse .data ) == 0 {
331
- return nil
332
- }
392
+ func (r * Router ) CallRO (ctx context.Context , bucketID uint64 ,
393
+ fnc string , args interface {}, opts VshardCallOptions ) (VshardCallResp , error ) {
394
+ return r .Call (ctx , bucketID , VshardCallModeRO , fnc , args , opts )
395
+ }
396
+
397
+ func (r * Router ) CallRW (ctx context.Context , bucketID uint64 ,
398
+ fnc string , args interface {}, opts VshardCallOptions ) (VshardCallResp , error ) {
399
+ return r .Call (ctx , bucketID , VshardCallModeRW , fnc , args , opts )
400
+ }
401
+
402
+ func (r * Router ) CallRE (ctx context.Context , bucketID uint64 ,
403
+ fnc string , args interface {}, opts VshardCallOptions ) (VshardCallResp , error ) {
404
+ return r .Call (ctx , bucketID , VshardCallModeRE , fnc , args , opts )
405
+ }
406
+
407
+ func (r * Router ) CallBRO (ctx context.Context , bucketID uint64 ,
408
+ fnc string , args interface {}, opts VshardCallOptions ) (VshardCallResp , error ) {
409
+ return r .Call (ctx , bucketID , VshardCallModeBRO , fnc , args , opts )
410
+ }
411
+
412
+ func (r * Router ) CallBRE (ctx context.Context , bucketID uint64 ,
413
+ fnc string , args interface {}, opts VshardCallOptions ) (VshardCallResp , error ) {
414
+ return r .Call (ctx , bucketID , VshardCallModeBRE , fnc , args , opts )
415
+ }
333
416
334
- var stub bool
417
+ // RouterCallImpl Perform shard operation function will restart operation
418
+ // after wrong bucket response until timeout is reached
419
+ func (r * Router ) RouterCallImpl (ctx context.Context ,
420
+ bucketID uint64 ,
421
+ opts CallOpts ,
422
+ fnc string ,
423
+ args interface {}) (interface {}, StorageResultTypedFunc , error ) {
424
+
425
+ var vshardCallOpts = VshardCallOptions {
426
+ Timeout : opts .Timeout ,
427
+ }
335
428
336
- return future .GetTyped (& []interface {}{& stub , result })
337
- }, nil
429
+ var vshardCallMode VshardCallMode
430
+
431
+ switch opts .VshardMode {
432
+ case WriteMode :
433
+ vshardCallMode = VshardCallModeRW
434
+ case ReadMode :
435
+ switch opts .PoolMode {
436
+ case pool .ANY :
437
+ vshardCallMode = VshardCallModeBRO
438
+ case pool .RO :
439
+ vshardCallMode = VshardCallModeRO
440
+ case pool .RW :
441
+ return nil , nil , fmt .Errorf ("unexpected opts %+v" , opts )
442
+ case pool .PreferRO :
443
+ vshardCallMode = VshardCallModeBRE
444
+ case pool .PreferRW :
445
+ return nil , nil , fmt .Errorf ("unexpected opts %+v" , opts )
446
+ default :
447
+ return nil , nil , fmt .Errorf ("unexpected opts.PoolMode %v" , opts .PoolMode )
448
+ }
449
+ default :
450
+ return nil , nil , fmt .Errorf ("unexpected opts.VshardMode %v" , opts .VshardMode )
451
+ }
452
+
453
+ vshardCallResp , err := r .Call (ctx , bucketID , vshardCallMode , fnc , args , vshardCallOpts )
454
+ if err != nil {
455
+ return nil , nil , err
338
456
}
457
+
458
+ data , err := vshardCallResp .Get ()
459
+ if err != nil {
460
+ return nil , nil , err
461
+ }
462
+
463
+ return data , func (result interface {}) error {
464
+ return vshardCallResp .GetTyped ([]interface {}{result })
465
+ }, nil
339
466
}
340
467
341
468
// RouterMapCallRWImpl perform call function on all masters in the cluster
@@ -349,7 +476,7 @@ func (r *Router) RouterMapCallRWImpl(
349
476
) (map [uuid.UUID ]interface {}, error ) {
350
477
const vshardStorageServiceCall = "vshard.storage._call"
351
478
352
- timeout := CallTimeoutMin
479
+ timeout := callTimeoutDefault
353
480
if opts .Timeout > 0 {
354
481
timeout = opts .Timeout
355
482
}
0 commit comments