Skip to content

fix(scripting): throw error for function references - #3502

Draft
D4isDAVID wants to merge 1 commit into
citizenfx:masterfrom
D4isDAVID:fix-pcall-function-references
Draft

D4isDAVID wants to merge 1 commit into
citizenfx:masterfrom
D4isDAVID:fix-pcall-function-references

Conversation

@D4isDAVID

@D4isDAVID D4isDAVID commented Jun 30, 2025 •

Copy link
Copy Markdown
Contributor

Goal of this PR

Return the result error when function references throw them.
This both fixes pcall not being able to catch and prevent errors from being printed, but also improves the error message(s) when the function reference isn't wrapped in a pcall.

Before & After

Lua
Resource a (callee)
exports('runLua', function(func)
    print('Run func (lua)')
    func()
    print('Post run func')
end)
Resource b (caller)
local function func()
    print('Running (lua)')
    error('oh no!')
end

RegisterCommand('test_luatolua', function()
    exports['a']:runLua(func)
end)
Before
cfx> test_luatolua
[            script:a] Run func (lua)
[            script:b] Running (lua)
[            script:b] SCRIPT ERROR: @b/server.lua:3: oh no!
[            script:b] > ref (@b/server.lua:3)
[            script:b] > ref (@a/server.lua:3)
[            script:b] > ref (@b/server.lua:35)
[            script:a] SCRIPT ERROR:
[            script:a] > ref (@b/server.lua:35)
[            script:b] SCRIPT ERROR: @b/server.lua:35:
[            script:b]  An error occurred while calling export `runLua` in resource `a`:
[            script:b]   nil
[            script:b]  ---
After
cfx> test_luatolua
[            script:a] Run func (lua)
[            script:b] Running (lua)
[    c-scripting-core] SCRIPT ERROR: @b/server.lua:35:
[    c-scripting-core]  An error occurred while calling export `runLua` in resource `a`:
[    c-scripting-core]   SCRIPT ERROR: @a/server.lua:3: SCRIPT ERROR: @b/server.lua:3: oh no!
[    c-scripting-core]   > ref (@b/server.lua:3)
[    c-scripting-core]   > ref (@a/server.lua:3)
[    c-scripting-core]   > ref (@b/server.lua:35)
[    c-scripting-core]
[    c-scripting-core]   > ref (@b/server.lua:35)
[    c-scripting-core]
[    c-scripting-core]  ---
[    c-scripting-core]
Lua (with pcall)
Resource a (callee)
exports('tryRunLua', function(func)
    print('Try run func (lua)')
    local success, result = pcall(func)

    if success then
        print('Post try run func')
    else
        print('Caught: ' .. tostring(result));
    end
end)
Resource b (caller)
local function func()
    print('Running (lua)')
    error('oh no!')
end

RegisterCommand('test_luatolua_try', function()
    exports['a']:tryRunLua(func)
end)
Before
cfx> test_luatolua_try
[            script:a] Try run func (lua)
[            script:b] Running (lua)
[            script:b] SCRIPT ERROR: @b/server.lua:3: oh no!
[            script:b] > ref (@b/server.lua:3)
[            script:b] > ref (@a/server.lua:9)
[            script:b] > ref (@b/server.lua:31)
[            script:a] Caught: nil
After
cfx> test_luatolua_try
[            script:a] Try run func (lua)
[            script:b] Running (lua)
[            script:a] Try run func (lua)
[            script:b] Running (lua)
[            script:a] Caught: SCRIPT ERROR: @b/server.lua:3: oh no!
[            script:a] > ref (@b/server.lua:3)
[            script:a] > ref (@a/server.lua:9)
[            script:a] > ref (@b/server.lua:31)
[            script:a]
JavaScript
Resource a (callee)
exports('runJs', (func) => {
    console.log('Run func (js)');
    func();
    console.log('Post run func');
})
Resource b (caller)
function func() {
    console.log('Running (js)');
    throw new Error('oh no!');
}

