Skip to content

Commit cacc6df

Browse files
authored
Merge pull request #1210 from iceljc/bugfix/fix-code-exec-timeout
refine tokens
2 parents 61cad7c + 732b43f commit cacc6df

File tree

6 files changed

+91
-81
lines changed

6 files changed

+91
-81
lines changed

src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using BotSharp.Abstraction.Coding.Models;
22
using BotSharp.Abstraction.Coding.Options;
33
using BotSharp.Abstraction.Coding.Responses;
4+
using System.Threading;
45

56
namespace BotSharp.Abstraction.Coding;
67

@@ -11,11 +12,12 @@ public interface ICodeProcessor
1112
/// <summary>
1213
/// Run code script
1314
/// </summary>
14-
/// <param name="codeScript">The code scirpt to run</param>
15+
/// <param name="codeScript">The code script to run</param>
1516
/// <param name="options">Code script execution options</param>
17+
/// <param name="cancellationToken">The cancellation token</param>
1618
/// <returns></returns>
1719
/// <exception cref="NotImplementedException"></exception>
18-
Task<CodeInterpretResponse> RunAsync(string codeScript, CodeInterpretOptions? options = null)
20+
Task<CodeInterpretResponse> RunAsync(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
1921
=> throw new NotImplementedException();
2022

2123
/// <summary>

src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeInterpretOptions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ public class CodeInterpretOptions
66
{
77
public string? ScriptName { get; set; }
88
public IEnumerable<KeyValue>? Arguments { get; set; }
9-
public bool UseMutex { get; set; }
9+
public bool UseLock { get; set; }
1010
public bool UseProcess { get; set; }
11-
public CancellationToken? CancellationToken { get; set; }
1211
}

src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ public class CodeInterpretResponse : ResponseBase
66

77
public override string ToString()
88
{
9-
return Result ?? ErrorMsg ?? $"Success: {Success}";
9+
return Result.IfNullOrEmptyAs(ErrorMsg ?? $"Success: {Success}")!;
1010
}
1111
}

src/Infrastructure/BotSharp.Abstraction/Instructs/Options/CodeInstructOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,11 @@ public class CodeInstructOptions
2929
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
3030
[JsonPropertyName("arguments")]
3131
public List<KeyValue>? Arguments { get; set; }
32+
33+
/// <summary>
34+
/// Timeout in seconds
35+
/// </summary>
36+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
37+
[JsonPropertyName("timeout_seconds")]
38+
public int? TimeoutSeconds { get; set; }
3239
}

src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,13 @@ await hook.OnResponseGenerated(new InstructResponseModel
254254
}
255255

256256
// Run code script
257-
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
257+
var seconds = codeOptions?.TimeoutSeconds > 0 ? codeOptions.TimeoutSeconds.Value : 3;
258+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(seconds));
258259
var codeResponse = await codeProcessor.RunAsync(context.CodeScript, options: new()
259260
{
260261
ScriptName = scriptName,
261-
Arguments = context.Arguments,
262-
CancellationToken = cts.Token
263-
});
262+
Arguments = context.Arguments
263+
}, cancellationToken: cts.Token);
264264

