You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							630 lines
						
					
					
						
							18 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							630 lines
						
					
					
						
							18 KiB
						
					
					
				| // Package rdma provides high-level RDMA operations for SeaweedFS integration | |
| package rdma | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"fmt" | |
| 	"sync" | |
| 	"time" | |
| 
 | |
| 	"seaweedfs-rdma-sidecar/pkg/ipc" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/storage/needle" | |
| 	"github.com/sirupsen/logrus" | |
| ) | |
| 
 | |
| // PooledConnection represents a pooled RDMA connection | |
| type PooledConnection struct { | |
| 	ipcClient *ipc.Client | |
| 	lastUsed  time.Time | |
| 	inUse     bool | |
| 	sessionID string | |
| 	created   time.Time | |
| } | |
| 
 | |
| // ConnectionPool manages a pool of RDMA connections | |
| type ConnectionPool struct { | |
| 	connections    []*PooledConnection | |
| 	mutex          sync.RWMutex | |
| 	maxConnections int | |
| 	maxIdleTime    time.Duration | |
| 	enginePath     string | |
| 	logger         *logrus.Logger | |
| } | |
| 
 | |
| // Client provides high-level RDMA operations with connection pooling | |
| type Client struct { | |
| 	pool           *ConnectionPool | |
| 	logger         *logrus.Logger | |
| 	enginePath     string | |
| 	capabilities   *ipc.GetCapabilitiesResponse | |
| 	connected      bool | |
| 	defaultTimeout time.Duration | |
| 
 | |
| 	// Legacy single connection (for backward compatibility) | |
| 	ipcClient *ipc.Client | |
| } | |
| 
 | |
| // Config holds configuration for the RDMA client | |
| type Config struct { | |
| 	EngineSocketPath string | |
| 	DefaultTimeout   time.Duration | |
| 	Logger           *logrus.Logger | |
| 
 | |
| 	// Connection pooling options | |
| 	EnablePooling  bool          // Enable connection pooling (default: true) | |
| 	MaxConnections int           // Max connections in pool (default: 10) | |
| 	MaxIdleTime    time.Duration // Max idle time before connection cleanup (default: 5min) | |
| } | |
| 
 | |
| // ReadRequest represents a SeaweedFS needle read request | |
| type ReadRequest struct { | |
| 	VolumeID  uint32 | |
| 	NeedleID  uint64 | |
| 	Cookie    uint32 | |
| 	Offset    uint64 | |
| 	Size      uint64 | |
| 	AuthToken *string | |
| } | |
| 
 | |
| // ReadResponse represents the result of an RDMA read operation | |
| type ReadResponse struct { | |
| 	Data         []byte | |
| 	BytesRead    uint64 | |
| 	Duration     time.Duration | |
| 	TransferRate float64 | |
| 	SessionID    string | |
| 	Success      bool | |
| 	Message      string | |
| } | |
| 
 | |
| // NewConnectionPool creates a new connection pool | |
| func NewConnectionPool(enginePath string, maxConnections int, maxIdleTime time.Duration, logger *logrus.Logger) *ConnectionPool { | |
| 	if maxConnections <= 0 { | |
| 		maxConnections = 10 // Default | |
| 	} | |
| 	if maxIdleTime <= 0 { | |
| 		maxIdleTime = 5 * time.Minute // Default | |
| 	} | |
| 
 | |
| 	return &ConnectionPool{ | |
| 		connections:    make([]*PooledConnection, 0, maxConnections), | |
| 		maxConnections: maxConnections, | |
| 		maxIdleTime:    maxIdleTime, | |
| 		enginePath:     enginePath, | |
| 		logger:         logger, | |
| 	} | |
| } | |
| 
 | |
| // getConnection gets an available connection from the pool or creates a new one | |
| func (p *ConnectionPool) getConnection(ctx context.Context) (*PooledConnection, error) { | |
| 	p.mutex.Lock() | |
| 	defer p.mutex.Unlock() | |
| 
 | |
| 	// Look for an available connection | |
| 	for _, conn := range p.connections { | |
| 		if !conn.inUse && time.Since(conn.lastUsed) < p.maxIdleTime { | |
| 			conn.inUse = true | |
| 			conn.lastUsed = time.Now() | |
| 			p.logger.WithField("session_id", conn.sessionID).Debug("🔌 Reusing pooled RDMA connection") | |
| 			return conn, nil | |
| 		} | |
| 	} | |
| 
 | |
| 	// Create new connection if under limit | |
| 	if len(p.connections) < p.maxConnections { | |
| 		ipcClient := ipc.NewClient(p.enginePath, p.logger) | |
| 		if err := ipcClient.Connect(ctx); err != nil { | |
| 			return nil, fmt.Errorf("failed to create new pooled connection: %w", err) | |
| 		} | |
| 
 | |
| 		conn := &PooledConnection{ | |
| 			ipcClient: ipcClient, | |
| 			lastUsed:  time.Now(), | |
| 			inUse:     true, | |
| 			sessionID: fmt.Sprintf("pool-%d-%d", len(p.connections), time.Now().Unix()), | |
| 			created:   time.Now(), | |
| 		} | |
| 
 | |
| 		p.connections = append(p.connections, conn) | |
| 		p.logger.WithFields(logrus.Fields{ | |
| 			"session_id": conn.sessionID, | |
| 			"pool_size":  len(p.connections), | |
| 		}).Info("🚀 Created new pooled RDMA connection") | |
| 
 | |
| 		return conn, nil | |
| 	} | |
| 
 | |
| 	// Pool is full, wait for an available connection | |
| 	return nil, fmt.Errorf("connection pool exhausted (max: %d)", p.maxConnections) | |
| } | |
| 
 | |
| // releaseConnection returns a connection to the pool | |
| func (p *ConnectionPool) releaseConnection(conn *PooledConnection) { | |
| 	p.mutex.Lock() | |
| 	defer p.mutex.Unlock() | |
| 
 | |
| 	conn.inUse = false | |
| 	conn.lastUsed = time.Now() | |
| 
 | |
| 	p.logger.WithField("session_id", conn.sessionID).Debug("🔄 Released RDMA connection back to pool") | |
| } | |
| 
 | |
| // cleanup removes idle connections from the pool | |
| func (p *ConnectionPool) cleanup() { | |
| 	p.mutex.Lock() | |
| 	defer p.mutex.Unlock() | |
| 
 | |
| 	now := time.Now() | |
| 	activeConnections := make([]*PooledConnection, 0, len(p.connections)) | |
| 
 | |
| 	for _, conn := range p.connections { | |
| 		if conn.inUse || now.Sub(conn.lastUsed) < p.maxIdleTime { | |
| 			activeConnections = append(activeConnections, conn) | |
| 		} else { | |
| 			// Close idle connection | |
| 			conn.ipcClient.Disconnect() | |
| 			p.logger.WithFields(logrus.Fields{ | |
| 				"session_id": conn.sessionID, | |
| 				"idle_time":  now.Sub(conn.lastUsed), | |
| 			}).Debug("🧹 Cleaned up idle RDMA connection") | |
| 		} | |
| 	} | |
| 
 | |
| 	p.connections = activeConnections | |
| } | |
| 
 | |
| // Close closes all connections in the pool | |
| func (p *ConnectionPool) Close() { | |
| 	p.mutex.Lock() | |
| 	defer p.mutex.Unlock() | |
| 
 | |
| 	for _, conn := range p.connections { | |
| 		conn.ipcClient.Disconnect() | |
| 	} | |
| 	p.connections = nil | |
| 	p.logger.Info("🔌 Connection pool closed") | |
| } | |
| 
 | |