RegisterCommand('test_jstojs', () => {
    exports['a'].runJs(func);
});
Before
cfx> test_jstojs
[            script:a] Run func (js)
[            script:b] Running (js)
[            script:b] SCRIPT ERROR in call ref: Error: oh no!
[            script:b] > Object.func [as callback] (@b/server.js:3)
[            script:b] > <unknown> (@b/citizen:/scripting/v8/main.js:184)
[            script:b] > new StackDumpError (@b/citizen:/scripting/v8/main.js:416)
[            script:b] > <unknown> (@b/citizen:/scripting/v8/main.js:436)
[            script:b] > global.FormatStackTrace (@b/citizen:/scripting/v8/main.js:15)
[            script:b] > getError (@b/citizen:/scripting/v8/main.js:422)
... (stack)
[            script:b]
[            script:a]
[            script:b]
After
cfx> test_jstojs
[            script:a] Run func (js)
[            script:b] Running (js)
[            script:b] Error: Error: SCRIPT ERROR in call ref: Error: oh no!
[            script:b] > Object.func [as callback] (@b/server.js:3)
[            script:b] > <unknown> (@b/citizen:/scripting/v8/main.js:191)
[            script:b] > new StackDumpError (@b/citizen:/scripting/v8/main.js:423)
[            script:b] > <unknown> (@b/citizen:/scripting/v8/main.js:443)
[            script:b] > global.FormatStackTrace (@b/citizen:/scripting/v8/main.js:15)
[            script:b] > getError (@b/citizen:/scripting/v8/main.js:429)
... (stack)
[            script:b]
[    c-scripting-core] Error: An error occurred while calling export runJs of resource a - see above for details
JavaScript (with try/catch)
Resource a (callee)
exports('tryRunJs', (func) => {
    try {
        console.log('Try run func (js)');
        func();
        console.log('Post try run func');
    } catch (err) {
        console.log('Caught: ' + err.toString());
    }
})
Resource b (caller)
function func() {
    console.log('Running (js)');
    throw new Error('oh no!');
}

RegisterCommand('test_jstojs_try', () => {
    exports['a'].tryRunJs(func);
});
Before
cfx> test_jstojs_try
[            script:a] Try run func (js)
[            script:b] Running (js)
[            script:b] SCRIPT ERROR in call ref: Error: oh no!
[            script:b] > Object.func [as callback] (@b/server.js:3)
[            script:b] > <unknown> (@b/citizen:/scripting/v8/main.js:184)
[            script:b] > new StackDumpError (@b/citizen:/scripting/v8/main.js:416)
[            script:b] > <unknown> (@b/citizen:/scripting/v8/main.js:436)
[            script:b] > global.FormatStackTrace (@b/citizen:/scripting/v8/main.js:15)
[            script:b] > getError (@b/citizen:/scripting/v8/main.js:422)
... (stack)
[            script:b]
[            script:a] Caught: Error: Error in nested ref call for a. a tried to call a function reference in a but the reference wasn't valid. (did a restart recently?)
After
cfx> test_jstojs_try
[            script:a] Try run func (js)
[            script:b] Running (js)
[            script:a] Caught: Error: SCRIPT ERROR in call ref: Error: oh no!
[            script:a] > Object.func [as callback] (@b/server.js:3)
[            script:a] > <unknown> (@b/citizen:/scripting/v8/main.js:191)
[            script:a] > new StackDumpError (@b/citizen:/scripting/v8/main.js:423)
[            script:a] > <unknown> (@b/citizen:/scripting/v8/main.js:443)
[            script:a] > global.FormatStackTrace (@b/citizen:/scripting/v8/main.js:15)
[            script:a] > getError (@b/citizen:/scripting/v8/main.js:429)
... (stack)
[            script:a]
C#
Resource a (callee)
using System;
using CitizenFX.Core;

namespace a.Server
{
    public class Server : BaseScript
    {
        public Server()
        {
            Exports.Add("runCs", new Action<CallbackDelegate>(Run));
        }

        private void Run(CallbackDelegate func)
        {
            Debug.WriteLine("Run func (cs)");
            func();
            Debug.WriteLine("Post run func");
        }
    }
}
Resource b (caller)
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CitizenFX.Core;

