mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 10:27:26 +08:00
* wip: conversion of hotstuff from flow into Q-oriented model * bulk of tests * remaining non-integration tests * add integration test, adjust log interface, small tweaks * further adjustments, restore full pacemaker shape * add component lifecycle management+supervisor * further refinements * resolve timeout hanging * mostly finalized state for consensus * bulk of engine swap out * lifecycle-ify most types * wiring nearly complete, missing needed hooks for proposals * plugged in, vetting message validation paths * global consensus, plugged in and verified * app shard now wired in too * do not decode empty keys.yml (#456) * remove obsolete engine.maxFrames config parameter (#454) * default to Info log level unless debug is enabled (#453) * respect config's "logging" section params, remove obsolete single-file logging (#452) * Trivial code cleanup aiming to reduce Go compiler warnings (#451) * simplify range traversal * simplify channel read for single select case * delete rand.Seed() deprecated in Go 1.20 and no-op as of Go 1.24 * simplify range traversal * simplify channel read for single select case * remove redundant type from array * simplify range traversal * simplify channel read for single select case * RC slate * finalize 2.1.0.5 * Update comments in StrictMonotonicCounter Fix comment formatting and clarify description. --------- Co-authored-by: Black Swan <3999712+blacks1ne@users.noreply.github.com>
113 lines
3.3 KiB
Go
113 lines
3.3 KiB
Go
package lifecycle
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"runtime"
|
|
|
|
"go.uber.org/atomic"
|
|
)
|
|
|
|
// Signaler sends the error out.
|
|
type Signaler struct {
|
|
errChan chan error
|
|
errThrown *atomic.Bool
|
|
}
|
|
|
|
func NewSignaler() (*Signaler, <-chan error) {
|
|
errChan := make(chan error, 1)
|
|
return &Signaler{
|
|
errChan: errChan,
|
|
errThrown: atomic.NewBool(false),
|
|
}, errChan
|
|
}
|
|
|
|
// Throw is a narrow drop-in replacement for panic, log.Fatal, log.Panic, etc
|
|
// anywhere there's something connected to the error channel. It only sends
|
|
// the first error it is called with to the error channel, and logs subsequent
|
|
// errors as unhandled.
|
|
func (s *Signaler) Throw(err error) {
|
|
defer runtime.Goexit()
|
|
if s.errThrown.CompareAndSwap(false, true) {
|
|
s.errChan <- err
|
|
close(s.errChan)
|
|
} else {
|
|
// TODO: we simply log the unhandled fatal to stderr for now, but we should
|
|
// probably allow the user to customize the logger / logging format used
|
|
log.New(os.Stderr, "", log.LstdFlags).Println(
|
|
fmt.Errorf("unhandled fatal: %w", err),
|
|
)
|
|
}
|
|
}
|
|
|
|
// SignalerContext is a constrained interface to provide a drop-in replacement
|
|
// for context.Context including in interfaces that compose it.
|
|
type SignalerContext interface {
|
|
context.Context
|
|
Throw(err error) // delegates to the signaler
|
|
}
|
|
|
|
// SignalerContextKey represents the key type for retrieving a SignalerContext
|
|
// from a value `context.Context`.
|
|
type SignalerContextKey struct{}
|
|
|
|
// private, to force context derivation / WithSignaler
|
|
type signalerCtx struct {
|
|
context.Context
|
|
*Signaler
|
|
}
|
|
|
|
// WithSignaler is the One True Way of getting a SignalerContext.
|
|
func WithSignaler(parent context.Context) (SignalerContext, <-chan error) {
|
|
sig, errChan := NewSignaler()
|
|
return &signalerCtx{parent, sig}, errChan
|
|
}
|
|
|
|
// WithSignalerContext wraps `SignalerContext` using `context.WithValue` so it
|
|
// can later be used with `Throw`.
|
|
func WithSignalerContext(
|
|
parent context.Context,
|
|
ctx SignalerContext,
|
|
) context.Context {
|
|
return context.WithValue(parent, SignalerContextKey{}, ctx)
|
|
}
|
|
|
|
// Throw enables throwing a fatal error using any context.Context.
|
|
//
|
|
// If we have an SignalerContext, we can directly ctx.Throw.
|
|
// But a lot of library methods expect context.Context, & we want to pass the
|
|
// same w/o boilerplate. Moreover, we could have built with:
|
|
//
|
|
// context.WithCancel(lifecycle.WithSignaler(ctx, sig)),
|
|
//
|
|
// "downcasting" to context.Context. Yet, we can still type-assert and recover.
|
|
//
|
|
// Throw can be a drop-in replacement anywhere we have a context.Context likely
|
|
// to support signals. IT WILL PANIC IF THE CONTEXT DOES NOT SUPPORT SIGNALS
|
|
func Throw(ctx context.Context, err error) {
|
|
signalerAbleContext, ok := ctx.Value(SignalerContextKey{}).(SignalerContext)
|
|
if ok {
|
|
signalerAbleContext.Throw(err)
|
|
} else {
|
|
// Be spectacular on how this does not -but should- handle fatals:
|
|
log.Fatalf(
|
|
"fatal error: signaler not found for context, please implement! Unhandled fatal error: %v",
|
|
err,
|
|
)
|
|
}
|
|
}
|
|
|
|
// WithSignallerAndCancel returns an fatal context, the cancel function for the
|
|
// context, and the error channel for the context.
|
|
func WithSignallerAndCancel(ctx context.Context) (
|
|
SignalerContext,
|
|
context.CancelFunc,
|
|
<-chan error,
|
|
) {
|
|
parent, cancel := context.WithCancel(ctx)
|
|
fatalCtx, errCh := WithSignaler(parent)
|
|
return fatalCtx, cancel, errCh
|
|
}
|