watchAndClose ensures closer is called if the context is canceled or the workspace reaches the stopped state. Watching the stopped state is a work-around for cases where the agent is not gracefully shut down and the connection is left open. If, for instance, the networking is stopped before the age
(ctx context.Context, closer func() error, logger slog.Logger, client *codersdk.Client, workspace codersdk.Workspace, errCh <-chan error)
| 938 | // |
| 939 | // See: https://github.com/coder/coder/issues/6180 |
| 940 | func watchAndClose(ctx context.Context, closer func() error, logger slog.Logger, client *codersdk.Client, workspace codersdk.Workspace, errCh <-chan error) { |
| 941 | // Ensure session is ended on both context cancellation |
| 942 | // and workspace stop. |
| 943 | defer func() { |
| 944 | err := closer() |
| 945 | if err != nil { |
| 946 | logger.Error(ctx, "error closing session", slog.Error(err)) |
| 947 | } |
| 948 | }() |
| 949 | |
| 950 | startWatchLoop: |
| 951 | for { |
| 952 | logger.Debug(ctx, "connecting to the coder server to watch workspace events") |
| 953 | var wsWatch <-chan codersdk.Workspace |
| 954 | var err error |
| 955 | for r := retry.New(time.Second, 15*time.Second); r.Wait(ctx); { |
| 956 | wsWatch, err = client.WatchWorkspace(ctx, workspace.ID) |
| 957 | if err == nil { |
| 958 | break |
| 959 | } |
| 960 | if ctx.Err() != nil { |
| 961 | logger.Debug(ctx, "context expired", slog.Error(ctx.Err())) |
| 962 | return |
| 963 | } |
| 964 | } |
| 965 | |
| 966 | for { |
| 967 | select { |
| 968 | case <-ctx.Done(): |
| 969 | logger.Debug(ctx, "context expired", slog.Error(ctx.Err())) |
| 970 | return |
| 971 | case w, ok := <-wsWatch: |
| 972 | if !ok { |
| 973 | continue startWatchLoop |
| 974 | } |
| 975 | |
| 976 | // Transitioning to stop or delete could mean that |
| 977 | // the agent will still gracefully stop. If a new |
| 978 | // build is starting, there's no reason to wait for |
| 979 | // the agent, it should be long gone. |
| 980 | if workspace.LatestBuild.ID != w.LatestBuild.ID && w.LatestBuild.Transition == codersdk.WorkspaceTransitionStart { |
| 981 | logger.Info(ctx, "new build started") |
| 982 | return |
| 983 | } |
| 984 | // Note, we only react to the stopped state here because we |
| 985 | // want to give the agent a chance to gracefully shut down |
| 986 | // during "stopping". |
| 987 | if w.LatestBuild.Status == codersdk.WorkspaceStatusStopped { |
| 988 | logger.Info(ctx, "workspace stopped") |
| 989 | return |
| 990 | } |
| 991 | case err := <-errCh: |
| 992 | logger.Error(ctx, "failed to collect network stats", slog.Error(err)) |
| 993 | return |
| 994 | } |
| 995 | } |
| 996 | } |
| 997 | } |