Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- Add UndeployAll() using label selectors to clean up monorepo components on project deletion (replaces name-based Undeploy in DeleteProject and the direct undeploy handler) - Add ResourceGC background worker that periodically finds K8s resources whose project label has no matching DB record, deletes after 1h safety window - Widen deployer client type from *kubernetes.Clientset to kubernetes.Interface for testability - UndeployAll accumulates errors via errors.Join instead of failing fast - Add checkout/checkin sidecar dev flow: temporary git tokens, branch checkout, review on checkin with cleanup workers - Add interactive sessions: pod binding, command execution, SSE streaming, ephemeral preview URLs with session cleanup workers - Add GET /workers/pool endpoint for aggregate capacity and queue depth - Add sessions:read and sessions:execute auth scopes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
118 lines
2.6 KiB
Go
118 lines
2.6 KiB
Go
package worker
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/orchard9/rdev/internal/logging"
|
|
)
|
|
|
|
// SessionCleanupService defines the interface for session cleanup operations.
|
|
type SessionCleanupService interface {
|
|
CleanupExpired(ctx context.Context) (int, error)
|
|
}
|
|
|
|
// SessionCleanup runs periodic cleanup of expired sessions.
|
|
// Expired sessions have their preview resources torn down and checkout tokens revoked.
|
|
type SessionCleanup struct {
|
|
service SessionCleanupService
|
|
cleanupInterval time.Duration
|
|
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// SessionCleanupConfig holds configuration for session cleanup.
|
|
type SessionCleanupConfig struct {
|
|
// CleanupInterval is how often to run cleanup.
|
|
// Default: 5 minutes.
|
|
CleanupInterval time.Duration
|
|
}
|
|
|
|
// DefaultSessionCleanupConfig returns sensible defaults.
|
|
func DefaultSessionCleanupConfig() *SessionCleanupConfig {
|
|
return &SessionCleanupConfig{
|
|
CleanupInterval: 5 * time.Minute,
|
|
}
|
|
}
|
|
|
|
// NewSessionCleanup creates a new session cleanup worker.
|
|
func NewSessionCleanup(service SessionCleanupService, cfg *SessionCleanupConfig) *SessionCleanup {
|
|
if cfg == nil {
|
|
cfg = DefaultSessionCleanupConfig()
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
return &SessionCleanup{
|
|
service: service,
|
|
cleanupInterval: cfg.CleanupInterval,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
}
|
|
|
|
// Start begins the cleanup loop.
|
|
func (c *SessionCleanup) Start() {
|
|
log := logging.FromContext(c.ctx).WithWorker("session-cleanup")
|
|
log.Info("session cleanup started",
|
|
"cleanup_interval", c.cleanupInterval,
|
|
)
|
|
|
|
c.wg.Add(1)
|
|
go c.cleanupLoop()
|
|
}
|
|
|
|
// Stop gracefully shuts down the cleanup worker.
|
|
func (c *SessionCleanup) Stop() {
|
|
log := logging.FromContext(c.ctx).WithWorker("session-cleanup")
|
|
log.Info("session cleanup stopping")
|
|
c.cancel()
|
|
c.wg.Wait()
|
|
log.Info("session cleanup stopped")
|
|
}
|
|
|
|
// cleanupLoop runs periodic cleanup.
|
|
func (c *SessionCleanup) cleanupLoop() {
|
|
defer c.wg.Done()
|
|
|
|
// Run immediately on start.
|
|
c.runCleanup()
|
|
|
|
ticker := time.NewTicker(c.cleanupInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-c.ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
c.runCleanup()
|
|
}
|
|
}
|
|
}
|
|
|
|
// runCleanup tears down expired sessions.
|
|
func (c *SessionCleanup) runCleanup() {
|
|
ctx, cancel := context.WithTimeout(c.ctx, TimeoutMaintenance)
|
|
defer cancel()
|
|
|
|
log := logging.FromContext(ctx).WithWorker("session-cleanup")
|
|
|
|
cleaned, err := c.service.CleanupExpired(ctx)
|
|
if err != nil {
|
|
log.Error("failed to cleanup expired sessions",
|
|
logging.FieldError, err,
|
|
)
|
|
return
|
|
}
|
|
|
|
if cleaned > 0 {
|
|
log.Info("cleaned up expired sessions",
|
|
"count", cleaned,
|
|
)
|
|
}
|
|
}
|