9p: split clunk into sync (Rerror) and async (fire-and-forget)

prompt, enqueue, ctl writes run async — they may block for an
entire agent turn and don't benefit from Rerror. Everything else
(disk I/O, state mutations, s/new) runs synchronously so errors
surface as Rerror without holding the connection.
This commit is contained in:
Levi Neely 2026-04-13 13:37:41 +02:00
parent d326ca88f7
commit 81f7379fb6
1 changed files with 21 additions and 8 deletions

View File

@ -737,13 +737,29 @@ func (s *Server) clunk(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
}
cs.mu.Unlock()
if len(data) > 0 {
if err := s.handleWrite(path, strings.TrimSpace(string(data))); err != nil {
input := strings.TrimSpace(string(data))
if s.isAsyncWrite(path) {
go s.handleWrite(path, input) //nolint:errcheck
} else if err := s.handleWrite(path, input); err != nil {
return errFcall(fc, err.Error())
}
}
return &plan9.Fcall{Type: plan9.Rclunk, Tag: fc.Tag}
}
// isAsyncWrite returns true for paths where writes may block (agent turns)
// and Rerror is not useful.
func (s *Server) isAsyncWrite(path string) bool {
if !strings.HasPrefix(path, "/s/") {
return false
}
switch pathBase(path) {
case "prompt", "enqueue", "ctl":
return true
}
return false
}
func (s *Server) remove(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
cs.mu.Lock()
f, ok := cs.fids[fc.Fid]
@ -861,13 +877,10 @@ func (s *Server) handleWrite(path, input string) error {
switch fileName {
case "prompt":
// prompt blocks for the entire agent turn; run async.
go func() {
sess.core.Submit(sess.ctx, input, publish)
sess.mu.Lock()
sess.replyVers++
sess.mu.Unlock()
}()
sess.core.Submit(sess.ctx, input, publish)
sess.mu.Lock()
sess.replyVers++
sess.mu.Unlock()
return nil
case "enqueue":