DEV: Multi goroutine implementation

This commit is contained in:
David Zhou 2025-05-14 10:33:59 -04:00
parent ec1caf10ed
commit 1edc65acdc

View file

@ -193,6 +193,13 @@ type Firehose struct {
// Testing state, only used in tests and private configs // Testing state, only used in tests and private configs
testingBuffer *bytes.Buffer testingBuffer *bytes.Buffer
testingIgnoreGenesisBlock bool testingIgnoreGenesisBlock bool
// Worker queue for block printing
blockOutputQueue chan *blockOutput
flushOutputDone sync.WaitGroup
startBlockInitialized bool
startBlockNum uint64
startBlockCond *sync.Cond
} }
const FirehoseProtocolVersion = "3.0" const FirehoseProtocolVersion = "3.0"
@ -246,6 +253,8 @@ func NewFirehose(config *FirehoseConfig) *Firehose {
callStack: NewCallStack(), callStack: NewCallStack(),
deferredCallState: NewDeferredCallState(), deferredCallState: NewDeferredCallState(),
latestCallEnterSuicided: false, latestCallEnterSuicided: false,
startBlockCond: sync.NewCond(&sync.Mutex{}),
} }
if config.private != nil { if config.private != nil {
@ -258,9 +267,44 @@ func NewFirehose(config *FirehoseConfig) *Firehose {
if config.ConcurrentBlockFlushing { if config.ConcurrentBlockFlushing {
log.Info("Firehose concurrent block flushing enabled, starting block " + log.Info("Firehose concurrent block flushing enabled, starting block " +
"print worker goroutine") "print worker goroutine")
firehose.blockPrintQueue = make(chan *blockPrintJob, 100) const numWorkers = 10 // TODO: This becomes a parameter
firehose.blockFlushDone.Add(1) firehose.blockPrintQueue = make(chan *blockPrintJob, 100) // TODO: Optimal buffer size tbd
go firehose.blockPrintWorker() firehose.blockOutputQueue = make(chan *blockOutput, 100)
for i := 0; i < numWorkers; i++ {
firehose.blockFlushDone.Add(1)
go firehose.blockPrintWorker()
}
// Output channel to order the flushing linearly
firehose.flushOutputDone.Add(1)
go func() {
defer firehose.flushOutputDone.Done()
buffer := make(map[uint64][]byte)
firehose.startBlockCond.L.Lock()
for !firehose.startBlockInitialized {
firehose.startBlockCond.Wait()
}
nextExpected := firehose.startBlockNum
firehose.startBlockCond.L.Unlock()
for result := range firehose.blockOutputQueue {
buffer[result.blockNum] = result.data
for {
data, ok := buffer[nextExpected]
if !ok {
break
}
firehose.flushToFirehose(data)
delete(buffer, nextExpected)
nextExpected++
}
}
}()
} }
return firehose return firehose
@ -502,6 +546,14 @@ func (f *Firehose) OnBlockEnd(err error) {
// Flush block to firehose and optionally use goroutine // Flush block to firehose and optionally use goroutine
if f.concurrentBlockFlushing { if f.concurrentBlockFlushing {
f.startBlockCond.L.Lock()
if !f.startBlockInitialized {
f.startBlockNum = f.block.Number
f.startBlockInitialized = true
f.startBlockCond.Broadcast()
}
f.startBlockCond.L.Unlock()
job := &blockPrintJob{ job := &blockPrintJob{
block: f.block, block: f.block,
finality: f.blockFinality, finality: f.blockFinality,
@ -1856,14 +1908,13 @@ func (f *Firehose) panicInvalidState(msg string, callerSkip int) string {
// printBlockToFirehose is a helper function to print a block to Firehose protocl format. // printBlockToFirehose is a helper function to print a block to Firehose protocl format.
func (f *Firehose) printBlockToFirehose(block *pbeth.Block, finalityStatus *FinalityStatus) { func (f *Firehose) printBlockToFirehose(block *pbeth.Block, finalityStatus *FinalityStatus) {
var buf bytes.Buffer
marshalled, err := proto.Marshal(block) marshalled, err := proto.Marshal(block)
if err != nil { if err != nil {
panic(fmt.Errorf("failed to marshal block: %w", err)) panic(fmt.Errorf("failed to marshal block: %w", err))
} }
// TODO: If multiple goroutine, this becomes shared resource
f.outputBuffer.Reset()
previousHash := block.PreviousID() previousHash := block.PreviousID()
previousNum := 0 previousNum := 0
if block.Number > 0 { if block.Number > 0 {
@ -1882,9 +1933,9 @@ func (f *Firehose) printBlockToFirehose(block *pbeth.Block, finalityStatus *Fina
} }
// **Important* The final space in the Sprintf template is mandatory! // **Important* The final space in the Sprintf template is mandatory!
f.outputBuffer.WriteString(fmt.Sprintf("FIRE BLOCK %d %s %d %s %d %d ", block.Number, hex.EncodeToString(block.Hash), previousNum, previousHash, libNum, block.MustTime().UnixNano())) buf.WriteString(fmt.Sprintf("FIRE BLOCK %d %s %d %s %d %d ", block.Number, hex.EncodeToString(block.Hash), previousNum, previousHash, libNum, block.MustTime().UnixNano()))
encoder := base64.NewEncoder(base64.StdEncoding, f.outputBuffer) encoder := base64.NewEncoder(base64.StdEncoding, &buf)
if _, err = encoder.Write(marshalled); err != nil { if _, err = encoder.Write(marshalled); err != nil {
panic(fmt.Errorf("write to encoder should have been infaillible: %w", err)) panic(fmt.Errorf("write to encoder should have been infaillible: %w", err))
} }
@ -1893,9 +1944,16 @@ func (f *Firehose) printBlockToFirehose(block *pbeth.Block, finalityStatus *Fina
panic(fmt.Errorf("closing encoder should have been infaillible: %w", err)) panic(fmt.Errorf("closing encoder should have been infaillible: %w", err))
} }
f.outputBuffer.WriteString("\n") buf.WriteString("\n")
f.flushToFirehose(f.outputBuffer.Bytes()) if f.concurrentBlockFlushing {
f.blockOutputQueue <- &blockOutput{
blockNum: block.Number,
data: buf.Bytes(),
}
} else {
f.flushToFirehose(buf.Bytes())
}
} }
// printToFirehose is an easy way to print to Firehose format, it essentially // printToFirehose is an easy way to print to Firehose format, it essentially
@ -2850,6 +2908,14 @@ func (f *Firehose) CloseBlockPrintQueue() {
f.closeChannels.Do(func() { f.closeChannels.Do(func() {
close(f.blockPrintQueue) close(f.blockPrintQueue)
f.blockFlushDone.Wait() f.blockFlushDone.Wait()
close(f.blockOutputQueue)
f.flushOutputDone.Wait()
}) })
} }
} }
type blockOutput struct {
blockNum uint64
data []byte
}