diff options
Diffstat (limited to 'internal/ipc')
| -rw-r--r-- | internal/ipc/ipc.go | 15 | ||||
| -rw-r--r-- | internal/ipc/server.go | 27 |
2 files changed, 38 insertions, 4 deletions
diff --git a/internal/ipc/ipc.go b/internal/ipc/ipc.go index 691dadf..f890715 100644 --- a/internal/ipc/ipc.go +++ b/internal/ipc/ipc.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net" + "sync" "mitmux/internal/store" ) @@ -51,7 +52,15 @@ type EntryDetail struct { // Client talks to a mitmuxd instance for request/response queries // (list, get). Use Subscribe separately for the live-update stream. +// +// One request/response round trip is in flight on the connection at a +// time, guarded by mu - a caller like the mitmux TUI dispatches each +// request as its own goroutine (a Bubble Tea tea.Cmd), and without this +// two overlapping calls (e.g. opening two entries in quick succession) +// would interleave their JSON on the wire or hand one call the other's +// response. type Client struct { + mu sync.Mutex conn net.Conn dec *json.Decoder enc *json.Encoder @@ -84,6 +93,8 @@ func (c *Client) Search(query string, limit int, beforeID int64) ([]store.Summar } func (c *Client) list(req Request) ([]store.Summary, error) { + c.mu.Lock() + defer c.mu.Unlock() if err := c.enc.Encode(req); err != nil { return nil, err } @@ -99,6 +110,8 @@ func (c *Client) list(req Request) ([]store.Summary, error) { // Get returns the full entry (raw bytes included) for id. func (c *Client) Get(id int64) (*EntryDetail, error) { + c.mu.Lock() + defer c.mu.Unlock() if err := c.enc.Encode(Request{Type: "get", ID: id}); err != nil { return nil, err } @@ -116,6 +129,8 @@ func (c *Client) Get(id int64) (*EntryDetail, error) { // no header injection) and returns the resulting entry, including the raw // response bytes. The exchange is also recorded to history. func (c *Client) Repeat(scheme, host string, raw []byte) (*EntryDetail, error) { + c.mu.Lock() + defer c.mu.Unlock() if err := c.enc.Encode(Request{Type: "repeat", Scheme: scheme, Host: host, Raw: raw}); err != nil { return nil, err } diff --git a/internal/ipc/server.go b/internal/ipc/server.go index 0e839fb..69044af 100644 --- a/internal/ipc/server.go +++ b/internal/ipc/server.go @@ -3,6 +3,7 @@ package ipc import ( "context" "encoding/json" + "io" "log" "net" "sync" @@ -129,13 +130,31 @@ func (s *Server) handleConn(conn net.Conn) { case "subscribe": sub := s.hub.subscribe() defer s.hub.unsubscribe(sub) - for e := range sub { - e := e - if err := enc.Encode(Response{Type: "new", New: &e}); err != nil { + + // The client never sends anything more on this connection, + // but reading it anyway is how we notice it went away when + // no new entry ever arrives to trigger a failed Encode - + // otherwise a subscriber that quits while the daemon is + // idle leaks its goroutine and channel indefinitely. + disconnected := make(chan struct{}) + go func() { + io.Copy(io.Discard, conn) + close(disconnected) + }() + + for { + select { + case e, ok := <-sub: + if !ok { + return + } + if err := enc.Encode(Response{Type: "new", New: &e}); err != nil { + return + } + case <-disconnected: return } } - return default: enc.Encode(Response{Type: "error", Error: "unknown request type: " + req.Type}) |