Created
September 19, 2023 06:42
-
-
Save hardeepnarang10/970ede593ed6d0c4191abdc5ae5fd53f to your computer and use it in GitHub Desktop.
A practical graceful stop implementation in go, one illustrated with channels, and a second, arguably better with a local context. Channels implementation expects an incoming context; recommended to pass a notify context. Grace duration is independent of the incoming context.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"log" | |
"time" | |
) | |
func gracefulStop(ctx context.Context, graceDuration time.Duration, stop func() error) error { | |
errChanIntermediary, errChanFinal := make(chan error), make(chan error) | |
defer close(errChanFinal) | |
defer close(errChanIntermediary) | |
go func(errChan chan<- error) { | |
if err := stop(); err != nil { | |
errChan <- fmt.Errorf("stop() call returned an error: %w", err) | |
} | |
}(errChanIntermediary) | |
<-ctx.Done() | |
preparedCtx, preparedCtxCancel := context.WithTimeout(context.Background(), graceDuration) | |
go func(prepCtx context.Context, prepCancel func(), errChannelIn <-chan error, errChannelOut chan<- error) { | |
for { | |
select { | |
case <-prepCtx.Done(): | |
return | |
case err := <-errChannelIn: | |
errChannelOut <- err | |
prepCancel() | |
return | |
default: | |
continue | |
} | |
} | |
}(preparedCtx, preparedCtxCancel, errChanIntermediary, errChanFinal) | |
<-preparedCtx.Done() | |
if len(errChanFinal) != 0 { | |
if err := <-errChanFinal; err != nil { | |
log.Panic(err) | |
} | |
} | |
return nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"time" | |
) | |
func gracefulStop(stop func() error, graceDuration time.Duration, graceTimeoutError error) error { | |
graceCtx, cancel := context.WithTimeout(context.Background(), graceDuration) | |
defer cancel() | |
errChan := make(chan error) | |
defer close(errChan) | |
context.AfterFunc(graceCtx, func() { | |
if err := stop(); err != nil { | |
errChan <- fmt.Errorf("stop() call returned an error: %w", err) | |
} else { | |
errChan <- nil | |
} | |
}) | |
for { | |
select { | |
case <-graceCtx.Done(): | |
return graceTimeoutError | |
case err := <-errChan: | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment