From 03880b06769f95e44a57b1352eb8cd922d6eb2c2 Mon Sep 17 00:00:00 2001
From: Jicheng Lu <103353@smsassist.com>
Date: Thu, 13 Nov 2025 16:27:25 -0600
Subject: [PATCH 1/2] refine py interpreter
---
.../Coding/ICodeProcessor.cs | 2 +-
.../Coding/Settings/CodingSettings.cs | 1 -
.../BotSharp.Core.Rules/Engines/RuleEngine.cs | 2 +-
.../Coding/CodeScriptExecutor.cs | 39 -----------
.../BotSharp.Core/Coding/CodingPlugin.cs | 2 -
.../Conversations/ConversationPlugin.cs | 1 -
.../Services/InstructService.Execute.cs | 2 +-
.../Functions/PyProgrammerFn.cs | 6 +-
.../Services/PyCodeInterpreter.cs | 69 +++++++++----------
src/WebStarter/appsettings.json | 3 +-
10 files changed, 41 insertions(+), 86 deletions(-)
delete mode 100644 src/Infrastructure/BotSharp.Core/Coding/CodeScriptExecutor.cs
diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs
index 3f550faa6..bd34eb257 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs
@@ -17,7 +17,7 @@ public interface ICodeProcessor
/// The cancellation token
///
///
- Task RunAsync(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
+ CodeInterpretResponse Run(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs
index 38f635222..49747798e 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs
@@ -19,5 +19,4 @@ public class CodeScriptExecutionSettings
public bool UseLock { get; set; }
public bool UseProcess { get; set; }
public int TimeoutSeconds { get; set; } = 3;
- public int MaxConcurrency { get; set; } = 1;
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs
index 5442134cf..442f64fea 100644
--- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs
+++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs
@@ -140,7 +140,7 @@ private async Task TriggerCodeScript(Agent agent, string triggerName, Rule
var (useLock, useProcess, timeoutSeconds) = GetCodeExecutionConfig();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds));
- var response = await processor.RunAsync(codeScript.Content, options: new()
+ var response = processor.Run(codeScript.Content, options: new()
{
ScriptName = scriptName,
Arguments = arguments,
diff --git a/src/Infrastructure/BotSharp.Core/Coding/CodeScriptExecutor.cs b/src/Infrastructure/BotSharp.Core/Coding/CodeScriptExecutor.cs
deleted file mode 100644
index a735c4dc3..000000000
--- a/src/Infrastructure/BotSharp.Core/Coding/CodeScriptExecutor.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-namespace BotSharp.Core.Coding;
-
-public class CodeScriptExecutor
-{
- private const int DEFAULT_MAX_CONCURRENCY = 1;
-
- private readonly CodingSettings _settings;
- private readonly ILogger _logger;
- private readonly SemaphoreSlim _semLock = new(initialCount: DEFAULT_MAX_CONCURRENCY, maxCount: DEFAULT_MAX_CONCURRENCY);
-
- public CodeScriptExecutor(
- CodingSettings settings,
- ILogger logger)
- {
- _settings = settings;
- _logger = logger;
-
- var maxConcurrency = settings.CodeExecution?.MaxConcurrency > 0 ? settings.CodeExecution.MaxConcurrency : DEFAULT_MAX_CONCURRENCY;
- _semLock = new(initialCount: maxConcurrency, maxCount: maxConcurrency);
- }
-
- public async Task ExecuteAsync(Func> func, CancellationToken cancellationToken = default)
- {
- try
- {
- await _semLock.WaitAsync(cancellationToken);
- return await func();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, $"Error in {nameof(CodeScriptExecutor)}.");
- return default(T);
- }
- finally
- {
- _semLock.Release();
- }
- }
-}
diff --git a/src/Infrastructure/BotSharp.Core/Coding/CodingPlugin.cs b/src/Infrastructure/BotSharp.Core/Coding/CodingPlugin.cs
index 4e0ccdda3..efbe13371 100644
--- a/src/Infrastructure/BotSharp.Core/Coding/CodingPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core/Coding/CodingPlugin.cs
@@ -13,7 +13,5 @@ public void RegisterDI(IServiceCollection services, IConfiguration config)
var coding = new CodingSettings();
config.Bind("Coding", coding);
services.AddSingleton(provider => coding);
-
- services.AddSingleton();
}
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
index e1c44a9a1..6de7da9d6 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
@@ -69,7 +69,6 @@ public void RegisterDI(IServiceCollection services, IConfiguration config)
services.AddScoped();
services.AddScoped();
- services.AddSingleton();
}
public bool AttachMenu(List menu)
diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs
index 8e4d3ec6d..c2dca0d9e 100644
--- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs
+++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs
@@ -258,7 +258,7 @@ await hook.OnResponseGenerated(new InstructResponseModel
// Run code script
var (useLock, useProcess, timeoutSeconds) = GetCodeExecutionConfig(codingSettings);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds));
- var codeResponse = await codeProcessor.RunAsync(context.CodeScript?.Content ?? string.Empty, options: new()
+ var codeResponse = codeProcessor.Run(context.CodeScript?.Content ?? string.Empty, options: new()
{
ScriptName = context.CodeScript?.Name,
Arguments = context.Arguments,
diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs
index bdbebcab1..642336aaf 100644
--- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs
+++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs
@@ -71,7 +71,7 @@ public async Task Execute(RoleDialogModel message)
try
{
- var (isSuccess, result) = await InnerRunCode(ret.PythonCode);
+ var (isSuccess, result) = InnerRunCode(ret.PythonCode);
if (isSuccess)
{
message.Content = result;
@@ -107,7 +107,7 @@ public async Task Execute(RoleDialogModel message)
///
///
///
- private async Task<(bool, string)> InnerRunCode(string codeScript)
+ private (bool, string) InnerRunCode(string codeScript)
{
var codeProvider = _codingSettings.CodeExecution?.Processor;
codeProvider = !string.IsNullOrEmpty(codeProvider) ? codeProvider : BuiltInCodeProcessor.PyInterpreter;
@@ -122,7 +122,7 @@ public async Task Execute(RoleDialogModel message)
var (useLock, useProcess, timeoutSeconds) = GetCodeExecutionConfig();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds));
- var response = await processor.RunAsync(codeScript, options: new()
+ var response = processor.Run(codeScript, options: new()
{
UseLock = useLock,
UseProcess = useProcess
diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs
index 815fab72d..db6a9feff 100644
--- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs
+++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs
@@ -12,42 +12,37 @@ public class PyCodeInterpreter : ICodeProcessor
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
- private readonly CodeScriptExecutor _executor;
private readonly CodingSettings _settings;
+ private static SemaphoreSlim _semLock = new(initialCount: 1, maxCount: 1);
public PyCodeInterpreter(
IServiceProvider services,
ILogger logger,
- CodeScriptExecutor executor,
CodingSettings settings)
{
_services = services;
_logger = logger;
- _executor = executor;
_settings = settings;
}
public string Provider => BuiltInCodeProcessor.PyInterpreter;
- public async Task RunAsync(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
+ public CodeInterpretResponse Run(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
{
if (options?.UseLock == true)
{
try
{
- return await _executor.ExecuteAsync(async () =>
- {
- return await InnerRunCode(codeScript, options, cancellationToken);
- }, cancellationToken: cancellationToken);
+ return InnerRunWithLock(codeScript, options, cancellationToken);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error when using code script executor.");
+ _logger.LogError(ex, $"Error when using code script with {nameof(SemaphoreSlim)}.");
return new() { ErrorMsg = ex.Message };
}
}
- return await InnerRunCode(codeScript, options, cancellationToken);
+ return InnerRunCode(codeScript, options, cancellationToken);
}
public async Task GenerateCodeScriptAsync(string text, CodeGenerationOptions? options = null)
@@ -105,7 +100,30 @@ public async Task GenerateCodeScriptAsync(string text, Cod
#region Private methods
- private async Task InnerRunCode(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
+ private CodeInterpretResponse InnerRunWithLock(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ var lockAcquired = false;
+ try
+ {
+ _semLock.Wait(cancellationToken);
+ lockAcquired = true;
+ return InnerRunCode(codeScript, options, cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"Error in {nameof(InnerRunWithLock)}");
+ return new() { ErrorMsg = ex.Message };
+ }
+ finally
+ {
+ if (lockAcquired)
+ {
+ _semLock.Release();
+ }
+ }
+ }
+
+ private CodeInterpretResponse InnerRunCode(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
{
var response = new CodeInterpretResponse();
var scriptName = options?.ScriptName ?? codeScript.SubstringMax(30);
@@ -116,11 +134,11 @@ private async Task InnerRunCode(string codeScript, CodeIn
if (options?.UseProcess == true)
{
- response = await CoreRunProcess(codeScript, options, cancellationToken);
+ response = CoreRunProcess(codeScript, options, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult();
}
else
{
- response = await CoreRunScript(codeScript, options, cancellationToken);
+ response = CoreRunScript(codeScript, options, cancellationToken);
}
_logger.LogWarning($"End running python code script in {Provider}: {scriptName}");
@@ -141,7 +159,7 @@ private async Task InnerRunCode(string codeScript, CodeIn
}
}
- private async Task CoreRunScript(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
+ private CodeInterpretResponse CoreRunScript(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -161,21 +179,6 @@ private async Task CoreRunScript(string codeScript, CodeI
try
{
- // Capture the Python thread ID for the current thread executing under the GIL
- var pythonThreadId = PythonEngine.GetPythonThreadID();
- using var reg = cancellationToken.Register(() =>
- {
- try
- {
- PythonEngine.Interrupt(pythonThreadId);
- _logger.LogWarning($"Cancellation requested: issued PythonEngine.Interrupt for thread {pythonThreadId} (request {requestId})");
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Failed to interrupt Python execution on cancellation.");
- }
- });
-
// Redirect standard output/error to capture it
dynamic outIO = io.StringIO();
dynamic errIO = io.StringIO();
@@ -206,8 +209,6 @@ private async Task CoreRunScript(string codeScript, CodeI
}
sys.argv = list;
- cancellationToken.ThrowIfCancellationRequested();
-
// Execute Python script
PythonEngine.Exec(codeScript, globals);
@@ -217,8 +218,6 @@ private async Task CoreRunScript(string codeScript, CodeI
var stdout = outIO.getvalue()?.ToString() as string;
var stderr = errIO.getvalue()?.ToString() as string;
- cancellationToken.ThrowIfCancellationRequested();
-
return new CodeInterpretResponse
{
Result = stdout?.TrimEnd('\r', '\n') ?? string.Empty,
@@ -237,10 +236,10 @@ private async Task CoreRunScript(string codeScript, CodeI
sys.stderr = sys.__stderr__;
sys.argv = new PyList();
}
- };
+ }
}, cancellationToken);
- return await execTask.WaitAsync(cancellationToken);
+ return execTask.WaitAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult();
}
diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json
index be70e0d47..dce34aac8 100644
--- a/src/WebStarter/appsettings.json
+++ b/src/WebStarter/appsettings.json
@@ -527,8 +527,7 @@
"Processor": null,
"UseLock": false,
"UseProcess": false,
- "TimeoutSeconds": 3,
- "MaxConcurrency": 1
+ "TimeoutSeconds": 3
}
},
From cd6eb1c908a51da72be57c53dd6357e887703317 Mon Sep 17 00:00:00 2001
From: Jicheng Lu <103353@smsassist.com>
Date: Thu, 13 Nov 2025 16:35:25 -0600
Subject: [PATCH 2/2] minor change
---
.../Services/PyCodeInterpreter.cs | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs
index db6a9feff..88d57a18b 100644
--- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs
+++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs
@@ -31,15 +31,7 @@ public CodeInterpretResponse Run(string codeScript, CodeInterpretOptions? option
{
if (options?.UseLock == true)
{
- try
- {
- return InnerRunWithLock(codeScript, options, cancellationToken);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, $"Error when using code script with {nameof(SemaphoreSlim)}.");
- return new() { ErrorMsg = ex.Message };
- }
+ return InnerRunWithLock(codeScript, options, cancellationToken);
}
return InnerRunCode(codeScript, options, cancellationToken);