package main import ( "flag" "fmt" "os" "os/signal" "time" "wpw-common/internal" "wpw-common/pkg/gen/biz/core" "context" coresvc "wpw-common/internal/biz/core" "wpw-common/internal/http" "wpw-common/internal/system" "wpw-common/pkg/gormlog" _ "github.com/joho/godotenv/autoload" "go.uber.org/zap" "gorm.io/driver/mysql" _ "gorm.io/driver/mysql" "gorm.io/driver/sqlite" _ "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) var ( appCtx = (internal.AppContext{}).Init() ctx context.Context appSecret string taskWaitDuration = time.Second * 10 serverListenAddr = "0.0.0.0:8000" dataSourceType = "sqlite" dsn = "./app.db" fallabackFlagValue func() ) func osEnvFallback(s string, key string) string { if s != "" { return s } return os.Getenv(key) } func gormSetup(driver string, dsn string, config *gorm.Config) (*gorm.DB, error) { if driver == "" { driver = "sqlite" if dsn == "" { dsn = "./app.db" } } switch driver { case "sqlite": return gorm.Open(sqlite.Open(dsn), config) case "mysql": return gorm.Open(mysql.Open(dsn), config) default: panic(fmt.Sprintf("unknown driver %s", driver)) } } func init() { var err error // flags flag.DurationVar(&taskWaitDuration, "grace-duration", taskWaitDuration, "graceful wait duration") flag.StringVar(&appSecret, "secret", appSecret, "Application secret") flag.StringVar(&serverListenAddr, "addr", serverListenAddr, "Server listen address") flag.StringVar(&dataSourceType, "dst", dataSourceType, "Data source") flag.StringVar(&dsn, "dsn", dsn, "DSN") fallabackFlagValue = func() { appSecret = osEnvFallback(appSecret, "SECRET") serverListenAddr = osEnvFallback(serverListenAddr, "ADDR") dataSourceType = osEnvFallback(dataSourceType, "DRIVER") dsn = osEnvFallback(dsn, "DSN") } appCtx.Logger, err = zap.NewDevelopment() if err != nil { panic(err) } // initalize application context ctx, appCtx.Cancel = context.WithCancel(context.Background()) ctx = internal.WithAppContext(ctx, &appCtx) } func main() { var err error flag.Parse() fallabackFlagValue() defer appCtx.Logger.Sync() sugar := appCtx.Logger.Sugar().Named("main") sugar.Info("starting app") sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt) // push secret to context if appSecret == "" { sugar.Fatal("application secret is empty") } appCtx.Data["secret"] = appSecret // initialize db if appCtx.Data["db"], err = gormSetup(dataSourceType, dsn, &gorm.Config{ Logger: gormlog.New(gormlog.GormLogParams{ LogLevel: logger.Info, SlowThreshold: time.Second * 2, IgnoreRecordNotFoundError: true, Logger: appCtx.Logger, }), }); err != nil { sugar.Fatalw("db error", "driver", dataSourceType, "err", err) } // system init system.Init(&appCtx) // register services http.RegisterService("/api/core/v1", core.NewCoreServiceProcessor( coresvc.NewCoreServiceFromAppContext(&appCtx))) // appCtx.DoTask(func(ctx context.Context) { sugar.Info("task start") defer sugar.Info("task done") for { select { case <-time.After(time.Second * 40): return case <-ctx.Done(): sugar.Info("task notify cancel") <-time.After(time.Second * 5) return } } }) // start server appCtx.DoTask(func(ctx context.Context) { errChan := make(chan error, 1) go func() { errChan <- http.Run(&appCtx, serverListenAddr) }() if err := <-errChan; err != nil { sugar.Errorw("server error", "err", err) } }) spin: for { select { case <-ctx.Done(): sugar.Info("waiting task done", "cnt", appCtx.TaskCount()) done := make(chan struct{}, 1) go func() { appCtx.WaitTask(); done <- struct{}{} }() select { case <-done: break spin case sig := <-sig: // cancel the wait sugar.Fatalw("waiting task cancelled", "sig", sig, "remaining", appCtx.TaskRemaining(), ) case <-time.After(taskWaitDuration): sugar.Fatalw("timeout waiting task done", "remaining", appCtx.TaskRemaining()) } case sig := <-sig: sugar.Infow("received signal", "sig", sig, ) appCtx.Cancel() } } }