Phase 4: Atomic operations and stress testing (bd-114, bd-110)
Completes daemon architecture implementation: Features: - Batch/transaction API (OpBatch) for multi-step atomic operations - Request timeout and cancellation support (30s default, configurable) - Comprehensive stress tests (4-10 concurrent agents, 800-1000 ops) - Performance benchmarks (daemon 2x faster than direct mode) Results: - Zero ID collisions across 1000+ concurrent creates - All acceptance criteria validated for bd-110 - Create: 2.4ms (daemon) vs 4.7ms (direct) - Update/List: similar 2x improvement Tests Added: - TestStressConcurrentAgents (8 agents, 800 creates) - TestStressBatchOperations (4 agents, 400 batch ops) - TestStressMixedOperations (6 agents, mixed read/write) - TestStressNoUniqueConstraintViolations (10 agents, 1000 creates) - BenchmarkDaemonCreate/Update/List/Latency - Fixed flaky TestConcurrentRequests (shared client issue) Files: - internal/rpc/protocol.go - Added OpBatch, BatchArgs, BatchResponse - internal/rpc/server.go - Implemented handleBatch with stop-on-failure - internal/rpc/client.go - Added SetTimeout and Batch methods - internal/rpc/stress_test.go - All stress tests - internal/rpc/bench_test.go - Performance benchmarks - DAEMON_STRESS_TEST.md - Complete documentation Closes bd-114, bd-110 Amp-Thread-ID: https://ampcode.com/threads/T-1c07c140-0420-49fe-add1-b0b83b1bdff5 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -13,26 +13,37 @@ import (
|
||||
type Client struct {
|
||||
conn net.Conn
|
||||
socketPath string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// TryConnect attempts to connect to the daemon socket
|
||||
// Returns nil if no daemon is running
|
||||
func TryConnect(socketPath string) (*Client, error) {
|
||||
if _, err := os.Stat(socketPath); os.IsNotExist(err) {
|
||||
if os.Getenv("BD_DEBUG") != "" {
|
||||
fmt.Fprintf(os.Stderr, "Debug: socket does not exist: %s\n", socketPath)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
conn, err := net.DialTimeout("unix", socketPath, 2*time.Second)
|
||||
if err != nil {
|
||||
if os.Getenv("BD_DEBUG") != "" {
|
||||
fmt.Fprintf(os.Stderr, "Debug: failed to dial socket: %v\n", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
conn: conn,
|
||||
socketPath: socketPath,
|
||||
timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
if err := client.Ping(); err != nil {
|
||||
if os.Getenv("BD_DEBUG") != "" {
|
||||
fmt.Fprintf(os.Stderr, "Debug: ping failed: %v\n", err)
|
||||
}
|
||||
conn.Close()
|
||||
return nil, nil
|
||||
}
|
||||
@@ -48,6 +59,11 @@ func (c *Client) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTimeout sets the request timeout duration
|
||||
func (c *Client) SetTimeout(timeout time.Duration) {
|
||||
c.timeout = timeout
|
||||
}
|
||||
|
||||
// Execute sends an RPC request and waits for a response
|
||||
func (c *Client) Execute(operation string, args interface{}) (*Response, error) {
|
||||
argsJSON, err := json.Marshal(args)
|
||||
@@ -65,6 +81,13 @@ func (c *Client) Execute(operation string, args interface{}) (*Response, error)
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
if c.timeout > 0 {
|
||||
deadline := time.Now().Add(c.timeout)
|
||||
if err := c.conn.SetDeadline(deadline); err != nil {
|
||||
return nil, fmt.Errorf("failed to set deadline: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
writer := bufio.NewWriter(c.conn)
|
||||
if _, err := writer.Write(reqJSON); err != nil {
|
||||
return nil, fmt.Errorf("failed to write request: %w", err)
|
||||
@@ -162,3 +185,8 @@ func (c *Client) AddLabel(args *LabelAddArgs) (*Response, error) {
|
||||
func (c *Client) RemoveLabel(args *LabelRemoveArgs) (*Response, error) {
|
||||
return c.Execute(OpLabelRemove, args)
|
||||
}
|
||||
|
||||
// Batch executes multiple operations atomically
|
||||
func (c *Client) Batch(args *BatchArgs) (*Response, error) {
|
||||
return c.Execute(OpBatch, args)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user