Skip to content

feat(binary): лимит памяти для ДвоичныеДанные из конфигурации (#1667)#1670

Open
johnnyshut wants to merge 2 commits intoEvilBeaver:developfrom
johnnyshut:fix/1667
Open

feat(binary): лимит памяти для ДвоичныеДанные из конфигурации (#1667)#1670
johnnyshut wants to merge 2 commits intoEvilBeaver:developfrom
johnnyshut:fix/1667

Conversation

@johnnyshut
Copy link
Copy Markdown
Contributor

@johnnyshut johnnyshut commented Apr 30, 2026

Зачем

Закрывает #1667: порог «держать двоичные данные в памяти до перехода на временный файл» больше не зашит константой только в коде — задаётся через конфигурацию.

Что сделали

  • Добавили ключ binaryData.inMemoryMaxBytes (целое число байт) в oscript.cfg и в состав настроек из OSCRIPT_CONFIG. Если ключ не задан или значение некорректно — используется 50 МБ (BinaryDataConfigurationDefaults.InMemoryMaxBytes).
  • Добавили IBinaryDataMemoryLimit и регистрацию в SetDefaultOptions (singleton на основе OneScriptCoreOptions).
  • Добавили BinaryDataRuntimeSettings: на время выполнения BslProcess.Run лимит попадает в AsyncLocal и используется BinaryDataContext и параметрless FileBackingStream(); вне процесса BSL остаётся значение по умолчанию.
  • Обновили FileBackingConstants.DEFAULT_MEMORY_LIMIT — тот же дефолт, что и в ScriptEngine.
  • Для CGI: WebRequestContext получает лимит из контейнера после сборки движка (до CreateProcess), т.к. запрос создаётся раньше старта BSL-процесса.

Как проверить

  • В oscript.cfg раскомментировать/задать, например: binaryData.inMemoryMaxBytes=1048576, убедиться, что поведение буферизации соответствует ожиданиям для больших потоков.
  • Сборка основных .NET-проектов: dotnet build src/oscript/oscript.csproj (как минимум).

Summary by CodeRabbit

  • New Features

    • Binary data in-memory buffer threshold is configurable via binaryData.inMemoryMaxBytes (default: 50 MB).
    • POST request body buffering and CGI request handling respect the configured in-memory limit.
  • Refactor

    • Runtime-configurable memory limits replace fixed thresholds, so switching between RAM and temporary-file backing follows the configured value across the engine.

Добавили ключ binaryData.inMemoryMaxBytes (байты) в oscript.cfg и переменную OSCRIPT_CONFIG.

Изменили OneScriptCoreOptions: разбор значения с проверкой и запасным значением по умолчанию (50 МБ).

Добавили BinaryDataConfigurationDefaults, IBinaryDataMemoryLimit и регистрацию в SetDefaultOptions.

Добавили BinaryDataRuntimeSettings (AsyncLocal): на время BslProcess.Run лимит берётся из DI; вне процесса — дефолт.

Изменили BinaryDataContext и конструктор FileBackingStream без параметров для использования эффективного лимита.

Изменили FileBackingConstants.DEFAULT_MEMORY_LIMIT — ссылка на общий дефолт из ScriptEngine.

Изменили CGI: WebRequestContext принимает лимит из сервисов до CreateProcess.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

📝 Walkthrough

Walkthrough

Implements configurable binary-data in-memory limit: adds configuration/defaults, DI contract, async-local runtime settings pushed per-process, and updates binary buffering and web request code to use the runtime-effective limit instead of a fixed constant.

Changes

Cohort / File(s) Summary
Configuration & Defaults
src/ScriptEngine/BinaryDataConfigurationDefaults.cs, src/ScriptEngine/OneScriptCoreOptions.cs, src/ScriptEngine.HostedScript/oscript.cfg
Add default 50 MiB constant holder and a new binaryData.inMemoryMaxBytes option with parsing/validation; document setting in config file.
Runtime Settings & DI
src/ScriptEngine/Machine/BinaryDataRuntimeSettings.cs, src/ScriptEngine/IBinaryDataMemoryLimit.cs, src/ScriptEngine/Hosting/EngineBuilderExtensions.cs, src/ScriptEngine/BslProcess.cs
Introduce async-local BinaryDataRuntimeSettings with push/pop from DI; add IBinaryDataMemoryLimit and register it in engine builder; manage lifecycle around process start/exit.
Binary Data Components
src/OneScript.StandardLibrary/Binary/BinaryDataContext.cs, src/OneScript.StandardLibrary/Binary/FileBackingConstants.cs, src/OneScript.StandardLibrary/Binary/FileBackingStream.cs
Replace fixed in-memory threshold usage with calls to BinaryDataRuntimeSettings.GetEffectiveInMemoryMaxBytes() (and delegate default constant to new configuration default).
Web Request Handling
src/oscript/CgiBehavior.cs, src/oscript/Web/WebRequestContext.cs
CGI startup resolves memory-limit from DI and passes it into WebRequestContext; add constructor overload to configure POST body in-memory threshold and use it when creating FileBackingStream.

Sequence Diagram

sequenceDiagram
    participant Config as Configuration
    participant DI as Dependency Injection
    participant BslProc as BslProcess
    participant RTSettings as BinaryDataRuntimeSettings
    participant BinOps as Binary Ops (BinaryDataContext / FileBackingStream)

    Config->>DI: Register IBinaryDataMemoryLimit (from config)
    BslProc->>DI: Start execution / obtain services
    BslProc->>RTSettings: PushFromServices(serviceContainer)
    RTSettings->>DI: Resolve IBinaryDataMemoryLimit
    RTSettings->>RTSettings: Store value in AsyncLocal
    BslProc->>BinOps: Perform binary operation (stream/read)
    BinOps->>RTSettings: GetEffectiveInMemoryMaxBytes()
    RTSettings-->>BinOps: Return configured limit
    BinOps->>BinOps: Choose LoadToBuffer or BackingTemporaryFile
    BslProc->>RTSettings: Pop()
    RTSettings->>RTSettings: Clear AsyncLocal
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • EvilBeaver

Poem

🐰 I nibble bytes both big and small,

Fifty megs I once would stall.
Now configs, DI, and async grace,
Let memory limits find their place.
Hop—flexible buffering for all! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title in Russian describes adding a configurable memory limit for binary data from configuration, which aligns with the main changeset purpose of making the in-memory buffer threshold for binary data configurable via settings rather than hardcoded.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/ScriptEngine/Machine/BinaryDataRuntimeSettings.cs (1)

20-24: ⚡ Quick win

Harden runtime settings against invalid DI-provided limits

PushFromServices currently trusts IBinaryDataMemoryLimit.MaxBytesInMemory. A custom host registration can pass invalid values and break downstream buffering behavior; sanitize here before storing to AsyncLocal.

💡 Suggested fix
         internal static void PushFromServices(IServiceContainer services)
         {
             var opts = services.TryResolve<IBinaryDataMemoryLimit>();
-            InMemoryMaxBytes.Value = opts?.MaxBytesInMemory ?? BinaryDataConfigurationDefaults.InMemoryMaxBytes;
+            var maxBytes = opts?.MaxBytesInMemory ?? BinaryDataConfigurationDefaults.InMemoryMaxBytes;
+            if (maxBytes <= 0 || maxBytes == int.MaxValue)
+                maxBytes = BinaryDataConfigurationDefaults.InMemoryMaxBytes;
+
+            InMemoryMaxBytes.Value = maxBytes;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ScriptEngine/Machine/BinaryDataRuntimeSettings.cs` around lines 20 - 24,
PushFromServices currently writes IBinaryDataMemoryLimit.MaxBytesInMemory
directly into the AsyncLocal InMemoryMaxBytes.Value; validate and sanitize the
DI-provided value first so invalid or negative numbers don't corrupt runtime
buffering. In PushFromServices, read opts?.MaxBytesInMemory into a local, check
for null/<=0 and replace with BinaryDataConfigurationDefaults.InMemoryMaxBytes,
and optionally clamp extremely large values to a sane upper bound before
assigning to InMemoryMaxBytes.Value; reference IBinaryDataMemoryLimit,
PushFromServices, InMemoryMaxBytes and
BinaryDataConfigurationDefaults.InMemoryMaxBytes when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/oscript/CgiBehavior.cs`:
- Around line 72-73: Replace the hard Resolve call so CGI startup uses a safe DI
lookup with a default fallback: try to get IBinaryDataMemoryLimit from
engine.Services (avoid engine.Services.Resolve which throws) and if null or
missing use BinaryDataConfigurationDefaults.InMemoryMaxBytes, then pass that
value into the WebRequestContext constructor; update the code locations
referencing IBinaryDataMemoryLimit, engine.Services.Resolve and the
WebRequestContext creation to use this conditional/fallback logic.

In `@src/oscript/Web/WebRequestContext.cs`:
- Around line 37-40: Validate the constructor parameter in WebRequestContext by
checking postBodyInMemoryMaxBytes at the start of the WebRequestContext(int
postBodyInMemoryMaxBytes) constructor and throw an ArgumentOutOfRangeException
(with a clear message mentioning _postBodyInMemoryMaxBytes and expected positive
byte count) when the value is invalid (e.g., <= 0 or otherwise out of allowed
range) so the error fails fast instead of propagating later into
FileBackingStream.
- Line 79: The code constructs FileBackingStream with the full content length
which causes MemoryStream to preallocate huge buffers; change the capacity
argument so initial allocation is capped by _postBodyInMemoryMaxBytes (e.g.,
pass Math.Min(len, _postBodyInMemoryMaxBytes) instead of len) when creating the
FileBackingStream (the call that assigns dest = new FileBackingStream(...)) so
the in-memory buffer is limited and spilling-to-disk behavior works as intended.

In `@src/ScriptEngine/BslProcess.cs`:
- Around line 45-46: Wrap the runtime push/pop and _isRunning mutation in a
try/finally so Pop() and _isRunning reset always run even if executor hooks
throw: call BinaryDataRuntimeSettings.PushFromServices and set _isRunning = true
before invoking Array.ForEach(_executorProviders, e =>
e.BeforeProcessStart(this)), then ensure BinaryDataRuntimeSettings.Pop() and
_isRunning = false are executed in a finally block; similarly, ensure any
Array.ForEach(_executorProviders, e => e.AfterProcessExit(this)) failures do not
prevent the finally from running (catch per-hook exceptions if desired) so Pop()
and _isRunning reset cannot be skipped.

---

Nitpick comments:
In `@src/ScriptEngine/Machine/BinaryDataRuntimeSettings.cs`:
- Around line 20-24: PushFromServices currently writes
IBinaryDataMemoryLimit.MaxBytesInMemory directly into the AsyncLocal
InMemoryMaxBytes.Value; validate and sanitize the DI-provided value first so
invalid or negative numbers don't corrupt runtime buffering. In
PushFromServices, read opts?.MaxBytesInMemory into a local, check for null/<=0
and replace with BinaryDataConfigurationDefaults.InMemoryMaxBytes, and
optionally clamp extremely large values to a sane upper bound before assigning
to InMemoryMaxBytes.Value; reference IBinaryDataMemoryLimit, PushFromServices,
InMemoryMaxBytes and BinaryDataConfigurationDefaults.InMemoryMaxBytes when
making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 22f9a33d-f478-4b86-9fcf-a6bc6faba769

📥 Commits

Reviewing files that changed from the base of the PR and between c455964 and a7f38e3.

📒 Files selected for processing (12)
  • src/OneScript.StandardLibrary/Binary/BinaryDataContext.cs
  • src/OneScript.StandardLibrary/Binary/FileBackingConstants.cs
  • src/OneScript.StandardLibrary/Binary/FileBackingStream.cs
  • src/ScriptEngine.HostedScript/oscript.cfg
  • src/ScriptEngine/BinaryDataConfigurationDefaults.cs
  • src/ScriptEngine/BslProcess.cs
  • src/ScriptEngine/Hosting/EngineBuilderExtensions.cs
  • src/ScriptEngine/IBinaryDataMemoryLimit.cs
  • src/ScriptEngine/Machine/BinaryDataRuntimeSettings.cs
  • src/ScriptEngine/OneScriptCoreOptions.cs
  • src/oscript/CgiBehavior.cs
  • src/oscript/Web/WebRequestContext.cs

Comment thread src/oscript/CgiBehavior.cs Outdated
Comment thread src/oscript/Web/WebRequestContext.cs
Comment thread src/oscript/Web/WebRequestContext.cs Outdated
Comment thread src/ScriptEngine/BslProcess.cs Outdated
… обработки ошибок

Логика уведомлений исполнителя перемещена в единый условный блок для большей ясности.
Улучшена обработка ошибок при завершении процесса для обеспечения надлежащей очистки ресурсов.
Обновлены параметры BinaryDataRuntimeSettings для обеспечения положительных ограничений памяти и резервных значений по умолчанию.
Скорректирован WebRequestContext для проверки ограничений памяти при создании экземпляра.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/oscript/Web/WebRequestContext.cs (1)

37-42: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject Int32.MaxValue at the constructor boundary too.

FileBackingStream treats Int32.MaxValue as SYSTEM_IN_MEMORY_LIMIT and throws, so WebRequestContext(int) still accepts a value it cannot actually use. Failing fast here keeps the public API consistent and avoids a later exception from ProcessPostData().

Suggested fix
-            if (postBodyInMemoryMaxBytes <= 0)
+            if (postBodyInMemoryMaxBytes <= 0 || postBodyInMemoryMaxBytes == int.MaxValue)
                 throw new ArgumentOutOfRangeException(nameof(postBodyInMemoryMaxBytes),
-                    "Лимит памяти для тела POST-запроса должен быть положительным числом байт.");
+                    "Лимит памяти для тела POST-запроса должен быть положительным числом байт и меньше Int32.MaxValue.");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/oscript/Web/WebRequestContext.cs` around lines 37 - 42, The
WebRequestContext constructor currently only rejects non-positive values but
must also reject Int32.MaxValue because FileBackingStream treats that value as
SYSTEM_IN_MEMORY_LIMIT and will throw later; update the WebRequestContext(int
postBodyInMemoryMaxBytes) constructor to validate that postBodyInMemoryMaxBytes
is > 0 and < Int32.MaxValue (or != Int32.MaxValue) and throw an
ArgumentOutOfRangeException with a clear message if the value is Int32.MaxValue
so callers fail fast (this keeps behavior consistent with FileBackingStream and
avoids a later exception in ProcessPostData()).
src/ScriptEngine/BslProcess.cs (1)

49-68: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Unwind executors that already started when a later BeforeProcessStart throws.

_isRunning becomes true only after the whole startup loop succeeds, so a failure in any later BeforeProcessStart skips AfterProcessExit for executors that already initialized state. StackMachineExecutor.BeforeProcessStart() allocates machine/debugger state (src/ScriptEngine/Machine/StackMachineExecutor.cs:25-45), so failed startup can still leak per-process setup here.

Suggested direction
+            var startedExecutors = new List<IExecutorProvider>();
             try
             {
                 if (notifyExecutors)
-                    Array.ForEach(_executorProviders, e => e.BeforeProcessStart(this));
+                {
+                    foreach (var executor in _executorProviders)
+                    {
+                        executor.BeforeProcessStart(this);
+                        startedExecutors.Add(executor);
+                    }
+                }
 
                 _isRunning = true;
 
                 return _bslExecutorsByModule[module.GetType()](this, target, module, method, arguments);
             }
             finally
             {
                 if (notifyExecutors)
                 {
                     try
                     {
-                        if (_isRunning)
-                            Array.ForEach(_executorProviders, e => e.AfterProcessExit(this));
+                        for (var i = startedExecutors.Count - 1; i >= 0; i--)
+                            startedExecutors[i].AfterProcessExit(this);
                     }
                     finally
                     {
                         BinaryDataRuntimeSettings.Pop();
                         _isRunning = false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ScriptEngine/BslProcess.cs` around lines 49 - 68, When calling each
executor's BeforeProcessStart (via Array.ForEach over _executorProviders) a
later exception can skip AfterProcessExit for earlier executors; modify
BslProcess.Start (the block invoking BeforeProcessStart/AfterProcessExit) to
track which providers successfully completed BeforeProcessStart (e.g., a local
list/flag per provider) and set _isRunning only for the process-level state,
then in the finally ensure you call AfterProcessExit for every provider that was
marked started (not gated solely on _isRunning), and still call
BinaryDataRuntimeSettings.Pop() and clear _isRunning; in short: record started
executors during the BeforeProcessStart loop and unwind them via their
AfterProcessExit even if startup throws.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/oscript/Web/WebRequestContext.cs`:
- Around line 37-42: The WebRequestContext constructor currently only rejects
non-positive values but must also reject Int32.MaxValue because
FileBackingStream treats that value as SYSTEM_IN_MEMORY_LIMIT and will throw
later; update the WebRequestContext(int postBodyInMemoryMaxBytes) constructor to
validate that postBodyInMemoryMaxBytes is > 0 and < Int32.MaxValue (or !=
Int32.MaxValue) and throw an ArgumentOutOfRangeException with a clear message if
the value is Int32.MaxValue so callers fail fast (this keeps behavior consistent
with FileBackingStream and avoids a later exception in ProcessPostData()).

In `@src/ScriptEngine/BslProcess.cs`:
- Around line 49-68: When calling each executor's BeforeProcessStart (via
Array.ForEach over _executorProviders) a later exception can skip
AfterProcessExit for earlier executors; modify BslProcess.Start (the block
invoking BeforeProcessStart/AfterProcessExit) to track which providers
successfully completed BeforeProcessStart (e.g., a local list/flag per provider)
and set _isRunning only for the process-level state, then in the finally ensure
you call AfterProcessExit for every provider that was marked started (not gated
solely on _isRunning), and still call BinaryDataRuntimeSettings.Pop() and clear
_isRunning; in short: record started executors during the BeforeProcessStart
loop and unwind them via their AfterProcessExit even if startup throws.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 67fd241a-6dc6-485e-962b-7b19be63e644

📥 Commits

Reviewing files that changed from the base of the PR and between a7f38e3 and 2c90bbf.

📒 Files selected for processing (4)
  • src/ScriptEngine/BslProcess.cs
  • src/ScriptEngine/Machine/BinaryDataRuntimeSettings.cs
  • src/oscript/CgiBehavior.cs
  • src/oscript/Web/WebRequestContext.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ScriptEngine/Machine/BinaryDataRuntimeSettings.cs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant