@@ -53,6 +53,9 @@ type Manager struct {
5353 // heartbeatTimeout specifies when to consider a connection dead (default 30s)
5454 heartbeatTimeout time.Duration
5555
56+ // orgLimiter enforces per-organization connection limits
57+ orgLimiter * OrgConnectionLimiter
58+
5659 // shutdownCtx is used to signal graceful shutdown to all connection goroutines
5760 shutdownCtx context.Context
5861 shutdownFn context.CancelFunc
@@ -63,17 +66,19 @@ type Manager struct {
6366
6467// ManagerConfig contains configuration parameters for the connection manager
6568type ManagerConfig struct {
66- MaxConnections int // Maximum concurrent connections (default 1000)
67- HeartbeatInterval time.Duration // Ping interval (default 20s)
68- HeartbeatTimeout time.Duration // Pong timeout (default 30s)
69+ MaxConnections int // Maximum concurrent connections (default 1000)
70+ HeartbeatInterval time.Duration // Ping interval (default 20s)
71+ HeartbeatTimeout time.Duration // Pong timeout (default 30s)
72+ MaxConnectionsPerOrg int // Maximum connections per organization (default 3)
6973}
7074
7175// DefaultManagerConfig returns sensible default configuration values
7276func DefaultManagerConfig () ManagerConfig {
7377 return ManagerConfig {
74- MaxConnections : 1000 ,
75- HeartbeatInterval : 20 * time .Second ,
76- HeartbeatTimeout : 30 * time .Second ,
78+ MaxConnections : 1000 ,
79+ HeartbeatInterval : 20 * time .Second ,
80+ HeartbeatTimeout : 30 * time .Second ,
81+ MaxConnectionsPerOrg : 3 ,
7782 }
7883}
7984
@@ -86,6 +91,7 @@ func NewManager(config ManagerConfig) *Manager {
8691 maxConnections : config .MaxConnections ,
8792 heartbeatInterval : config .HeartbeatInterval ,
8893 heartbeatTimeout : config .HeartbeatTimeout ,
94+ orgLimiter : NewOrgConnectionLimiter (config .MaxConnectionsPerOrg ),
8995 shutdownCtx : ctx ,
9096 shutdownFn : cancel ,
9197 }
@@ -98,25 +104,37 @@ func NewManager(config ManagerConfig) *Manager {
98104// - gatewayID: UUID of the authenticated gateway
99105// - transport: Transport implementation for message delivery
100106// - authToken: API key used for authentication
107+ // - orgID: UUID of the organization that owns the gateway
101108//
102109// Returns the Connection instance and any error encountered.
103110//
104111// Design decision: Support multiple connections per gateway ID by storing
105112// connections in a slice. This enables gateway clustering where multiple
106113// instances share the same gateway identity.
107- func (m * Manager ) Register (gatewayID string , transport Transport , authToken string ) (* Connection , error ) {
108- // Check connection limit
114+ func (m * Manager ) Register (gatewayID string , transport Transport , authToken string ,
115+ orgID string ) (* Connection , error ) {
116+
117+ // Create connection ID early so we can use it for org limiter
118+ connectionID := uuid .New ().String ()
119+
120+ // Check per-org limit first
121+ if err := m .orgLimiter .AddConnection (orgID , connectionID ); err != nil {
122+ return nil , err
123+ }
124+
125+ // Check global connection limit
109126 m .mu .Lock ()
110127 if m .connectionCount >= m .maxConnections {
111128 m .mu .Unlock ()
129+ // Rollback org limiter
130+ m .orgLimiter .RemoveConnection (orgID , connectionID )
112131 return nil , fmt .Errorf ("maximum connection limit reached (%d)" , m .maxConnections )
113132 }
114133 m .connectionCount ++
115134 m .mu .Unlock ()
116135
117- // Create connection with unique connection ID
118- connectionID := uuid .New ().String ()
119- conn := NewConnection (gatewayID , connectionID , transport , authToken )
136+ // Create connection
137+ conn := NewConnection (gatewayID , connectionID , transport , authToken , orgID )
120138
121139 // Add connection to registry
122140 connsInterface , _ := m .connections .LoadOrStore (gatewayID , []* Connection {})
@@ -128,8 +146,8 @@ func (m *Manager) Register(gatewayID string, transport Transport, authToken stri
128146 m .wg .Add (1 )
129147 go m .monitorHeartbeat (conn )
130148
131- log .Printf ("[INFO] Gateway connected: gatewayID=%s connectionID=%s totalConnections=%d" ,
132- gatewayID , connectionID , m .GetConnectionCount ())
149+ log .Printf ("[INFO] Gateway connected: gatewayID=%s connectionID=%s orgID=%s totalConnections=%d orgConnections =%d" ,
150+ gatewayID , connectionID , orgID , m .GetConnectionCount (), m . orgLimiter . GetOrgConnectionCount ( orgID ))
133151
134152 return conn , nil
135153}
@@ -176,13 +194,18 @@ func (m *Manager) Unregister(gatewayID, connectionID string) {
176194 gatewayID , connectionID , err )
177195 }
178196
197+ // Remove from org limiter
198+ if removed .OrganizationID != "" {
199+ m .orgLimiter .RemoveConnection (removed .OrganizationID , connectionID )
200+ }
201+
179202 // Decrement connection count
180203 m .mu .Lock ()
181204 m .connectionCount --
182205 m .mu .Unlock ()
183206
184- log .Printf ("[INFO] Gateway disconnected: gatewayID=%s connectionID=%s totalConnections=%d" ,
185- gatewayID , connectionID , m .GetConnectionCount ())
207+ log .Printf ("[INFO] Gateway disconnected: gatewayID=%s connectionID=%s orgID=%s totalConnections=%d" ,
208+ gatewayID , connectionID , removed . OrganizationID , m .GetConnectionCount ())
186209}
187210
188211// GetConnections retrieves all connections for a specific gateway ID.
@@ -301,3 +324,13 @@ func (m *Manager) Shutdown() {
301324
302325 log .Println ("[INFO] WebSocket manager shutdown complete" )
303326}
327+
328+ // GetOrgConnectionStats returns connection statistics for a specific organization
329+ func (m * Manager ) GetOrgConnectionStats (orgID string ) OrgConnectionStats {
330+ return m .orgLimiter .GetOrgStats (orgID )
331+ }
332+
333+ // GetAllOrgConnectionStats returns connection counts for all organizations
334+ func (m * Manager ) GetAllOrgConnectionStats () map [string ]int {
335+ return m .orgLimiter .GetAllOrgConnectionCounts ()
336+ }
0 commit comments