Switch lua library to NLua and implement cancellation

This commit is contained in:
Daniel_I_Am 2020-09-27 12:13:20 +02:00
parent 514a835233
commit 29813a599c
No known key found for this signature in database
GPG Key ID: 80C428FCC9743E84
5 changed files with 46 additions and 42 deletions

View File

@ -16,9 +16,9 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.6" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" />
<PackageReference Include="NeoLua" Version="1.3.11" />
<PackageReference Include="NLog" Version="4.7.2" /> <PackageReference Include="NLog" Version="4.7.2" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.6.4" /> <PackageReference Include="NLog.Extensions.Logging" Version="1.6.4" />
<PackageReference Include="NLua" Version="1.5.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.2" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql.Design" Version="1.1.2" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql.Design" Version="1.1.2" />
</ItemGroup> </ItemGroup>

View File

@ -6,7 +6,6 @@ using ChaosBot.Services;
using ChaosBot.Services.ProgrammingLanguageInterpreter; using ChaosBot.Services.ProgrammingLanguageInterpreter;
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Neo.IronLua;
namespace ChaosBot.Discord.Services namespace ChaosBot.Discord.Services
{ {

View File

@ -1,10 +1,10 @@
using System.Threading;
using Discord.Commands; using Discord.Commands;
namespace ChaosBot.Services.ProgrammingLanguageInterpreter namespace ChaosBot.Services.ProgrammingLanguageInterpreter
{ {
public interface IProgrammingLanguageInterpreter public interface IProgrammingLanguageInterpreter
{ {
string Interpret(SocketCommandContext context, string content, string command); string Interpret(CancellationToken ct, SocketCommandContext context, string content, string command);
void StopExecution();
} }
} }

View File

@ -1,48 +1,54 @@
using System;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Threading;
using Discord.Commands; using Discord.Commands;
using Neo.IronLua; using NLua;
using NLog;
namespace ChaosBot.Services.ProgrammingLanguageInterpreter namespace ChaosBot.Services.ProgrammingLanguageInterpreter
{ {
internal class LuaProgrammingLanguageInterpreter : IProgrammingLanguageInterpreter internal class LuaProgrammingLanguageInterpreter : IProgrammingLanguageInterpreter
{ {
private static readonly ILogger Logger = Program.GetLogger();
private readonly StringBuilder _outputBuilder = new StringBuilder(); private readonly StringBuilder _outputBuilder = new StringBuilder();
private Lua _lua; private CancellationToken _ct;
private Lua _state;
public string Interpret(SocketCommandContext context, string content, string command) public string Interpret(CancellationToken ct, SocketCommandContext context, string content, string command)
{ {
using (_lua = new Lua()) LoggingFacade.Debug($"Interpreting code for {command} using Lua");
{ LoggingFacade.Trace($"Using CancellationToken: {ct}");
// This needs to be dynamic if we want to call _ct = ct;
// functions from within the lua environment LoggingFacade.Trace("Starting Lua interpreter");
// This is a runtime type check _state = new Lua();
dynamic env = _lua.CreateEnvironment(); LoggingFacade.Trace("Setting Lua debug hook");
_state.SetDebugHook(KeraLua.LuaHookMask.Line, 0);
_state.DebugHook += LuaDebugHook;
ParamsDelegate printDel = Print; MethodInfo printMethod = GetType().GetMethod("Print", BindingFlags.NonPublic | BindingFlags.Instance);
env.print = printDel; LoggingFacade.Info($"Overwriting print function with {printMethod}");
_state["print"] = printMethod;
env.context = context; LoggingFacade.Trace("Disabling Lua `import` function");
_state.DoString ("\nimport = function () end\n");
string code = content; LoggingFacade.Trace("Running user Lua code");
_state.DoString(content, command);
env.dochunk(code, $"{command}.lua");
LoggingFacade.Trace("Returning Lua output");
return _outputBuilder.ToString(); return _outputBuilder.ToString();
} }
}
public void StopExecution() private void LuaDebugHook(object sender, NLua.Event.DebugHookEventArgs e)
{ {
_lua.Dispose(); if (_ct.IsCancellationRequested)
{
Lua l = (Lua) sender;
l.State.Error("Timeout kill");
}
} }
private delegate void ParamsDelegate(params object[] parameters); // This is being used in the `Interpret` function to register the print function
// ReSharper disable once UnusedMember.Local
private void Print(params object[] parameters) private void Print(params object[] parameters)
{ {
string str = string.Join(" ", from param in parameters select param == null ? string.Empty : param.ToString()); string str = string.Join(" ", from param in parameters select param == null ? string.Empty : param.ToString());

View File

@ -21,21 +21,19 @@ namespace ChaosBot.Services.ProgrammingLanguageInterpreter
try try
{ {
Thread taskThread = null; CancellationTokenSource tokenSource = new CancellationTokenSource();
Task<string> task = Task.Run(() => Task<string> task = Task.Run(() =>
{ {
taskThread = Thread.CurrentThread;
Thread.CurrentThread.Priority = ThreadPriority.Lowest; Thread.CurrentThread.Priority = ThreadPriority.Lowest;
return interpreter.Interpret(context, customCommand.Content, customCommand.Command); return interpreter.Interpret(tokenSource.Token, context, customCommand.Content, customCommand.Command);
}); }, tokenSource.Token);
const int timeout = 1000; const int timeout = 250;
bool isTaskCompleted = task.Wait(TimeSpan.FromMilliseconds(timeout)); bool isTaskCompleted = task.Wait(TimeSpan.FromMilliseconds(timeout));
if (!isTaskCompleted) if (!isTaskCompleted)
{ {
interpreter.StopExecution(); tokenSource.Cancel();
taskThread.Abort();
throw new TimeoutException($"Command '{customCommand.Command}' took more than {timeout}ms to run. It has been killed."); throw new TimeoutException($"Command '{customCommand.Command}' took more than {timeout}ms to run. It has been killed.");
} }
@ -49,6 +47,7 @@ namespace ChaosBot.Services.ProgrammingLanguageInterpreter
} }
catch (Exception ex) catch (Exception ex)
{ {
LoggingFacade.Exception(ex);
errorReason = $"There was an error with your code ({ex.GetType().Name}): {ex.Message}"; errorReason = $"There was an error with your code ({ex.GetType().Name}): {ex.Message}";
return false; return false;
} }