GODT-2039: bridge monitors bridge-gui via its PID.

This commit is contained in:
Xavier Michelon
2022-11-08 11:08:11 +01:00
committed by Romain LE JEUNE
parent f5148074fd
commit 3b9a3aaad2
7 changed files with 74 additions and 9 deletions

View File

@ -76,6 +76,7 @@ const (
flagRestart = "restart" flagRestart = "restart"
FlagLauncher = "launcher" FlagLauncher = "launcher"
FlagNoWindow = "no-window" FlagNoWindow = "no-window"
FlagParentPID = "parent-pid"
) )
type Base struct { type Base struct {
@ -324,6 +325,12 @@ func (b *Base) NewApp(mainLoop func(*Base, *cli.Context) error) *cli.App {
Usage: "The launcher to use to restart the application", Usage: "The launcher to use to restart the application",
Hidden: true, Hidden: true,
}, },
&cli.IntFlag{
Name: FlagParentPID,
Usage: "The PID of the process that started the application. Ignored if frontend is not gRPC",
Hidden: true,
Value: -1,
},
} }
return app return app

View File

@ -86,6 +86,7 @@ func main(b *base.Base, c *cli.Context) error { //nolint:funlen
b.Updater, b.Updater,
b, b,
b.Locations, b.Locations,
c.Int(base.FlagParentPID),
) )
cache, cacheErr := loadMessageCache(b) cache, cacheErr := loadMessageCache(b)

View File

@ -220,7 +220,12 @@ void launchBridge(QStringList const &args)
else else
app().log().debug(QString("Bridge executable path: %1").arg(QDir::toNativeSeparators(bridgeExePath))); app().log().debug(QString("Bridge executable path: %1").arg(QDir::toNativeSeparators(bridgeExePath)));
overseer = std::make_unique<Overseer>(new ProcessMonitor(bridgeExePath, QStringList("--grpc") + args, nullptr), nullptr);
qint64 const pid = qApp->applicationPid();
QStringList const params = QStringList { "--grpc", "--parent-pid", QString::number(pid) } + args ;
app().log().info(QString("Launching bridge process with command \"%1\" %2").arg(bridgeExePath, params.join(" ")));
overseer = std::make_unique<Overseer>(new ProcessMonitor(bridgeExePath, params , nullptr), nullptr);
overseer->startWorker(true); overseer->startWorker(true);
} }

View File

