packages feed

kiroku-store 0.9.0.0 → 0.9.0.1

raw patch · 4 files changed

+53/−5 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,5 +1,15 @@ # Changelog +## 0.9.0.1 — 2026-09-25++### Bug Fixes++* The publisher now forces its scalar global position before storing it when+  no all-stream queue subscribers are registered (BUG-3). This prevents a+  chain of unevaluated position updates from retaining earlier Hasql results+  and large objects during sustained appends. A post-major-GC regression test+  covers the idle publisher path.+ ## 0.9.0.0 — 2026-09-25  ### Breaking Changes
kiroku-store.cabal view
@@ -1,6 +1,6 @@ cabal-version:   3.0 name:            kiroku-store-version:         0.9.0.0+version:         0.9.0.1 synopsis:        High-performance PostgreSQL event store description:   Kiroku is a PostgreSQL-backed event store for Haskell applications. It@@ -139,7 +139,7 @@     Test.VisibleGlobalHeadPosition     Test.VisibleGlobalHeadPositionMock -  ghc-options:    -threaded -rtsopts -with-rtsopts=-N+  ghc-options:    -threaded -rtsopts "-with-rtsopts=-N -T"   build-depends:     , aeson                 >=2.1   && <2.3     , async                 >=2.2   && <2.3
src/Kiroku/Store/Subscription/EventPublisher.hs view
@@ -248,7 +248,10 @@                     if IntMap.null subs'                         then do                             GlobalPosition cur <- readTVar posVar-                            writeTVar posVar (GlobalPosition (max cur tailPos))+                            let nextPos = max cur tailPos+                            -- Do not store a chain of lazy max expressions that+                            -- retains earlier tail-query results.+                            nextPos `seq` writeTVar posVar (GlobalPosition nextPos)                             pure False                         else pure True                 when raced fullFetch
test/Test/PublisherIdleAdvance.hs view
@@ -8,16 +8,18 @@ import Control.Concurrent.STM (readTVarIO) import Control.Exception (bracket) import Control.Lens ((&), (.~), (^.))-import Control.Monad (when)+import Control.Monad (forM, when) import Data.Aeson qualified as Aeson import Data.Generics.Labels () import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef) import Data.IntMap.Strict qualified as IntMap import Data.List (sort) import Data.String (fromString)+import GHC.Stats (GCDetails (..), RTSStats (..), getRTSStats, getRTSStatsEnabled) import Kiroku.Store import Kiroku.Store.Subscription.EventPublisher qualified as Pub-import Test.Helpers (caughtUpEventHandler, makeEvent, waitForPublisher, waitForSubscriptionLive, withTestStoreSettings)+import System.Mem (performMajorGC)+import Test.Helpers (caughtUpEventHandler, makeEvent, waitForPublisher, waitForSubscriptionLive, withTestStore, withTestStoreSettings) import Test.Hspec  timeoutMicros :: Int@@ -62,8 +64,41 @@ publisherSubscriberCount store =     IntMap.size <$> readTVarIO (Pub.subscribers (store ^. #publisher)) +sampleLargeObjects :: IO Integer+sampleLargeObjects = do+    enabled <- getRTSStatsEnabled+    when (not enabled) (fail "publisher retention test requires RTS statistics")+    performMajorGC+    fromIntegral . gcdetails_large_objects_bytes . gc <$> getRTSStats+ spec :: Spec spec = describe "publisher idle advance" $ do+    it "does not retain append results while advancing with no queue subscribers" $ do+        withTestStore $ \store -> do+            let blockSize :: Int+                blockSize = 2_000+                blocks :: Int+                blocks = 6+                event = makeEvent "Retention" (Aeson.object [])+                appendOne = do+                    result <- runStoreIO store $ appendToStream (StreamName "pubidle-retention") AnyVersion [event]+                    case result of+                        Left err -> fail ("append failed: " <> show err)+                        Right _ -> pure ()+            samples <- forM [1 .. blocks] $ \_ -> do+                sequence_ (replicate blockSize appendOne)+                -- Allow the notification-driven publisher to finish its tail+                -- query without reading (and thereby forcing) its position.+                threadDelay 100_000+                sampleLargeObjects+            publisherSubscriberCount store `shouldReturn` 0+            case samples of+                firstSample : _ -> do+                    let growth = last samples - firstSample+                    when (growth >= 3 * 1024 * 1024) $+                        expectationFailure ("large-object bytes grew by " <> show growth <> "; samples: " <> show samples)+                [] -> expectationFailure "publisher retention test collected no heap samples"+     it "advances lastPublished without decoding any rows when no subscriber is registered" $ do         counter <- newIORef 0         withTestStoreSettings (countingSettings counter) $ \store -> do