Skip to content

Commit 38c7dcd

Browse files
authored
fix: avoid vscodessh exit when server restarts (coder#13970)
Mitigates an issue where vscodessh would restart when the control plane restarts, causing the entire SSH session to be reestablished.
1 parent d2b0353 commit 38c7dcd

File tree

1 file changed

+42
-9
lines changed

1 file changed

+42
-9
lines changed

cli/vscodessh.go

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,11 @@ func (r *RootCmd) vscodeSSH() *serpent.Command {
151151
// command via the ProxyCommand SSH option.
152152
pid := os.Getppid()
153153

154-
logger := inv.Logger
154+
// Use a stripped down writer that doesn't sync, otherwise you get
155+
// "failed to sync sloghuman: sync /dev/stderr: The handle is
156+
// invalid" on Windows. Syncing isn't required for stdout/stderr
157+
// anyways.
158+
logger := inv.Logger.AppendSinks(sloghuman.Sink(slogWriter{w: inv.Stderr})).Leveled(slog.LevelDebug)
155159
if logDir != "" {
156160
logFilePath := filepath.Join(logDir, fmt.Sprintf("%d.log", pid))
157161
logFile, err := fs.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY, 0o600)
@@ -160,7 +164,7 @@ func (r *RootCmd) vscodeSSH() *serpent.Command {
160164
}
161165
dc := cliutil.DiscardAfterClose(logFile)
162166
defer dc.Close()
163-
logger = logger.AppendSinks(sloghuman.Sink(dc)).Leveled(slog.LevelDebug)
167+
logger = logger.AppendSinks(sloghuman.Sink(dc))
164168
}
165169
if r.disableDirect {
166170
logger.Info(ctx, "direct connections disabled")
@@ -204,31 +208,48 @@ func (r *RootCmd) vscodeSSH() *serpent.Command {
204208
// command via the ProxyCommand SSH option.
205209
networkInfoFilePath := filepath.Join(networkInfoDir, fmt.Sprintf("%d.json", pid))
206210

207-
statsErrChan := make(chan error, 1)
211+
var (
212+
firstErrTime time.Time
213+
errCh = make(chan error, 1)
214+
)
208215
cb := func(start, end time.Time, virtual, _ map[netlogtype.Connection]netlogtype.Counts) {
209-
sendErr := func(err error) {
216+
sendErr := func(tolerate bool, err error) {
217+
logger.Error(ctx, "collect network stats", slog.Error(err))
218+
// Tolerate up to 1 minute of errors.
219+
if tolerate {
220+
if firstErrTime.IsZero() {
221+
logger.Info(ctx, "tolerating network stats errors for up to 1 minute")
222+
firstErrTime = time.Now()
223+
}
224+
if time.Since(firstErrTime) < time.Minute {
225+
return
226+
}
227+
}
228+
210229
select {
211-
case statsErrChan <- err:
230+
case errCh <- err:
212231
default:
213232
}
214233
}
215234

216235
stats, err := collectNetworkStats(ctx, agentConn, start, end, virtual)
217236
if err != nil {
218-
sendErr(err)
237+
sendErr(true, err)
219238
return
220239
}
221240

222241
rawStats, err := json.Marshal(stats)
223242
if err != nil {
224-
sendErr(err)
243+
sendErr(false, err)
225244
return
226245
}
227246
err = afero.WriteFile(fs, networkInfoFilePath, rawStats, 0o600)
228247
if err != nil {
229-
sendErr(err)
248+
sendErr(false, err)
230249
return
231250
}
251+
252+
firstErrTime = time.Time{}
232253
}
233254

234255
now := time.Now()
@@ -238,7 +259,7 @@ func (r *RootCmd) vscodeSSH() *serpent.Command {
238259
select {
239260
case <-ctx.Done():
240261
return nil
241-
case err := <-statsErrChan:
262+
case err := <-errCh:
242263
return err
243264
}
244265
},
@@ -280,6 +301,18 @@ func (r *RootCmd) vscodeSSH() *serpent.Command {
280301
return cmd
281302
}
282303

304+
// slogWriter wraps an io.Writer and removes all other methods (such as Sync),
305+
// which may cause undesired/broken behavior.
306+
type slogWriter struct {
307+
w io.Writer
308+
}
309+
310+
var _ io.Writer = slogWriter{}
311+
312+
func (s slogWriter) Write(p []byte) (n int, err error) {
313+
return s.w.Write(p)
314+
}
315+
283316
type sshNetworkStats struct {
284317
P2P bool `json:"p2p"`
285318
Latency float64 `json:"latency"`

0 commit comments

Comments
 (0)