packages feed

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

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Stress.AsyncTimeout
Description : Stress testing asynchronous execution, timeouts, and cancellation
Copyright   : (c) 2026 Tushar Adhatrao
License     : MIT
Stability   : experimental

Validates non-blocking async execution, timeout detection via 'waitFor',
clean cancellation of infinite loops / runaway guest executions via 'cancel',
and parallel async operations across multiple VM instances.
-}
module Stress.AsyncTimeout
  ( runAsyncTimeoutSuite
  , AsyncMetrics (..)
  ) where

import Control.Concurrent.Async (forConcurrently)
import Control.Exception (throwIO)
import System.Clock (Clock (Monotonic), diffTimeSpec, getTime, toNanoSecs)
import Text.Printf (printf)

import Stress.WasmBytecode (chaosWasm, fibWasm)
import WasmEdge.Async qualified as Async
import WasmEdge.Types (WasmVal (..))
import WasmEdge.VM

-- | Metrics recorded during the async & timeout stress tests.
data AsyncMetrics = AsyncMetrics
  { amNormalDurationSec :: !Double
  , amCancelClean :: !Bool
  , amConcurrentRuns :: !Int
  , amTotalDurationSec :: !Double
  }
  deriving stock (Show)

-- | Run the Async, Timeout, and Cancellation stress suite.
runAsyncTimeoutSuite :: IO AsyncMetrics
runAsyncTimeoutSuite = do
  putStrLn "============================================================"
  putStrLn "  STRESS TEST: Asynchronous Execution, Timeouts & Cancellation"
  putStrLn "============================================================"

  startTotal <- getTime Monotonic

  -- 1. Normal async execution with getReturns
  putStrLn "--> [1/3] Normal async execution of fib(24)..."
  t0 <- getTime Monotonic
  normalRes <- withVM Nothing Nothing $ \vm -> do
    _ <- loadWasmBytes vm fibWasm
    _ <- validate vm
    _ <- instantiate vm
    asyncHandle <- asyncExecute vm "fib" [WasmI32 24]
    -- Wait for completion and fetch return values
    res <- Async.getReturns asyncHandle
    case res of
      Left err -> throwIO (userError $ "Async execution failed: " ++ show err)
      Right [WasmI32 v]
        | v == 46368 -> pure v
        | otherwise -> throwIO (userError $ "Incorrect async fib result: " ++ show v)
      Right other -> throwIO (userError $ "Unexpected return: " ++ show other)
  t1 <- getTime Monotonic
  let normalSec = fromIntegral (toNanoSecs (diffTimeSpec t1 t0)) / 1e9 :: Double
  putStrLn $ printf "    Async fib(24) = %d completed successfully in %.3f s" normalRes normalSec

  -- 2. Runaway execution cancellation test (infinite loop)
  putStrLn "--> [2/3] Runaway guest execution timeout & cancellation test..."
  cancelSuccess <- withVM Nothing Nothing $ \vm -> do
    _ <- loadWasmBytes vm chaosWasm
    _ <- validate vm
    _ <- instantiate vm
    putStrLn "    Launching 'infinite_loop' in background async task..."
    asyncHandle <- asyncExecute vm "infinite_loop" []
    -- Wait up to 50ms
    ready50ms <- Async.waitFor asyncHandle 50
    if ready50ms
      then throwIO (userError "Infinite loop unexpectedly finished within 50ms!")
      else putStrLn "    Timeout verified: guest function still running after 50ms"
    -- Cancel the runaway execution
    putStrLn "    Sending cancel signal..."
    Async.cancel asyncHandle
    -- Wait for background thread to cleanly exit
    Async.wait asyncHandle
    putStrLn "    Background thread joined successfully after cancellation"
    pure True

  -- 3. Concurrent Async Runs: 10 parallel VMs running async tasks
  putStrLn "--> [3/3] 10 concurrent async executions across independent VMs..."
  let parallelRuns = 10 :: Int
  results <- forConcurrently [1 .. parallelRuns] $ \i -> do
    withVM Nothing Nothing $ \vm -> do
      _ <- loadWasmBytes vm fibWasm
      _ <- validate vm
      _ <- instantiate vm
      let n = 15 + fromIntegral (i `mod` 5)
      asyncH <- asyncExecute vm "fib" [WasmI32 n]
      res <- Async.getReturns asyncH
      case res of
        Right [WasmI32 v] -> pure v
        _ -> throwIO (userError "Parallel async failed")
  putStrLn $
    printf "    Completed %d parallel async executions: all returned valid results" (length results)

  endTotal <- getTime Monotonic
  let totalSec = fromIntegral (toNanoSecs (diffTimeSpec endTotal startTotal)) / 1e9 :: Double
  printf "==> Async & Cancellation Suite PASSED in %.3f s\n\n" totalSec

  pure $
    AsyncMetrics
      { amNormalDurationSec = normalSec
      , amCancelClean = cancelSuccess
      , amConcurrentRuns = parallelRuns
      , amTotalDurationSec = totalSec
      }