ENV Update
This commit is contained in:
@@ -25,6 +25,10 @@ RUN mkdir -p /app/data && chown -R app:app /app
|
|||||||
EXPOSE 8002
|
EXPOSE 8002
|
||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV PORT=8002
|
ENV PORT=8002
|
||||||
|
ENV MAX_ACTIVITY_LOG_ENTRIES=400
|
||||||
|
ENV ADMIN_LOG_BROADCAST_LIMIT=200
|
||||||
|
ENV STALE_ROOM_CLEANUP_INTERVAL=5m
|
||||||
|
ENV STALE_ROOM_TTL=30m
|
||||||
|
|
||||||
USER app
|
USER app
|
||||||
|
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -43,6 +43,10 @@ Enterprise-style Scrum Poker application using Go, Gin, and SSE for real-time ro
|
|||||||
- `HOST`: server host/interface to bind (default all interfaces, equivalent to `0.0.0.0`)
|
- `HOST`: server host/interface to bind (default all interfaces, equivalent to `0.0.0.0`)
|
||||||
- `PORT`: server port (default `8002`)
|
- `PORT`: server port (default `8002`)
|
||||||
- `DATA_PATH`: directory for room JSON files (default `./data`)
|
- `DATA_PATH`: directory for room JSON files (default `./data`)
|
||||||
|
- `MAX_ACTIVITY_LOG_ENTRIES`: max stored activity log entries per room (default `400`)
|
||||||
|
- `ADMIN_LOG_BROADCAST_LIMIT`: max recent admin log entries sent in SSE payloads (default `200`)
|
||||||
|
- `STALE_ROOM_CLEANUP_INTERVAL`: cleanup ticker interval for stale rooms as Go duration (default `5m`)
|
||||||
|
- `STALE_ROOM_TTL`: delete empty rooms when last activity is older than this Go duration (default `30m`)
|
||||||
|
|
||||||
## Run Locally
|
## Run Locally
|
||||||
|
|
||||||
@@ -60,6 +64,16 @@ HOST=localhost PORT=8002 go run ./src
|
|||||||
HOST=0.0.0.0 PORT=8002 go run ./src
|
HOST=0.0.0.0 PORT=8002 go run ./src
|
||||||
```
|
```
|
||||||
|
|
||||||
|
State tuning example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MAX_ACTIVITY_LOG_ENTRIES=600 \
|
||||||
|
ADMIN_LOG_BROADCAST_LIMIT=300 \
|
||||||
|
STALE_ROOM_CLEANUP_INTERVAL=2m \
|
||||||
|
STALE_ROOM_TTL=45m \
|
||||||
|
go run ./src
|
||||||
|
```
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
Build:
|
Build:
|
||||||
|
|||||||
@@ -1,11 +1,47 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
DataPath string
|
DataPath string
|
||||||
|
MaxActivityLogEntries int
|
||||||
|
AdminLogBroadcastLimit int
|
||||||
|
StaleRoomCleanupInterval time.Duration
|
||||||
|
StaleRoomTTL time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func envIntOrDefault(name string, fallback int) int {
|
||||||
|
raw := os.Getenv(name)
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := strconv.Atoi(raw)
|
||||||
|
if err != nil || parsed <= 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func envDurationOrDefault(name string, fallback time.Duration) time.Duration {
|
||||||
|
raw := os.Getenv(name)
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := time.ParseDuration(raw)
|
||||||
|
if err != nil || parsed <= 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() Config {
|
func Load() Config {
|
||||||
@@ -24,9 +60,18 @@ func Load() Config {
|
|||||||
dataPath = "./data"
|
dataPath = "./data"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxActivityLogEntries := envIntOrDefault("MAX_ACTIVITY_LOG_ENTRIES", 400)
|
||||||
|
adminLogBroadcastLimit := envIntOrDefault("ADMIN_LOG_BROADCAST_LIMIT", 200)
|
||||||
|
staleRoomCleanupInterval := envDurationOrDefault("STALE_ROOM_CLEANUP_INTERVAL", 5*time.Minute)
|
||||||
|
staleRoomTTL := envDurationOrDefault("STALE_ROOM_TTL", 30*time.Minute)
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
Host: host,
|
Host: host,
|
||||||
Port: port,
|
Port: port,
|
||||||
DataPath: dataPath,
|
DataPath: dataPath,
|
||||||
|
MaxActivityLogEntries: maxActivityLogEntries,
|
||||||
|
AdminLogBroadcastLimit: adminLogBroadcastLimit,
|
||||||
|
StaleRoomCleanupInterval: staleRoomCleanupInterval,
|
||||||
|
StaleRoomTTL: staleRoomTTL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,12 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
|
|
||||||
manager, err := state.NewManager(cfg.DataPath)
|
manager, err := state.NewManager(cfg.DataPath, state.ManagerOptions{
|
||||||
|
MaxActivityLogEntries: cfg.MaxActivityLogEntries,
|
||||||
|
AdminLogBroadcastLimit: cfg.AdminLogBroadcastLimit,
|
||||||
|
StaleRoomCleanupInterval: cfg.StaleRoomCleanupInterval,
|
||||||
|
StaleRoomTTL: cfg.StaleRoomTTL,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to initialize state manager: %v", err)
|
log.Fatalf("failed to initialize state manager: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,27 +11,61 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxActivityLogEntries = 400
|
defaultMaxActivityLogEntries = 400
|
||||||
adminLogBroadcastLimit = 200
|
defaultAdminLogBroadcastLimit = 200
|
||||||
staleRoomCleanupInterval = 5 * time.Minute
|
defaultStaleRoomCleanupInterval = 5 * time.Minute
|
||||||
staleRoomTTL = 30 * time.Minute
|
defaultStaleRoomTTL = 30 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
rooms map[string]*Room
|
rooms map[string]*Room
|
||||||
store *DiskStore
|
store *DiskStore
|
||||||
|
|
||||||
|
maxActivityLogEntries int
|
||||||
|
adminLogBroadcastLimit int
|
||||||
|
staleRoomCleanupInterval time.Duration
|
||||||
|
staleRoomTTL time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(dataPath string) (*Manager, error) {
|
type ManagerOptions struct {
|
||||||
|
MaxActivityLogEntries int
|
||||||
|
AdminLogBroadcastLimit int
|
||||||
|
StaleRoomCleanupInterval time.Duration
|
||||||
|
StaleRoomTTL time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeManagerOptions(opts ManagerOptions) ManagerOptions {
|
||||||
|
if opts.MaxActivityLogEntries <= 0 {
|
||||||
|
opts.MaxActivityLogEntries = defaultMaxActivityLogEntries
|
||||||
|
}
|
||||||
|
if opts.AdminLogBroadcastLimit <= 0 {
|
||||||
|
opts.AdminLogBroadcastLimit = defaultAdminLogBroadcastLimit
|
||||||
|
}
|
||||||
|
if opts.StaleRoomCleanupInterval <= 0 {
|
||||||
|
opts.StaleRoomCleanupInterval = defaultStaleRoomCleanupInterval
|
||||||
|
}
|
||||||
|
if opts.StaleRoomTTL <= 0 {
|
||||||
|
opts.StaleRoomTTL = defaultStaleRoomTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(dataPath string, opts ManagerOptions) (*Manager, error) {
|
||||||
store, err := NewDiskStore(dataPath)
|
store, err := NewDiskStore(dataPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
normalizedOpts := normalizeManagerOptions(opts)
|
||||||
|
|
||||||
manager := &Manager{
|
manager := &Manager{
|
||||||
rooms: make(map[string]*Room),
|
rooms: make(map[string]*Room),
|
||||||
store: store,
|
store: store,
|
||||||
|
maxActivityLogEntries: normalizedOpts.MaxActivityLogEntries,
|
||||||
|
adminLogBroadcastLimit: normalizedOpts.AdminLogBroadcastLimit,
|
||||||
|
staleRoomCleanupInterval: normalizedOpts.StaleRoomCleanupInterval,
|
||||||
|
staleRoomTTL: normalizedOpts.StaleRoomTTL,
|
||||||
}
|
}
|
||||||
|
|
||||||
if loadErr := manager.loadFromDisk(); loadErr != nil {
|
if loadErr := manager.loadFromDisk(); loadErr != nil {
|
||||||
@@ -43,7 +77,7 @@ func NewManager(dataPath string) (*Manager, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) startCleanupLoop() {
|
func (m *Manager) startCleanupLoop() {
|
||||||
ticker := time.NewTicker(staleRoomCleanupInterval)
|
ticker := time.NewTicker(m.staleRoomCleanupInterval)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
@@ -65,7 +99,7 @@ func (m *Manager) cleanupStaleRooms(now time.Time) {
|
|||||||
room.mu.Lock()
|
room.mu.Lock()
|
||||||
roomID := room.ID
|
roomID := room.ID
|
||||||
hasConnected := hasConnectedParticipantsLocked(room)
|
hasConnected := hasConnectedParticipantsLocked(room)
|
||||||
recentlyActive := now.Sub(room.UpdatedAt) < staleRoomTTL
|
recentlyActive := now.Sub(room.UpdatedAt) < m.staleRoomTTL
|
||||||
hasSubscribers := len(room.subscribers) > 0
|
hasSubscribers := len(room.subscribers) > 0
|
||||||
room.mu.Unlock()
|
room.mu.Unlock()
|
||||||
|
|
||||||
@@ -81,7 +115,7 @@ func (m *Manager) cleanupStaleRooms(now time.Time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
current.mu.Lock()
|
current.mu.Lock()
|
||||||
if hasConnectedParticipantsLocked(current) || now.Sub(current.UpdatedAt) < staleRoomTTL || len(current.subscribers) > 0 {
|
if hasConnectedParticipantsLocked(current) || now.Sub(current.UpdatedAt) < m.staleRoomTTL || len(current.subscribers) > 0 {
|
||||||
current.mu.Unlock()
|
current.mu.Unlock()
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
continue
|
continue
|
||||||
@@ -747,8 +781,8 @@ func (m *Manager) marshalRoomState(room *Room, viewerParticipantID string) ([]by
|
|||||||
state.Links.AdminLink = "/room/" + room.ID + "?adminToken=" + room.AdminToken
|
state.Links.AdminLink = "/room/" + room.ID + "?adminToken=" + room.AdminToken
|
||||||
|
|
||||||
start := 0
|
start := 0
|
||||||
if len(room.ActivityLog) > adminLogBroadcastLimit {
|
if len(room.ActivityLog) > m.adminLogBroadcastLimit {
|
||||||
start = len(room.ActivityLog) - adminLogBroadcastLimit
|
start = len(room.ActivityLog) - m.adminLogBroadcastLimit
|
||||||
}
|
}
|
||||||
state.AdminLogs = make([]PublicActivityLogEntry, 0, len(room.ActivityLog)-start)
|
state.AdminLogs = make([]PublicActivityLogEntry, 0, len(room.ActivityLog)-start)
|
||||||
for _, item := range room.ActivityLog[start:] {
|
for _, item := range room.ActivityLog[start:] {
|
||||||
@@ -798,8 +832,8 @@ func (m *Manager) appendActivityLogLocked(room *Room, format string, args ...any
|
|||||||
Message: fmt.Sprintf(format, args...),
|
Message: fmt.Sprintf(format, args...),
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(room.ActivityLog) > maxActivityLogEntries {
|
if len(room.ActivityLog) > m.maxActivityLogEntries {
|
||||||
room.ActivityLog = append([]ActivityLogEntry(nil), room.ActivityLog[len(room.ActivityLog)-maxActivityLogEntries:]...)
|
room.ActivityLog = append([]ActivityLogEntry(nil), room.ActivityLog[len(room.ActivityLog)-m.maxActivityLogEntries:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user