Skip to content

Commit

Permalink
mono: faster Tick on client by bypassing remoting AppDomain transitio…
Browse files Browse the repository at this point in the history
…ns, fix thread suspension deadlock
  • Loading branch information
blattersturm committed Feb 26, 2019
1 parent 463c946 commit 5575937
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 6 deletions.
10 changes: 10 additions & 0 deletions code/client/clrcore/InternalManager.cs
Expand Up @@ -19,12 +19,17 @@ class InternalManager : MarshalByRefObject, InternalManagerInterface

public static IScriptHost ScriptHost { get; internal set; }

// actually, domain-global
private static InternalManager GlobalManager { get; set; }

[SecuritySafeCritical]
public InternalManager()
{
//CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
//CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;

GlobalManager = this;

Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;

Expand Down Expand Up @@ -160,6 +165,11 @@ public static void AddDelay(int delay, AsyncCallback callback)
ms_delays.Add(Tuple.Create(DateTime.UtcNow.AddMilliseconds(delay), callback));
}

public static void TickGlobal()
{
GlobalManager.Tick();
}

public void Tick()
{
try
Expand Down
87 changes: 87 additions & 0 deletions code/client/clrcore/MonoScriptRuntime.cs
@@ -1,5 +1,8 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Security;

Expand All @@ -16,6 +19,12 @@ class MonoScriptRuntime : IScriptRuntime, IScriptFileHandlingRuntime, IScriptTic

private static readonly Random ms_random = new Random();

[SecuritySafeCritical]
static MonoScriptRuntime()
{

}

public MonoScriptRuntime()
{
m_instanceId = ms_random.Next();
Expand Down Expand Up @@ -136,14 +145,42 @@ public void LoadFile(string scriptFile)
}
}

[SecuritySafeCritical]
public void Tick()
{
using (GetPushRuntime())
{
#if IS_FXSERVER
m_intManager?.Tick();
#else
// we *shouldn't* do .Id here as that's yet another free remoting call
ms_fastTickInDomain.method(m_appDomain);
#endif
}
}

[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string dllToLoad);

[DllImport("kernel32.dll")]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

private static FastMethod<Action<AppDomain>> ms_fastTickInDomain =
#if IS_FXSERVER
null;
#else
BuildFastTick();
#endif

[SecurityCritical]
private unsafe static FastMethod<Action<AppDomain>> BuildFastTick()
{
var lib = LoadLibrary("citizen-scripting-mono.dll");
var fn = GetProcAddress(lib, "GI_TickInDomain");

return new FastMethod<Action<AppDomain>>("TickInDomain", fn);
}

public void TriggerEvent(string eventName, byte[] argsSerialized, int serializedSize, string sourceId)
{
try
Expand Down Expand Up @@ -305,4 +342,54 @@ public override object InitializeLifetimeService()
}
}
}

internal class FastMethod<T> where T : Delegate
{
[SecuritySafeCritical]
public FastMethod(string name, object obj, Type tint, int midx)
{
if (!Marshal.IsComObject(obj))
{
throw new ArgumentException("Not a COM interface.");
}

var castObj = Marshal.GetComInterfaceForObject(obj, tint);
var vtblStart = Marshal.ReadIntPtr(castObj);
vtblStart += (IntPtr.Size * midx) + (IntPtr.Size * 3);

Initialize(name, Marshal.ReadIntPtr(vtblStart));
}

[SecuritySafeCritical]
public FastMethod(string name, IntPtr fn)
{
Initialize(name, fn);
}

[SecurityCritical]
private void Initialize(string name, IntPtr fn)
{
var invokeMethod = typeof(T).GetMethod("Invoke");
var paramTypes = invokeMethod.GetParameters().Select(p => p.ParameterType).ToArray();

m_method = new DynamicMethod(name,
invokeMethod.ReturnType, paramTypes, typeof(MonoScriptRuntime).Module, true);
ILGenerator generator = m_method.GetILGenerator();

for (int i = 0; i < paramTypes.Length; i++)
{
generator.Emit(OpCodes.Ldarg, i);
}

generator.Emit(OpCodes.Ldc_I8, fn.ToInt64());
generator.EmitCalli(OpCodes.Calli, CallingConventions.Standard, invokeMethod.ReturnType, paramTypes, null);
generator.Emit(OpCodes.Ret);

method = (T)m_method.CreateDelegate(typeof(T));
}

private DynamicMethod m_method;

public T method;
}
}
35 changes: 31 additions & 4 deletions code/client/clrcore/PushRuntime.cs
@@ -1,32 +1,59 @@
using System;
using System.Runtime.InteropServices;
using System.Security;