@ -56,6 +56,7 @@ func New(
updater types.Updater, updater types.Updater,
restarter types.Restarter, restarter types.Restarter,
locations *locations.Locations, locations *locations.Locations,
parentPID int,
) Frontend { ) Frontend {
switch frontendType { switch frontendType {
case GRPC: case GRPC:
@ -66,6 +67,7 @@ func New(
updater, updater,
restarter, restarter,
locations, locations,
parentPID,
) )
case CLI: case CLI:

View File

@ -40,6 +40,9 @@ import (
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain" "github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
"github.com/ProtonMail/proton-bridge/v2/pkg/listener" "github.com/ProtonMail/proton-bridge/v2/pkg/listener"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi" "github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
"github.com/bradenaw/juniper/xslices"
"github.com/elastic/go-sysinfo"
sysinfotypes "github.com/elastic/go-sysinfo/types"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -85,6 +88,8 @@ type Service struct { // nolint:structcheck
locations *locations.Locations locations *locations.Locations
token string token string
pemCert string pemCert string
parentPID int
parentPIDDoneCh chan struct{}
} }
// NewService returns a new instance of the service. // NewService returns a new instance of the service.
@ -95,6 +100,7 @@ func NewService(
updater types.Updater, updater types.Updater,
restarter types.Restarter, restarter types.Restarter,
locations *locations.Locations, locations *locations.Locations,
parentPID int,
) *Service { ) *Service {
s := Service{ s := Service{
UnimplementedBridgeServer: UnimplementedBridgeServer{}, UnimplementedBridgeServer: UnimplementedBridgeServer{},
@ -110,6 +116,8 @@ func NewService(
firstTimeAutostart: sync.Once{}, firstTimeAutostart: sync.Once{},
locations: locations, locations: locations,
token: uuid.NewString(), token: uuid.NewString(),
parentPID: parentPID,
parentPIDDoneCh: make(chan struct{}),
} }
// Initializing.Done is only called sync.Once. Please keep the increment // Initializing.Done is only called sync.Once. Please keep the increment
@ -177,6 +185,12 @@ func (s *Service) Loop(b types.Bridger) error {
s.initAutostart() s.initAutostart()
s.startGRPCServer() s.startGRPCServer()
if s.parentPID < 0 {
s.log.Info("Not monitoring parent PID")
} else {
go s.monitorParentPID()
}
defer func() { defer func() {
s.bridge.SetBool(settings.FirstStartGUIKey, false) s.bridge.SetBool(settings.FirstStartGUIKey, false)
}() }()
@ -520,3 +534,36 @@ func (s *Service) validateStreamServerToken(
return handler(srv, ss) return handler(srv, ss)
} }
// monitorParentPID check at regular intervals that the parent process is still alive, and if not shuts down the server
// and the applications.
func (s *Service) monitorParentPID() {
s.log.Infof("Starting to monitor parent PID %v", s.parentPID)
ticker := time.NewTicker(5 * time.Second)
for {
select {
case <-ticker.C:
if s.parentPID < 0 {
continue
}
processes, err := sysinfo.Processes() // sysinfo.Process(pid) does not seem to work on Windows.
if err != nil {
s.log.Debug("Could not retrieve process list")
continue
}
if !xslices.Any(processes, func(p sysinfotypes.Process) bool { return p != nil && p.PID() == s.parentPID }) {
s.log.Info("Parent process does not exist anymore. Initiating shutdown")
go s.quit() // quit will write to the parentPIDDoneCh, so we launch a goroutine.
} else {
s.log.Tracef("Parent process %v is still alive", s.parentPID)
}
case <-s.parentPIDDoneCh:
s.log.Infof("Stopping process monitoring for PID %v", s.parentPID)
return
}
}
}

View File

@ -95,15 +95,19 @@ func (s *Service) GuiReady(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empt
// Quit implement the Quit gRPC service call. // Quit implement the Quit gRPC service call.
func (s *Service) Quit(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { func (s *Service) Quit(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
s.log.Debug("Quit") s.log.Debug("Quit")
return &emptypb.Empty{}, s.quit() s.quit()
return &emptypb.Empty{}, nil
} }
func (s *Service) quit() error { func (s *Service) quit() {
// Windows is notably slow at Quitting. We do it in a goroutine to speed things up a bit. // Windows is notably slow at Quitting. We do it in a goroutine to speed things up a bit.
go func() { go func() {
var err error if s.parentPID >= 0 {
s.parentPIDDoneCh <- struct{}{}
}
if s.isStreamingEvents() { if s.isStreamingEvents() {
if err = s.stopEventStream(); err != nil { if err := s.stopEventStream(); err != nil {
s.log.WithError(err).Error("Quit failed.") s.log.WithError(err).Error("Quit failed.")
} }
} }
@ -111,8 +115,6 @@ func (s *Service) quit() error {
// The following call is launched as a goroutine, as it will wait for current calls to end, including this one. // The following call is launched as a goroutine, as it will wait for current calls to end, including this one.
s.grpcServer.GracefulStop() s.grpcServer.GracefulStop()
}() }()
return nil
} }
// Restart implement the Restart gRPC service call. // Restart implement the Restart gRPC service call.

View File

@ -71,8 +71,9 @@ func (s *Service) RunEventStream(request *EventStreamRequest, server Bridge_RunE
return err return err
} }
case <-server.Context().Done(): case <-server.Context().Done():
s.log.Debug("Client closed the stream, exiting") s.log.Info("Client closed the stream, initiating shutdown")
return s.quit() s.quit()
return nil
} }
} }
} }