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  }