namespace CitizenFX.Core
{
internal class PushRuntime : IDisposable
{
private readonly IScriptRuntime m_runtime;
private readonly IntPtr m_runtime;
private readonly IScriptRuntime m_actualRuntime;
private static IScriptRuntimeHandler ms_runtimeHandler;
private static IntPtr ms_runtimeHandlerIface;

private static FastMethod<Action<IntPtr, IntPtr>> ms_pushMethod;
private static FastMethod<Action<IntPtr, IntPtr>> ms_popMethod;

[SecuritySafeCritical]
public PushRuntime(IScriptRuntime runtime)
{
EnsureRuntimeHandler();

#if !IS_FXSERVER
var comRuntime = Marshal.GetComInterfaceForObject(runtime, typeof(IScriptRuntime));
ms_pushMethod.method(ms_runtimeHandlerIface, comRuntime);

m_runtime = comRuntime;
#else
ms_runtimeHandler.PushRuntime(runtime);

m_runtime = runtime;
m_actualRuntime = runtime;
#endif
}

public void Dispose()
{
ms_runtimeHandler.PopRuntime(m_runtime);
#if IS_FXSERVER
ms_runtimeHandler.PopRuntime(m_actualRuntime);
#else
ms_popMethod.method(ms_runtimeHandlerIface, m_runtime);
#endif
}

[SecuritySafeCritical]
private static void EnsureRuntimeHandler()
{
if (ms_runtimeHandler == null)
{
ms_runtimeHandler = InternalManager.CreateInstance<IScriptRuntimeHandler>(new Guid(0xc41e7194, 0x7556, 0x4c02, 0xba, 0x45, 0xa9, 0xc8, 0x4d, 0x18, 0xad, 0x43));

#if !IS_FXSERVER
ms_pushMethod = new FastMethod<Action<IntPtr, IntPtr>>("PushRuntime", ms_runtimeHandler, typeof(IScriptRuntimeHandler), 0);
ms_popMethod = new FastMethod<Action<IntPtr, IntPtr>>("PopRuntime", ms_runtimeHandler, typeof(IScriptRuntimeHandler), 2);

ms_runtimeHandlerIface = Marshal.GetComInterfaceForObject(ms_runtimeHandler, typeof(IScriptRuntimeHandler));
#endif
}
}
}
}
}
Binary file modified code/components/citizen-scripting-mono/deps/lib/mono-2.0-sgen.lib
Binary file not shown.
61 changes: 61 additions & 0 deletions code/components/citizen-scripting-mono/src/MonoComponentHost.cpp
Expand Up @@ -17,6 +17,12 @@
#include <mono/metadata/assembly.h>
#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/threads.h>

extern "C" {
void mono_thread_push_appdomain_ref(MonoDomain *domain);
void mono_thread_pop_appdomain_ref(void);
}

#include <mono/metadata/exception.h>
#include <mono/metadata/mono-debug.h>
#include <mono/metadata/mono-gc.h>
Expand Down Expand Up @@ -138,7 +144,32 @@ struct _MonoProfiler

MonoProfiler _monoProfiler;

#ifdef _WIN32
// custom heap so we won't end up depending on any suspended threads
// (we need to be safe even if the GC suspended the world)
static HANDLE g_heap = HeapCreate(0, 0, 0);

