feat: migrate app locks to Francis (#1624)

Co-authored-by: Elias Schneider <login@eliasschneider.com>
Co-authored-by: Kyle Mendell <kmendell@ofkm.us>
This commit is contained in:
Alessandro (Ale) Segala
2026-07-29 22:15:40 +02:00
committed by GitHub
co-authored by Elias Schneider Kyle Mendell
parent 7d37089029
commit 2ef7c93fee
13 changed files with 181 additions and 777 deletions
+9 -27
View File
@@ -9,6 +9,7 @@ import (
_ "github.com/golang-migrate/migrate/v4/source/file"
"github.com/italypaleale/francis/components"
"github.com/italypaleale/francis/host/local"
"github.com/italypaleale/go-kit/servicerunner"
"gorm.io/gorm"
@@ -16,7 +17,6 @@ import (
"github.com/pocket-id/pocket-id/backend/internal/common"
"github.com/pocket-id/pocket-id/backend/internal/instanceid"
"github.com/pocket-id/pocket-id/backend/internal/job"
"github.com/pocket-id/pocket-id/backend/internal/service"
"github.com/pocket-id/pocket-id/backend/internal/storage"
)
@@ -43,7 +43,7 @@ func Bootstrap(ctx context.Context) error {
}
if pg != nil {
defer func() {
// Close the database connection pool only after the shutdown functions have run: some of them (e.g. releasing the application lock) still need to query the database.
// Close the database connection pool only after the shutdown functions have run: some of them (e.g. the actor host deregistering itself from the cluster) still need to query the database.
pg.Close()
}()
}
@@ -112,40 +112,19 @@ func Bootstrap(ctx context.Context) error {
if err != nil {
return fmt.Errorf("failed to initialize services: %w", err)
}
services = append(services, svc.appLockService.RunRenewal)
// Migrate the pre-actor signup tokens into their actors, once the actor host is ready
services = append(services, actorsReady.Await(svc.userSignUpModule.RunSignupTokenMigration))
// Acquire the lock from the app lock service
waitUntil, err := svc.appLockService.Acquire(ctx, false)
if errors.Is(err, service.ErrLockUnavailable) {
return errors.New("it appears that there's already one instance of Pocket ID running; running multiple replicas of Pocket ID is currently not supported")
} else if err != nil {
return fmt.Errorf("failed to acquire application lock: %w", err)
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(time.Until(waitUntil)):
}
shutdowns.Add(func(shutdownCtx context.Context) error {
sErr := svc.appLockService.Release(shutdownCtx)
if sErr != nil {
return fmt.Errorf("failed to release application lock: %w", sErr)
}
return nil
})
// Register scheduled jobs, only in non-test mode
if common.EnvConfig.AppEnv != "test" {
err = registerScheduledJobs(ctx, db, svc, scheduler)
if err != nil {
return fmt.Errorf("failed to register scheduled jobs: %w", err)
}
services = append(services, scheduler.Run)
// The scheduler must wait on the actor host being ready, since jobs invoke actors
services = append(services, actorsReady.Await(scheduler.Run))
}
// Init the router
@@ -162,7 +141,10 @@ func Bootstrap(ctx context.Context) error {
// Run all background services
// This call blocks until the context is canceled
err = servicerunner.NewServiceRunner(services...).Run(ctx)
if err != nil {
if errors.Is(err, components.ErrClusterFull) {
// TODO: Once HA mode is supported, add a note about enabling it
return errors.New("it appears that there's already one instance of Pocket ID running - running multiple replicas is not (yet) supported")
} else if err != nil {
return fmt.Errorf("failed to run services: %w", err)
}