| // NewClient creates a new RDMA client | |
| func NewClient(config *Config) *Client { | |
| 	if config.Logger == nil { | |
| 		config.Logger = logrus.New() | |
| 		config.Logger.SetLevel(logrus.InfoLevel) | |
| 	} | |
| 
 | |
| 	if config.DefaultTimeout == 0 { | |
| 		config.DefaultTimeout = 30 * time.Second | |
| 	} | |
| 
 | |
| 	client := &Client{ | |
| 		logger:         config.Logger, | |
| 		enginePath:     config.EngineSocketPath, | |
| 		defaultTimeout: config.DefaultTimeout, | |
| 	} | |
| 
 | |
| 	// Initialize connection pooling if enabled (default: true) | |
| 	enablePooling := config.EnablePooling | |
| 	if config.MaxConnections == 0 && config.MaxIdleTime == 0 { | |
| 		// Default to enabled if not explicitly configured | |
| 		enablePooling = true | |
| 	} | |
| 
 | |
| 	if enablePooling { | |
| 		client.pool = NewConnectionPool( | |
| 			config.EngineSocketPath, | |
| 			config.MaxConnections, | |
| 			config.MaxIdleTime, | |
| 			config.Logger, | |
| 		) | |
| 
 | |
| 		// Start cleanup goroutine | |
| 		go client.startCleanupRoutine() | |
| 
 | |
| 		config.Logger.WithFields(logrus.Fields{ | |
| 			"max_connections": client.pool.maxConnections, | |
| 			"max_idle_time":   client.pool.maxIdleTime, | |
| 		}).Info("🔌 RDMA connection pooling enabled") | |
| 	} else { | |
| 		// Legacy single connection mode | |
| 		client.ipcClient = ipc.NewClient(config.EngineSocketPath, config.Logger) | |
| 		config.Logger.Info("🔌 RDMA single connection mode (pooling disabled)") | |
| 	} | |
| 
 | |
| 	return client | |
| } | |
| 
 | |
| // startCleanupRoutine starts a background goroutine to clean up idle connections | |
| func (c *Client) startCleanupRoutine() { | |
| 	ticker := time.NewTicker(1 * time.Minute) // Cleanup every minute | |
| 	go func() { | |
| 		defer ticker.Stop() | |
| 		for range ticker.C { | |
| 			if c.pool != nil { | |
| 				c.pool.cleanup() | |
| 			} | |
| 		} | |
| 	}() | |
| } | |
| 
 | |
| // Connect establishes connection to the Rust RDMA engine and queries capabilities | |
| func (c *Client) Connect(ctx context.Context) error { | |
| 	c.logger.Info("🚀 Connecting to RDMA engine") | |
| 
 | |
| 	if c.pool != nil { | |
| 		// Connection pooling mode - connections are created on-demand | |
| 		c.connected = true | |
| 		c.logger.Info("✅ RDMA client ready (connection pooling enabled)") | |
| 		return nil | |
| 	} | |
| 
 | |
| 	// Single connection mode | |
| 	if err := c.ipcClient.Connect(ctx); err != nil { | |
| 		return fmt.Errorf("failed to connect to IPC: %w", err) | |
| 	} | |
| 
 | |
| 	// Test connectivity with ping | |
| 	clientID := "rdma-client" | |
| 	pong, err := c.ipcClient.Ping(ctx, &clientID) | |
| 	if err != nil { | |
| 		c.ipcClient.Disconnect() | |
| 		return fmt.Errorf("failed to ping RDMA engine: %w", err) | |
| 	} | |
| 
 | |
| 	latency := time.Duration(pong.ServerRttNs) | |
| 	c.logger.WithFields(logrus.Fields{ | |
| 		"latency":    latency, | |
| 		"server_rtt": time.Duration(pong.ServerRttNs), | |
| 	}).Info("📡 RDMA engine ping successful") | |
| 
 | |
| 	// Get capabilities | |
| 	caps, err := c.ipcClient.GetCapabilities(ctx, &clientID) | |
| 	if err != nil { | |
| 		c.ipcClient.Disconnect() | |
| 		return fmt.Errorf("failed to get engine capabilities: %w", err) | |
| 	} | |
| 
 | |
| 	c.capabilities = caps | |
| 	c.connected = true | |
| 
 | |
| 	c.logger.WithFields(logrus.Fields{ | |
| 		"version":           caps.Version, | |
| 		"device_name":       caps.DeviceName, | |
| 		"vendor_id":         caps.VendorId, | |
| 		"max_sessions":      caps.MaxSessions, | |
| 		"max_transfer_size": caps.MaxTransferSize, | |
| 		"active_sessions":   caps.ActiveSessions, | |
| 		"real_rdma":         caps.RealRdma, | |
| 		"port_gid":          caps.PortGid, | |
| 		"port_lid":          caps.PortLid, | |
| 	}).Info("✅ RDMA engine connected and ready") | |
| 
 | |
| 	return nil | |
| } | |
| 
 | |