namespace b.Server
{
    public class Server : BaseScript
    {
        public Server()
        {}

        private void Func()
        {
            Debug.WriteLine("Running (cs)");
            throw new Exception("oh no!");
        }

        [Command("test_cstocs")]
        public async Task TestCsToCs(int source, List<object> args, string rawCommand)
        {
            Exports["a"].runCs(new Action(Func));
        }
    }
}
Before
cfx> test_cstocs
[            script:a] Run func (cs)
[            script:b] Running (cs)
[            script:b] SCRIPT ERROR in reference call: System.Exception: oh no!
[            script:b] > void b.Server.ServerMain.Func() (@b/b.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:16)
[            script:b] > void a.Server.Server.Run(CallbackDelegate func) (@a/a.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\a\mono\Server.cs:19)
[            script:b] > object <Module>.runtime_invoke_void__this___object(object this, IntPtr params, IntPtr exc, IntPtr method) (@a/a.Server.net.dll:0)
[            script:b] > void a.Server.Server.Run(CallbackDelegate func) (@a/a.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\a\mono\Server.cs:19)
[            script:b] > object <Module>.runtime_invoke_void__this___object(object this, IntPtr params, IntPtr exc, IntPtr method) (@a/a.Server.net.dll:0)
[            script:b] > async Task b.Server.ServerMain.TestCsToCs(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:40)
[            script:b] > Task b.Server.ServerMain.TestCsToCs(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll:0)
[            script:b] > object <Module>.runtime_invoke_object__this___int_object_object(object this, IntPtr params, IntPtr exc, IntPtr method) (@b/b.Server.net.dll:0)
[            script:b] > async Task b.Server.ServerMain.TestCsToCs(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:40)
[            script:b] > Task b.Server.ServerMain.TestCsToCs(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll:0)
[            script:b] > object <Module>.runtime_invoke_object__this___int_object_object(object this, IntPtr params, IntPtr exc, IntPtr method) (@b/b.Server.net.dll:0)
[            script:b]
[            script:b] Unhandled task exception:
[            script:b] System.IO.EndOfStreamException: IO.EOF_ReadBeyondEOF
[            script:b]   at System.IO.BinaryReader.ReadByte () [0x00018] in <fbc4ec45371543bfba3678ebb82caf6d>:0
[            script:b]   at CitizenFX.Core.MsgPackDeserializer.UnpackAny (System.IO.BinaryReader reader) [0x00000] in C:\gl\builds\cfx-fivem-3\code\client\clrcore\MsgPackDeserializer.cs:29
[            script:b]   at CitizenFX.Core.MsgPackDeserializer.Deserialize (System.Byte[] data, System.String netSource) [0x00018] in C:\gl\builds\cfx-fivem-3\code\client\clrcore\MsgPackDeserializer.cs:24
[            script:b]   at CitizenFX.Core.MsgPackDeserializer+<>c__DisplayClass36_0.<CreateRemoteFunctionReference>b__0 (System.Object[] args) [0x00013] in C:\gl\builds\cfx-fivem-3\code\client\clrcore\MsgPackDeserializer.cs:269
[            script:b]   at (wrapper dynamic-method) System.Object.CallSite.Target(System.Runtime.CompilerServices.Closure,System.Runtime.CompilerServices.CallSite,object,object[])
[            script:b]   at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet] (System.Runtime.CompilerServices.CallSite site, T0 arg0, T1 arg1) [0x0011d] in <cf8f429092f345a6bd742c3476e00e06>:0
[            script:b]   at CitizenFX.Core.ExportSet.TryInvokeMember (System.Dynamic.InvokeMemberBinder binder, System.Object[] args, System.Object& result) [0x000f1] in C:\gl\builds\cfx-fivem-3\code\client\clrcore\ExportDictionary.cs:68
[            script:b]   at (wrapper dynamic-method) System.Object.CallSite.Target(System.Runtime.CompilerServices.Closure,System.Runtime.CompilerServices.CallSite,object,System.Action)
[            script:b]   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1] (System.Runtime.CompilerServices.CallSite site, T0 arg0, T1 arg1) [0x00113] in <cf8f429092f345a6bd742c3476e00e06>:0
[            script:b]   at b.Server.ServerMain.TestCsToCs (System.Int32 source, System.Collections.Generic.List`1[T] args, System.String rawCommand) [0x00007] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:40
After
cfx> test_cstocs
[            script:a] Run func (cs)
[            script:b] Running (cs)
[            script:b] Unhandled task exception:
[            script:b] System.Exception: Exception_WasThrown
[            script:b]   at CitizenFX.Core.MsgPackDeserializer+<>c__DisplayClass36_0.<CreateRemoteFunctionReference>b__0 (System.Object[] args) [0x0006b] in D:\Projects\Coding\FiveM\citizenfx\fivem\code\client\clrcore\MsgPackDeserializer.cs:280
[            script:b]   at (wrapper dynamic-method) System.Object.CallSite.Target(System.Runtime.CompilerServices.Closure,System.Runtime.CompilerServices.CallSite,object,object[])
[            script:b]   at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet] (System.Runtime.CompilerServices.CallSite site, T0 arg0, T1 arg1) [0x0011d] in <cf8f429092f345a6bd742c3476e00e06>:0
[            script:b]   at CitizenFX.Core.ExportSet.TryInvokeMember (System.Dynamic.InvokeMemberBinder binder, System.Object[] args, System.Object& result) [0x000fe] in D:\Projects\Coding\FiveM\citizenfx\fivem\code\client\clrcore\ExportDictionary.cs:68
[            script:b]   at (wrapper dynamic-method) System.Object.CallSite.Target(System.Runtime.CompilerServices.Closure,System.Runtime.CompilerServices.CallSite,object,System.Action)
[            script:b]   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1] (System.Runtime.CompilerServices.CallSite site, T0 arg0, T1 arg1) [0x00113] in <cf8f429092f345a6bd742c3476e00e06>:0
[            script:b]   at b.Server.ServerMain.TestCsToCs (System.Int32 source, System.Collections.Generic.List`1[T] args, System.String rawCommand) [0x00007] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:40
C# (with try/catch)
Resource a (callee)
using System;
using CitizenFX.Core;

