Add event-driven daemon architecture (Phase 1 foundation)
- Add fsnotify dependency for file watching - Create daemon_debouncer.go: batch rapid events (500ms window) - Create daemon_watcher.go: monitor JSONL and git refs changes - Create daemon_event_loop.go: event-driven sync loop - Add mutation channel to RPC server (create/update/close events) - Add BEADS_DAEMON_MODE env var (poll/events, default: poll) Phase 1 implementation: opt-in via BEADS_DAEMON_MODE=events Target: <500ms latency (vs 5000ms), ~60% CPU reduction Related: bd-49 (epic), bd-50, bd-51, bd-53, bd-54, bd-55, bd-56 Amp-Thread-ID: https://ampcode.com/threads/T-35a3d0d7-4e19-421d-8392-63755035036e Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -46,6 +46,15 @@ type Server struct {
|
||||
readyChan chan struct{}
|
||||
// Auto-import single-flight guard
|
||||
importInProgress atomic.Bool
|
||||
// Mutation events for event-driven daemon
|
||||
mutationChan chan MutationEvent
|
||||
}
|
||||
|
||||
// MutationEvent represents a database mutation for event-driven sync
|
||||
type MutationEvent struct {
|
||||
Type string // "create", "update", "delete", "comment"
|
||||
IssueID string // e.g., "bd-42"
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// NewServer creates a new RPC server
|
||||
@@ -79,7 +88,28 @@ func NewServer(socketPath string, store storage.Storage, workspacePath string, d
|
||||
connSemaphore: make(chan struct{}, maxConns),
|
||||
requestTimeout: requestTimeout,
|
||||
readyChan: make(chan struct{}),
|
||||
mutationChan: make(chan MutationEvent, 100), // Buffered to avoid blocking
|
||||
}
|
||||
s.lastActivityTime.Store(time.Now())
|
||||
return s
|
||||
}
|
||||
|
||||
// emitMutation sends a mutation event to the daemon's event-driven loop.
|
||||
// Non-blocking: drops event if channel is full (sync will happen eventually).
|
||||
func (s *Server) emitMutation(eventType, issueID string) {
|
||||
select {
|
||||
case s.mutationChan <- MutationEvent{
|
||||
Type: eventType,
|
||||
IssueID: issueID,
|
||||
Timestamp: time.Now(),
|
||||
}:
|
||||
// Event sent successfully
|
||||
default:
|
||||
// Channel full, event dropped (not critical - sync will happen eventually)
|
||||
}
|
||||
}
|
||||
|
||||
// MutationChan returns the mutation event channel for the daemon to consume
|
||||
func (s *Server) MutationChan() <-chan MutationEvent {
|
||||
return s.mutationChan
|
||||
}
|
||||
|
||||
@@ -159,6 +159,9 @@ func (s *Server) handleCreate(req *Request) Response {
|
||||
}
|
||||
}
|
||||
|
||||
// Emit mutation event for event-driven daemon
|
||||
s.emitMutation("create", issue.ID)
|
||||
|
||||
data, _ := json.Marshal(issue)
|
||||
return Response{
|
||||
Success: true,
|
||||
@@ -190,6 +193,9 @@ func (s *Server) handleUpdate(req *Request) Response {
|
||||
}
|
||||
}
|
||||
|
||||
// Emit mutation event for event-driven daemon
|
||||
s.emitMutation("update", updateArgs.ID)
|
||||
|
||||
issue, err := store.GetIssue(ctx, updateArgs.ID)
|
||||
if err != nil {
|
||||
return Response{
|
||||
@@ -224,6 +230,9 @@ func (s *Server) handleClose(req *Request) Response {
|
||||
}
|
||||
}
|
||||
|
||||
// Emit mutation event for event-driven daemon
|
||||
s.emitMutation("update", closeArgs.ID)
|
||||
|
||||
issue, _ := store.GetIssue(ctx, closeArgs.ID)
|
||||
data, _ := json.Marshal(issue)
|
||||
return Response{
|
||||
|
||||
Reference in New Issue
Block a user