diff --git a/src/OneScript.StandardLibrary/Binary/BinaryDataContext.cs b/src/OneScript.StandardLibrary/Binary/BinaryDataContext.cs index e56d15ed3..24dbc0dd4 100644 --- a/src/OneScript.StandardLibrary/Binary/BinaryDataContext.cs +++ b/src/OneScript.StandardLibrary/Binary/BinaryDataContext.cs @@ -49,7 +49,7 @@ public BinaryDataContext(Stream stream) private void ReadFromStream(Stream stream) { - if (stream.Length < FileBackingConstants.DEFAULT_MEMORY_LIMIT) + if (stream.Length < BinaryDataRuntimeSettings.GetEffectiveInMemoryMaxBytes()) { LoadToBuffer(stream); } diff --git a/src/OneScript.StandardLibrary/Binary/FileBackingConstants.cs b/src/OneScript.StandardLibrary/Binary/FileBackingConstants.cs index eeb073ad9..c3f7e8ac9 100644 --- a/src/OneScript.StandardLibrary/Binary/FileBackingConstants.cs +++ b/src/OneScript.StandardLibrary/Binary/FileBackingConstants.cs @@ -6,12 +6,13 @@ This Source Code Form is subject to the terms of the ----------------------------------------------------------*/ using System; +using ScriptEngine; namespace OneScript.StandardLibrary.Binary { public static class FileBackingConstants { - public const int DEFAULT_MEMORY_LIMIT = 1024 * 1024 * 50; // 50 Mb + public const int DEFAULT_MEMORY_LIMIT = BinaryDataConfigurationDefaults.InMemoryMaxBytes; public const int SYSTEM_IN_MEMORY_LIMIT = Int32.MaxValue; } } \ No newline at end of file diff --git a/src/OneScript.StandardLibrary/Binary/FileBackingStream.cs b/src/OneScript.StandardLibrary/Binary/FileBackingStream.cs index 7a0502643..515a1f07e 100644 --- a/src/OneScript.StandardLibrary/Binary/FileBackingStream.cs +++ b/src/OneScript.StandardLibrary/Binary/FileBackingStream.cs @@ -7,6 +7,7 @@ This Source Code Form is subject to the terms of the using System; using System.IO; +using ScriptEngine.Machine; namespace OneScript.StandardLibrary.Binary { @@ -20,7 +21,7 @@ public sealed class FileBackingStream : Stream private string _backingFileName; - public FileBackingStream() : this(FileBackingConstants.DEFAULT_MEMORY_LIMIT) + public FileBackingStream() : this(BinaryDataRuntimeSettings.GetEffectiveInMemoryMaxBytes()) { } diff --git a/src/ScriptEngine.HostedScript/oscript.cfg b/src/ScriptEngine.HostedScript/oscript.cfg index 1ddb2504f..6a664aa51 100644 --- a/src/ScriptEngine.HostedScript/oscript.cfg +++ b/src/ScriptEngine.HostedScript/oscript.cfg @@ -19,4 +19,8 @@ lib.system = ../lib #encoding.script=utf-8 -#systemlanguage = ru \ No newline at end of file +#systemlanguage = ru + +# Порог в байтах: пока размер двоичных данных в памяти не превышает это значение, данные держатся в RAM; +# при большем размере используется временный файл. По умолчанию 52428800 (50 МБ). +#binaryData.inMemoryMaxBytes=52428800 \ No newline at end of file diff --git a/src/ScriptEngine/BinaryDataConfigurationDefaults.cs b/src/ScriptEngine/BinaryDataConfigurationDefaults.cs new file mode 100644 index 000000000..346858a5c --- /dev/null +++ b/src/ScriptEngine/BinaryDataConfigurationDefaults.cs @@ -0,0 +1,20 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +namespace ScriptEngine +{ + /// + /// Значения по умолчанию для буферизации двоичных данных в памяти до перехода на временный файл. + /// + public static class BinaryDataConfigurationDefaults + { + /// + /// Лимит по умолчанию (50 МБ), если в конфигурации не задан ключ binaryData.inMemoryMaxBytes. + /// + public const int InMemoryMaxBytes = 1024 * 1024 * 50; + } +} diff --git a/src/ScriptEngine/BslProcess.cs b/src/ScriptEngine/BslProcess.cs index 65b8c9de2..f4aa49d34 100644 --- a/src/ScriptEngine/BslProcess.cs +++ b/src/ScriptEngine/BslProcess.cs @@ -40,23 +40,33 @@ public BslProcess(int id, ExecutionContext context, IEnumerable e.BeforeProcessStart(this)); - } - _isRunning = true; + if (notifyExecutors) + BinaryDataRuntimeSettings.PushFromServices(Services); try { + if (notifyExecutors) + Array.ForEach(_executorProviders, e => e.BeforeProcessStart(this)); + + _isRunning = true; + return _bslExecutorsByModule[module.GetType()](this, target, module, method, arguments); } finally { if (notifyExecutors) { - Array.ForEach(_executorProviders, e => e.AfterProcessExit(this)); - _isRunning = false; + try + { + if (_isRunning) + Array.ForEach(_executorProviders, e => e.AfterProcessExit(this)); + } + finally + { + BinaryDataRuntimeSettings.Pop(); + _isRunning = false; + } } } } diff --git a/src/ScriptEngine/Hosting/EngineBuilderExtensions.cs b/src/ScriptEngine/Hosting/EngineBuilderExtensions.cs index 37225644d..f62b03fc0 100644 --- a/src/ScriptEngine/Hosting/EngineBuilderExtensions.cs +++ b/src/ScriptEngine/Hosting/EngineBuilderExtensions.cs @@ -79,6 +79,9 @@ public static IEngineBuilder SetDefaultOptions(this IEngineBuilder builder) var holder = sp.Resolve(); return holder.GetConfig(); }); + + services.RegisterSingleton(sp => + new BinaryDataMemoryLimitFromOptions(sp.Resolve())); services.Register(); diff --git a/src/ScriptEngine/IBinaryDataMemoryLimit.cs b/src/ScriptEngine/IBinaryDataMemoryLimit.cs new file mode 100644 index 000000000..aeb5744ac --- /dev/null +++ b/src/ScriptEngine/IBinaryDataMemoryLimit.cs @@ -0,0 +1,28 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +namespace ScriptEngine +{ + /// + /// Лимит объёма данных в памяти для объектов «ДвоичныеДанные» и смежных потоков + /// до выгрузки во временный файл (байты). + /// + public interface IBinaryDataMemoryLimit + { + int MaxBytesInMemory { get; } + } + + internal sealed class BinaryDataMemoryLimitFromOptions : IBinaryDataMemoryLimit + { + public BinaryDataMemoryLimitFromOptions(OneScriptCoreOptions options) + { + MaxBytesInMemory = options.BinaryDataInMemoryMaxBytes; + } + + public int MaxBytesInMemory { get; } + } +} diff --git a/src/ScriptEngine/Machine/BinaryDataRuntimeSettings.cs b/src/ScriptEngine/Machine/BinaryDataRuntimeSettings.cs new file mode 100644 index 000000000..6f0302e3c --- /dev/null +++ b/src/ScriptEngine/Machine/BinaryDataRuntimeSettings.cs @@ -0,0 +1,43 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +using System.Threading; +using OneScript.DependencyInjection; + +namespace ScriptEngine.Machine +{ + /// + /// Лимит памяти для двоичных данных на время выполнения процесса BSL (AsyncLocal). + /// + public static class BinaryDataRuntimeSettings + { + private static readonly AsyncLocal InMemoryMaxBytes = new AsyncLocal(); + + internal static void PushFromServices(IServiceContainer services) + { + var opts = services.TryResolve(); + var maxBytes = opts?.MaxBytesInMemory ?? BinaryDataConfigurationDefaults.InMemoryMaxBytes; + if (maxBytes <= 0) + maxBytes = BinaryDataConfigurationDefaults.InMemoryMaxBytes; + InMemoryMaxBytes.Value = maxBytes; + } + + internal static void Pop() + { + InMemoryMaxBytes.Value = null; + } + + /// + /// Текущий лимит «в памяти до временного файла» для двоичных данных (байты). + /// Вне процесса BSL возвращает значение по умолчанию из . + /// + public static int GetEffectiveInMemoryMaxBytes() + { + return InMemoryMaxBytes.Value ?? BinaryDataConfigurationDefaults.InMemoryMaxBytes; + } + } +} diff --git a/src/ScriptEngine/OneScriptCoreOptions.cs b/src/ScriptEngine/OneScriptCoreOptions.cs index 7d90b0793..609901df7 100644 --- a/src/ScriptEngine/OneScriptCoreOptions.cs +++ b/src/ScriptEngine/OneScriptCoreOptions.cs @@ -7,6 +7,7 @@ This Source Code Form is subject to the terms of the using System; using System.Collections.Generic; +using System.Globalization; using System.Text; using OneScript.Commons; using OneScript.Native.Compiler; @@ -21,6 +22,7 @@ public class OneScriptCoreOptions private const string PREPROCESSOR_DEFINITIONS_KEY = "preprocessor.define"; private const string DEFAULT_RUNTIME_KEY = "runtime.default"; private const string EXPLICIT_IMPORT = "lang.explicitImports"; + private const string BINARY_DATA_IN_MEMORY_MAX = "binaryData.inMemoryMaxBytes"; public OneScriptCoreOptions(KeyValueConfig config) { @@ -29,6 +31,7 @@ public OneScriptCoreOptions(KeyValueConfig config) PreprocessorDefinitions = SetupDefinitions(config[PREPROCESSOR_DEFINITIONS_KEY]); UseNativeAsDefaultRuntime = SetupDefaultRuntime(config[DEFAULT_RUNTIME_KEY]); ExplicitImports = SetupExplicitImports(config[EXPLICIT_IMPORT]); + BinaryDataInMemoryMaxBytes = SetupBinaryDataInMemoryMax(config[BINARY_DATA_IN_MEMORY_MAX]); } public string SystemLanguage { get; } @@ -41,6 +44,12 @@ public OneScriptCoreOptions(KeyValueConfig config) public ExplicitImportsBehavior ExplicitImports { get; } + /// + /// Максимальный объём двоичных данных в памяти до перехода на временный файл (байты). + /// Задаётся ключом binaryData.inMemoryMaxBytes в конфигурации. + /// + public int BinaryDataInMemoryMaxBytes { get; } + private static IEnumerable SetupDefinitions(string s) { return s?.Split(',') ?? Array.Empty(); @@ -79,5 +88,25 @@ private static ExplicitImportsBehavior SetupExplicitImports(string keyValue) return ExplicitImportsBehavior.Warn; } } + + private static int SetupBinaryDataInMemoryMax(string rawValue) + { + if (string.IsNullOrWhiteSpace(rawValue)) + return BinaryDataConfigurationDefaults.InMemoryMaxBytes; + + if (!int.TryParse(rawValue.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var bytes)) + { + SystemLogger.Write($"Invalid value for {BINARY_DATA_IN_MEMORY_MAX}: {rawValue}"); + return BinaryDataConfigurationDefaults.InMemoryMaxBytes; + } + + if (bytes <= 0 || bytes == int.MaxValue) + { + SystemLogger.Write($"Value for {BINARY_DATA_IN_MEMORY_MAX} must be between 1 and {int.MaxValue - 1}: {bytes}"); + return BinaryDataConfigurationDefaults.InMemoryMaxBytes; + } + + return bytes; + } } } \ No newline at end of file diff --git a/src/oscript/CgiBehavior.cs b/src/oscript/CgiBehavior.cs index 911782454..13a103d65 100644 --- a/src/oscript/CgiBehavior.cs +++ b/src/oscript/CgiBehavior.cs @@ -15,6 +15,7 @@ This Source Code Form is subject to the terms of the using OneScript.StandardLibrary; using oscript.Web; +using ScriptEngine; using ScriptEngine.HostedScript; using ScriptEngine.Hosting; using ScriptEngine.Machine; @@ -67,8 +68,13 @@ private int RunCGIMode(string scriptFile) }); var engine = ConsoleHostBuilder.Build(builder); - - var request = new WebRequestContext(); + + var binaryMemoryLimit = engine.Services.TryResolve()?.MaxBytesInMemory + ?? BinaryDataConfigurationDefaults.InMemoryMaxBytes; + if (binaryMemoryLimit <= 0) + binaryMemoryLimit = BinaryDataConfigurationDefaults.InMemoryMaxBytes; + + var request = new WebRequestContext(binaryMemoryLimit); engine.InjectGlobalProperty("ВебЗапрос", "WebRequest", request, true); engine.InjectObject(this); diff --git a/src/oscript/Web/WebRequestContext.cs b/src/oscript/Web/WebRequestContext.cs index 30f46d52c..d80a1bf0c 100644 --- a/src/oscript/Web/WebRequestContext.cs +++ b/src/oscript/Web/WebRequestContext.cs @@ -15,6 +15,7 @@ This Source Code Form is subject to the terms of the using OneScript.StandardLibrary.Collections; using OneScript.StandardLibrary.Text; using oscript.Web.Multipart; +using ScriptEngine; using ScriptEngine.Machine; using ScriptEngine.Machine.Contexts; @@ -27,8 +28,18 @@ public sealed class WebRequestContext : AutoContext, IDisposa private FileBackingStream _postBody; - public WebRequestContext() + private readonly int _postBodyInMemoryMaxBytes; + + public WebRequestContext() : this(BinaryDataConfigurationDefaults.InMemoryMaxBytes) + { + } + + public WebRequestContext(int postBodyInMemoryMaxBytes) { + if (postBodyInMemoryMaxBytes <= 0) + throw new ArgumentOutOfRangeException(nameof(postBodyInMemoryMaxBytes), + "Лимит памяти для тела POST-запроса должен быть положительным числом байт."); + _postBodyInMemoryMaxBytes = postBodyInMemoryMaxBytes; var get = Environment.GetEnvironmentVariable("QUERY_STRING"); if (get != null) FillGetMap(get); @@ -68,7 +79,8 @@ private void ProcessPostData() var type = Environment.GetEnvironmentVariable("CONTENT_TYPE"); using var stdin = Console.OpenStandardInput(); - var dest = new FileBackingStream(FileBackingConstants.DEFAULT_MEMORY_LIMIT, len); + var initialCapacity = Math.Min(len, _postBodyInMemoryMaxBytes); + var dest = new FileBackingStream(_postBodyInMemoryMaxBytes, initialCapacity); stdin.CopyTo(dest); dest.Position = 0;