diff --git a/hogvm/python/cli.py b/hogvm/python/cli.py index f18c316ce61c9..9a7df552edf76 100644 --- a/hogvm/python/cli.py +++ b/hogvm/python/cli.py @@ -1,3 +1,4 @@ +from datetime import timedelta import sys import json from .execute import execute_bytecode @@ -18,6 +19,6 @@ code = file.read() code = json.loads(code) -response = execute_bytecode(code, globals=None, timeout=5, team=None, debug=debug) +response = execute_bytecode(code, globals=None, timeout=timedelta(seconds=5), team=None, debug=debug) for line in response.stdout: print(line) # noqa: T201 diff --git a/hogvm/python/execute.py b/hogvm/python/execute.py index 2a17447c9bd62..357bfb7e2893e 100644 --- a/hogvm/python/execute.py +++ b/hogvm/python/execute.py @@ -1,3 +1,4 @@ +from datetime import timedelta import re import time from copy import deepcopy @@ -26,7 +27,7 @@ def execute_bytecode( bytecode: list[Any], globals: Optional[dict[str, Any]] = None, functions: Optional[dict[str, Callable[..., Any]]] = None, - timeout=5, + timeout=timedelta(seconds=5), team: Optional["Team"] = None, debug=False, ) -> BytecodeResult: @@ -60,8 +61,8 @@ def pop_stack(): return BytecodeResult(result=None, stdout=stdout, bytecode=bytecode) def check_timeout(): - if time.time() - start_time > timeout and not debug: - raise HogVMException(f"Execution timed out after {timeout} seconds. Performed {ops} ops.") + if time.time() - start_time > timeout.seconds and not debug: + raise HogVMException(f"Execution timed out after {timeout.seconds} seconds. Performed {ops} ops.") while True: ops += 1 diff --git a/hogvm/typescript/src/execute.ts b/hogvm/typescript/src/execute.ts index 4101d64f69d1b..865f57408a713 100644 --- a/hogvm/typescript/src/execute.ts +++ b/hogvm/typescript/src/execute.ts @@ -3,7 +3,7 @@ import { ASYNC_STL, STL } from './stl/stl' import { convertHogToJS, convertJSToHog, getNestedValue, like, setNestedValue } from './utils' const DEFAULT_MAX_ASYNC_STEPS = 100 -const DEFAULT_TIMEOUT = 5 // seconds +const DEFAULT_TIMEOUT_MS = 5000 // ms export interface VMState { /** Bytecode running in the VM */ @@ -25,10 +25,13 @@ export interface VMState { } export interface ExecOptions { + /** Global variables to be passed into the function */ globals?: Record functions?: Record any> asyncFunctions?: Record Promise> + /** Timeout in milliseconds */ timeout?: number + /** Max number of async function that can happen. When reached the function will throw */ maxAsyncSteps?: number } @@ -66,7 +69,7 @@ export async function execAsync(bytecode: any[], options?: ExecOptions): Promise const result = await ASYNC_STL[response.asyncFunctionName]( response.asyncFunctionArgs, response.asyncFunctionName, - options?.timeout ?? DEFAULT_TIMEOUT + options?.timeout ?? DEFAULT_TIMEOUT_MS ) vmState.stack.push(result) } else { @@ -105,7 +108,7 @@ export function exec(code: any[] | VMState, options?: ExecOptions): ExecResult { const declaredFunctions: Record = vmState ? vmState.declaredFunctions : {} let ip = vmState ? vmState.ip : 1 let ops = vmState ? vmState.ops : 0 - const timeout = options?.timeout ?? DEFAULT_TIMEOUT + const timeout = options?.timeout ?? DEFAULT_TIMEOUT_MS const maxAsyncSteps = options?.maxAsyncSteps ?? DEFAULT_MAX_ASYNC_STEPS function popStack(): any { @@ -122,8 +125,8 @@ export function exec(code: any[] | VMState, options?: ExecOptions): ExecResult { return bytecode![++ip] } function checkTimeout(): void { - if (syncDuration + Date.now() - startTime > timeout * 1000) { - throw new Error(`Execution timed out after ${timeout} seconds. Performed ${ops} ops.`) + if (syncDuration + Date.now() - startTime > timeout) { + throw new Error(`Execution timed out after ${timeout / 1000} seconds. Performed ${ops} ops.`) } } diff --git a/posthog/hogql/bytecode.py b/posthog/hogql/bytecode.py index 1a5933a88bc92..48ddcd2f7e283 100644 --- a/posthog/hogql/bytecode.py +++ b/posthog/hogql/bytecode.py @@ -1,4 +1,5 @@ import dataclasses +from datetime import timedelta from typing import Any, Optional, cast, TYPE_CHECKING from collections.abc import Callable @@ -388,7 +389,7 @@ def execute_hog( team: Optional["Team"] = None, globals: Optional[dict[str, Any]] = None, functions: Optional[dict[str, Callable[..., Any]]] = None, - timeout=10, + timeout=timedelta(seconds=10), ) -> BytecodeResult: source_code = source_code.strip() if source_code.count("\n") == 0: