@@ -16,7 +16,6 @@ import (
16
16
"os"
17
17
"os/user"
18
18
"path/filepath"
19
- "reflect"
20
19
"sort"
21
20
"strconv"
22
21
"strings"
@@ -60,7 +59,7 @@ type Options struct {
60
59
ReconnectingPTYTimeout time.Duration
61
60
EnvironmentVariables map [string ]string
62
61
Logger slog.Logger
63
- AgentPorts map [int ]string
62
+ IgnorePorts map [int ]string
64
63
SSHMaxTimeout time.Duration
65
64
TailnetListenPort uint16
66
65
}
@@ -76,7 +75,12 @@ type Client interface {
76
75
PatchStartupLogs (ctx context.Context , req agentsdk.PatchStartupLogs ) error
77
76
}
78
77
79
- func New (options Options ) io.Closer {
78
+ type Agent interface {
79
+ HTTPDebug () http.Handler
80
+ io.Closer
81
+ }
82
+
83
+ func New (options Options ) Agent {
80
84
if options .ReconnectingPTYTimeout == 0 {
81
85
options .ReconnectingPTYTimeout = 5 * time .Minute
82
86
}
@@ -112,7 +116,7 @@ func New(options Options) io.Closer {
112
116
tempDir : options .TempDir ,
113
117
lifecycleUpdate : make (chan struct {}, 1 ),
114
118
lifecycleReported : make (chan codersdk.WorkspaceAgentLifecycle , 1 ),
115
- ignorePorts : options .AgentPorts ,
119
+ ignorePorts : options .IgnorePorts ,
116
120
connStatsChan : make (chan * agentsdk.Stats , 1 ),
117
121
sshMaxTimeout : options .SSHMaxTimeout ,
118
122
}
@@ -161,7 +165,7 @@ type agent struct {
161
165
}
162
166
163
167
func (a * agent ) init (ctx context.Context ) {
164
- sshSrv , err := agentssh .NewServer (ctx , a .logger .Named ("ssh-server" ), a .sshMaxTimeout )
168
+ sshSrv , err := agentssh .NewServer (ctx , a .logger .Named ("ssh-server" ), a .filesystem , a . sshMaxTimeout , "" )
165
169
if err != nil {
166
170
panic (err )
167
171
}
@@ -648,6 +652,7 @@ func (a *agent) createTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) (_
648
652
}
649
653
break
650
654
}
655
+ logger .Debug (ctx , "accepted conn" , slog .F ("remote" , conn .RemoteAddr ().String ()))
651
656
wg .Add (1 )
652
657
closed := make (chan struct {})
653
658
go func () {
@@ -676,6 +681,7 @@ func (a *agent) createTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) (_
676
681
var msg codersdk.WorkspaceAgentReconnectingPTYInit
677
682
err = json .Unmarshal (data , & msg )
678
683
if err != nil {
684
+ logger .Warn (ctx , "failed to unmarshal init" , slog .F ("raw" , data ))
679
685
return
680
686
}
681
687
_ = a .handleReconnectingPTY (ctx , logger , msg , conn )
@@ -967,6 +973,7 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, m
967
973
968
974
connectionID := uuid .NewString ()
969
975
logger = logger .With (slog .F ("id" , msg .ID ), slog .F ("connection_id" , connectionID ))
976
+ logger .Debug (ctx , "starting handler" )
970
977
971
978
defer func () {
972
979
if err := retErr ; err != nil {
@@ -1034,20 +1041,20 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, m
1034
1041
// 1. The timeout completed.
1035
1042
// 2. The parent context was canceled.
1036
1043
<- ctx .Done ()
1044
+ logger .Debug (ctx , "context done" , slog .Error (ctx .Err ()))
1037
1045
_ = process .Kill ()
1038
1046
}()
1039
- go func () {
1040
- // If the process dies randomly, we should
1041
- // close the pty.
1042
- _ = process .Wait ()
1043
- rpty .Close ()
1044
- }()
1047
+ // We don't need to separately monitor for the process exiting.
1048
+ // When it exits, our ptty.OutputReader() will return EOF after
1049
+ // reading all process output.
1045
1050
if err = a .trackConnGoroutine (func () {
1046
1051
buffer := make ([]byte , 1024 )
1047
1052
for {
1048
- read , err := rpty .ptty .Output ().Read (buffer )
1053
+ read , err := rpty .ptty .OutputReader ().Read (buffer )
1049
1054
if err != nil {
1050
1055
// When the PTY is closed, this is triggered.
1056
+ // Error is typically a benign EOF, so only log for debugging.
1057
+ logger .Debug (ctx , "unable to read pty output, command exited?" , slog .Error (err ))
1051
1058
break
1052
1059
}
1053
1060
part := buffer [:read ]
@@ -1059,8 +1066,15 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, m
1059
1066
break
1060
1067
}
1061
1068
rpty .activeConnsMutex .Lock ()
1062
- for _ , conn := range rpty .activeConns {
1063
- _ , _ = conn .Write (part )
1069
+ for cid , conn := range rpty .activeConns {
1070
+ _ , err = conn .Write (part )
1071
+ if err != nil {
1072
+ logger .Debug (ctx ,
1073
+ "error writing to active conn" ,
1074
+ slog .F ("other_conn_id" , cid ),
1075
+ slog .Error (err ),
1076
+ )
1077
+ }
1064
1078
}
1065
1079
rpty .activeConnsMutex .Unlock ()
1066
1080
}
@@ -1138,7 +1152,7 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, m
1138
1152
logger .Warn (ctx , "read conn" , slog .Error (err ))
1139
1153
return nil
1140
1154
}
1141
- _ , err = rpty .ptty .Input ().Write ([]byte (req .Data ))
1155
+ _ , err = rpty .ptty .InputWriter ().Write ([]byte (req .Data ))
1142
1156
if err != nil {
1143
1157
logger .Warn (ctx , "write to pty" , slog .Error (err ))
1144
1158
return nil
@@ -1221,11 +1235,11 @@ func (a *agent) startReportingConnectionStats(ctx context.Context) {
1221
1235
// Convert from microseconds to milliseconds.
1222
1236
stats .ConnectionMedianLatencyMS /= 1000
1223
1237
1224
- lastStat := a . latestStat . Load ()
1225
- if lastStat != nil && reflect . DeepEqual ( lastStat , stats ) {
1226
- a . logger . Info ( ctx , "skipping stat because nothing changed" )
1227
- return
1228
- }
1238
+ // Collect agent metrics.
1239
+ // Agent metrics are changing all the time, so there is no need to perform
1240
+ // reflect.DeepEqual to see if stats should be transferred.
1241
+ stats . Metrics = collectMetrics ()
1242
+
1229
1243
a .latestStat .Store (stats )
1230
1244
1231
1245
select {
@@ -1267,6 +1281,27 @@ func (a *agent) isClosed() bool {
1267
1281
}
1268
1282
}
1269
1283
1284
+ func (a * agent ) HTTPDebug () http.Handler {
1285
+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1286
+ a .closeMutex .Lock ()
1287
+ network := a .network
1288
+ a .closeMutex .Unlock ()
1289
+
1290
+ if network == nil {
1291
+ w .WriteHeader (http .StatusOK )
1292
+ _ , _ = w .Write ([]byte ("network is not ready yet" ))
1293
+ return
1294
+ }
1295
+
1296
+ if r .URL .Path == "/debug/magicsock" {
1297
+ network .MagicsockServeHTTPDebug (w , r )
1298
+ } else {
1299
+ w .WriteHeader (http .StatusNotFound )
1300
+ _ , _ = w .Write ([]byte ("404 not found" ))
1301
+ }
1302
+ })
1303
+ }
1304
+
1270
1305
func (a * agent ) Close () error {
1271
1306
a .closeMutex .Lock ()
1272
1307
defer a .closeMutex .Unlock ()
@@ -1358,7 +1393,7 @@ type reconnectingPTY struct {
1358
1393
circularBuffer * circbuf.Buffer
1359
1394
circularBufferMutex sync.RWMutex
1360
1395
timeout * time.Timer
1361
- ptty pty.PTY
1396
+ ptty pty.PTYCmd
1362
1397
}
1363
1398
1364
1399
// Close ends all connections to the reconnecting
0 commit comments