Commit Diff


commit - 0cab7a6b2a82d6a3d7694db9a63775bc9712b689
commit + 734014cedecabe12fecb96a37fc38cbf0fd072c1
blob - a046c5b38ceac71fde10ad03e5e7067730439258
blob + 9f8d7db2bb5428180b5d89637e21b7ae4c035982
--- src/Main.purs
+++ src/Main.purs
@@ -55,6 +55,7 @@ type UserContext =
   , displayName :: String
   , enrichMetadataFiber :: Maybe (Fiber Unit)
   , backupFiber :: Maybe (Fiber Unit)
+  , syncFibers :: Array (Fiber Unit)
   }
 
 normalizePath :: String -> String
@@ -397,22 +398,31 @@ startUser { slug, name, config } = do
     Just lfmUser, Just apiKey -> Just <$> forkAff (lfSync conn apiKey lfmUser slug writeLock)
     _, _ -> pure Nothing
 
+  -- Join the initial sync fibers so their completion is logged before loops start.
   void $ forkAff do
     for_ lbFiber joinFiber
     for_ lfFiber joinFiber
-    for_ config.listenbrainzUser \username ->
-      void $ forkAff $ lbSyncLoop conn username slug writeLock
-    case config.lastfmUser, config.lastfmApiKey of
-      Just lfmUser, Just apiKey -> void $ forkAff $ lfSyncLoop conn apiKey lfmUser slug writeLock
-      _, _ -> pure unit
 
+  -- Spawn the recurring sync loops eagerly; each starts with a 60s delay so
+  -- they won't race with the initial sync above. Track them so cleanupUser
+  -- can kill them on shutdown.
+  loopFibers <- traverse forkAff
+    $
+      [ case config.listenbrainzUser of
+          Just username -> Just $ lbSyncLoop conn username slug writeLock
+          Nothing -> Nothing
+      , case config.lastfmUser, config.lastfmApiKey of
+          Just lfmUser, Just apiKey -> Just $ lfSyncLoop conn apiKey lfmUser slug writeLock
+          _, _ -> Nothing
+      ] # Data.Array.mapMaybe identity
+
   enrichMetadataFiber <- forkAff $ enrichMetadata conn config slug
   backupFiber <-
     if config.backupEnabled then Just <$> forkAff (backupDb conn config.databaseFile (s3ConfigFromUser config) (Int.toNumber config.backupIntervalHours * 3600000.0) slug)
     else pure Nothing
 
   let displayName = fromMaybe (if slug == "" then "root" else slug) name
-  pure { conn, writeLock, config, slug, displayName, enrichMetadataFiber: Just enrichMetadataFiber, backupFiber }
+  pure { conn, writeLock, config, slug, displayName, enrichMetadataFiber: Just enrichMetadataFiber, backupFiber, syncFibers: loopFibers }
 
 cleanupUser :: UserContext -> Aff Unit
 cleanupUser ctx = do
@@ -422,6 +432,8 @@ cleanupUser ctx = do
     void $ try $ killFiber (Exception.error "Server shutting down") fiber
   for_ ctx.backupFiber \fiber ->
     void $ try $ killFiber (Exception.error "Server shutting down") fiber
+  for_ ctx.syncFibers \fiber ->
+    void $ try $ killFiber (Exception.error "Server shutting down") fiber
   Log.info $ "Closed user: " <> label
 
 foreign import dotenvConfig :: Effect Unit
blob - 784967cb6fcddfccd29f459da314d8089529f650
blob + 8e8fad7726de0b1cc40222bf74c0dba363919439
--- test/Main.purs
+++ test/Main.purs
@@ -2,12 +2,12 @@ module Test.Main where
 
 import Prelude
 
-import Unsafe.Coerce (unsafeCoerce)
 import Data.Argonaut (decodeJson, encodeJson, parseJson)
 import Data.Array (length)
 import Data.Either (Either(..), isRight)
 import Data.Maybe (Maybe(..))
 import Effect (Effect)
+import Effect.Aff.AVar as Avar
 import Test.Spec (describe, it)
 import Test.Spec.Assertions (shouldEqual, fail)
 import Data.String.Regex (regex, parseFlags)
@@ -256,8 +256,11 @@ main = runSpecAndExitProcess [consoleReporter] do
                 , backupEnabled: false
                 , backupIntervalHours: 0
                 }
-              ctx1 = { conn: conn1, writeLock: unsafeCoerce unit, config: dummyConfig, slug: "user1", displayName: "User 1", enrichMetadataFiber: Nothing, backupFiber: Nothing }
-              ctx2 = { conn: conn2, writeLock: unsafeCoerce unit, config: dummyConfig, slug: "user2", displayName: "User 2", enrichMetadataFiber: Nothing, backupFiber: Nothing }
+            lock1 <- Avar.new unit
+            lock2 <- Avar.new unit
+            let
+              ctx1 = { conn: conn1, writeLock: lock1, config: dummyConfig, slug: "user1", displayName: "User 1", enrichMetadataFiber: Nothing, backupFiber: Nothing, syncFibers: [] }
+              ctx2 = { conn: conn2, writeLock: lock2, config: dummyConfig, slug: "user2", displayName: "User 2", enrichMetadataFiber: Nothing, backupFiber: Nothing, syncFibers: [] }
               contexts = [ ctx1, ctx2 ]
 
             res1 <- findUserByToken contexts token1