| // Disconnect closes the connection to the RDMA engine | |
| func (c *Client) Disconnect() { | |
| 	if c.connected { | |
| 		if c.pool != nil { | |
| 			// Connection pooling mode | |
| 			c.pool.Close() | |
| 			c.logger.Info("🔌 Disconnected from RDMA engine (pool closed)") | |
| 		} else { | |
| 			// Single connection mode | |
| 			c.ipcClient.Disconnect() | |
| 			c.logger.Info("🔌 Disconnected from RDMA engine") | |
| 		} | |
| 		c.connected = false | |
| 	} | |
| } | |
| 
 | |
| // IsConnected returns true if connected to the RDMA engine | |
| func (c *Client) IsConnected() bool { | |
| 	if c.pool != nil { | |
| 		// Connection pooling mode - always connected if pool exists | |
| 		return c.connected | |
| 	} else { | |
| 		// Single connection mode | |
| 		return c.connected && c.ipcClient.IsConnected() | |
| 	} | |
| } | |
| 
 | |
| // GetCapabilities returns the RDMA engine capabilities | |
| func (c *Client) GetCapabilities() *ipc.GetCapabilitiesResponse { | |
| 	return c.capabilities | |
| } | |
| 
 | |
| // Read performs an RDMA read operation for a SeaweedFS needle | |
| func (c *Client) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, error) { | |
| 	if !c.IsConnected() { | |
| 		return nil, fmt.Errorf("not connected to RDMA engine") | |
| 	} | |
| 
 | |
| 	startTime := time.Now() | |
| 
 | |
| 	c.logger.WithFields(logrus.Fields{ | |
| 		"volume_id": req.VolumeID, | |
| 		"needle_id": req.NeedleID, | |
| 		"offset":    req.Offset, | |
| 		"size":      req.Size, | |
| 	}).Debug("📖 Starting RDMA read operation") | |
| 
 | |
| 	if c.pool != nil { | |
| 		// Connection pooling mode | |
| 		return c.readWithPool(ctx, req, startTime) | |
| 	} | |
| 
 | |
| 	// Single connection mode | |
| 	// Create IPC request | |
| 	ipcReq := &ipc.StartReadRequest{ | |
| 		VolumeID:    req.VolumeID, | |
| 		NeedleID:    req.NeedleID, | |
| 		Cookie:      req.Cookie, | |
| 		Offset:      req.Offset, | |
| 		Size:        req.Size, | |
| 		RemoteAddr:  0, // Will be set by engine (mock for now) | |
| 		RemoteKey:   0, // Will be set by engine (mock for now) | |
| 		TimeoutSecs: uint64(c.defaultTimeout.Seconds()), | |
| 		AuthToken:   req.AuthToken, | |
| 	} | |
| 
 | |
| 	// Start RDMA read | |
| 	startResp, err := c.ipcClient.StartRead(ctx, ipcReq) | |
| 	if err != nil { | |
| 		c.logger.WithError(err).Error("❌ Failed to start RDMA read") | |
| 		return nil, fmt.Errorf("failed to start RDMA read: %w", err) | |
| 	} | |
| 
 | |
| 	// In the new protocol, if we got a StartReadResponse, the operation was successful | |
|  | |
| 	c.logger.WithFields(logrus.Fields{ | |
| 		"session_id":    startResp.SessionID, | |
| 		"local_addr":    fmt.Sprintf("0x%x", startResp.LocalAddr), | |
| 		"local_key":     startResp.LocalKey, | |
| 		"transfer_size": startResp.TransferSize, | |
| 		"expected_crc":  fmt.Sprintf("0x%x", startResp.ExpectedCrc), | |
| 		"expires_at":    time.Unix(0, int64(startResp.ExpiresAtNs)).Format(time.RFC3339), | |
| 	}).Debug("📖 RDMA read session started") | |
| 
 | |
| 	// Complete the RDMA read | |
| 	completeResp, err := c.ipcClient.CompleteRead(ctx, startResp.SessionID, true, startResp.TransferSize, &startResp.ExpectedCrc) | |
| 	if err != nil { | |
| 		c.logger.WithError(err).Error("❌ Failed to complete RDMA read") | |
| 		return nil, fmt.Errorf("failed to complete RDMA read: %w", err) | |
| 	} | |
| 
 | |
| 	duration := time.Since(startTime) | |
| 
 | |
| 	if !completeResp.Success { | |
| 		errorMsg := "unknown error" | |
| 		if completeResp.Message != nil { | |
| 			errorMsg = *completeResp.Message | |
| 		} | |
| 		c.logger.WithFields(logrus.Fields{ | |
| 			"session_id":    startResp.SessionID, | |
| 			"error_message": errorMsg, | |
| 		}).Error("❌ RDMA read completion failed") | |
| 		return nil, fmt.Errorf("RDMA read completion failed: %s", errorMsg) | |
| 	} | |
| 
 | |
| 	// Calculate transfer rate (bytes/second) | |
| 	transferRate := float64(startResp.TransferSize) / duration.Seconds() | |
| 
 | |
| 	c.logger.WithFields(logrus.Fields{ | |
| 		"session_id":    startResp.SessionID, | |
| 		"bytes_read":    startResp.TransferSize, | |
| 		"duration":      duration, | |
| 		"transfer_rate": transferRate, | |
| 		"server_crc":    completeResp.ServerCrc, | |
| 	}).Info("✅ RDMA read completed successfully") | |
| 
 | |
| 	// MOCK DATA IMPLEMENTATION - FOR DEVELOPMENT/TESTING ONLY | |
| 	//  | |
| 	// This section generates placeholder data for the mock RDMA implementation. | |
| 	// In a production RDMA implementation, this should be replaced with: | |
| 	// | |
| 	// 1. The actual data transferred via RDMA from the remote memory region | |
| 	// 2. Data validation using checksums/CRC from the RDMA completion | |
| 	// 3. Proper error handling for RDMA transfer failures | |
| 	// 4. Memory region cleanup and deregistration | |
| 	// | |
| 	// TODO for real RDMA implementation: | |
| 	// - Replace mockData with actual RDMA buffer contents | |
| 	// - Validate data integrity using server CRC: completeResp.ServerCrc | |
| 	// - Handle partial transfers and retry logic | |
| 	// - Implement proper memory management for RDMA regions | |
| 	// | |
| 	// Current mock behavior: Generates a simple pattern (0,1,2...255,0,1,2...) | |
| 	// This allows testing of the integration pipeline without real hardware | |
| 	mockData := make([]byte, startResp.TransferSize) | |
| 	for i := range mockData { | |
| 		mockData[i] = byte(i % 256) // Simple repeating pattern for verification | |
| 	} | |
| 	// END MOCK DATA IMPLEMENTATION | |
|  | |
| 	return &ReadResponse{ | |
| 		Data:         mockData, | |
| 		BytesRead:    startResp.TransferSize, | |
| 		Duration:     duration, | |
| 		TransferRate: transferRate, | |
| 		SessionID:    startResp.SessionID, | |
| 		Success:      true, | |
| 		Message:      "RDMA read completed successfully", | |
| 	}, nil | |
| } | |
| 
 | |
