// Package main is the main package for the WeKnora server // It contains the main function and the entry point for the server // // @title WeKnora API // @version 1.0 // @description WeKnora 知识库管理系统 API 文档 // @termsOfService http://swagger.io/terms/ // // @contact.name WeKnora Github // @contact.url https://github.com/Tencent/WeKnora // // @BasePath /api/v1 // // @securityDefinitions.apikey Bearer // @in header // @name Authorization // @description 用户登录认证:输入 Bearer {token} 格式的 JWT 令牌 // @securityDefinitions.apikey ApiKeyAuth // @in header // @name X-API-Key // @description 租户身份认证:输入 sk- 开头的 API Key package main import ( "context" "fmt" "net" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" "github.com/Tencent/WeKnora/internal/config" "github.com/Tencent/WeKnora/internal/container" "github.com/Tencent/WeKnora/internal/logger" "github.com/Tencent/WeKnora/internal/runtime" "github.com/Tencent/WeKnora/internal/tracing" "github.com/Tencent/WeKnora/internal/types/interfaces" "golang.org/x/sys/unix" ) func main() { // Set Gin mode if os.Getenv("GIN_MODE") == "release" { gin.SetMode(gin.ReleaseMode) } else { gin.SetMode(gin.DebugMode) } // Build dependency injection container c := container.BuildContainer(runtime.GetContainer()) // Run application err := c.Invoke(func( cfg *config.Config, router *gin.Engine, tracer *tracing.Tracer, resourceCleaner interfaces.ResourceCleaner, ) error { // Create HTTP server server := &http.Server{ Handler: router, } addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) listener, err := listenWithRetry(addr, 10, 300*time.Millisecond) if err != nil { return fmt.Errorf("failed to start server: %v", err) } ctx, done := context.WithCancel(context.Background()) signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) go func() { sig := <-signals logger.Infof(context.Background(), "Received signal: %v, starting server shutdown...", sig) // Close listener first to release port immediately, // so the next process can bind during our graceful drain. listener.Close() shutdownTimeout := cfg.Server.ShutdownTimeout if shutdownTimeout == 0 { shutdownTimeout = 30 * time.Second } shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), shutdownTimeout) defer shutdownCancel() // Second signal → force close all connections immediately go func() { sig := <-signals logger.Warnf(context.Background(), "Received second signal: %v, forcing shutdown...", sig) server.Close() }() if err := server.Shutdown(shutdownCtx); err != nil { logger.Errorf(context.Background(), "Server forced to shutdown: %v", err) server.Close() } logger.Info(context.Background(), "Cleaning up resources...") errs := resourceCleaner.Cleanup(shutdownCtx) if len(errs) > 0 { logger.Errorf(context.Background(), "Errors occurred during resource cleanup: %v", errs) } logger.Info(context.Background(), "Server has exited") done() }() logger.Infof(context.Background(), "Server is running at %s", addr) if err := server.Serve(listener); err != nil && err != http.ErrServerClosed { return fmt.Errorf("server error: %v", err) } <-ctx.Done() return nil }) if err != nil { logger.Fatalf(context.Background(), "Failed to run application: %v", err) } } // listenWithRetry retries listening with exponential backoff and SO_REUSEPORT, // useful during hot-reload when the previous process may not have released the port yet. func listenWithRetry(addr string, maxRetries int, baseDelay time.Duration) (net.Listener, error) { lc := net.ListenConfig{ Control: func(network, address string, c syscall.RawConn) error { return c.Control(func(fd uintptr) { _ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1) }) }, } var lastErr error for i := 0; i < maxRetries; i++ { listener, err := lc.Listen(context.Background(), "tcp", addr) if err == nil { return listener, nil } lastErr = err if i < maxRetries-1 { delay := baseDelay * time.Duration(1< 3*time.Second { delay = 3 * time.Second } logger.Warnf(context.Background(), "Port %s in use, retrying in %v... (%d/%d)", addr, delay, i+1, maxRetries) time.Sleep(delay) } } return nil, lastErr }