github.com/decred/politeia@v1.4.0/politeiad/batch.go (about) 1 // Copyright (c) 2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 package main 5 6 import ( 7 "sync" 8 9 v2 "github.com/decred/politeia/politeiad/api/v2" 10 "github.com/decred/politeia/util" 11 ) 12 13 // pluginRead is the same function signature as the backendv2 PluginRead 14 // function. This allows test coverage to be added to the batch implementation 15 // using a custom pluginRead function setup for testing. 16 type pluginRead func(token []byte, pluginID, 17 cmd, payload string) (string, error) 18 19 // batch contains a batch of plugin commands and implements the methods that 20 // allow for the concurrent execution of these plugin commands. 21 type batch struct { 22 sync.Mutex 23 entries []batchEntry 24 } 25 26 // batchEntry contains a single plugin command and the reply/error that 27 // resulted from the execution of the plugin command. 28 type batchEntry struct { 29 cmd v2.PluginCmd 30 reply string // JSON encoded reply payload 31 err error // Only set if an error is encountered 32 } 33 34 // newBatch returns a new batch. 35 func newBatch(pluginCmds []v2.PluginCmd) *batch { 36 entries := make([]batchEntry, 0, len(pluginCmds)) 37 for _, cmd := range pluginCmds { 38 entries = append(entries, batchEntry{ 39 cmd: cmd, 40 }) 41 } 42 return &batch{ 43 entries: entries, 44 } 45 } 46 47 // execConcurrently executes the batch of plugin commands concurrently. 48 func (b *batch) execConcurrently(fn pluginRead) { 49 // Execute commands concurrently 50 var wg sync.WaitGroup 51 for i := 0; i < len(b.entries); i++ { 52 wg.Add(1) 53 go b.execReadCmd(fn, b.getCmd(i), i, &wg) 54 } 55 56 // Wait for all commands to finish executing 57 wg.Wait() 58 } 59 60 // execReadCmd executes a single plugin read-only command. 61 func (b *batch) execReadCmd(fn pluginRead, cmd v2.PluginCmd, index int, wg *sync.WaitGroup) { 62 // Decrement the wait group on exit 63 defer wg.Done() 64 65 // Decode the token. The token is optional 66 // for plugin reads. 67 var ( 68 token []byte 69 err error 70 ) 71 if cmd.Token != "" { 72 token, err = decodeTokenAnyLength(cmd.Token) 73 if err != nil { 74 // Invalid token 75 err = v2.UserErrorReply{ 76 ErrorCode: v2.ErrorCodeTokenInvalid, 77 ErrorContext: util.TokenRegexp(), 78 } 79 b.setReply(index, "", err) 80 return 81 } 82 } 83 84 // Execute the read command 85 reply, err := fn(token, cmd.ID, cmd.Command, cmd.Payload) 86 if err != nil { 87 b.setReply(index, "", err) 88 return 89 } 90 91 b.setReply(index, reply, nil) 92 } 93 94 // getCmd returns the PluginCmd at the provided index. 95 func (b *batch) getCmd(index int) v2.PluginCmd { 96 b.Lock() 97 defer b.Unlock() 98 99 return b.entries[index].cmd 100 } 101 102 // setReply sets the reply for the plugin command at that provided index. 103 func (b *batch) setReply(index int, reply string, err error) { 104 b.Lock() 105 defer b.Unlock() 106 107 c := b.entries[index] 108 c.reply = reply 109 c.err = err 110 b.entries[index] = c 111 }