| // ReadRange performs an RDMA read for a specific range within a needle | |
| func (c *Client) ReadRange(ctx context.Context, volumeID uint32, needleID uint64, cookie uint32, offset, size uint64) (*ReadResponse, error) { | |
| 	req := &ReadRequest{ | |
| 		VolumeID: volumeID, | |
| 		NeedleID: needleID, | |
| 		Cookie:   cookie, | |
| 		Offset:   offset, | |
| 		Size:     size, | |
| 	} | |
| 	return c.Read(ctx, req) | |
| } | |
| 
 | |
| // ReadFileRange performs an RDMA read using SeaweedFS file ID format | |
| func (c *Client) ReadFileRange(ctx context.Context, fileID string, offset, size uint64) (*ReadResponse, error) { | |
| 	// Parse file ID (e.g., "3,01637037d6" -> volume=3, needle=0x01637037d6, cookie extracted) | |
| 	volumeID, needleID, cookie, err := parseFileID(fileID) | |
| 	if err != nil { | |
| 		return nil, fmt.Errorf("invalid file ID %s: %w", fileID, err) | |
| 	} | |
| 	 | |
| 	req := &ReadRequest{ | |
| 		VolumeID: volumeID, | |
| 		NeedleID: needleID, | |
| 		Cookie:   cookie, | |
| 		Offset:   offset, | |
| 		Size:     size, | |
| 	} | |
| 	return c.Read(ctx, req) | |
| } | |
| 
 | |
