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.
47 lines
1.9 KiB
Go
47 lines
1.9 KiB
Go
package router
|
|
|
|
import (
|
|
"bookhoard/internal/handlers"
|
|
)
|
|
|
|
func registerSyncRoutes(cfg *Config) {
|
|
e := cfg.Echo
|
|
|
|
// JWT middleware for protected routes
|
|
jwtMiddleware := createJWTMiddleware(cfg)
|
|
|
|
protected := e.Group("/api", jwtMiddleware)
|
|
|
|
// Create handler for sync-specific routes
|
|
h := handlers.NewHandler(cfg.Queries, cfg.ConnManager, cfg.QueueProcessor)
|
|
|
|
// Book matching and unlinked book resolution routes
|
|
sync := protected.Group("/sync")
|
|
sync.POST("/bulk-link-books", h.BulkLinkBooks)
|
|
sync.POST("/auto-link-books", h.AutoLinkBooks)
|
|
sync.GET("/unlinked-books/:id/suggestions", h.GetUnlinkedBookSuggestions)
|
|
|
|
// KOReader sync routes (device authentication required)
|
|
koreaderSync := e.Group("/api/sync/koreader")
|
|
koreaderSync.POST("/progress", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.SyncProgress))
|
|
koreaderSync.GET("/metadata/:uuid", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.GetMetadata))
|
|
koreaderSync.GET("/library", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.GetLibrary))
|
|
koreaderSync.POST("/bookmarks", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.SyncBookmarks))
|
|
|
|
// Kobo sync routes (device authentication required)
|
|
koboHandler := handlers.NewKoboHandler(cfg.Queries, cfg.ConnManager)
|
|
koboSync := e.Group("/api/sync/kobo")
|
|
koboSync.POST("/markup", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.Markup))
|
|
koboSync.POST("/bookmark", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.Bookmark))
|
|
koboSync.POST("/v1/analytics/gettests", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.AnalyticsGettests))
|
|
koboSync.GET("/v1/initialization", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.Initialization))
|
|
koboSync.POST("/sync-from-server", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.SyncFromServer))
|
|
}
|
|
|
|
func registerWebSocketRoutes(cfg *Config) {
|
|
e := cfg.Echo
|
|
|
|
// WebSocket endpoint for real-time sync
|
|
e.GET("/ws/sync", cfg.WSHandler.HandleWebSocket)
|
|
}
|