9p: add 10s read timeout to all store read operations

This commit is contained in:
Levi Neely 2026-04-27 13:23:54 +02:00
parent b44ce18d54
commit fee59a2f7c
1 changed files with 38 additions and 11 deletions

View File

@ -322,6 +322,33 @@ func storeWrite(s store.Store, name string, data []byte) error {
return e.Write(data) return e.Write(data)
} }
// readTimeout is the server-side deadline for all non-blocking store reads.
const readTimeout = 10 * time.Second
// storeReadCtx is like storeRead but aborts if the context is cancelled or
// the read takes longer than readTimeout.
func storeReadCtx(ctx context.Context, s store.Store, name string) ([]byte, error) {
type result struct {
data []byte
err error
}
ch := make(chan result, 1)
go func() {
data, err := storeRead(s, name)
ch <- result{data, err}
}()
timer := time.NewTimer(readTimeout)
defer timer.Stop()
select {
case r := <-ch:
return r.data, r.err
case <-ctx.Done():
return nil, ctx.Err()
case <-timer.C:
return nil, fmt.Errorf("read timeout")
}
}
// storeBlockingRead opens an entry and performs a blocking read. // storeBlockingRead opens an entry and performs a blocking read.
func storeBlockingRead(s store.Store, name string, ctx context.Context, base string) ([]byte, error) { func storeBlockingRead(s store.Store, name string, ctx context.Context, base string) ([]byte, error) {
e, err := s.Open(name) e, err := s.Open(name)
@ -721,7 +748,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
// Fixed files directly under /s/ are served from the session store. // Fixed files directly under /s/ are served from the session store.
if isSessionStoreFile(path) { if isSessionStoreFile(path) {
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
content, err := storeRead(s.sessionStore, pathBase(path)) content, err := storeReadCtx(ctx, s.sessionStore, pathBase(path))
if err != nil { if err != nil {
s.log.Debug("Rread path=%q err=%v", path, err) s.log.Debug("Rread path=%q err=%v", path, err)
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())
@ -750,7 +777,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
// Agent config files are served from the agent store. // Agent config files are served from the agent store.
if strings.HasPrefix(path, "/a/") { if strings.HasPrefix(path, "/a/") {
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
content, err := storeRead(s.agentStore, pathBase(path)) content, err := storeReadCtx(ctx, s.agentStore, pathBase(path))
if err != nil { if err != nil {
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())
} }
@ -760,7 +787,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
// Prompt files are served from the prompt store. // Prompt files are served from the prompt store.
if strings.HasPrefix(path, "/p/") { if strings.HasPrefix(path, "/p/") {
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
content, err := storeRead(s.promptStore, pathBase(path)) content, err := storeReadCtx(ctx, s.promptStore, pathBase(path))
if err != nil { if err != nil {
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())
} }
@ -770,7 +797,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
// Memory files are served from the memory store. // Memory files are served from the memory store.
if strings.HasPrefix(path, "/m/") { if strings.HasPrefix(path, "/m/") {
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
content, err := storeRead(s.memStore, pathBase(path)) content, err := storeReadCtx(ctx, s.memStore, pathBase(path))
if err != nil { if err != nil {
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())
} }
@ -781,7 +808,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
// Skill files are served from the skill store. // Skill files are served from the skill store.
if strings.HasPrefix(path, "/sk/") { if strings.HasPrefix(path, "/sk/") {
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
content, err := storeRead(s.skillStore, pathBase(path)) content, err := storeReadCtx(ctx, s.skillStore, pathBase(path))
if err != nil { if err != nil {
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())
} }
@ -791,7 +818,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
// Tool files are served from the tool store. // Tool files are served from the tool store.
if strings.HasPrefix(path, "/t/") { if strings.HasPrefix(path, "/t/") {
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
content, err := storeRead(s.toolStore, pathBase(path)) content, err := storeReadCtx(ctx, s.toolStore, pathBase(path))
if err != nil { if err != nil {
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())
} }
@ -801,7 +828,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
// Util files are served from the util store. // Util files are served from the util store.
if strings.HasPrefix(path, "/u/") { if strings.HasPrefix(path, "/u/") {
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
content, err := storeRead(s.utilStore, pathBase(path)) content, err := storeReadCtx(ctx, s.utilStore, pathBase(path))
if err != nil { if err != nil {
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())
} }
@ -811,7 +838,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
// Plugin files are served from the plugin store. // Plugin files are served from the plugin store.
if strings.HasPrefix(path, "/x/") { if strings.HasPrefix(path, "/x/") {
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
content, err := storeRead(s.pluginStore, pathBase(path)) content, err := storeReadCtx(ctx, s.pluginStore, pathBase(path))
if err != nil { if err != nil {
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())
} }
@ -821,7 +848,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
// Tmp files are served from the tmp store. // Tmp files are served from the tmp store.
if strings.HasPrefix(path, "/tmp/") { if strings.HasPrefix(path, "/tmp/") {
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
content, err := storeRead(s.tmpStore, pathBase(path)) content, err := storeReadCtx(ctx, s.tmpStore, pathBase(path))
if err != nil { if err != nil {
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())
} }
@ -831,7 +858,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
// Transcript files are served from the transcript store. // Transcript files are served from the transcript store.
if strings.HasPrefix(path, "/tr/") { if strings.HasPrefix(path, "/tr/") {
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
content, err := storeRead(s.transcriptStore, pathBase(path)) content, err := storeReadCtx(ctx, s.transcriptStore, pathBase(path))
if err != nil { if err != nil {
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())
} }
@ -885,7 +912,7 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
} }
return s.readSlice(fc, content) return s.readSlice(fc, content)
} }
content, err := storeRead(sfs, parts[2]) content, err := storeReadCtx(ctx, sfs, parts[2])
if err != nil { if err != nil {
s.log.Debug("Rread session file err=%v", err) s.log.Debug("Rread session file err=%v", err)
return errFcall(fc, err.Error()) return errFcall(fc, err.Error())