265265
if (codeResponse == null || !codeResponse.Success)
266266
{

src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs

Lines changed: 74 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ public PyCodeInterpreter(
3030

3131
public string Provider => "botsharp-py-interpreter";
3232

33-
public async Task<CodeInterpretResponse> RunAsync(string codeScript, CodeInterpretOptions? options = null)
33+
public async Task<CodeInterpretResponse> RunAsync(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
3434
{
35-
if (options?.UseMutex == true)
35+
if (options?.UseLock == true)
3636
{
3737
return await _executor.ExecuteAsync(async () =>
3838
{
39-
return await InnerRunCode(codeScript, options);
40-
}, cancellationToken: options?.CancellationToken ?? CancellationToken.None);
39+
return await InnerRunCode(codeScript, options, cancellationToken);
40+
}, cancellationToken: cancellationToken);
4141
}
4242

43-
return await InnerRunCode(codeScript, options);
43+
return await InnerRunCode(codeScript, options, cancellationToken);
4444
}
4545

4646
public async Task<CodeGenerationResult> GenerateCodeScriptAsync(string text, CodeGenerationOptions? options = null)
@@ -98,7 +98,7 @@ public async Task<CodeGenerationResult> GenerateCodeScriptAsync(string text, Cod
9898

9999

100100
#region Private methods
101-
private async Task<CodeInterpretResponse> InnerRunCode(string codeScript, CodeInterpretOptions? options = null)
101+
private async Task<CodeInterpretResponse> InnerRunCode(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
102102
{
103103
var response = new CodeInterpretResponse();
104104
var scriptName = options?.ScriptName ?? codeScript.SubstringMax(30);
@@ -109,11 +109,11 @@ private async Task<CodeInterpretResponse> InnerRunCode(string codeScript, CodeIn
109109

110110
if (options?.UseProcess == true)
111111
{
112-
response = await CoreRunProcess(codeScript, options);
112+
response = await CoreRunProcess(codeScript, options, cancellationToken);
113113
}
114114
else
115115
{
116-
response = await CoreRunScript(codeScript, options);
116+
response = await CoreRunScript(codeScript, options, cancellationToken);
117117
}
118118

119119
_logger.LogWarning($"End running python code script in {Provider}: {scriptName}");
@@ -134,88 +134,90 @@ private async Task<CodeInterpretResponse> InnerRunCode(string codeScript, CodeIn
134134
}
135135
}
136136

137-
private async Task<CodeInterpretResponse> CoreRunScript(string codeScript, CodeInterpretOptions? options = null)
137+
private async Task<CodeInterpretResponse> CoreRunScript(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
138138
{
139139
_logger.LogWarning($"Begin {nameof(CoreRunScript)} in {Provider}: ${options?.ScriptName}");
140140

141-
var token = options?.CancellationToken ?? CancellationToken.None;
142-
token.ThrowIfCancellationRequested();
141+
cancellationToken.ThrowIfCancellationRequested();
143142

144-
using (Py.GIL())
143+
var execTask = Task.Factory.StartNew(() =>
145144
{
146-
token.ThrowIfCancellationRequested();
147-
148-
// Import necessary Python modules
149-
dynamic sys = Py.Import("sys");
150-
dynamic io = Py.Import("io");
151-
152-
try
145+
using (Py.GIL())
153146
{
154-
// Redirect standard output/error to capture it
155-
dynamic stringIO = io.StringIO();
156-
sys.stdout = stringIO;
157-
sys.stderr = stringIO;
158-
159-
// Set global items
160-
using var globals = new PyDict();
161-
if (codeScript.Contains("__main__") == true)
162-
{
163-
globals.SetItem("__name__", new PyString("__main__"));
164-
}
147+
// Import necessary Python modules
148+
dynamic sys = Py.Import("sys");
149+
dynamic io = Py.Import("io");
165150

166-
// Set arguments
167-
var list = new PyList();
168-
if (options?.Arguments?.Any() == true)
151+
try
169152
{
170-
list.Append(new PyString(options?.ScriptName ?? "script.py"));
153+
// Redirect standard output/error to capture it
154+
dynamic outIO = io.StringIO();
155+
dynamic errIO = io.StringIO();
156+
sys.stdout = outIO;
157+
sys.stderr = errIO;
158+
159+
// Set global items
160+
using var globals = new PyDict();
161+
if (codeScript.Contains("__main__") == true)
162+
{
163+
globals.SetItem("__name__", new PyString("__main__"));
164+
}
171165

172-
foreach (var arg in options!.Arguments)
166+
// Set arguments
167+
var list = new PyList();
168+
if (options?.Arguments?.Any() == true)
173169
{
174-
if (!string.IsNullOrWhiteSpace(arg.Key) && !string.IsNullOrWhiteSpace(arg.Value))
170+
list.Append(new PyString(options?.ScriptName ?? "script.py"));
171+
172+
foreach (var arg in options!.Arguments)
175173
{
176-
list.Append(new PyString($"--{arg.Key}"));
177-
list.Append(new PyString($"{arg.Value}"));
174+
if (!string.IsNullOrWhiteSpace(arg.Key) && !string.IsNullOrWhiteSpace(arg.Value))
175+
{
176+
list.Append(new PyString($"--{arg.Key}"));
177+
list.Append(new PyString($"{arg.Value}"));
178+
}
178179
}
179180
}
180-
}
181-
sys.argv = list;
181+
sys.argv = list;
182182

183-
token.ThrowIfCancellationRequested();
183+
cancellationToken.ThrowIfCancellationRequested();
184184

185-
// Execute Python script
186-
PythonEngine.Exec(codeScript, globals);
185+
// Execute Python script
186+
PythonEngine.Exec(codeScript, globals);
187187

188-
// Get result
189-
var result = stringIO.getvalue()?.ToString() as string;
188+
// Get result
189+
var stdout = outIO.getvalue()?.ToString() as string;
190+
var stderr = errIO.getvalue()?.ToString() as string;
190191

191-
token.ThrowIfCancellationRequested();
192+
cancellationToken.ThrowIfCancellationRequested();
192193

193-
return new CodeInterpretResponse
194+
return new CodeInterpretResponse
195+
{
196+
Result = stdout?.TrimEnd('\r', '\n') ?? string.Empty,
197+
Success = true
198+
};
199+
}
200+
catch (Exception ex)
194201
{
195-
Result = result?.TrimEnd('\r', '\n') ?? string.Empty,
196-
Success = true
197-
};
198-
}
199-
catch (Exception ex)
200-
{
201-
_logger.LogError(ex, $"Error in {nameof(CoreRunScript)} in {Provider}.");
202-
throw;
203-
}
204-
finally
205-
{
206-
// Restore the original stdout/stderr/argv
207-
sys.stdout = sys.__stdout__;
208-
sys.stderr = sys.__stderr__;
209-
sys.argv = new PyList();
210-
}
211-
};
202+
_logger.LogError(ex, $"Error in {nameof(CoreRunScript)} in {Provider}.");
203+
return new() { ErrorMsg = ex.Message };
204+
}
205+
finally
206+
{
207+
// Restore the original stdout/stderr/argv
208+
sys.stdout = sys.__stdout__;
209+
sys.stderr = sys.__stderr__;
210+
sys.argv = new PyList();
211+
}
212+
};
213+
}, cancellationToken);
214+
215+
return await execTask.WaitAsync(cancellationToken);
212216
}
213217

214218

215-
private async Task<CodeInterpretResponse> CoreRunProcess(string codeScript, CodeInterpretOptions? options = null)
219+
private async Task<CodeInterpretResponse> CoreRunProcess(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
216220
{
217-
var token = options?.CancellationToken ?? CancellationToken.None;
218-
219221
var psi = new ProcessStartInfo
220222
{
221223
FileName = "python",
@@ -252,7 +254,7 @@ private async Task<CodeInterpretResponse> CoreRunProcess(string codeScript, Code
252254

253255
try
254256
{
255-
using var reg = token.Register(() =>
257+
using var reg = cancellationToken.Register(() =>
256258
{
257259
try
258260
{
@@ -264,12 +266,12 @@ private async Task<CodeInterpretResponse> CoreRunProcess(string codeScript, Code
264266
catch { }
265267
});
266268

267-
var stdoutTask = proc.StandardOutput.ReadToEndAsync(token);
268-
var stderrTask = proc.StandardError.ReadToEndAsync(token);
269+
var stdoutTask = proc.StandardOutput.ReadToEndAsync(cancellationToken);
270+
var stderrTask = proc.StandardError.ReadToEndAsync(cancellationToken);
269271

270-
await Task.WhenAll([proc.WaitForExitAsync(token), stdoutTask, stderrTask]);
272+
await Task.WhenAll([proc.WaitForExitAsync(cancellationToken), stdoutTask, stderrTask]);
271273

272-
token.ThrowIfCancellationRequested();
274+
cancellationToken.ThrowIfCancellationRequested();
273275

274276
return new CodeInterpretResponse
275277
{

0 commit comments

Comments
 (0)