From ee3c4129337c6010e95e881e01329fbce57cd80e Mon Sep 17 00:00:00 2001 From: Snarling <84951833+Snarling@users.noreply.github.com> Date: Tue, 28 Feb 2023 06:54:01 -0500 Subject: [PATCH] SETTINGS: Add infinite loop detection setting (#402) --- src/GameOptions/ui/SystemPage.tsx | 11 +++++++++++ src/Netscript/NetscriptHelpers.ts | 21 +++++++++++++++++++++ src/Settings/Settings.ts | 2 ++ 3 files changed, 34 insertions(+) diff --git a/src/GameOptions/ui/SystemPage.tsx b/src/GameOptions/ui/SystemPage.tsx index d36e9936a..9f4b9ebbc 100644 --- a/src/GameOptions/ui/SystemPage.tsx +++ b/src/GameOptions/ui/SystemPage.tsx @@ -140,6 +140,17 @@ export const SystemPage = (): React.ReactElement => { marks /> + (Settings.infiniteLoopDetection = newValue)} + text="Infinite loop detection" + tooltip={ + <> + If this is set, scripts can generate an execution timeout error when an ns function is called and the script + has been running synchronous operations for at least 7s. + + } + /> (Settings.SuppressSavedGameToast = newValue)} diff --git a/src/Netscript/NetscriptHelpers.ts b/src/Netscript/NetscriptHelpers.ts index 9f94fb3ed..6104f0951 100644 --- a/src/Netscript/NetscriptHelpers.ts +++ b/src/Netscript/NetscriptHelpers.ts @@ -33,6 +33,7 @@ import { BaseServer } from "../Server/BaseServer"; import { dialogBoxCreate } from "../ui/React/DialogBox"; import { checkEnum } from "../utils/helpers/enum"; import { RamCostConstants } from "./RamCostGenerator"; +import { Settings } from "../Settings/Settings"; export const helpers = { string, @@ -303,6 +304,15 @@ function checkSingularityAccess(ctx: NetscriptContext): void { } } +/** The last time the page was able to update this tracking variable, which is used for timeout detection */ +let lastTime = Date.now(); +setInterval(() => (lastTime = Date.now()), 1000); +// This event should prevent false positive timeout errors when user alt-tabs / otherwise hides the window +document.addEventListener( + "visibilitychange", + () => (lastTime = document.visibilityState === "hidden" ? Infinity : Date.now()), +); + /** Create an error if a script is dead or if concurrent ns function calls are made */ function checkEnvFlags(ctx: NetscriptContext): void { const ws = ctx.workerScript; @@ -323,6 +333,17 @@ function checkEnvFlags(ctx: NetscriptContext): void { "CONCURRENCY", ); } + // 8s allows scripts at least 7s of synchronous execution time before error, since updates are every 1s. + if (Settings.infiniteLoopDetection && Date.now() - 8000 > lastTime) { + // Prevent error getting piggybacked to another script due to a stale timer. + lastTime = Date.now(); + + throw makeRuntimeErrorMsg( + ctx, + "Possible infinite loop detected while running function.\nThis error is enabled by Options -> System -> Infinite loop detection.", + "EXECUTION TIMEOUT", + ); + } } /** Set a timeout for performing a task, mark the script as busy in the meantime. */ diff --git a/src/Settings/Settings.ts b/src/Settings/Settings.ts index 693db595a..0208f55c3 100644 --- a/src/Settings/Settings.ts +++ b/src/Settings/Settings.ts @@ -102,6 +102,8 @@ export const Settings = { useEngineeringNotation: false, /** Whether to disable suffixes and always use exponential form (scientific or engineering). */ disableSuffixes: false, + /** Whether to check for execution timeout errors */ + infiniteLoopDetection: true, load(saveString: string) { const save = JSON.parse(saveString);