packages feed

wasmedge-hs-0.17.1.0: examples/Stress/LeakCheck.hs

{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Stress.LeakCheck
Description : Rapid VM lifecycle and memory leak detection stress test
Copyright   : (c) 2026 Tushar Adhatrao
License     : MIT
Stability   : experimental

Rapidly creates and destroys VMs, Memory instances, Stores, and Compilers in
a tight loop (5,000+ iterations) to verify that ForeignPtr finalizers cleanly
release all C resources without memory leaks or pointer corruption.
-}
module Stress.LeakCheck
  ( runLeakCheckSuite
  , LeakMetrics (..)
  ) where

import Control.Exception (throwIO)
import Control.Monad (forM_)
import System.Clock (Clock (Monotonic), diffTimeSpec, getTime, toNanoSecs)
import System.Mem (performMajorGC)
import Text.Printf (printf)

import Stress.WasmBytecode (addWasm)
import WasmEdge.Compiler (createCompiler, deleteCompiler)
import WasmEdge.Configure (createConfigure, deleteConfigure)
import WasmEdge.Memory (createMemory, createMemoryType, deleteMemory, deleteMemoryType)
import WasmEdge.Store (createStore, deleteStore)
import WasmEdge.Types (Limit (..), WasmVal (..))
import WasmEdge.VM (runWasmBytes, withVM)

-- | Metrics from the lifecycle leak check suite
data LeakMetrics = LeakMetrics
  { lmVMIterations :: !Int
  , lmContextIterations :: !Int
  , lmDurationSec :: !Double
  , lmRatePerSec :: !Double
  , lmZeroLeakVerified :: !Bool
  }
  deriving stock (Show)

-- | Run the Rapid Lifecycle & Leak Check suite.
runLeakCheckSuite :: IO LeakMetrics
runLeakCheckSuite = do
  putStrLn "============================================================"
  putStrLn "  STRESS TEST: Rapid Lifecycle Stability & Memory Leak Check"
  putStrLn "============================================================"

  let vmIterations = 5000 :: Int
      ctxIterations = 1000 :: Int

  -- Force a clean baseline GC
  performMajorGC

  startTotal <- getTime Monotonic

  -- 1. 5,000 rapid VM creation -> execution -> destruction cycles
  putStrLn $
    printf "--> [1/2] Executing %d rapid VM creation -> execute -> teardown cycles..." vmIterations
  tVM0 <- getTime Monotonic
  forM_ [1 .. vmIterations] $ \i -> do
    res <- withVM Nothing Nothing $ \vm ->
      runWasmBytes vm addWasm "add" [WasmI32 (fromIntegral i), WasmI32 1]
    case res of
      Right [WasmI32 v]
        | fromIntegral v == i + 1 -> pure ()
        | otherwise -> throwIO (userError $ "Calculation error at iteration " ++ show i)
      Left err -> throwIO (userError $ "VM execution error at iteration " ++ show i ++ ": " ++ show err)
      Right other -> throwIO (userError $ "Unexpected return at iteration " ++ show i ++ ": " ++ show other)
  tVM1 <- getTime Monotonic

  let vmSec = fromIntegral (toNanoSecs (diffTimeSpec tVM1 tVM0)) / 1e9 :: Double
      vmRPS = fromIntegral vmIterations / vmSec
  printf
    "    VM Lifecycle: completed %d iterations in %.3f s (%.1f VMs/sec)\n"
    vmIterations
    vmSec
    vmRPS

  -- 2. 1,000 iterations creating & deleting Memory, Store, Configure, and Compiler contexts
  putStrLn $
    printf
      "--> [2/2] Executing %d iterations of raw FFI context allocation & deallocation..."
      ctxIterations
  tCtx0 <- getTime Monotonic
  forM_ [1 .. ctxIterations] $ \_ -> do
    -- Configure
    cfg <- createConfigure
    deleteConfigure cfg

    -- Store
    store <- createStore
    deleteStore store

    -- MemoryType & MemoryInstance
    mt <- createMemoryType (Limit 1 (Just 10) False)
    mem <- createMemory mt
    deleteMemory mem
    deleteMemoryType mt

    -- Compiler
    comp <- createCompiler Nothing
    deleteCompiler comp
  tCtx1 <- getTime Monotonic

  let ctxSec = fromIntegral (toNanoSecs (diffTimeSpec tCtx1 tCtx0)) / 1e9 :: Double
      ctxRPS = fromIntegral ctxIterations / ctxSec
  printf
    "    Context Lifecycle: completed %d iterations in %.3f s (%.1f cycles/sec)\n"
    ctxIterations
    ctxSec
    ctxRPS

  -- Force major GC to ensure all finalizers have executed
  performMajorGC

  endTotal <- getTime Monotonic
  let totalSec = fromIntegral (toNanoSecs (diffTimeSpec endTotal startTotal)) / 1e9 :: Double
      totalOps = vmIterations + ctxIterations
      overallRate = fromIntegral totalOps / totalSec

  printf "==> Lifecycle & Leak Check Suite PASSED in %.3f s (0 leaks detected)\n\n" totalSec

  pure $
    LeakMetrics
      { lmVMIterations = vmIterations
      , lmContextIterations = ctxIterations
      , lmDurationSec = totalSec
      , lmRatePerSec = overallRate
      , lmZeroLeakVerified = True
      }