namespace a.Server
{
    public class Server : BaseScript
    {
        public Server()
        {
            Exports.Add("tryRunCs", new Action<CallbackDelegate>(TryRun));
        }

        private void TryRun(CallbackDelegate func)
        {
            try {
                Debug.WriteLine("Try run func (cs)");
                func();
                Debug.WriteLine("Post try run func");
            } catch (Exception e) {
                Debug.WriteLine("Caught: " + e.ToString());
            }
        }
    }
}
Resource b (caller)
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CitizenFX.Core;

namespace b.Server
{
    public class Server : BaseScript
    {
        public Server()
        {}

        private void Func()
        {
            Debug.WriteLine("Running (cs)");
            throw new Exception("oh no!");
        }

        [Command("test_cstocs_try")]
        public async Task TestCsToCsTry(int source, List<object> args, string rawCommand)
        {
            Exports["a"].tryRunCs(new Action(Func));
        }
    }
}
Before

(!) Post run func appears in this log meaning the function reference error doesn't stop the rest of the code.

cfx> test_cstocs_try
[            script:a] Try run func (cs)
[            script:b] Running (cs)
[            script:b] SCRIPT ERROR in reference call: System.Exception: oh no!
[            script:b] > void b.Server.ServerMain.Func() (@b/b.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:16)
[            script:b] > void a.Server.Server.TryRun(CallbackDelegate func) (@a/a.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\a\mono\Server.cs:27)
[            script:b] > object <Module>.runtime_invoke_void__this___object(object this, IntPtr params, IntPtr exc, IntPtr method) (@a/a.Server.net.dll:0)
[            script:b] > void a.Server.Server.TryRun(CallbackDelegate func) (@a/a.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\a\mono\Server.cs:27)
[            script:b] > object <Module>.runtime_invoke_void__this___object(object this, IntPtr params, IntPtr exc, IntPtr method) (@a/a.Server.net.dll:0)
[            script:b] > async Task b.Server.ServerMain.TestCsToCsTry(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:34)
[            script:b] > Task b.Server.ServerMain.TestCsToCsTry(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll:0)
[            script:b] > object <Module>.runtime_invoke_object__this___int_object_object(object this, IntPtr params, IntPtr exc, IntPtr method) (@b/b.Server.net.dll:0)
[            script:b] > async Task b.Server.ServerMain.TestCsToCsTry(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:34)
[            script:b] > Task b.Server.ServerMain.TestCsToCsTry(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll:0)
[            script:b] > object <Module>.runtime_invoke_object__this___int_object_object(object this, IntPtr params, IntPtr exc, IntPtr method) (@b/b.Server.net.dll:0)
[            script:b]
[            script:a] Caught: System.IO.EndOfStreamException: IO.EOF_ReadBeyondEOF
[            script:a]   at System.IO.BinaryReader.ReadByte () [0x00018] in <fbc4ec45371543bfba3678ebb82caf6d>:0
[            script:a]   at CitizenFX.Core.MsgPackDeserializer.UnpackAny (System.IO.BinaryReader reader) [0x00000] in C:\gl\builds\cfx-fivem-3\code\client\clrcore\MsgPackDeserializer.cs:29
[            script:a]   at CitizenFX.Core.MsgPackDeserializer.Deserialize (System.Byte[] data, System.String netSource) [0x00018] in C:\gl\builds\cfx-fivem-3\code\client\clrcore\MsgPackDeserializer.cs:24
[            script:a]   at CitizenFX.Core.MsgPackDeserializer+<>c__DisplayClass36_0.<CreateRemoteFunctionReference>b__0 (System.Object[] args) [0x00013] in C:\gl\builds\cfx-fivem-3\code\client\clrcore\MsgPackDeserializer.cs:269
[            script:a]   at a.Server.Server.TryRun (CitizenFX.Core.CallbackDelegate func) [0x0000a] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\a\mono\Server.cs:27
After
cfx> test_cstocs_try
[            script:a] Try run func (cs)
[            script:b] Running (cs)
[            script:a] Caught: System.Exception: > void b.Server.ServerMain.Func() (@b/b.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:16)
[            script:a] > void a.Server.Server.TryRun(CallbackDelegate func) (@a/a.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\a\mono\Server.cs:27)
[            script:a] > object <Module>.runtime_invoke_void__this___object(object this, IntPtr params, IntPtr exc, IntPtr method) (@a/a.Server.net.dll:0)
[            script:a] > void a.Server.Server.TryRun(CallbackDelegate func) (@a/a.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\a\mono\Server.cs:27)
[            script:a] > object <Module>.runtime_invoke_void__this___object(object this, IntPtr params, IntPtr exc, IntPtr method) (@a/a.Server.net.dll:0)
[            script:a] > async Task b.Server.ServerMain.TestCsToCsTry(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:34)
[            script:a] > Task b.Server.ServerMain.TestCsToCsTry(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll:0)
[            script:a] > object <Module>.runtime_invoke_object__this___int_object_object(object this, IntPtr params, IntPtr exc, IntPtr method) (@b/b.Server.net.dll:0)
[            script:a] > async Task b.Server.ServerMain.TestCsToCsTry(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll <- D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\b\mono\ServerMain.cs:34)
[            script:a] > Task b.Server.ServerMain.TestCsToCsTry(int source, List<object> args, string rawCommand) (@b/b.Server.net.dll:0)
[            script:a] > object <Module>.runtime_invoke_object__this___int_object_object(object this, IntPtr params, IntPtr exc, IntPtr method) (@b/b.Server.net.dll:0)
[            script:a]
[            script:a]   at CitizenFX.Core.MsgPackDeserializer+<>c__DisplayClass36_0.<CreateRemoteFunctionReference>b__0 (System.Object[] args) [0x0006b] in D:\Projects\Coding\FiveM\citizenfx\fivem\code\client\clrcore\MsgPackDeserializer.cs:280
[            script:a]   at a.Server.Server.TryRun (CitizenFX.Core.CallbackDelegate func) [0x0000a] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\a\mono\Server.cs:27
Mono v2
Resource av2 (callee)
using CitizenFX.Core;

