Need help on running user-made code without halting other server scripts

I made an ingame code executor, similiar to an Online Judge, that runs the player’s code, then check if the answer returned from the code is correct.
Problem is if the player’s code never yields, it halts the code executor, along with other server scripts too.

For example, if the player submits this:

x = 0
while true do
  x += 1
end

and I have another server script doing its own thing:

while true do
	task.wait(1)
	print("look! im running! look at me go")
end

the script will never print when the player’s code is running, until the code is stopped by the script timeout.

My code executor (simplified) looks like this:

local RunService = game:GetService("RunService")

local ENV = {
	ipairs = ipairs,
	math = math,
	string = string,
	pairs = pairs,
	table = table,
	tonumber = tonumber,
	tostring = tostring,
}

local CodeExecutor = {}

function CodeExecutor.TestCode(code: string, input: any, answer: any): (string, string)
	local fake, err = loadstring(code)

	local newENV = table.clone(ENV)
	newENV.input = input
	local real, err = setfenv(fake, table.clone(newENV))

	--/\/\/\/\/\/\/\

	local success, judgement, result
	local runningUserCodeThing: thread

	runningUserCodeThing = task.spawn(function()
		success, result = pcall(real)

		if not success then
			judgement = "RE"
		end
	end)

	local timePassed = 0
	while judgement == nil do
		timePassed += RunService.Heartbeat:Wait()

        if result ~= nil then
			--yay it worked
		elseif timePassed >= 1 then
			task.cancel(runningUserCodeThing)
			judgement = "TLE"
		end
	end

	return judgement, if judgement == "RE" or judgement == "PE" then result else nil
end

return CodeExecutor

I have tried to wrap the user code in a task.spawn, then task.cancel it when it has ran for too long, but RunService.Heartbeat:Wait() never fires during running the user code.
How would I fix this?

maybe you could loadstring()?

(please be careful, people can do bad things with this.)

Uhh… I did?

	local fake, err = loadstring(code) --line 16

	local newENV = table.clone(ENV)
	newENV.input = input
	local real, err = setfenv(fake, table.clone(newENV))

The post mentioned vLua though, so I guess I’ll look into it right now

(btw, I know it’s dangerous, but to my knowledge if you limit the script environment it should be fine?)

Have you tried coroutines instead of task.wait?

I assume you meant task.spawn instead, but nope, the halting still occurs.
(how i modified:)

	local runningUserCodeThing: thread

	runningUserCodeThing = coroutine.create(function()
		success, result = pcall(real)

		if not success then
			judgement = "RE"
		end
	end)
	coroutine.resume(runningUserCodeThing)

	local timePassed = 0
	while judgement == nil do
		timePassed += RunService.Heartbeat:Wait()

        if result ~= nil then
			--yay it worked
		elseif timePassed >= 1 then
			coroutine.close(runningUserCodeThing)
			judgement = "TLE"
		end
	end