template<typename T>
struct StaticHeapAllocator
{
using value_type = T;

T* allocate(size_t n)
{
return (T*)HeapAlloc(g_heap, 0, n * sizeof(T));
}

void deallocate(T* p, size_t n)
{
HeapFree(g_heap, 0, p);
}
};

static std::map<MonoDomain*, uint64_t, std::less<>, StaticHeapAllocator<std::pair<MonoDomain* const, uint64_t>>> g_memoryUsages;
#else
static std::map<MonoDomain*, uint64_t> g_memoryUsages;
#endif

static std::shared_mutex g_memoryUsagesMutex;

static bool g_requestedMemoryUsage;
Expand Down Expand Up @@ -182,6 +213,34 @@ static uint64_t GI_GetMemoryUsage()
return g_memoryUsages[monoDomain];
}

MonoMethod* g_tickMethod;

extern "C" DLL_EXPORT void GI_TickInDomain(MonoAppDomain* domain)
{
auto targetDomain = mono_domain_from_appdomain(domain);
auto currentDomain = mono_domain_get();

if (currentDomain != targetDomain)
{
mono_thread_push_appdomain_ref(targetDomain);

if (!mono_domain_set(targetDomain, false))
{
mono_thread_pop_appdomain_ref();
return;
}
}

MonoObject* exc = nullptr;
mono_runtime_invoke(g_tickMethod, nullptr, nullptr, &exc);

if (currentDomain != targetDomain)
{
mono_domain_set(currentDomain, true);
mono_thread_pop_appdomain_ref();
}
}

MonoMethod* g_getImplementsMethod;
MonoMethod* g_createObjectMethod;

Expand Down Expand Up @@ -252,6 +311,7 @@ static void InitMono()

mono_add_internal_call("CitizenFX.Core.GameInterface::PrintLog", reinterpret_cast<void*>(GI_PrintLogCall));
mono_add_internal_call("CitizenFX.Core.GameInterface::fwFree", reinterpret_cast<void*>(fwFree));
mono_add_internal_call("CitizenFX.Core.GameInterface::TickInDomain", reinterpret_cast<void*>(GI_TickInDomain));
mono_add_internal_call("CitizenFX.Core.GameInterface::GetMemoryUsage", reinterpret_cast<void*>(GI_GetMemoryUsage));

std::string platformPath = MakeRelativeNarrowPath("citizen/clr2/lib/mono/4.5/CitizenFX.Core.dll");
Expand All @@ -277,6 +337,7 @@ static void InitMono()
method_search("CitizenFX.Core.RuntimeManager:Initialize", rtInitMethod);
method_search("CitizenFX.Core.RuntimeManager:GetImplementedClasses", g_getImplementsMethod);
method_search("CitizenFX.Core.RuntimeManager:CreateObjectInstance", g_createObjectMethod);
method_search("CitizenFX.Core.InternalManager:TickGlobal", g_tickMethod);

if (!methodSearchSuccess)
{
Expand Down
6 changes: 4 additions & 2 deletions code/premake5.lua
Expand Up @@ -209,8 +209,10 @@ end)
disablewarnings 'CS1591'

dotnetframework '4.6'

clr 'Unsafe'

csversion '7.3'

files { "client/clrcore/*.cs", "client/clrcore/Math/*.cs" }

Expand All @@ -222,7 +224,7 @@ end)

links { "System.dll", "Microsoft.CSharp.dll", "System.Core.dll", "../data/client/citizen/clr2/lib/mono/4.5/MsgPack.dll" }

buildoptions '/debug:portable /langversion:7.1'
buildoptions '/debug:portable /langversion:7.3'

configuration "Debug*"
targetdir (binroot .. '/debug/citizen/clr2/lib/mono/4.5/')
Expand Down
Binary file modified data/client/bin/mono-2.0-sgen.dll
Binary file not shown.
Binary file modified data/client/citizen/clr2/lib/mono/4.5/mscorlib.dll
Binary file not shown.

0 comments on commit 5575937

Please sign in to comment.