namespace av2.Server
{
    public class Server : BaseScript
    {
        public Server()
        {}

        [Export("runCs2")]
        private async Coroutine Run(Callback func)
        {
            Debug.WriteLine("Run func (cs2)");
            await func();
            Debug.WriteLine("Post run func");
        }
    }
}
Resource bv2 (caller)
using CitizenFX.Core;

namespace bv2.Server
{
    public class Server : BaseScript
    {
        public Server()
        {}

        private void Funcc()
        {
            Debug.WriteLine("Running (cs2)");
            throw new Exception("oh no!");
        }

        [Command("test_cs2tocs2")]
        public async Coroutine TestCs2ToCs2(int source, object[] args, string rawCommand)
        {
            await Exports["av2", "runCs2"](new Action(Funcc));
        }
    }
}
Before

(!) Post run func appears in this log meaning the function reference error isn't thrown up to the callee.

cfx> test_cs2tocs2
[          script:av2] Run func (cs2)
[          script:bv2] Running (cs2)
[          script:bv2] SCRIPT ERROR: Could not invoke reference function bv2.Server.Server.Funcc
[          script:bv2] Failed with exception:
[          script:bv2] System.Exception: oh no!
[          script:bv2]   at bv2.Server.Server.Funcc () [0x0000a] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\bv2\monov2\Server.cs:24
[          script:bv2]   at (wrapper dynamic-method) System.Object.bv2.Server.Server.Funcc(object,CitizenFX.Core.Remote,object[])
[          script:bv2]   at CitizenFX.Core.ReferenceFunctionManager.Invoke (System.Int32 reference, System.Byte* arguments, System.UInt32 argsSize) [0x00024] in C:\gl\builds\cfx-fivem-3\code\client\clrcore-v2\Interop\ReferenceFunctionManager.cs:147
[          script:av2] Post run func
After
cfx> test_cs2tocs2
[          script:av2] Run func (cs2)
[          script:bv2] Running (cs2)
[    c-scripting-core] System.Exception: System.Exception: SCRIPT ERROR in reference function: System.Exception: oh no!
[    c-scripting-core]   at bv2.Server.Server.Funcc () [0x0000a] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\bv2\monov2\Server.cs:24
[    c-scripting-core]   at (wrapper dynamic-method) System.Object.bv2.Server.Server.Funcc(object,CitizenFX.Core.Remote,object[])
[    c-scripting-core]   at CitizenFX.Core.ReferenceFunctionManager.Invoke (System.Int32 reference, System.Byte* arguments, System.UInt32 argsSize) [0x0002a] in D:\Projects\Coding\FiveM\citizenfx\fivem\code\client\clrcore-v2\Interop\ReferenceFunctionManager.cs:147
[    c-scripting-core]   at CitizenFX.Core._LocalFunction.Invoke (System.Object[] args) [0x0005d] in D:\Projects\Coding\FiveM\citizenfx\fivem\code\client\clrcore-v2\Interop\LocalFunction.cs:58
[    c-scripting-core]   at av2.Server.Server.Run (CitizenFX.Core.Callback func) [0x00014] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\av2\monov2\Server.cs:16
[    c-scripting-core]   at CitizenFX.Core._LocalFunction.Invoke (System.Object[] args) [0x0005d] in D:\Projects\Coding\FiveM\citizenfx\fivem\code\client\clrcore-v2\Interop\LocalFunction.cs:58
[    c-scripting-core]   at CitizenFX.Core.ExportsManager.LocalInvoke (CitizenFX.Core.CString fullExportName, System.Object[] args) [0x0002a] in D:\Projects\Coding\FiveM\citizenfx\fivem\code\client\clrcore-v2\Interop\ExportsManager.cs:143
[    c-scripting-core]   at CitizenFX.Core.Exports+<>c__DisplayClass9_0.<get_Item>b__0 (System.Object[] args) [0x00000] in D:\Projects\Coding\FiveM\citizenfx\fivem\code\client\clrcore-v2\Interop\Exports.cs:57
[    c-scripting-core]   at bv2.Server.Server.TestCs2ToCs2 (System.Int32 source, System.Object[] args, System.String rawCommand) [0x00011] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\bv2\monov2\Server.cs:36
Mono v2 (with try/catch)
Resource av2 (callee)
using CitizenFX.Core;

namespace av2.Server
{
    public class Server : BaseScript
    {
        public Server()
        {}

        [Export("tryRunCs2")]
        private async Coroutine TryRun(Callback func)
        {
            try {
                Debug.WriteLine("Try run func (cs2)");
                await func();
                Debug.WriteLine("Post try run func");
            } catch (Exception e) {
                Debug.WriteLine("Caught: " + e.ToString());
            }
        }
    }
}
Resource bv2 (caller)
using CitizenFX.Core;

namespace bv2.Server
{
    public class Server : BaseScript
    {
        public Server()
        {}

        private void Funcc()
        {
            Debug.WriteLine("Running (cs2)");
            throw new Exception("oh no!");
        }

        [Command("test_cs2tocs2_try")]
        public async Coroutine TestCs2ToCs2Try(int source, object[] args, string rawCommand)
        {
            await Exports["av2", "tryRunCs2"](new Action(Funcc));
        }
    }
}
Before

(!) Post try run func appears in this log meaning the function reference error isn't thrown up to the callee.

cfx> test_cs2tocs2_try
[          script:av2] Try run func (cs2)
[          script:bv2] Running (cs2)
[          script:bv2] SCRIPT ERROR: Could not invoke reference function bv2.Server.Server.Funcc
[          script:bv2] Failed with exception:
[          script:bv2] System.Exception: oh no!
[          script:bv2]   at bv2.Server.Server.Funcc () [0x0000a] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\bv2\monov2\Server.cs:24
[          script:bv2]   at (wrapper dynamic-method) System.Object.bv2.Server.Server.Funcc(object,CitizenFX.Core.Remote,object[])
[          script:bv2]   at CitizenFX.Core.ReferenceFunctionManager.Invoke (System.Int32 reference, System.Byte* arguments, System.UInt32 argsSize) [0x00024] in C:\gl\builds\cfx-fivem-3\code\client\clrcore-v2\Interop\ReferenceFunctionManager.cs:147
[          script:av2] Post try run func
After
cfx> test_cs2tocs2_try
[          script:av2] Try run func (cs2)
[          script:bv2] Running (cs2)
[          script:av2] Caught: System.Exception: SCRIPT ERROR in reference function: System.Exception: oh no!
[          script:av2]   at bv2.Server.Server.Funcc () [0x0000a] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\bv2\monov2\Server.cs:24
[          script:av2]   at (wrapper dynamic-method) System.Object.bv2.Server.Server.Funcc(object,CitizenFX.Core.Remote,object[])
[          script:av2]   at CitizenFX.Core.ReferenceFunctionManager.Invoke (System.Int32 reference, System.Byte* arguments, System.UInt32 argsSize) [0x0002a] in D:\Projects\Coding\FiveM\citizenfx\fivem\code\client\clrcore-v2\Interop\ReferenceFunctionManager.cs:147
[          script:av2]   at CitizenFX.Core._LocalFunction.Invoke (System.Object[] args) [0x0005d] in D:\Projects\Coding\FiveM\citizenfx\fivem\code\client\clrcore-v2\Interop\LocalFunction.cs:58
[          script:av2]   at av2.Server.Server.TryRun (CitizenFX.Core.Callback func) [0x00017] in D:\Servers\Dev\FiveM\cfx-test\resources\[local]\[funcref test]\av2\monov2\Server.cs:25

