@@ -2,20 +2,23 @@ package service
22
33import (
44 "context"
5+ "errors"
56 "fmt"
67 "net"
78 "net/http"
89 "net/rpc"
9- "os"
10+ "os/signal "
1011 "sort"
1112 "strings"
13+ "sync"
1214 "time"
1315
1416 "github.com/cenkalti/backoff"
1517 "github.com/mohae/deepcopy"
1618 "github.com/olekukonko/tablewriter"
1719 occfg "github.com/opencloud-eu/opencloud/pkg/config"
1820 "github.com/opencloud-eu/opencloud/pkg/log"
21+ "github.com/opencloud-eu/opencloud/pkg/runner"
1922 ogrpc "github.com/opencloud-eu/opencloud/pkg/service/grpc"
2023 "github.com/opencloud-eu/opencloud/pkg/shared"
2124 activitylog "github.com/opencloud-eu/opencloud/services/activitylog/pkg/command"
@@ -358,8 +361,9 @@ func Start(ctx context.Context, o ...Option) error {
358361 return err
359362 }
360363
361- // get a cancel function to stop the service
362- ctx , cancel := context .WithCancel (ctx )
364+ // cancel the context when a signal is received.
365+ notifyCtx , cancel := signal .NotifyContext (ctx , runner .StopSignals ... )
366+ defer cancel ()
363367
364368 // tolerance controls backoff cycles from the supervisor.
365369 tolerance := 5
@@ -397,14 +401,14 @@ func Start(ctx context.Context, o ...Option) error {
397401 if err != nil {
398402 s .Log .Fatal ().Err (err ).Msg ("could not start listener" )
399403 }
404+ srv := new (http.Server )
400405
401406 defer func () {
402407 if r := recover (); r != nil {
403408 reason := strings.Builder {}
404409 if _ , err = net .Dial ("tcp" , net .JoinHostPort (s .cfg .Runtime .Host , s .cfg .Runtime .Port )); err != nil {
405410 reason .WriteString ("runtime address already in use" )
406411 }
407-
408412 fmt .Println (reason .String ())
409413 }
410414 }()
@@ -417,10 +421,7 @@ func Start(ctx context.Context, o ...Option) error {
417421 // go supervisor.Serve()
418422 // because that will briefly create a race condition as it starts up, if you try to .Add() services immediately afterward.
419423 // https://pkg.go.dev/github.com/thejerf/suture/[email protected] #Supervisor 420- go s .Supervisor .ServeBackground (s .context )
421-
422- // trap will block on context done channel for interruptions.
423- go trap (s , ctx )
424+ go s .Supervisor .ServeBackground (s .context ) // TODO Why does Supervisor uses s.context?
424425
425426 for i , service := range s .Services {
426427 scheduleServiceTokens (s , service )
@@ -434,7 +435,15 @@ func Start(ctx context.Context, o ...Option) error {
434435 // schedule services that are optional
435436 scheduleServiceTokens (s , s .Additional )
436437
437- return http .Serve (l , nil )
438+ go func () {
439+ if err = srv .Serve (l ); err != nil && ! errors .Is (err , http .ErrServerClosed ) {
440+ s .Log .Fatal ().Err (err ).Msg ("could not start rpc server" )
441+ }
442+ }()
443+
444+ // trapShutdownCtx will block on the context-done channel for interruptions.
445+ trapShutdownCtx (s , srv , notifyCtx )
446+ return nil
438447}
439448
440449// scheduleServiceTokens adds service tokens to the service supervisor.
@@ -501,20 +510,51 @@ func (s *Service) List(_ struct{}, reply *string) error {
501510 return nil
502511}
503512
504- // trap blocks on halt channel. When the runtime is interrupted it
505- // signals the controller to stop any supervised process.
506- func trap (s * Service , ctx context.Context ) {
513+ func trapShutdownCtx (s * Service , srv * http.Server , ctx context.Context ) {
507514 <- ctx .Done ()
515+ wg := sync.WaitGroup {}
516+ wg .Add (1 )
517+ go func () {
518+ defer wg .Done ()
519+ // TODO: To discuss the default timeout
520+ ctx , cancel := context .WithTimeout (context .Background (), 20 * time .Second )
521+ defer cancel ()
522+ if err := srv .Shutdown (ctx ); err != nil {
523+ s .Log .Error ().Err (err ).Msg ("could not shutdown tcp listener" )
524+ return
525+ }
526+ s .Log .Info ().Msg ("tcp listener shutdown" )
527+ }()
528+
508529 for sName := range s .serviceToken {
509530 for i := range s .serviceToken [sName ] {
510- if err := s .Supervisor .Remove (s.serviceToken [sName ][i ]); err != nil {
511- s .Log .Error ().Err (err ).Str ("service" , "runtime service" ).Msgf ("terminating with signal: %v" , s )
512- }
531+ wg .Add (1 )
532+ go func () {
533+ s .Log .Warn ().Msgf ("=== RemoveAndWait for %s" , sName )
534+ defer wg .Done ()
535+ // TODO: To discuss the default timeout
536+ if err := s .Supervisor .RemoveAndWait (s.serviceToken [sName ][i ], 20 * time .Second ); err != nil && ! errors .Is (err , suture .ErrSupervisorNotRunning ) {
537+ s .Log .Error ().Err (err ).Str ("service" , sName ).Msgf ("terminating with signal: %+v" , s )
538+ }
539+ s .Log .Warn ().Msgf ("=== Done RemoveAndWait for %s" , sName )
540+ }()
513541 }
514542 }
515- s .Log .Debug ().Str ("service" , "runtime service" ).Msgf ("terminating with signal: %v" , s )
516- time .Sleep (3 * time .Second ) // give the services time to deregister
517- os .Exit (0 ) // FIXME this cause an early exit that prevents services from shitting down properly
543+
544+ done := make (chan struct {})
545+ go func () {
546+ wg .Wait ()
547+ close (done )
548+ }()
549+
550+ select {
551+ // TODO: To discuss the default timeout
552+ case <- time .After (30 * time .Second ):
553+ s .Log .Fatal ().Msg ("ocis graceful shutdown timeout reached, terminating" )
554+ case <- done :
555+ s .Log .Info ().Msg ("all ocis services gracefully stopped" )
556+ return
557+ }
518558}
519559
520560// pingNats will attempt to connect to nats, blocking until a connection is established
0 commit comments