| // parseFileID extracts volume ID, needle ID, and cookie from a SeaweedFS file ID | |
| // Uses existing SeaweedFS parsing logic to ensure compatibility | |
| func parseFileID(fileId string) (volumeID uint32, needleID uint64, cookie uint32, err error) { | |
| 	// Use existing SeaweedFS file ID parsing | |
| 	fid, err := needle.ParseFileIdFromString(fileId) | |
| 	if err != nil { | |
| 		return 0, 0, 0, fmt.Errorf("failed to parse file ID %s: %w", fileId, err) | |
| 	} | |
| 
 | |
| 	volumeID = uint32(fid.VolumeId) | |
| 	needleID = uint64(fid.Key) | |
| 	cookie = uint32(fid.Cookie) | |
| 
 | |
| 	return volumeID, needleID, cookie, nil | |
| } | |
| 
 | |
| // ReadFull performs an RDMA read for an entire needle | |
| func (c *Client) ReadFull(ctx context.Context, volumeID uint32, needleID uint64, cookie uint32) (*ReadResponse, error) { | |
| 	req := &ReadRequest{ | |
| 		VolumeID: volumeID, | |
| 		NeedleID: needleID, | |
| 		Cookie:   cookie, | |
| 		Offset:   0, | |
| 		Size:     0, // 0 means read entire needle | |
| 	} | |
| 	return c.Read(ctx, req) | |
| } | |
| 
 | |
| // Ping tests connectivity to the RDMA engine | |
| func (c *Client) Ping(ctx context.Context) (time.Duration, error) { | |
| 	if !c.IsConnected() { | |
| 		return 0, fmt.Errorf("not connected to RDMA engine") | |
| 	} | |
| 
 | |
| 	clientID := "health-check" | |
| 	start := time.Now() | |
| 	pong, err := c.ipcClient.Ping(ctx, &clientID) | |
| 	if err != nil { | |
| 		return 0, err | |
| 	} | |
| 
 | |
| 	totalLatency := time.Since(start) | |
| 	serverRtt := time.Duration(pong.ServerRttNs) | |
| 
 | |
| 	c.logger.WithFields(logrus.Fields{ | |
| 		"total_latency": totalLatency, | |
| 		"server_rtt":    serverRtt, | |
| 		"client_id":     clientID, | |
| 	}).Debug("🏓 RDMA engine ping successful") | |
| 
 | |
| 	return totalLatency, nil | |
| } | |
| 
 | |