How is this PR achieving the goal

Error handling for function references is done in the scheduler, so when an error is detected, simply throw it rather than printing it and throwing nil.

This PR applies to the following area(s)

ScRT: Lua, ScRT: C#, ScRT: JS

Successfully tested on

Platforms: Windows

Checklist

  • Code compiles and has been tested successfully.
  • Code explains itself well and/or is documented.
  • My commit message explains what the changes do and what they are for.
  • No extra compilation warnings are added by these changes.

Fixes issues

Fixes #3501

@github-actions github-actions Bot added ScRT: Lua Issues/PRs related to the Lua scripting runtime triage Needs a preliminary assessment to determine the urgency and required action labels Jun 30, 2025
@prikolium-cfx
prikolium-cfx requested a review from iridium-cfx July 1, 2025 06:13
@D4isDAVID

D4isDAVID commented Jul 1, 2025 •

Copy link
Copy Markdown
Contributor Author

Might look into the other ScRTs as the same issue might be there as well. I'll probably open separate PRs for those in that case

@D4isDAVID
D4isDAVID force-pushed the fix-pcall-function-references branch from 107dee1 to 03499c6 Compare July 1, 2025 17:33
@D4isDAVID D4isDAVID changed the title fix(scheduler): return the error for function references fix(scripting/lua): throw error for function references Jul 1, 2025
@iridium-cfx

