mirror of
https://github.com/safing/portbase
synced 2025-09-01 10:09:50 +00:00
Revamp module structure
- Add shutdown mechanics to module - Adapt dbmodule to new mechanics
This commit is contained in:
parent
090669728d
commit
402429cd70
11 changed files with 108 additions and 37 deletions
|
@ -13,7 +13,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
modules.Register("api", prep, start, nil, "core")
|
modules.Register("api", prep, start, stop, "base", "database", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
func prep() error {
|
func prep() error {
|
||||||
|
|
|
@ -22,7 +22,7 @@ func SetDataRoot(root *utils.DirStructure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
modules.Register("config", prep, start, nil, "core")
|
modules.Register("config", prep, start, nil, "base", "database")
|
||||||
}
|
}
|
||||||
|
|
||||||
func prep() error {
|
func prep() error {
|
||||||
|
|
|
@ -23,7 +23,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
modules.Register("random", prep, Start, stop)
|
modules.Register("random", prep, Start, stop, "base")
|
||||||
|
|
||||||
config.Register(&config.Option{
|
config.Register(&config.Option{
|
||||||
Name: "RNG Cipher",
|
Name: "RNG Cipher",
|
||||||
|
|
|
@ -2,51 +2,46 @@ package dbmodule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/safing/portbase/database"
|
"github.com/safing/portbase/database"
|
||||||
"github.com/safing/portbase/modules"
|
"github.com/safing/portbase/modules"
|
||||||
|
"github.com/safing/portbase/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
databaseDir string
|
databasePath string
|
||||||
shutdownSignal = make(chan struct{})
|
databaseStructureRoot *utils.DirStructure
|
||||||
maintenanceWg sync.WaitGroup
|
|
||||||
|
module *modules.Module
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetDatabaseLocation sets the location of the database. Must be called before modules.Start and will be overridden by command line options. Intended for unit tests.
|
func init() {
|
||||||
func SetDatabaseLocation(location string) {
|
module = modules.Register("database", prep, start, stop, "base")
|
||||||
databaseDir = location
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
// SetDatabaseLocation sets the location of the database for initialization. Supply either a path or dir structure.
|
||||||
flag.StringVar(&databaseDir, "db", "", "set database directory")
|
func SetDatabaseLocation(dirPath string, dirStructureRoot *utils.DirStructure) {
|
||||||
|
databasePath = dirPath
|
||||||
modules.Register("database", prep, start, stop)
|
databaseStructureRoot = dirStructureRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
func prep() error {
|
func prep() error {
|
||||||
if databaseDir == "" {
|
if databasePath == "" && databaseStructureRoot == nil {
|
||||||
return errors.New("no database location specified, set with `-db=/path/to/db`")
|
return errors.New("no database location specified")
|
||||||
}
|
|
||||||
ok := database.SetLocation(databaseDir)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("database location already set")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() error {
|
func start() error {
|
||||||
err := database.Initialize()
|
err := database.Initialize(databasePath, databaseStructureRoot)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
startMaintainer()
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startMaintainer()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func stop() error {
|
func stop() error {
|
||||||
close(shutdownSignal)
|
|
||||||
maintenanceWg.Wait()
|
|
||||||
return database.Shutdown()
|
return database.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func startMaintainer() {
|
func startMaintainer() {
|
||||||
maintenanceWg.Add(1)
|
module.AddWorkers(1)
|
||||||
go maintenanceWorker()
|
go maintenanceWorker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +37,8 @@ func maintenanceWorker() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("database: thorough maintenance error: %s", err)
|
log.Errorf("database: thorough maintenance error: %s", err)
|
||||||
}
|
}
|
||||||
case <-shutdownSignal:
|
case <-module.ShuttingDown():
|
||||||
maintenanceWg.Done()
|
module.FinishWorker()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ var (
|
||||||
databasesStructure *utils.DirStructure
|
databasesStructure *utils.DirStructure
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize initialized the database at the specified location. Supply either a path or dir structure.
|
// Initialize initializes the database at the specified location. Supply either a path or dir structure.
|
||||||
func Initialize(dirPath string, dirStructureRoot *utils.DirStructure) error {
|
func Initialize(dirPath string, dirStructureRoot *utils.DirStructure) error {
|
||||||
if initialized.SetToIf(false, true) {
|
if initialized.SetToIf(false, true) {
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package modules
|
||||||
import "flag"
|
import "flag"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// HelpFlag triggers printing flag.Usage. It's exported for custom help handling.
|
||||||
HelpFlag bool
|
HelpFlag bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
package modules
|
package modules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/log"
|
||||||
"github.com/tevino/abool"
|
"github.com/tevino/abool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,32 +23,103 @@ var (
|
||||||
// Module represents a module.
|
// Module represents a module.
|
||||||
type Module struct {
|
type Module struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
|
// lifecycle mgmt
|
||||||
Prepped *abool.AtomicBool
|
Prepped *abool.AtomicBool
|
||||||
Started *abool.AtomicBool
|
Started *abool.AtomicBool
|
||||||
Stopped *abool.AtomicBool
|
Stopped *abool.AtomicBool
|
||||||
inTransition *abool.AtomicBool
|
inTransition *abool.AtomicBool
|
||||||
|
|
||||||
|
// lifecycle callback functions
|
||||||
prep func() error
|
prep func() error
|
||||||
start func() error
|
start func() error
|
||||||
stop func() error
|
stop func() error
|
||||||
|
|
||||||
|
// shutdown mgmt
|
||||||
|
Ctx context.Context
|
||||||
|
cancelCtx func()
|
||||||
|
shutdownFlag *abool.AtomicBool
|
||||||
|
workerGroup sync.WaitGroup
|
||||||
|
workerCnt *int32
|
||||||
|
|
||||||
|
// dependency mgmt
|
||||||
depNames []string
|
depNames []string
|
||||||
depModules []*Module
|
depModules []*Module
|
||||||
depReverse []*Module
|
depReverse []*Module
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddWorkers adds workers to the worker waitgroup. This is a failsafe wrapper for sync.Waitgroup.
|
||||||
|
func (m *Module) AddWorkers(n uint) {
|
||||||
|
if !m.ShutdownInProgress() {
|
||||||
|
if atomic.AddInt32(m.workerCnt, int32(n)) > 0 {
|
||||||
|
// only add to workgroup if cnt is positive (try to compensate wrong usage)
|
||||||
|
m.workerGroup.Add(int(n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishWorker removes a worker from the worker waitgroup. This is a failsafe wrapper for sync.Waitgroup.
|
||||||
|
func (m *Module) FinishWorker() {
|
||||||
|
// check worker cnt
|
||||||
|
if atomic.AddInt32(m.workerCnt, -1) < 0 {
|
||||||
|
log.Warningf("modules: %s module tried to finish more workers than added, this may lead to undefined behavior when shutting down", m.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// also mark worker done in workgroup
|
||||||
|
m.workerGroup.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShutdownInProgress returns whether the module has started shutting down. In most cases, you should use ShuttingDown instead.
|
||||||
|
func (m *Module) ShutdownInProgress() bool {
|
||||||
|
return m.shutdownFlag.IsSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShuttingDown lets you listen for the shutdown signal.
|
||||||
|
func (m *Module) ShuttingDown() <-chan struct{} {
|
||||||
|
return m.Ctx.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) shutdown() error {
|
||||||
|
// signal shutdown
|
||||||
|
m.shutdownFlag.Set()
|
||||||
|
m.cancelCtx()
|
||||||
|
|
||||||
|
// wait for workers
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
m.workerGroup.Wait()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
return errors.New("timed out while waiting for module workers to finish")
|
||||||
|
}
|
||||||
|
|
||||||
|
// call shutdown function
|
||||||
|
return m.stop()
|
||||||
|
}
|
||||||
|
|
||||||
func dummyAction() error {
|
func dummyAction() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register registers a new module.
|
// Register registers a new module. The control functions `prep`, `start` and `stop` are technically optional. `stop` is called _after_ all added module workers finished.
|
||||||
func Register(name string, prep, start, stop func() error, dependencies ...string) *Module {
|
func Register(name string, prep, start, stop func() error, dependencies ...string) *Module {
|
||||||
|
ctx, cancelCtx := context.WithCancel(context.Background())
|
||||||
|
var workerCnt int32
|
||||||
|
|
||||||
newModule := &Module{
|
newModule := &Module{
|
||||||
Name: name,
|
Name: name,
|
||||||
Prepped: abool.NewBool(false),
|
Prepped: abool.NewBool(false),
|
||||||
Started: abool.NewBool(false),
|
Started: abool.NewBool(false),
|
||||||
Stopped: abool.NewBool(false),
|
Stopped: abool.NewBool(false),
|
||||||
inTransition: abool.NewBool(false),
|
inTransition: abool.NewBool(false),
|
||||||
|
Ctx: ctx,
|
||||||
|
cancelCtx: cancelCtx,
|
||||||
|
shutdownFlag: abool.NewBool(false),
|
||||||
|
workerGroup: sync.WaitGroup{},
|
||||||
|
workerCnt: &workerCnt,
|
||||||
prep: prep,
|
prep: prep,
|
||||||
start: start,
|
start: start,
|
||||||
stop: stop,
|
stop: stop,
|
||||||
|
|
|
@ -202,11 +202,11 @@ func TestErrors(t *testing.T) {
|
||||||
startCompleteSignal = make(chan struct{})
|
startCompleteSignal = make(chan struct{})
|
||||||
|
|
||||||
// test help flag
|
// test help flag
|
||||||
helpFlag = true
|
HelpFlag = true
|
||||||
err = Start()
|
err = Start()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("should fail")
|
t.Error("should fail")
|
||||||
}
|
}
|
||||||
helpFlag = false
|
HelpFlag = false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ func stopModules() error {
|
||||||
go func() {
|
go func() {
|
||||||
reports <- &report{
|
reports <- &report{
|
||||||
module: execM,
|
module: execM,
|
||||||
err: execM.stop(),
|
err: execM.shutdown(),
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
modules.Register("notifications", nil, start, nil, "core")
|
modules.Register("notifications", nil, start, nil, "base", "database")
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() error {
|
func start() error {
|
||||||
|
|
Loading…
Add table
Reference in a new issue