fa72f1b323
The run-cancel registry is in-memory; a container restart mid-run leaves accounts/tasks persisted as 'running' with no goroutine, wedging cancel (not-in-map -> 409) and blocking remove/re-run. - startup: ResetRunningOnStartup clears stale 'running' -> 'idle' on boot - cancel handler: when no live goroutine, ClearStuckAccount + ReconcileTaskStatus reset the stuck account (and its task) instead of returning 409 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
65 lines
1.8 KiB
Go
65 lines
1.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
|
|
"github.com/golang-migrate/migrate/v4"
|
|
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
|
|
|
"github.com/vasyansk/imap-copier/internal/config"
|
|
"github.com/vasyansk/imap-copier/internal/httpapi"
|
|
"github.com/vasyansk/imap-copier/internal/orchestrator"
|
|
"github.com/vasyansk/imap-copier/internal/store"
|
|
"github.com/vasyansk/imap-copier/internal/wshub"
|
|
)
|
|
|
|
func main() {
|
|
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
slog.Error("config", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
if err := runMigrations(cfg.DatabaseURL); err != nil {
|
|
slog.Error("migrate", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
st, err := store.New(context.Background(), cfg.DatabaseURL)
|
|
if err != nil {
|
|
slog.Error("store", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
// Clear phantom "running" left by a prior crash/restart — no goroutines
|
|
// survive a restart, so any persisted "running" is stale.
|
|
if t, a, err := st.ResetRunningOnStartup(context.Background()); err != nil {
|
|
slog.Error("reset stale running", "err", err)
|
|
os.Exit(1)
|
|
} else if t > 0 || a > 0 {
|
|
slog.Warn("reset stale running statuses on startup", "tasks", t, "accounts", a)
|
|
}
|
|
hub := wshub.New()
|
|
orch := orchestrator.New(st, hub, cfg.EncKey, cfg.WorkerConcurrency)
|
|
srv := httpapi.NewServer(cfg, st, orch, hub)
|
|
|
|
slog.Info("listening", "addr", cfg.HTTPAddr)
|
|
if err := http.ListenAndServe(cfg.HTTPAddr, srv.Router()); err != nil {
|
|
slog.Error("serve", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func runMigrations(dsn string) error {
|
|
m, err := migrate.New("file://migrations", dsn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|