| // readWithPool performs RDMA read using connection pooling | |
| func (c *Client) readWithPool(ctx context.Context, req *ReadRequest, startTime time.Time) (*ReadResponse, error) { | |
| 	// Get connection from pool | |
| 	conn, err := c.pool.getConnection(ctx) | |
| 	if err != nil { | |
| 		return nil, fmt.Errorf("failed to get pooled connection: %w", err) | |
| 	} | |
| 	defer c.pool.releaseConnection(conn) | |
| 
 | |
| 	c.logger.WithField("session_id", conn.sessionID).Debug("🔌 Using pooled RDMA connection") | |
| 
 | |
| 	// Create IPC request | |
| 	ipcReq := &ipc.StartReadRequest{ | |
| 		VolumeID:    req.VolumeID, | |
| 		NeedleID:    req.NeedleID, | |
| 		Cookie:      req.Cookie, | |
| 		Offset:      req.Offset, | |
| 		Size:        req.Size, | |
| 		RemoteAddr:  0, // Will be set by engine (mock for now) | |
| 		RemoteKey:   0, // Will be set by engine (mock for now) | |
| 		TimeoutSecs: uint64(c.defaultTimeout.Seconds()), | |
| 		AuthToken:   req.AuthToken, | |
| 	} | |
| 
 | |
| 	// Start RDMA read | |
| 	startResp, err := conn.ipcClient.StartRead(ctx, ipcReq) | |
| 	if err != nil { | |
| 		c.logger.WithError(err).Error("❌ Failed to start RDMA read (pooled)") | |
| 		return nil, fmt.Errorf("failed to start RDMA read: %w", err) | |
| 	} | |
| 
 | |
| 	c.logger.WithFields(logrus.Fields{ | |
| 		"session_id":    startResp.SessionID, | |
| 		"local_addr":    fmt.Sprintf("0x%x", startResp.LocalAddr), | |
| 		"local_key":     startResp.LocalKey, | |
| 		"transfer_size": startResp.TransferSize, | |
| 		"expected_crc":  fmt.Sprintf("0x%x", startResp.ExpectedCrc), | |
| 		"expires_at":    time.Unix(0, int64(startResp.ExpiresAtNs)).Format(time.RFC3339), | |
| 		"pooled":        true, | |
| 	}).Debug("📖 RDMA read session started (pooled)") | |
| 
 | |
| 	// Complete the RDMA read | |
| 	completeResp, err := conn.ipcClient.CompleteRead(ctx, startResp.SessionID, true, startResp.TransferSize, &startResp.ExpectedCrc) | |
| 	if err != nil { | |
| 		c.logger.WithError(err).Error("❌ Failed to complete RDMA read (pooled)") | |
| 		return nil, fmt.Errorf("failed to complete RDMA read: %w", err) | |
| 	} | |
| 
 | |
| 	duration := time.Since(startTime) | |
| 
 | |
| 	if !completeResp.Success { | |
| 		errorMsg := "unknown error" | |
| 		if completeResp.Message != nil { | |
| 			errorMsg = *completeResp.Message | |
| 		} | |
| 		c.logger.WithFields(logrus.Fields{ | |
| 			"session_id":    conn.sessionID, | |
| 			"error_message": errorMsg, | |
| 			"pooled":        true, | |
| 		}).Error("❌ RDMA read completion failed (pooled)") | |
| 		return nil, fmt.Errorf("RDMA read completion failed: %s", errorMsg) | |
| 	} | |
| 
 | |
| 	// Calculate transfer rate (bytes/second) | |
| 	transferRate := float64(startResp.TransferSize) / duration.Seconds() | |
| 
 | |
| 	c.logger.WithFields(logrus.Fields{ | |
| 		"session_id":    conn.sessionID, | |
| 		"bytes_read":    startResp.TransferSize, | |
| 		"duration":      duration, | |
| 		"transfer_rate": transferRate, | |
| 		"server_crc":    completeResp.ServerCrc, | |
| 		"pooled":        true, | |
| 	}).Info("✅ RDMA read completed successfully (pooled)") | |
| 
 | |
| 	// For the mock implementation, we'll return placeholder data | |
| 	// In the real implementation, this would be the actual RDMA transferred data | |
| 	mockData := make([]byte, startResp.TransferSize) | |
| 	for i := range mockData { | |
| 		mockData[i] = byte(i % 256) // Simple pattern for testing | |
| 	} | |
| 
 | |
| 	return &ReadResponse{ | |
| 		Data:         mockData, | |
| 		BytesRead:    startResp.TransferSize, | |
| 		Duration:     duration, | |
| 		TransferRate: transferRate, | |
| 		SessionID:    conn.sessionID, | |
| 		Success:      true, | |
| 		Message:      "RDMA read successful (pooled)", | |
| 	}, nil | |
| }
 |