diff --git a/src/OneScript.StandardLibrary/Binary/BinaryDataContext.cs b/src/OneScript.StandardLibrary/Binary/BinaryDataContext.cs
index 4ef0ee605..53f473f15 100644
--- a/src/OneScript.StandardLibrary/Binary/BinaryDataContext.cs
+++ b/src/OneScript.StandardLibrary/Binary/BinaryDataContext.cs
@@ -125,19 +125,21 @@ public Stream GetStream()
return InMemory ? new MemoryStream(_buffer, 0, _buffer.Length, false, true) : _backingFile.OpenReadStream();
}
+ ///
+ /// Данные целиком. Данные во временном файле при каждом обращении читаются в новый массив,
+ /// а сами остаются в файле: большие файлы для этого в нем и хранятся
+ ///
public byte[] Buffer
{
get
{
- if (!InMemory)
- {
- using var readStream = _backingFile.OpenReadStream();
- LoadToBuffer(readStream);
- _backingFile.Dispose();
- _backingFile = null;
- }
+ if (InMemory)
+ return _buffer;
- return _buffer;
+ using var readStream = _backingFile.OpenReadStream();
+ var buffer = new byte[readStream.Length];
+ readStream.ReadExactly(buffer);
+ return buffer;
}
}
diff --git a/src/OneScript.StandardLibrary/Binary/GlobalBinaryData.cs b/src/OneScript.StandardLibrary/Binary/GlobalBinaryData.cs
index 7bc79fc20..f47833190 100644
--- a/src/OneScript.StandardLibrary/Binary/GlobalBinaryData.cs
+++ b/src/OneScript.StandardLibrary/Binary/GlobalBinaryData.cs
@@ -383,7 +383,7 @@ public BinaryDataContext GetBinaryDataFromBase64BinaryData(BinaryDataContext dat
try
{
var enc = new UTF8Encoding(false,true);
- var str = enc.GetString(data.Buffer, 0, data.Buffer.Length);
+ var str = enc.GetString(data.Buffer);
return new BinaryDataContext(Convert.FromBase64String(str));
}
catch
@@ -554,7 +554,8 @@ public BinaryDataBuffer GetBinaryDataBufferFromBinaryData(BinaryDataContext data
{
CheckAndThrowIfNull(data);
- return new BinaryDataBuffer(data.Buffer);
+ // Копия: запись в буфер не должна менять двоичные данные (из файла Buffer и так читает новый массив)
+ return new BinaryDataBuffer(data.InMemory ? (byte[])data.Buffer.Clone() : data.Buffer);
}
///
@@ -567,7 +568,8 @@ public BinaryDataContext GetBinaryDataFromBinaryDataBuffer(BinaryDataBuffer buff
{
CheckAndThrowIfNull(buffer);
- return new BinaryDataContext(buffer.Bytes);
+ // Копия: двоичные данные не должны меняться вместе с буфером
+ return new BinaryDataContext((byte[])buffer.Bytes.Clone());
}
}
diff --git a/src/OneScript.StandardLibrary/NativeApi/NativeApiVariant.cs b/src/OneScript.StandardLibrary/NativeApi/NativeApiVariant.cs
index 8d5f7ddb2..172aadd6f 100644
--- a/src/OneScript.StandardLibrary/NativeApi/NativeApiVariant.cs
+++ b/src/OneScript.StandardLibrary/NativeApi/NativeApiVariant.cs
@@ -45,7 +45,8 @@ public void Assign(IValue value)
NativeApiProxy.SetVariantReal(Ptr, Convert.ToDouble(value.AsNumber()));
break;
case BinaryDataContext binaryData:
- NativeApiProxy.SetVariantBlob(Ptr, binaryData.Buffer, binaryData.Buffer.Length);
+ var blob = binaryData.Buffer;
+ NativeApiProxy.SetVariantBlob(Ptr, blob, blob.Length);
break;
case DateTime dt:
NativeApiProxy.SetVariantTm(
diff --git a/src/Tests/OneScript.StandardLibrary.Tests/BinaryDataThreadSafetyTests.cs b/src/Tests/OneScript.StandardLibrary.Tests/BinaryDataThreadSafetyTests.cs
new file mode 100644
index 000000000..02acc63a8
--- /dev/null
+++ b/src/Tests/OneScript.StandardLibrary.Tests/BinaryDataThreadSafetyTests.cs
@@ -0,0 +1,84 @@
+/*----------------------------------------------------------
+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;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using FluentAssertions;
+using OneScript.StandardLibrary.Binary;
+using Xunit;
+
+namespace OneScript.StandardLibrary.Tests
+{
+ public class BinaryDataThreadSafetyTests
+ {
+ [Fact]
+ public void FileBackedDataIsReadFromManyThreads()
+ {
+ const int threadsCount = 8;
+ var expected = new byte[64 * 1024];
+ new Random(42).NextBytes(expected);
+ var errors = new ConcurrentQueue();
+
+ for (var iteration = 0; iteration < 50; iteration++)
+ {
+ // Больше лимита памяти: данные во временном файле
+ using var data = new BinaryDataContext(new MemoryStream(expected), 1);
+ using var barrier = new Barrier(threadsCount);
+
+ var threads = Enumerable.Range(0, threadsCount).Select(number => new Thread(() =>
+ {
+ barrier.SignalAndWait();
+ try
+ {
+ if (!ReadAll(data, viaBuffer: number % 2 == 0).AsSpan().SequenceEqual(expected))
+ errors.Enqueue("данные отличаются");
+ }
+ catch (Exception e)
+ {
+ errors.Enqueue($"{e.GetType().Name}: {e.Message}");
+ }
+ })).ToArray();
+
+ foreach (var thread in threads)
+ {
+ thread.Start();
+ }
+ foreach (var thread in threads)
+ {
+ thread.Join();
+ }
+ }
+
+ errors.Should().BeEmpty();
+ }
+
+ [Fact]
+ public void BufferKeepsFileBackedDataInFile()
+ {
+ var expected = new byte[] { 1, 2, 3 };
+ using var data = new BinaryDataContext(new MemoryStream(expected), 1);
+
+ data.Buffer.Should().Equal(expected);
+ data.InMemory.Should().BeFalse();
+ ReadAll(data, viaBuffer: false).Should().Equal(expected);
+ }
+
+ private static byte[] ReadAll(BinaryDataContext data, bool viaBuffer)
+ {
+ if (viaBuffer)
+ return data.Buffer;
+
+ using var stream = data.GetStream();
+ using var copy = new MemoryStream();
+ stream.CopyTo(copy);
+ return copy.ToArray();
+ }
+ }
+}
diff --git a/tests/BinaryData-global.os b/tests/BinaryData-global.os
index 0de250614..96aaffa4a 100644
--- a/tests/BinaryData-global.os
+++ b/tests/BinaryData-global.os
@@ -29,6 +29,8 @@
ВсеТесты.Добавить("ТестДолжен_Проверить_ОтсутствиеBOMДляКодировокБезBOM");
ВсеТесты.Добавить("ТестДолжен_Проверить_КодировкиДляПолучитьСтрокуИзДвоичныхДанных");
ВсеТесты.Добавить("ТестДолжен_Проверить_КодировкиДляПолучитьСтрокуИзБуфераДвоичныхДанных");
+ ВсеТесты.Добавить("ТестДолжен_Проверить_ЗаписьВБуферИзДвоичныхДанныхНеМеняетИх");
+ ВсеТесты.Добавить("ТестДолжен_Проверить_ДвоичныеДанныеИзБуфераНеМеняютсяВместеСБуфером");
Возврат ВсеТесты;
КонецФункции
@@ -471,3 +473,27 @@
юТест.ПроверитьРавенство(ПолучитьСтрокуИзБуфераДвоичныхДанных(ДвоичныеДанные, Кодировка), "⌥",
"Неверная Строка из ДвоичныхДанных без BOM для "+Кодировка);
КонецПроцедуры
+
+Процедура ТестДолжен_Проверить_ЗаписьВБуферИзДвоичныхДанныхНеМеняетИх() Экспорт
+
+ ДвоичныеДанные = ПолучитьДвоичныеДанныеИзСтроки("AAAA");
+ Буфер1 = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(ДвоичныеДанные);
+ Буфер2 = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(ДвоичныеДанные);
+
+ Буфер1.Установить(0, КодСимвола("Z"));
+
+ юТест.ПроверитьРавенство("AAAA", ПолучитьСтрокуИзДвоичныхДанных(ДвоичныеДанные));
+ юТест.ПроверитьРавенство(КодСимвола("A"), Буфер2[0]);
+
+КонецПроцедуры
+
+Процедура ТестДолжен_Проверить_ДвоичныеДанныеИзБуфераНеМеняютсяВместеСБуфером() Экспорт
+
+ Буфер = ПолучитьБуферДвоичныхДанныхИзСтроки("AAAA");
+ ДвоичныеДанные = ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Буфер);
+
+ Буфер.Установить(0, КодСимвола("Z"));
+
+ юТест.ПроверитьРавенство("AAAA", ПолучитьСтрокуИзДвоичныхДанных(ДвоичныеДанные));
+
+КонецПроцедуры