packages feed

effectful-tracing-0.1.0.0: test-space-leak/Main.hs

-- |
-- Module      : Main
-- Description : Space-leak regression guard for the span lifecycle.
--
-- This is a standalone workload, separate from the main tasty suite, run under
-- a deliberately tiny maximum stack (@-K1K@, set in the cabal stanza). It opens
-- and closes a large number of spans through the in-memory interpreter and then
-- forces every captured span with a strict fold to a scalar.
--
-- The point of the tiny stack is to turn a space leak into a hard failure. If a
-- regression let completed spans accumulate as a chain of thunks (a lazy
-- accumulator, a non-strict sink write, an un-forced field), forcing them here
-- would build an O(n) evaluation stack and overflow @-K1K@, failing the test.
-- The current strict lifecycle (spans forced to WHNF before they reach the
-- sink, a strict 'foldl'' here) runs in O(1) stack regardless of span count.
--
-- It is intentionally tasty-free: the tasty/hedgehog machinery legitimately
-- needs more than a 1K stack, so it cannot share this process's RTS options.
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Control.Monad (forM_, when)
import System.Exit (exitFailure)
#if !MIN_VERSION_base(4,20,0)
import Data.List (foldl')
#endif

import Effectful (Eff, runEff, (:>))

import Effectful.Tracing (Span (spanAttributes), Tracer, addAttribute, withSpan)
import Effectful.Tracing.Interpreter.InMemory
  ( newCapturedSpans
  , readCapturedSpans
  , runTracerInMemory
  )

-- | How many spans to open, close, and then force. Large enough that an O(n)
-- stack from a thunk chain would blow the 1K limit many times over.
spanCount :: Int
spanCount = 100000

-- | A flat sequence of completed spans, each carrying one attribute.
workload :: (Tracer :> es) => Eff es ()
workload =
  forM_ [1 .. spanCount] $ \i ->
    withSpan "tick" (addAttribute "i" (i :: Int))

main :: IO ()
main = do
  total <- runEff $ do
    captured <- newCapturedSpans
    _ <- runTracerInMemory captured workload
    spans <- readCapturedSpans captured
    -- Strict fold to a scalar. A leaky lazy accumulator (or un-forced span
    -- field) would defer this work into an O(n) thunk chain and overflow the
    -- 1K stack when the result is finally demanded below.
    pure $! foldl' (\acc s -> acc + length (spanAttributes s)) (0 :: Int) spans
  when (total /= spanCount) $ do
    putStrLn ("space-leak guard: expected " <> show spanCount <> " attributes, saw " <> show total)
    exitFailure
  putStrLn ("space-leak guard: forced " <> show spanCount <> " completed spans under -K1K")