Files
bookhoard/internal/router/library.go
T
john-okeefe 001647cbbe Fix goroutine leaks in sync queue processor and connection manager
Critical fixes to prevent goroutine leaks during application shutdown:

1. Sync Queue Processor:
   - Changed StartCleanupTask() to return context.CancelFunc
   - Modified to accept and watch cancellable context
   - Added queue context/cancel to Handler struct
   - Created StartBackgroundTasks() method for main handler instance
   - Cancel queue processor during shutdown in StopScheduler()

2. Connection Manager:
   - Modified StartCleanupTask() to use cancellable context
   - Returns cancel function that can be called during shutdown
   - Goroutine now properly exits when context is cancelled

3. Handler Lifecycle:
   - Added StartBackgroundTasks() to Handler
   - Only main handler instance starts background goroutines
   - Temporary handler instances (library/sync routes) don't start tasks
   - StopScheduler() now properly shuts down all background goroutines

4. Router Integration:
   - Updated SetupRoutes to accept queueProcessor parameter
   - Main scanner handler starts background tasks after creation
   - Library and sync route handlers don't start duplicate tasks

Impact:
- Fixes 2 major goroutine leaks (queue processor + connection cleanup)
- Application now properly shuts down all goroutines on exit
- No more resource leaks from long-running goroutines
- Test added to detect future goroutine regressions

Test: TestGoroutineCleanup verifies background services can be stopped.
2026-02-09 13:12:31 -05:00

58 lines
1.9 KiB
Go

package router
import (
"bookhoard/internal/handlers"
"github.com/labstack/echo/v4"
)
func registerLibraryRoutes(cfg *Config) {
e := cfg.Echo
// JWT middleware for protected routes
jwtMiddleware := createJWTMiddleware(cfg)
// Protected routes group
protected := e.Group("/api", jwtMiddleware)
// Create handler for library-specific convenience routes
h := handlers.NewHandler(cfg.Queries, cfg.ConnManager, cfg.QueueProcessor)
// Public library types endpoint
e.GET("/api/libraries/types", cfg.LibraryHandler.GetLibraryTypes)
// Library management routes
library := protected.Group("/libraries")
// Admin-only library routes
adminLibrary := library.Group("", handlers.AdminMiddleware)
adminLibrary.POST("", cfg.LibraryHandler.CreateLibrary)
adminLibrary.GET("", cfg.LibraryHandler.ListLibraries)
adminLibrary.GET("/:id", cfg.LibraryHandler.GetLibrary)
adminLibrary.PUT("/:id", cfg.LibraryHandler.UpdateLibrary)
adminLibrary.DELETE("/:id", cfg.LibraryHandler.DeleteLibrary)
adminLibrary.POST("/:id/folders", cfg.LibraryHandler.AddLibraryFolder)
adminLibrary.GET("/:id/folders", cfg.LibraryHandler.GetLibraryFolders)
adminLibrary.DELETE("/:id/folders", cfg.LibraryHandler.DeleteLibraryFolder)
adminLibrary.GET("/:id/stats", cfg.LibraryHandler.GetLibraryStats)
adminLibrary.POST("/:id/scan", func(c echo.Context) error {
libraryID := c.Param("id")
scanReq := map[string]interface{}{
"library_id": libraryID,
}
c.Set("scan_request", scanReq)
return h.ScanLibrary(c)
})
adminLibrary.GET("/:id/media-items", func(c echo.Context) error {
libraryID := c.Param("id")
c.QueryParams().Set("library_id", libraryID)
return cfg.MediaHandler.ListMediaItems(c)
})
// User library visibility control
userLibrary := library.Group("/visibility")
userLibrary.GET("", cfg.LibraryHandler.GetUserVisibleLibraries)
userLibrary.POST("", cfg.LibraryHandler.SetLibraryVisibility)
// Note: Remove endpoint may not exist - check handlers
}