Copy link
Copy Markdown
Contributor

Will this work correctly when interacting with other languages (JS/C#)?
I think you would also need to update the other languages to support returning the error data in the same way.

@D4isDAVID

Copy link
Copy Markdown
Contributor Author

Yeah, this PR currently only fixes exports created with Lua. If other languages use Lua exports it will be fine, but the other languages still have broken exports. I'm on/off on making a fix for the other languages because the underlying code for those seems a bit weirder than the Lua scheduler.

@iridium-cfx

Copy link
Copy Markdown
Contributor

Yeah, this PR currently only fixes exports created with Lua. If other languages use Lua exports it will be fine, but the other languages still have broken exports. I'm on/off on making a fix for the other languages because the underlying code for those seems a bit weirder than the Lua scheduler.

Are you sure they will work correctly? Because other runtimes will still be expecting the msgpack'd result to only contain the result, not the bool/value pair.

@D4isDAVID

Copy link
Copy Markdown
Contributor Author

Ah, I get what you're saying. I'll check later today, but now that I think about it it probably won't work without the fix being in the other langs.

@D4isDAVID

Copy link
Copy Markdown
Contributor Author

Ok, I think I figured out the fix for JS, next up is C# and then I'll update the PR

@D4isDAVID
D4isDAVID force-pushed the fix-pcall-function-references branch 2 times, most recently from f3999a3 to 1f821dc Compare July 19, 2025 18:02
@D4isDAVID D4isDAVID changed the title fix(scripting/lua): throw error for function references fix(scripting): throw error for function references Jul 22, 2025
@D4isDAVID

Copy link
Copy Markdown
Contributor Author

@iridium-cfx PR has been updated with a fix for the rest of the languages.

@Scorpion7162

Copy link
Copy Markdown

This will help with debugging a lot, big upvote for this 👊

@D4isDAVID D4isDAVID closed this Aug 10, 2025
@D4isDAVID D4isDAVID reopened this Aug 10, 2025
@D4isDAVID

Copy link
Copy Markdown
Contributor Author

Oops....

@D4isDAVID
D4isDAVID marked this pull request as draft January 18, 2026 20:15
@D4isDAVID

Copy link
Copy Markdown
Contributor Author

Converted to draft as this currently only works when the top-level code isn't in a function reference.
Needs more work as it won't show errors in certain contexts such as when running commands.

@D4isDAVID
D4isDAVID force-pushed the fix-pcall-function-references branch 2 times, most recently from e8fc5ee to 2174a5c Compare February 22, 2026 22:00
@D4isDAVID D4isDAVID closed this Nov 8, 2026
@D4isDAVID
D4isDAVID force-pushed the fix-pcall-function-references branch from 2174a5c to 5c3065c Compare November 8, 2026 00:06
@D4isDAVID D4isDAVID reopened this Nov 8, 2026
@D4isDAVID
D4isDAVID force-pushed the fix-pcall-function-references branch from e015583 to f8c1e3a Compare November 11, 2026 23:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ScRT: Lua Issues/PRs related to the Lua scripting runtime triage Needs a preliminary assessment to determine the urgency and required action

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pcall does not catch errors for function references

3 participants