mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-10-07 00:12:55 +08:00
Initial commit, pt. 47
This commit is contained in:
@@ -40,7 +40,9 @@ func main() {
|
|||||||
netPprofAddr := pflag.String("go-net-pprof-addr", "", "address to listen to for net/pprof requests")
|
netPprofAddr := pflag.String("go-net-pprof-addr", "", "address to listen to for net/pprof requests")
|
||||||
cpuProfile := pflag.String("go-profile-cpu", "", "file to write cpu profile to")
|
cpuProfile := pflag.String("go-profile-cpu", "", "file to write cpu profile to")
|
||||||
heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to")
|
heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to")
|
||||||
|
sentryDSN := pflag.String("sentry-dsn", "", "DSN of a Sentry instance to send error reports")
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
|
|
||||||
l := logrus.Default().WithLevel(loggerLevel)
|
l := logrus.Default().WithLevel(loggerLevel)
|
||||||
|
|
||||||
if *cpuProfile != "" {
|
if *cpuProfile != "" {
|
||||||
@@ -84,6 +86,24 @@ func main() {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx = logger.CtxWithLogger(ctx, l)
|
ctx = logger.CtxWithLogger(ctx, l)
|
||||||
|
|
||||||
|
if *sentryDSN != "" {
|
||||||
|
l.Infof("setting up Sentry at DSN '%s'", *sentryDSN)
|
||||||
|
sentryClient, err := sentry.NewClient(sentry.ClientOptions{
|
||||||
|
Dsn: *sentryDSN,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
l.Fatal(err)
|
||||||
|
}
|
||||||
|
sentryErrorMonitor := errmonsentry.New(sentryClient)
|
||||||
|
ctx = errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
|
||||||
|
|
||||||
|
l = l.WithPreHooks(observability.NewErrorMonitorLoggerHook(
|
||||||
|
sentryErrorMonitor,
|
||||||
|
))
|
||||||
|
ctx = logger.CtxWithLogger(ctx, l)
|
||||||
|
}
|
||||||
|
|
||||||
logger.Default = func() logger.Logger {
|
logger.Default = func() logger.Logger {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
@@ -134,27 +154,6 @@ func main() {
|
|||||||
l.Fatalf("unable to initialize the streamd instance: %v", err)
|
l.Fatalf("unable to initialize the streamd instance: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
l := l
|
|
||||||
if streamD.Config.SentryDSN != "" {
|
|
||||||
l.Infof("setting up Sentry at DSN '%s'", streamD.Config.SentryDSN)
|
|
||||||
sentryClient, err := sentry.NewClient(sentry.ClientOptions{
|
|
||||||
Dsn: streamD.Config.SentryDSN,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
l.Fatal(err)
|
|
||||||
}
|
|
||||||
sentryErrorMonitor := errmonsentry.New(sentryClient)
|
|
||||||
ctx = errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
|
|
||||||
|
|
||||||
l = l.WithPreHooks(observability.NewErrorMonitorLoggerHook(
|
|
||||||
sentryErrorMonitor,
|
|
||||||
))
|
|
||||||
ctx = logger.CtxWithLogger(ctx, l)
|
|
||||||
logger.Default = func() logger.Logger {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err = streamD.Run(ctx); err != nil {
|
if err = streamD.Run(ctx); err != nil {
|
||||||
l.Errorf("streamd returned an error: %v", err)
|
l.Errorf("streamd returned an error: %v", err)
|
||||||
|
50
cmd/streampanel/context.go
Normal file
50
cmd/streampanel/context.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
||||||
|
errmonsentry "github.com/facebookincubator/go-belt/tool/experimental/errmon/implementation/sentry"
|
||||||
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
|
"github.com/facebookincubator/go-belt/tool/logger/implementation/logrus"
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getContext(flags Flags) context.Context {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
ll := logrus.DefaultLogrusLogger()
|
||||||
|
l := logrus.New(ll).WithLevel(flags.LoggerLevel)
|
||||||
|
|
||||||
|
if flags.LogFile != "" {
|
||||||
|
f, err := os.OpenFile(flags.LogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0750)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("failed to open log file '%s': %v", flags.LogFile, err)
|
||||||
|
}
|
||||||
|
ll.SetOutput(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.SentryDSN != "" {
|
||||||
|
l.Infof("setting up Sentry at DSN '%s'", flags.SentryDSN)
|
||||||
|
sentryClient, err := sentry.NewClient(sentry.ClientOptions{
|
||||||
|
Dsn: flags.SentryDSN,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
l.Fatal(err)
|
||||||
|
}
|
||||||
|
sentryErrorMonitor := errmonsentry.New(sentryClient)
|
||||||
|
ctx = errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
|
||||||
|
l = l.WithPreHooks(observability.NewErrorMonitorLoggerHook(
|
||||||
|
sentryErrorMonitor,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = logger.CtxWithLogger(ctx, l)
|
||||||
|
logger.Default = func() logger.Logger {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}
|
87
cmd/streampanel/flag.go
Normal file
87
cmd/streampanel/flag.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/mainprocess"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streampanel/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flags struct {
|
||||||
|
LoggerLevel logger.Level
|
||||||
|
ListenAddr string
|
||||||
|
RemoteAddr string
|
||||||
|
ConfigPath string
|
||||||
|
NetPprofAddr string
|
||||||
|
CPUProfile string
|
||||||
|
HeapProfile string
|
||||||
|
SentryDSN string
|
||||||
|
Page string
|
||||||
|
LogFile string
|
||||||
|
Subprocess string
|
||||||
|
SplitProcess bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFlags() Flags {
|
||||||
|
loggerLevel := logger.LevelWarning
|
||||||
|
pflag.Var(&loggerLevel, "log-level", "Log level")
|
||||||
|
listenAddr := pflag.String("listen-addr", "", "the address to listen for incoming connections to")
|
||||||
|
remoteAddr := pflag.String("remote-addr", "", "the address (for example 127.0.0.1:3594) of streamd to connect to, instead of running the stream controllers locally")
|
||||||
|
configPath := pflag.String("config-path", "~/.streampanel.yaml", "the path to the config file")
|
||||||
|
netPprofAddr := pflag.String("go-net-pprof-addr", "", "address to listen to for net/pprof requests")
|
||||||
|
cpuProfile := pflag.String("go-profile-cpu", "", "file to write cpu profile to")
|
||||||
|
heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to")
|
||||||
|
sentryDSN := pflag.String("sentry-dsn", "", "DSN of a Sentry instance to send error reports")
|
||||||
|
page := pflag.String("page", string(consts.PageControl), "DSN of a Sentry instance to send error reports")
|
||||||
|
logFile := pflag.String("log-file", "", "log file to write logs into")
|
||||||
|
subprocess := pflag.String("subprocess", "", "[internal use flag] run a specific sub-process (format: processName:addressToConnect)")
|
||||||
|
splitProcess := pflag.Bool("split-process", !isMobile(), "split the process into multiple processes for better stability")
|
||||||
|
pflag.Parse()
|
||||||
|
|
||||||
|
return Flags{
|
||||||
|
LoggerLevel: loggerLevel,
|
||||||
|
ListenAddr: *listenAddr,
|
||||||
|
RemoteAddr: *remoteAddr,
|
||||||
|
ConfigPath: *configPath,
|
||||||
|
NetPprofAddr: *netPprofAddr,
|
||||||
|
CPUProfile: *cpuProfile,
|
||||||
|
HeapProfile: *heapProfile,
|
||||||
|
SentryDSN: *sentryDSN,
|
||||||
|
Page: *page,
|
||||||
|
LogFile: *logFile,
|
||||||
|
Subprocess: *subprocess,
|
||||||
|
SplitProcess: *splitProcess,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetFlags struct{}
|
||||||
|
type GetFlagsResult struct {
|
||||||
|
Flags Flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFlags(
|
||||||
|
ctx context.Context,
|
||||||
|
mainProcess *mainprocess.Client,
|
||||||
|
) Flags {
|
||||||
|
err := mainProcess.SendMessage(ctx, "main", GetFlags{})
|
||||||
|
assertNoError(err)
|
||||||
|
|
||||||
|
var flags Flags
|
||||||
|
err = mainProcess.ReadOne(
|
||||||
|
ctx,
|
||||||
|
func(ctx context.Context, source mainprocess.ProcessName, content any) error {
|
||||||
|
result, ok := content.(GetFlagsResult)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("got unexpected type '%T' instead of %T", content, GetFlagsResult{})
|
||||||
|
}
|
||||||
|
flags = result.Flags
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assertNoError(err)
|
||||||
|
|
||||||
|
return flags
|
||||||
|
}
|
186
cmd/streampanel/fork.go
Normal file
186
cmd/streampanel/fork.go
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/mainprocess"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProcessName = mainprocess.ProcessName
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProcessNameMain = ProcessName("main")
|
||||||
|
ProcessNameStreamd = ProcessName("streamd")
|
||||||
|
ProcessNameUI = ProcessName("ui")
|
||||||
|
)
|
||||||
|
|
||||||
|
var forkLocker sync.Mutex
|
||||||
|
var forkMap = map[ProcessName]*exec.Cmd{}
|
||||||
|
|
||||||
|
func getFork(procName ProcessName) *exec.Cmd {
|
||||||
|
forkLocker.Lock()
|
||||||
|
defer forkLocker.Unlock()
|
||||||
|
return forkMap[procName]
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFork(procName ProcessName, f *exec.Cmd) {
|
||||||
|
forkLocker.Lock()
|
||||||
|
defer forkLocker.Unlock()
|
||||||
|
forkMap[procName] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(StreamDDied{})
|
||||||
|
gob.Register(GetFlags{})
|
||||||
|
gob.Register(GetFlagsResult{})
|
||||||
|
gob.Register(GetStreamdAddress{})
|
||||||
|
gob.Register(GetStreamdAddressResult{})
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnvPassword = "STREAMPANEL_PASSWORD"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runSubprocess(
|
||||||
|
ctx context.Context,
|
||||||
|
subprocessFlag string,
|
||||||
|
) {
|
||||||
|
parts := strings.SplitN(subprocessFlag, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
logger.Fatalf(ctx, "expected 2 parts in --subprocess: name and address, separated via a colon")
|
||||||
|
}
|
||||||
|
procName := ProcessName(parts[0])
|
||||||
|
addr := parts[1]
|
||||||
|
switch procName {
|
||||||
|
case ProcessNameStreamd:
|
||||||
|
forkStreamd(ctx, addr, os.Getenv(EnvPassword))
|
||||||
|
case ProcessNameUI:
|
||||||
|
forkUI(ctx, addr, os.Getenv(EnvPassword))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSplitProcesses(
|
||||||
|
ctx context.Context,
|
||||||
|
flags Flags,
|
||||||
|
) {
|
||||||
|
procList := []ProcessName{
|
||||||
|
ProcessNameUI,
|
||||||
|
}
|
||||||
|
if flags.RemoteAddr == "" {
|
||||||
|
procList = append(procList, ProcessNameStreamd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m *mainprocess.Manager
|
||||||
|
var err error
|
||||||
|
m, err = mainprocess.NewManager(
|
||||||
|
func(
|
||||||
|
ctx context.Context,
|
||||||
|
procName ProcessName,
|
||||||
|
addr string,
|
||||||
|
password string,
|
||||||
|
isRestart bool,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
f := getFork(procName)
|
||||||
|
if f != nil {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
//f.Process.Kill()
|
||||||
|
logger.Debugf(ctx, "waiting for process '%s' to die", procName)
|
||||||
|
f.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof(ctx, "running process '%s'", procName)
|
||||||
|
err := runFork(ctx, flags, procName, addr, password)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if isRestart && flags.RemoteAddr == "" {
|
||||||
|
switch procName {
|
||||||
|
case ProcessNameStreamd:
|
||||||
|
err := m.SendMessage(ctx, ProcessNameUI, StreamDDied{})
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf(ctx, "failed to send a StreamDDied message to '%s': %v", ProcessNameUI, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
procList...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf(ctx, "failed to start process manager: %v", err)
|
||||||
|
}
|
||||||
|
defer m.Close()
|
||||||
|
go m.Serve(ctx, func(ctx context.Context, source mainprocess.ProcessName, content any) error {
|
||||||
|
switch content.(type) {
|
||||||
|
case GetFlags:
|
||||||
|
msg := GetFlagsResult{
|
||||||
|
Flags: flags,
|
||||||
|
}
|
||||||
|
err := m.SendMessage(ctx, source, msg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf(ctx, "failed to send message %#+v to '%s': %v", msg, source, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
}
|
||||||
|
|
||||||
|
err := m.VerifyEverybodyConnected(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf(ctx, "%s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
const debugDontFork = false
|
||||||
|
|
||||||
|
func runFork(
|
||||||
|
ctx context.Context,
|
||||||
|
flags Flags,
|
||||||
|
procName ProcessName,
|
||||||
|
addr, password string,
|
||||||
|
) error {
|
||||||
|
logger.Debugf(ctx, "running fork: '%s' '%s' '%s'", procName, addr, password)
|
||||||
|
defer logger.Debugf(ctx, "/running fork: '%s' '%s' '%s'", procName, addr, password)
|
||||||
|
if debugDontFork {
|
||||||
|
return fakeFork(ctx, procName, addr, password)
|
||||||
|
}
|
||||||
|
os.Setenv(EnvPassword, password)
|
||||||
|
args := []string{os.Args[0], "--sentry-dsn=" + flags.SentryDSN, "--log-level=" + flags.LoggerLevel.String(), "--subprocess=" + string(procName) + ":" + addr}
|
||||||
|
logger.Infof(ctx, "running '%s %s'", args[0], strings.Join(args[1:], " "))
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
setFork(procName, cmd)
|
||||||
|
go cmd.Run()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeFork(ctx context.Context, procName ProcessName, addr, password string) error {
|
||||||
|
switch procName {
|
||||||
|
case ProcessNameStreamd:
|
||||||
|
go forkUI(ctx, addr, password)
|
||||||
|
return nil
|
||||||
|
case ProcessNameUI:
|
||||||
|
go forkStreamd(ctx, addr, password)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unexpected process name: %s", procName)
|
||||||
|
}
|
13
cmd/streampanel/is_mobile.go
Normal file
13
cmd/streampanel/is_mobile.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isMobile() bool {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "android":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@@ -2,161 +2,54 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"runtime/pprof"
|
|
||||||
|
|
||||||
"github.com/facebookincubator/go-belt"
|
"github.com/facebookincubator/go-belt"
|
||||||
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
|
||||||
errmonsentry "github.com/facebookincubator/go-belt/tool/experimental/errmon/implementation/sentry"
|
|
||||||
"github.com/facebookincubator/go-belt/tool/logger"
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
"github.com/facebookincubator/go-belt/tool/logger/implementation/logrus"
|
|
||||||
"github.com/getsentry/sentry-go"
|
|
||||||
"github.com/kraken-hpc/go-fork"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc"
|
"github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamd/server"
|
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streampanel"
|
"github.com/xaionaro-go/streamctl/pkg/streampanel"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streampanel/consts"
|
|
||||||
_ "github.com/xaionaro-go/streamctl/pkg/streamserver"
|
_ "github.com/xaionaro-go/streamctl/pkg/streamserver"
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
fork.RegisterFunc("streamd", streamd)
|
|
||||||
fork.Init()
|
|
||||||
}
|
|
||||||
|
|
||||||
func streamd(remoteAddr string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const forceNetPProfOnAndroid = true
|
const forceNetPProfOnAndroid = true
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
loggerLevel := logger.LevelWarning
|
flags := parseFlags()
|
||||||
pflag.Var(&loggerLevel, "log-level", "Log level")
|
ctx := getContext(flags)
|
||||||
listenAddr := pflag.String("listen-addr", "", "the address to listen for incoming connections to")
|
|
||||||
remoteAddr := pflag.String("remote-addr", "", "the address (for example 127.0.0.1:3594) of streamd to connect to, instead of running the stream controllers locally")
|
|
||||||
configPath := pflag.String("config-path", "~/.streampanel.yaml", "the path to the config file")
|
|
||||||
netPprofAddr := pflag.String("go-net-pprof-addr", "", "address to listen to for net/pprof requests")
|
|
||||||
cpuProfile := pflag.String("go-profile-cpu", "", "file to write cpu profile to")
|
|
||||||
heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to")
|
|
||||||
sentryDSN := pflag.String("sentry-dsn", "", "DSN of a Sentry instance to send error reports")
|
|
||||||
page := pflag.String("page", string(consts.PageControl), "DSN of a Sentry instance to send error reports")
|
|
||||||
splitProcess := pflag.Bool("split-process", !isMobile(), "split the process into multiple processes for better stability")
|
|
||||||
pflag.Parse()
|
|
||||||
|
|
||||||
l := logrus.Default().WithLevel(loggerLevel)
|
|
||||||
logger.Default = func() logger.Logger {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
if *cpuProfile != "" {
|
|
||||||
f, err := os.Create(*cpuProfile)
|
|
||||||
if err != nil {
|
|
||||||
l.Fatalf("unable to create file '%s': %v", *cpuProfile, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
|
||||||
l.Fatalf("unable to write to file '%s': %v", *cpuProfile, err)
|
|
||||||
}
|
|
||||||
defer pprof.StopCPUProfile()
|
|
||||||
}
|
|
||||||
|
|
||||||
if *heapProfile != "" {
|
|
||||||
f, err := os.Create(*heapProfile)
|
|
||||||
if err != nil {
|
|
||||||
l.Fatalf("unable to create file '%s': %v", *heapProfile, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
runtime.GC()
|
|
||||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
|
||||||
l.Fatalf("unable to write to file '%s': %v", *heapProfile, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if *netPprofAddr != "" || (forceNetPProfOnAndroid && runtime.GOOS == "android") {
|
|
||||||
go func() {
|
|
||||||
if *netPprofAddr == "" {
|
|
||||||
*netPprofAddr = "localhost:0"
|
|
||||||
}
|
|
||||||
l.Infof("starting to listen for net/pprof requests at '%s'", *netPprofAddr)
|
|
||||||
l.Error(http.ListenAndServe(*netPprofAddr, nil))
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldValue := runtime.GOMAXPROCS(0); oldValue < 16 {
|
|
||||||
l.Infof("increased GOMAXPROCS from %d to %d", oldValue, 16)
|
|
||||||
runtime.GOMAXPROCS(16)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *splitProcess && *listenAddr == "" {
|
|
||||||
listenAddr = ptr("localhost:0")
|
|
||||||
}
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", *listenAddr)
|
|
||||||
if err != nil {
|
|
||||||
l.Fatalf("failed to listen: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *splitProcess {
|
|
||||||
fork.Fork("streamd", listener.Addr().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
listener.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if *splitProcess && *remoteAddr == "" {
|
|
||||||
remoteAddr = ptr(listener.Addr().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts []streampanel.Option
|
|
||||||
if *remoteAddr != "" {
|
|
||||||
opts = append(opts, streampanel.OptionRemoteStreamDAddr(*remoteAddr))
|
|
||||||
}
|
|
||||||
if *sentryDSN != "" {
|
|
||||||
opts = append(opts, streampanel.OptionSentryDSN(*sentryDSN))
|
|
||||||
}
|
|
||||||
panel, panelErr := streampanel.New(*configPath, opts...)
|
|
||||||
|
|
||||||
if panel != nil && panel.Config.SentryDSN != "" {
|
|
||||||
l.Infof("setting up Sentry at DSN '%s'", panel.Config.SentryDSN)
|
|
||||||
sentryClient, err := sentry.NewClient(sentry.ClientOptions{
|
|
||||||
Dsn: panel.Config.SentryDSN,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
l.Fatal(err)
|
|
||||||
}
|
|
||||||
sentryErrorMonitor := errmonsentry.New(sentryClient)
|
|
||||||
ctx = errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
|
|
||||||
l = l.WithPreHooks(observability.NewErrorMonitorLoggerHook(
|
|
||||||
sentryErrorMonitor,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = logger.CtxWithLogger(ctx, l)
|
|
||||||
logger.Default = func() logger.Logger {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
defer belt.Flush(ctx)
|
defer belt.Flush(ctx)
|
||||||
|
cancelFunc := initRuntime(ctx, flags, "main")
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
if panelErr != nil {
|
if flags.Subprocess != "" {
|
||||||
l.Fatal(panelErr)
|
runSubprocess(ctx, flags.Subprocess)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if *listenAddr != "" {
|
if flags.SplitProcess {
|
||||||
grpcServer := grpc.NewServer()
|
runSplitProcesses(ctx, flags)
|
||||||
streamdGRPC := server.NewGRPCServer(panel.StreamD)
|
return
|
||||||
streamd_grpc.RegisterStreamDServer(grpcServer, streamdGRPC)
|
}
|
||||||
|
|
||||||
|
runPanel(ctx, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPanel(
|
||||||
|
ctx context.Context,
|
||||||
|
flags Flags,
|
||||||
|
) {
|
||||||
|
var opts []streampanel.Option
|
||||||
|
if flags.RemoteAddr != "" {
|
||||||
|
opts = append(opts, streampanel.OptionRemoteStreamDAddr(flags.RemoteAddr))
|
||||||
|
}
|
||||||
|
|
||||||
|
panel, panelErr := streampanel.New(flags.ConfigPath, opts...)
|
||||||
|
if panelErr != nil {
|
||||||
|
logger.Fatal(ctx, panelErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.ListenAddr != "" {
|
||||||
|
listener, grpcServer, streamdGRPC := initGRPCServer(ctx, panel.StreamD, flags.ListenAddr)
|
||||||
|
|
||||||
// to erase an oauth request answered locally from "UnansweredOAuthRequests" in the GRPC server:
|
// to erase an oauth request answered locally from "UnansweredOAuthRequests" in the GRPC server:
|
||||||
panel.OnInternallySubmittedOAuthCode = func(
|
panel.OnInternallySubmittedOAuthCode = func(
|
||||||
@@ -171,20 +64,18 @@ func main() {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// start the server:
|
err := grpcServer.Serve(listener)
|
||||||
go func() {
|
|
||||||
l.Infof("started server at %s", *listenAddr)
|
|
||||||
err = grpcServer.Serve(listener)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Fatal(err)
|
logger.Fatalf(ctx, "unable to server the gRPC server: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var loopOpts []streampanel.LoopOption
|
var loopOpts []streampanel.LoopOption
|
||||||
loopOpts = append(loopOpts, streampanel.LoopOptionStartingPage(*page))
|
if flags.Page != "" {
|
||||||
err = panel.Loop(ctx, loopOpts...)
|
loopOpts = append(loopOpts, streampanel.LoopOptionStartingPage(flags.Page))
|
||||||
|
}
|
||||||
|
err := panel.Loop(ctx, loopOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Fatal(err)
|
logger.Fatal(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
63
cmd/streampanel/runtime.go
Normal file
63
cmd/streampanel/runtime.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
|
|
||||||
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initRuntime(ctx context.Context, flags Flags, _procName ProcessName) context.CancelFunc {
|
||||||
|
procName := string(_procName)
|
||||||
|
var closeFuncs []func()
|
||||||
|
|
||||||
|
l := logger.FromCtx(ctx)
|
||||||
|
|
||||||
|
if flags.CPUProfile != "" {
|
||||||
|
f, err := os.Create(flags.CPUProfile + "-" + procName)
|
||||||
|
if err != nil {
|
||||||
|
l.Fatalf("unable to create file '%s': %v", flags.CPUProfile+"-"+procName, err)
|
||||||
|
}
|
||||||
|
closeFuncs = append(closeFuncs, func() { f.Close() })
|
||||||
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
|
l.Fatalf("unable to write to file '%s': %v", flags.CPUProfile+"-"+procName, err)
|
||||||
|
}
|
||||||
|
closeFuncs = append(closeFuncs, pprof.StopCPUProfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.HeapProfile != "" {
|
||||||
|
f, err := os.Create(flags.HeapProfile + "-" + procName)
|
||||||
|
if err != nil {
|
||||||
|
l.Fatalf("unable to create file '%s': %v", flags.HeapProfile+"-"+procName, err)
|
||||||
|
}
|
||||||
|
closeFuncs = append(closeFuncs, func() { f.Close() })
|
||||||
|
runtime.GC()
|
||||||
|
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||||
|
l.Fatalf("unable to write to file '%s': %v", flags.HeapProfile+"-"+procName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.NetPprofAddr != "" || (forceNetPProfOnAndroid && runtime.GOOS == "android") {
|
||||||
|
go func() {
|
||||||
|
if flags.NetPprofAddr == "" {
|
||||||
|
flags.NetPprofAddr = "localhost:0"
|
||||||
|
}
|
||||||
|
l.Infof("starting to listen for net/pprof requests at '%s'", flags.NetPprofAddr)
|
||||||
|
l.Error(http.ListenAndServe(flags.NetPprofAddr, nil))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldValue := runtime.GOMAXPROCS(0); oldValue < 16 {
|
||||||
|
l.Infof("increased GOMAXPROCS from %d to %d", oldValue, 16)
|
||||||
|
runtime.GOMAXPROCS(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
for i := len(closeFuncs) - 1; i >= 0; i-- {
|
||||||
|
closeFuncs[i]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
cmd/streampanel/streamd.go
Normal file
150
cmd/streampanel/streamd.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/facebookincubator/go-belt"
|
||||||
|
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
||||||
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
|
"github.com/xaionaro-go/streamctl/cmd/streamd/ui"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/mainprocess"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamd"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamd/api"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamd/config"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamd/server"
|
||||||
|
streampanelconfig "github.com/xaionaro-go/streamctl/pkg/streampanel/config"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/xpath"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func forkStreamd(ctx context.Context, mainProcessAddr, password string) {
|
||||||
|
procName := ProcessNameStreamd
|
||||||
|
ctx = belt.WithField(ctx, "process", procName)
|
||||||
|
|
||||||
|
mainProcess, err := mainprocess.NewClient(
|
||||||
|
procName,
|
||||||
|
mainProcessAddr,
|
||||||
|
password,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
flags := getFlags(ctx, mainProcess)
|
||||||
|
ctx = getContext(flags)
|
||||||
|
ctx = belt.WithField(ctx, "process", procName)
|
||||||
|
defer belt.Flush(ctx)
|
||||||
|
logger.Debugf(ctx, "flags == %#+v", flags)
|
||||||
|
cancelFunc := initRuntime(ctx, flags, procName)
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
runStreamd(ctx, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runStreamd(
|
||||||
|
ctx context.Context,
|
||||||
|
flags Flags,
|
||||||
|
) {
|
||||||
|
logger.Debugf(ctx, "runStreamd: %#+v", flags)
|
||||||
|
defer logger.Debugf(ctx, "/runStreamd")
|
||||||
|
if flags.RemoteAddr != "" {
|
||||||
|
logger.Fatal(ctx, "not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath, err := xpath.Expand(flags.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg streampanelconfig.Config
|
||||||
|
err = streampanelconfig.ReadConfigFromPath(configPath, &cfg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf(ctx, "unable to read the config from path '%s': %v", flags.ConfigPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var streamdGRPCLocker sync.Mutex
|
||||||
|
|
||||||
|
var streamdGRPC *server.GRPCServer
|
||||||
|
ui := ui.NewUI(
|
||||||
|
ctx,
|
||||||
|
func(listenPort uint16, platID streamcontrol.PlatformName, authURL string) bool {
|
||||||
|
logger.Tracef(ctx, "streamd.UI.OpenOAuthURL(%d, %s, '%s')", listenPort, platID, authURL)
|
||||||
|
defer logger.Tracef(ctx, "/streamd.UI.OpenOAuthURL(%d, %s, '%s')", listenPort, platID, authURL)
|
||||||
|
|
||||||
|
streamdGRPCLocker.Lock()
|
||||||
|
logger.Tracef(ctx, "streamdGRPCLocker.Lock()-ed")
|
||||||
|
defer logger.Tracef(ctx, "streamdGRPCLocker.Lock()-ed")
|
||||||
|
defer streamdGRPCLocker.Unlock()
|
||||||
|
|
||||||
|
err := streamdGRPC.OpenOAuthURL(ctx, listenPort, platID, authURL)
|
||||||
|
errmon.ObserveErrorCtx(ctx, err)
|
||||||
|
return err == nil
|
||||||
|
},
|
||||||
|
func(ctx context.Context, s string) {
|
||||||
|
logger.Infof(ctx, "restarting streamd")
|
||||||
|
os.Exit(0)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
streamD, err := streamd.New(
|
||||||
|
cfg.BuiltinStreamD,
|
||||||
|
ui,
|
||||||
|
func(ctx context.Context, cfg config.Config) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
belt.CtxBelt(ctx),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf(ctx, "unable to initialize streamd: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var listener net.Listener
|
||||||
|
var grpcServer *grpc.Server
|
||||||
|
listener, grpcServer, streamdGRPC = initGRPCServer(ctx, streamD, flags.ListenAddr)
|
||||||
|
|
||||||
|
err = streamD.Run(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf(ctx, "unable to start streamd: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = grpcServer.Serve(listener)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf(ctx, "unable to server the gRPC server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Fatalf(ctx, "internal error: was supposed to never reach this line")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initGRPCServer(
|
||||||
|
ctx context.Context,
|
||||||
|
streamD api.StreamD,
|
||||||
|
listenAddr string,
|
||||||
|
) (net.Listener, *grpc.Server, *server.GRPCServer) {
|
||||||
|
listener, err := net.Listen("tcp", listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf(ctx, "failed to listen: %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
listener.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
grpcServer := grpc.NewServer()
|
||||||
|
streamdGRPC := server.NewGRPCServer(streamD)
|
||||||
|
streamd_grpc.RegisterStreamDServer(grpcServer, streamdGRPC)
|
||||||
|
|
||||||
|
// start the server:
|
||||||
|
go func() {
|
||||||
|
logger.Infof(ctx, "started server at %s", listener.Addr().String())
|
||||||
|
err = grpcServer.Serve(listener)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(ctx, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return listener, grpcServer, streamdGRPC
|
||||||
|
}
|
89
cmd/streampanel/ui.go
Normal file
89
cmd/streampanel/ui.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/facebookincubator/go-belt"
|
||||||
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/mainprocess"
|
||||||
|
)
|
||||||
|
|
||||||
|
func forkUI(ctx context.Context, mainProcessAddr, password string) {
|
||||||
|
procName := ProcessNameUI
|
||||||
|
ctx = belt.WithField(ctx, "process", procName)
|
||||||
|
|
||||||
|
mainProcess, err := mainprocess.NewClient(
|
||||||
|
procName,
|
||||||
|
mainProcessAddr,
|
||||||
|
password,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
flags := getFlags(ctx, mainProcess)
|
||||||
|
ctx = getContext(flags)
|
||||||
|
ctx = belt.WithField(ctx, "process", procName)
|
||||||
|
defer belt.Flush(ctx)
|
||||||
|
logger.Debugf(ctx, "flags == %#+v", flags)
|
||||||
|
cancelFunc := initRuntime(ctx, flags, procName)
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
streamdAddr := getStreamDAddress(ctx, mainProcess)
|
||||||
|
go mainProcess.Serve(
|
||||||
|
ctx,
|
||||||
|
func(ctx context.Context, source mainprocess.ProcessName, content any) error {
|
||||||
|
switch content.(type) {
|
||||||
|
case StreamDDied:
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
flags.RemoteAddr = streamdAddr
|
||||||
|
runPanel(ctx, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamDDied struct{}
|
||||||
|
|
||||||
|
type GetStreamdAddress struct{}
|
||||||
|
type GetStreamdAddressResult struct {
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStreamDAddress(
|
||||||
|
ctx context.Context,
|
||||||
|
mainProcess *mainprocess.Client,
|
||||||
|
) string {
|
||||||
|
err := mainProcess.SendMessage(ctx, "streamd", GetStreamdAddress{})
|
||||||
|
assertNoError(err)
|
||||||
|
|
||||||
|
var addr string
|
||||||
|
for {
|
||||||
|
readOnceMore := false
|
||||||
|
err = mainProcess.ReadOne(
|
||||||
|
ctx,
|
||||||
|
func(ctx context.Context, source mainprocess.ProcessName, content any) error {
|
||||||
|
switch msg := content.(type) {
|
||||||
|
case GetStreamdAddressResult:
|
||||||
|
addr = msg.Address
|
||||||
|
case StreamDDied:
|
||||||
|
readOnceMore = true
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("got unexpected type '%T' instead of %T", content, GetStreamdAddressResult{})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assertNoError(err)
|
||||||
|
|
||||||
|
if readOnceMore {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
11
cmd/streampanel/utils.go
Normal file
11
cmd/streampanel/utils.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func ptr[T any](in T) *T {
|
||||||
|
return &in
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertNoError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
3
go.mod
3
go.mod
@@ -164,7 +164,6 @@ require (
|
|||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||||
github.com/stretchr/testify v1.9.0 // indirect
|
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 // indirect
|
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
|
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
|
||||||
@@ -219,7 +218,6 @@ require (
|
|||||||
github.com/hyprspace/hyprspace v0.10.1
|
github.com/hyprspace/hyprspace v0.10.1
|
||||||
github.com/immune-gmbh/attestation-sdk v0.0.0-20230711173209-f44e4502aeca
|
github.com/immune-gmbh/attestation-sdk v0.0.0-20230711173209-f44e4502aeca
|
||||||
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237
|
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237
|
||||||
github.com/kraken-hpc/go-fork v0.1.1
|
|
||||||
github.com/libp2p/go-libp2p v0.33.2
|
github.com/libp2p/go-libp2p v0.33.2
|
||||||
github.com/libp2p/go-libp2p-kad-dht v0.25.2
|
github.com/libp2p/go-libp2p-kad-dht v0.25.2
|
||||||
github.com/multiformats/go-multiaddr v0.12.3
|
github.com/multiformats/go-multiaddr v0.12.3
|
||||||
@@ -228,6 +226,7 @@ require (
|
|||||||
github.com/rs/zerolog v1.33.0
|
github.com/rs/zerolog v1.33.0
|
||||||
github.com/sethvargo/go-password v0.3.1
|
github.com/sethvargo/go-password v0.3.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/xaionaro-go/datacounter v1.0.4
|
github.com/xaionaro-go/datacounter v1.0.4
|
||||||
github.com/xaionaro-go/unsafetools v0.0.0-20210722164218-75ba48cf7b3c
|
github.com/xaionaro-go/unsafetools v0.0.0-20210722164218-75ba48cf7b3c
|
||||||
github.com/yl2chen/cidranger v1.0.2
|
github.com/yl2chen/cidranger v1.0.2
|
||||||
|
2
go.sum
2
go.sum
@@ -454,8 +454,6 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kraken-hpc/go-fork v0.1.1 h1:O3X/ynoNy/eS7UIcZYef8ndFq2RXEIOue9kZqyzF0Sk=
|
|
||||||
github.com/kraken-hpc/go-fork v0.1.1/go.mod h1:uu0e5h+V4ONH5Qk/xuVlyNXJXy/swhqGIEMK7w+9dNc=
|
|
||||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
||||||
|
@@ -12,14 +12,12 @@ import (
|
|||||||
type Client struct {
|
type Client struct {
|
||||||
Conn net.Conn
|
Conn net.Conn
|
||||||
Password string
|
Password string
|
||||||
OnReceivedMessage OnReceivedMessageFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(
|
func NewClient(
|
||||||
myName string,
|
myName ProcessName,
|
||||||
addr string,
|
addr string,
|
||||||
password string,
|
password string,
|
||||||
onReceivedMessage OnReceivedMessageFunc,
|
|
||||||
) (*Client, error) {
|
) (*Client, error) {
|
||||||
conn, err := net.Dial("tcp", addr)
|
conn, err := net.Dial("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -49,13 +47,12 @@ func NewClient(
|
|||||||
return &Client{
|
return &Client{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
Password: password,
|
Password: password,
|
||||||
OnReceivedMessage: onReceivedMessage,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SendMessage(
|
func (c *Client) SendMessage(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
dst string,
|
dst ProcessName,
|
||||||
content any,
|
content any,
|
||||||
) error {
|
) error {
|
||||||
encoder := gob.NewEncoder(c.Conn)
|
encoder := gob.NewEncoder(c.Conn)
|
||||||
@@ -76,7 +73,10 @@ func (c *Client) Close() error {
|
|||||||
return c.Conn.Close()
|
return c.Conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Serve(ctx context.Context) error {
|
func (c *Client) Serve(
|
||||||
|
ctx context.Context,
|
||||||
|
onReceivedMessage OnReceivedMessageFunc,
|
||||||
|
) error {
|
||||||
ctx, cancelFn := context.WithCancel(ctx)
|
ctx, cancelFn := context.WithCancel(ctx)
|
||||||
defer cancelFn()
|
defer cancelFn()
|
||||||
go func() {
|
go func() {
|
||||||
@@ -94,6 +94,16 @@ func (c *Client) Serve(ctx context.Context) error {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.ReadOne(ctx, onReceivedMessage); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ReadOne(
|
||||||
|
ctx context.Context,
|
||||||
|
onReceivedMessage OnReceivedMessageFunc,
|
||||||
|
) error {
|
||||||
var msg MessageFromMain
|
var msg MessageFromMain
|
||||||
decoder := gob.NewDecoder(c.Conn)
|
decoder := gob.NewDecoder(c.Conn)
|
||||||
err := decoder.Decode(&msg)
|
err := decoder.Decode(&msg)
|
||||||
@@ -101,19 +111,9 @@ func (c *Client) Serve(ctx context.Context) error {
|
|||||||
return fmt.Errorf("unable to receive&decode message: %w", err)
|
return fmt.Errorf("unable to receive&decode message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.onReceivedMessage(ctx, msg); err != nil {
|
if err := onReceivedMessage(ctx, msg.Source, msg.Content); err != nil {
|
||||||
logger.Error(ctx, err)
|
return fmt.Errorf("unable to process the message '%#+v': %w", msg, err)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) onReceivedMessage(
|
|
||||||
ctx context.Context,
|
|
||||||
msg MessageFromMain,
|
|
||||||
) error {
|
|
||||||
if c.OnReceivedMessage == nil {
|
|
||||||
return fmt.Errorf("OnReceivedMessage function is not set")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.OnReceivedMessage(ctx, msg.Source, msg.Content)
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -15,26 +15,34 @@ import (
|
|||||||
|
|
||||||
type OnReceivedMessageFunc func(
|
type OnReceivedMessageFunc func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
source string,
|
source ProcessName,
|
||||||
content any,
|
content any,
|
||||||
) error
|
) error
|
||||||
|
|
||||||
|
type LaunchClientFunc func(
|
||||||
|
ctx context.Context,
|
||||||
|
procName ProcessName,
|
||||||
|
addr string,
|
||||||
|
password string,
|
||||||
|
isRestart bool,
|
||||||
|
) error
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
password string
|
password string
|
||||||
|
|
||||||
connsLocker sync.Mutex
|
connsLocker sync.Mutex
|
||||||
conns map[string]net.Conn
|
conns map[ProcessName]net.Conn
|
||||||
connsChanged chan struct{}
|
connsChanged chan struct{}
|
||||||
|
|
||||||
allClientProcesses []string
|
allClientProcesses []ProcessName
|
||||||
|
|
||||||
OnReceivedMessage OnReceivedMessageFunc
|
LaunchClient LaunchClientFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(
|
func NewManager(
|
||||||
onReceivedMessage OnReceivedMessageFunc,
|
launchClient LaunchClientFunc,
|
||||||
expectedClients ...string,
|
expectedClients ...ProcessName,
|
||||||
) (*Manager, error) {
|
) (*Manager, error) {
|
||||||
listener, err := net.Listen("tcp", "localhost:0")
|
listener, err := net.Listen("tcp", "localhost:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,12 +55,12 @@ func NewManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Manager{
|
return &Manager{
|
||||||
OnReceivedMessage: onReceivedMessage,
|
LaunchClient: launchClient,
|
||||||
|
|
||||||
listener: listener,
|
listener: listener,
|
||||||
password: password,
|
password: password,
|
||||||
|
|
||||||
conns: map[string]net.Conn{},
|
conns: map[ProcessName]net.Conn{},
|
||||||
connsChanged: make(chan struct{}),
|
connsChanged: make(chan struct{}),
|
||||||
|
|
||||||
allClientProcesses: expectedClients,
|
allClientProcesses: expectedClients,
|
||||||
@@ -71,7 +79,24 @@ func (m *Manager) Close() error {
|
|||||||
return m.listener.Close()
|
return m.listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Serve(ctx context.Context) error {
|
func (m *Manager) VerifyEverybodyConnected(
|
||||||
|
ctx context.Context,
|
||||||
|
) error {
|
||||||
|
m.connsLocker.Lock()
|
||||||
|
defer m.connsLocker.Unlock()
|
||||||
|
|
||||||
|
for _, name := range m.allClientProcesses {
|
||||||
|
if _, ok := m.conns[name]; !ok {
|
||||||
|
return fmt.Errorf("client '%s' is not connected", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Serve(
|
||||||
|
ctx context.Context,
|
||||||
|
onReceivedMessage OnReceivedMessageFunc,
|
||||||
|
) error {
|
||||||
logger.Tracef(ctx, "serving listener at %s", m.listener.Addr())
|
logger.Tracef(ctx, "serving listener at %s", m.listener.Addr())
|
||||||
defer logger.Tracef(ctx, "/serving listener at %s", m.listener.Addr())
|
defer logger.Tracef(ctx, "/serving listener at %s", m.listener.Addr())
|
||||||
|
|
||||||
@@ -86,6 +111,15 @@ func (m *Manager) Serve(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if m.LaunchClient != nil {
|
||||||
|
for _, name := range m.allClientProcesses {
|
||||||
|
err := m.LaunchClient(ctx, name, m.listener.Addr().String(), m.password, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to launch '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -99,22 +133,24 @@ func (m *Manager) Serve(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
logger.Tracef(ctx, "accepted a connection from '%s'", conn.RemoteAddr())
|
logger.Tracef(ctx, "accepted a connection from '%s'", conn.RemoteAddr())
|
||||||
|
|
||||||
m.addNewConnection(ctx, conn)
|
m.addNewConnection(ctx, conn, onReceivedMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) addNewConnection(
|
func (m *Manager) addNewConnection(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn net.Conn,
|
conn net.Conn,
|
||||||
|
onReceivedMessage OnReceivedMessageFunc,
|
||||||
) {
|
) {
|
||||||
go func() {
|
go func() {
|
||||||
m.handleConnection(ctx, conn)
|
m.handleConnection(ctx, conn, onReceivedMessage)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) handleConnection(
|
func (m *Manager) handleConnection(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn net.Conn,
|
conn net.Conn,
|
||||||
|
onReceivedMessage OnReceivedMessageFunc,
|
||||||
) {
|
) {
|
||||||
var regMessage RegistrationMessage
|
var regMessage RegistrationMessage
|
||||||
logger.Tracef(ctx, "handleConnection from %s", conn.RemoteAddr())
|
logger.Tracef(ctx, "handleConnection from %s", conn.RemoteAddr())
|
||||||
@@ -152,7 +188,7 @@ func (m *Manager) handleConnection(
|
|||||||
logger.Error(ctx, err)
|
logger.Error(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func(sourceName string) {
|
defer func(sourceName ProcessName) {
|
||||||
m.unregisterConnection(sourceName)
|
m.unregisterConnection(sourceName)
|
||||||
}(regMessage.Source)
|
}(regMessage.Source)
|
||||||
if err := encoder.Encode(RegistrationResult{}); err != nil {
|
if err := encoder.Encode(RegistrationResult{}); err != nil {
|
||||||
@@ -162,6 +198,16 @@ func (m *Manager) handleConnection(
|
|||||||
}
|
}
|
||||||
ctx = belt.WithField(ctx, "client", regMessage.Source)
|
ctx = belt.WithField(ctx, "client", regMessage.Source)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if m.LaunchClient == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := m.LaunchClient(ctx, regMessage.Source, m.listener.Addr().String(), m.password, true)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -185,7 +231,7 @@ func (m *Manager) handleConnection(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.processMessage(ctx, regMessage.Source, message); err != nil {
|
if err := m.processMessage(ctx, regMessage.Source, message, onReceivedMessage); err != nil {
|
||||||
logger.Errorf(
|
logger.Errorf(
|
||||||
ctx,
|
ctx,
|
||||||
"unable to process the message %#+v from %s (%s): %w",
|
"unable to process the message %#+v from %s (%s): %w",
|
||||||
@@ -198,8 +244,9 @@ func (m *Manager) handleConnection(
|
|||||||
|
|
||||||
func (m *Manager) processMessage(
|
func (m *Manager) processMessage(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
source string,
|
source ProcessName,
|
||||||
message MessageToMain,
|
message MessageToMain,
|
||||||
|
onReceivedMessage OnReceivedMessageFunc,
|
||||||
) (_ret error) {
|
) (_ret error) {
|
||||||
logger.Tracef(ctx, "processing message from '%s': %#+v", source, message)
|
logger.Tracef(ctx, "processing message from '%s': %#+v", source, message)
|
||||||
defer func() { logger.Tracef(ctx, "/processing message from '%s': %#+v: %v", source, message, _ret) }()
|
defer func() { logger.Tracef(ctx, "/processing message from '%s': %#+v: %v", source, message, _ret) }()
|
||||||
@@ -209,7 +256,7 @@ func (m *Manager) processMessage(
|
|||||||
logger.Tracef(ctx, "a broadcast message from '%s': %#+v", source, message.Content)
|
logger.Tracef(ctx, "a broadcast message from '%s': %#+v", source, message.Content)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var err *multierror.Error
|
var err *multierror.Error
|
||||||
err = multierror.Append(err, m.onReceivedMessage(ctx, source, message.Content))
|
err = multierror.Append(err, onReceivedMessage(ctx, source, message.Content))
|
||||||
|
|
||||||
errCh := make(chan error)
|
errCh := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -222,7 +269,7 @@ func (m *Manager) processMessage(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(dst string) {
|
go func(dst ProcessName) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
errCh <- m.sendMessage(ctx, source, dst, message.Content)
|
errCh <- m.sendMessage(ctx, source, dst, message.Content)
|
||||||
}(dst)
|
}(dst)
|
||||||
@@ -232,37 +279,22 @@ func (m *Manager) processMessage(
|
|||||||
return err.ErrorOrNil()
|
return err.ErrorOrNil()
|
||||||
case "main":
|
case "main":
|
||||||
logger.Tracef(ctx, "a message to the main process from '%s': %#+v", source, message.Content)
|
logger.Tracef(ctx, "a message to the main process from '%s': %#+v", source, message.Content)
|
||||||
return m.onReceivedMessage(ctx, source, message.Content)
|
return onReceivedMessage(ctx, source, message.Content)
|
||||||
default:
|
default:
|
||||||
logger.Tracef(ctx, "a message to '%s' from '%s': %#+v", message.Destination, source, message.Content)
|
logger.Tracef(ctx, "a message to '%s' from '%s': %#+v", message.Destination, source, message.Content)
|
||||||
return m.sendMessage(ctx, source, message.Destination, message.Content)
|
return m.sendMessage(ctx, source, message.Destination, message.Content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) onReceivedMessage(
|
|
||||||
ctx context.Context,
|
|
||||||
source string,
|
|
||||||
content any,
|
|
||||||
) error {
|
|
||||||
if m.OnReceivedMessage == nil {
|
|
||||||
err := fmt.Errorf("OnReceivedMessage is not set")
|
|
||||||
logger.Tracef(ctx, "%v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Tracef(ctx, "calling the OnReceivedMessage function")
|
|
||||||
return m.OnReceivedMessage(ctx, source, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageFromMain struct {
|
type MessageFromMain struct {
|
||||||
Source string
|
Source ProcessName
|
||||||
Password string
|
Password string
|
||||||
Destination string
|
Destination ProcessName
|
||||||
Content any
|
Content any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) isExpectedProcess(
|
func (m *Manager) isExpectedProcess(
|
||||||
name string,
|
name ProcessName,
|
||||||
) bool {
|
) bool {
|
||||||
for _, p := range m.allClientProcesses {
|
for _, p := range m.allClientProcesses {
|
||||||
if name == p {
|
if name == p {
|
||||||
@@ -274,8 +306,8 @@ func (m *Manager) isExpectedProcess(
|
|||||||
|
|
||||||
func (m *Manager) sendMessage(
|
func (m *Manager) sendMessage(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
source string,
|
source ProcessName,
|
||||||
destination string,
|
destination ProcessName,
|
||||||
content any,
|
content any,
|
||||||
) (_ret error) {
|
) (_ret error) {
|
||||||
logger.Tracef(ctx, "sending message message %#+v from '%s' to '%s'", content, source, destination)
|
logger.Tracef(ctx, "sending message message %#+v from '%s' to '%s'", content, source, destination)
|
||||||
@@ -309,7 +341,7 @@ func (m *Manager) sendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) waitForProcess(
|
func (m *Manager) waitForProcess(
|
||||||
name string,
|
name ProcessName,
|
||||||
) (net.Conn, error) {
|
) (net.Conn, error) {
|
||||||
if !m.isExpectedProcess(name) {
|
if !m.isExpectedProcess(name) {
|
||||||
return nil, fmt.Errorf("process '%s' is not ever expected", name)
|
return nil, fmt.Errorf("process '%s' is not ever expected", name)
|
||||||
@@ -336,7 +368,7 @@ func (m *Manager) checkPassword(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) registerConnection(
|
func (m *Manager) registerConnection(
|
||||||
sourceName string,
|
sourceName ProcessName,
|
||||||
conn net.Conn,
|
conn net.Conn,
|
||||||
) error {
|
) error {
|
||||||
if !m.isExpectedProcess(sourceName) {
|
if !m.isExpectedProcess(sourceName) {
|
||||||
@@ -356,7 +388,7 @@ func (m *Manager) registerConnection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) unregisterConnection(
|
func (m *Manager) unregisterConnection(
|
||||||
sourceName string,
|
sourceName ProcessName,
|
||||||
) {
|
) {
|
||||||
m.connsLocker.Lock()
|
m.connsLocker.Lock()
|
||||||
defer m.connsLocker.Unlock()
|
defer m.connsLocker.Unlock()
|
||||||
@@ -368,7 +400,7 @@ func (m *Manager) unregisterConnection(
|
|||||||
|
|
||||||
type RegistrationMessage struct {
|
type RegistrationMessage struct {
|
||||||
Password string
|
Password string
|
||||||
Source string
|
Source ProcessName
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegistrationResult struct {
|
type RegistrationResult struct {
|
||||||
@@ -377,6 +409,30 @@ type RegistrationResult struct {
|
|||||||
|
|
||||||
type MessageToMain struct {
|
type MessageToMain struct {
|
||||||
Password string
|
Password string
|
||||||
Destination string
|
Destination ProcessName
|
||||||
Content any
|
Content any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SendMessage(
|
||||||
|
ctx context.Context,
|
||||||
|
dst ProcessName,
|
||||||
|
content any,
|
||||||
|
) error {
|
||||||
|
conn, err := m.waitForProcess(dst)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to wait for process '%s': %w", dst, err)
|
||||||
|
}
|
||||||
|
encoder := gob.NewEncoder(conn)
|
||||||
|
msg := MessageFromMain{
|
||||||
|
Source: "main",
|
||||||
|
Password: m.password,
|
||||||
|
Destination: dst,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
err = encoder.Encode(msg)
|
||||||
|
logger.Tracef(ctx, "sending message %#+v: %v", msg, err)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to encode&send message %#+v: %w", msg, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
3
pkg/mainprocess/types.go
Normal file
3
pkg/mainprocess/types.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package mainprocess
|
||||||
|
|
||||||
|
type ProcessName string
|
@@ -50,35 +50,36 @@ func Test(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m, err := NewManager(
|
m, err := NewManager(
|
||||||
func(ctx context.Context, source string, content any) error {
|
nil,
|
||||||
handleCall("main", content)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
"child0", "child1",
|
"child0", "child1",
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer m.Close()
|
defer m.Close()
|
||||||
go m.Serve(belt.WithField(ctx, "process", "main"))
|
go m.Serve(
|
||||||
|
belt.WithField(ctx, "process", "main"),
|
||||||
|
func(ctx context.Context, source ProcessName, content any) error {
|
||||||
|
handleCall("main", content)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
c0, err := NewClient("child0", m.Addr().String(), m.Password(), func(ctx context.Context, source string, content any) error {
|
c0, err := NewClient("child0", m.Addr().String(), m.Password())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer c0.Close()
|
||||||
|
go c0.Serve(belt.WithField(ctx, "process", "child0"), func(ctx context.Context, source ProcessName, content any) error {
|
||||||
handleCall("child0", content)
|
handleCall("child0", content)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
|
||||||
defer c0.Close()
|
|
||||||
go c0.Serve(belt.WithField(ctx, "process", "child0"))
|
|
||||||
|
|
||||||
c1, err := NewClient("child1", m.Addr().String(), m.Password(), func(ctx context.Context, source string, content any) error {
|
c1, err := NewClient("child1", m.Addr().String(), m.Password())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer c1.Close()
|
||||||
|
go c1.Serve(belt.WithField(ctx, "process", "child1"), func(ctx context.Context, source ProcessName, content any) error {
|
||||||
handleCall("child1", content)
|
handleCall("child1", content)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
|
||||||
defer c1.Close()
|
|
||||||
go c1.Serve(belt.WithField(ctx, "process", "child1"))
|
|
||||||
|
|
||||||
_, err = NewClient("child2", m.Addr().String(), m.Password(), func(ctx context.Context, source string, content any) error {
|
_, err = NewClient("child2", m.Addr().String(), m.Password())
|
||||||
return nil
|
|
||||||
})
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
waitCh0 := handleCallHappened["main"]
|
waitCh0 := handleCallHappened["main"]
|
||||||
|
@@ -19,13 +19,11 @@ type ProfileMetadata struct {
|
|||||||
MaxOrder int
|
MaxOrder int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
CachePath *string `yaml:"cache_path"`
|
CachePath *string `yaml:"cache_path"`
|
||||||
GitRepo GitRepoConfig
|
GitRepo GitRepoConfig
|
||||||
Backends streamcontrol.Config
|
Backends streamcontrol.Config
|
||||||
ProfileMetadata map[streamcontrol.ProfileName]ProfileMetadata
|
ProfileMetadata map[streamcontrol.ProfileName]ProfileMetadata
|
||||||
SentryDSN string `yaml:"sentry_dsn"`
|
|
||||||
StreamServer streamserver.Config `yaml:"stream_server"`
|
StreamServer streamserver.Config `yaml:"stream_server"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
pkg/streamd/config/git_repo_config.go
Normal file
8
pkg/streamd/config/git_repo_config.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type GitRepoConfig struct {
|
||||||
|
Enable *bool
|
||||||
|
URL string `yaml:"url,omitempty"`
|
||||||
|
PrivateKey string `yaml:"private_key,omitempty"`
|
||||||
|
LatestSyncCommit string `yaml:"latest_sync_commit,omitempty"` // TODO: deprecate this field, it's just a non-needed mechanism (better to check against git history).
|
||||||
|
}
|
@@ -17,7 +17,6 @@ type ScreenshotConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
SentryDSN string `yaml:"sentry_dsn"`
|
|
||||||
RemoteStreamDAddr string `yaml:"streamd_remote"`
|
RemoteStreamDAddr string `yaml:"streamd_remote"`
|
||||||
BuiltinStreamD streamd.Config `yaml:"streamd_builtin"`
|
BuiltinStreamD streamd.Config `yaml:"streamd_builtin"`
|
||||||
Screenshot ScreenshotConfig `yaml:"screenshot"`
|
Screenshot ScreenshotConfig `yaml:"screenshot"`
|
||||||
|
@@ -18,9 +18,3 @@ type OptionRemoteStreamDAddr string
|
|||||||
func (o OptionRemoteStreamDAddr) Apply(cfg *Config) {
|
func (o OptionRemoteStreamDAddr) Apply(cfg *Config) {
|
||||||
cfg.RemoteStreamDAddr = string(o)
|
cfg.RemoteStreamDAddr = string(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptionSentryDSN string
|
|
||||||
|
|
||||||
func (o OptionSentryDSN) Apply(cfg *Config) {
|
|
||||||
cfg.SentryDSN = string(o)
|
|
||||||
}
|
|
||||||
|
14
pkg/streampanel/fyne.go
Normal file
14
pkg/streampanel/fyne.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package streampanel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hideWindow(w fyne.Window) {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
w.Hide()
|
||||||
|
}
|
||||||
|
}
|
@@ -6,4 +6,3 @@ type Config = config.Config
|
|||||||
type Option = config.Option
|
type Option = config.Option
|
||||||
type Options = config.Options
|
type Options = config.Options
|
||||||
type OptionRemoteStreamDAddr = config.OptionRemoteStreamDAddr
|
type OptionRemoteStreamDAddr = config.OptionRemoteStreamDAddr
|
||||||
type OptionSentryDSN = config.OptionSentryDSN
|
|
||||||
|
@@ -200,6 +200,23 @@ func (p *Panel) dumpConfig(ctx context.Context) {
|
|||||||
logger.Tracef(ctx, "the current config is: %s", buf.String())
|
logger.Tracef(ctx, "the current config is: %s", buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Panel) lazyInitStreamD(ctx context.Context) error {
|
||||||
|
if p.StreamD != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Config.RemoteStreamDAddr != "" {
|
||||||
|
if err := p.initRemoteStreamD(ctx); err != nil {
|
||||||
|
return fmt.Errorf("unable to initialize the remote stream controller '%s': %w", p.Config.RemoteStreamDAddr, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := p.initBuiltinStreamD(ctx); err != nil {
|
||||||
|
return fmt.Errorf("unable to initialize the builtin stream controller '%s': %w", p.configPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Panel) Loop(ctx context.Context, opts ...LoopOption) error {
|
func (p *Panel) Loop(ctx context.Context, opts ...LoopOption) error {
|
||||||
if p.defaultContext != nil {
|
if p.defaultContext != nil {
|
||||||
return fmt.Errorf("Loop was already used, and cannot be used the second time")
|
return fmt.Errorf("Loop was already used, and cannot be used the second time")
|
||||||
@@ -210,14 +227,8 @@ func (p *Panel) Loop(ctx context.Context, opts ...LoopOption) error {
|
|||||||
|
|
||||||
p.defaultContext = ctx
|
p.defaultContext = ctx
|
||||||
|
|
||||||
if p.Config.RemoteStreamDAddr != "" {
|
if err := p.lazyInitStreamD(ctx); err != nil {
|
||||||
if err := p.initRemoteStreamD(ctx); err != nil {
|
return fmt.Errorf("unable to initialize stream controller: %w", err)
|
||||||
return fmt.Errorf("unable to initialize the remote stream controller '%s': %w", p.Config.RemoteStreamDAddr, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := p.initBuiltinStreamD(ctx); err != nil {
|
|
||||||
return fmt.Errorf("unable to initialize the builtin stream controller '%s': %w", p.configPath, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.app = fyneapp.New()
|
p.app = fyneapp.New()
|
||||||
|
7
pkg/streampanel/util.go
Normal file
7
pkg/streampanel/util.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package streampanel
|
||||||
|
|
||||||
|
func assert(b bool) {
|
||||||
|
if !b {
|
||||||
|
panic("assertion failed")
|
||||||
|
}
|
||||||
|
}
|
77
pkg/streamserver/server/traffic_counter.go
Normal file
77
pkg/streamserver/server/traffic_counter.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NumBytesWroter interface {
|
||||||
|
NumBytesWrote() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type NumBytesReader interface {
|
||||||
|
NumBytesRead() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type NumBytesReaderWroter interface {
|
||||||
|
NumBytesReader
|
||||||
|
NumBytesWroter
|
||||||
|
}
|
||||||
|
|
||||||
|
type Counter interface {
|
||||||
|
Count() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrafficCounter struct {
|
||||||
|
sync.Mutex
|
||||||
|
ReaderCounter Counter
|
||||||
|
WriterCounter Counter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TrafficCounter) NumBytesWrote() uint64 {
|
||||||
|
if tc == nil {
|
||||||
|
return math.MaxUint64
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.Lock()
|
||||||
|
defer tc.Unlock()
|
||||||
|
if tc.WriterCounter == nil {
|
||||||
|
return math.MaxUint64
|
||||||
|
}
|
||||||
|
return tc.WriterCounter.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TrafficCounter) NumBytesRead() uint64 {
|
||||||
|
if tc == nil {
|
||||||
|
return math.MaxUint64
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.Lock()
|
||||||
|
defer tc.Unlock()
|
||||||
|
if tc.ReaderCounter == nil {
|
||||||
|
return math.MaxUint64
|
||||||
|
}
|
||||||
|
fmt.Println(tc.ReaderCounter.Count())
|
||||||
|
return tc.ReaderCounter.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntPtrCounter struct {
|
||||||
|
Pointer *int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIntPtrCounter(v *int) *IntPtrCounter {
|
||||||
|
return &IntPtrCounter{
|
||||||
|
Pointer: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IntPtrCounter) Count() uint64 {
|
||||||
|
if c == nil || c.Pointer == nil {
|
||||||
|
return math.MaxUint64
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint64(atomic.LoadInt32((*int32)(unsafe.Pointer(c.Pointer))))
|
||||||
|
}
|
7
pkg/streamserver/util.go
Normal file
7
pkg/streamserver/util.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package streamserver
|
||||||
|
|
||||||
|
func assert(b bool) {
|
||||||
|
if !b {
|
||||||
|
panic("assertion failed")
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user