Skip to content

Commit a928e5a

Browse files
committed
use an arena allocator to better control lifetime for query allocations
1 parent 3ed9191 commit a928e5a

File tree

5 files changed

+121
-25
lines changed

5 files changed

+121
-25
lines changed

HarmonyCore/FileIO/Queryable/PreparedQueryPlan.dbl

+33-8
Original file line numberDiff line numberDiff line change
@@ -412,21 +412,38 @@ namespace Harmony.Core.FileIO.Queryable
412412
data joinSelectObject, @Synergex.SynergyDE.Select.JoinSelect
413413
data queryBuffer, @QueryBuffer
414414
data fromObj, @From
415-
415+
data cleanedUp = false
416416
lambda cleanup(networkDead)
417417
begin
418+
419+
if(cleanedUp)
420+
throw new Exception("Cleanup called twice")
421+
418422
if(networkDead && queryBuffer != ^null)
419423
queryBuffer.FailedDueToNetwork = true
420424

421-
rows?.Dispose()
425+
;;COMPILER BUG - this should compile but gives a not implemented error
426+
;;foreach data buffer in queryBuffer.TypeBuffers
427+
428+
data buffer, @QueryBuffer.TypeBuffer
429+
foreach buffer in queryBuffer.TypeBuffers
430+
begin
431+
if(buffer.MemoryHandle.Handle == ^null)
432+
throw new Exception("Handle was cleared early")
433+
434+
if(buffer.IOChannel == 0)
435+
throw new Exception("IOChannel was cleared early")
436+
end
437+
fromObj?.Dispose()
422438
joinSelectObject?.Dispose()
423439
queryBuffer?.Dispose()
424-
fromObj?.Dispose()
440+
425441

426442
rows = ^null
427443
joinSelectObject = ^null
428444
queryBuffer = ^null
429445
fromObj = ^null
446+
cleanedUp = true
430447
end
431448
data cleanupAction, @Action<boolean>, cleanup
432449
try
@@ -564,7 +581,11 @@ namespace Harmony.Core.FileIO.Queryable
564581
ThrowIfDisposed()
565582

566583
if(CurrentNestedEnumerator != ^null)
567-
mreturn CurrentNestedEnumerator.MoveNext()
584+
begin
585+
data moveNextResult = CurrentNestedEnumerator.MoveNext()
586+
mreturn moveNextResult
587+
end
588+
568589

569590
data endOfIter = false
570591
do forever
@@ -598,7 +619,8 @@ namespace Harmony.Core.FileIO.Queryable
598619
phase = "FinishIterate"
599620
data fullResult = DataObjectMaterializer.FinishIteration(iterState).GetEnumerator()
600621
CurrentNestedEnumerator = fullResult
601-
mreturn fullResult.MoveNext()
622+
data moveResult = fullResult.MoveNext()
623+
mreturn moveResult
602624
end
603625
end
604626
catch(ex, @Synergex.SynergyDE.NetworkException)
@@ -638,6 +660,9 @@ namespace Harmony.Core.FileIO.Queryable
638660
isFinalizer, boolean
639661
endparams
640662
proc
663+
if(disposed)
664+
mreturn
665+
641666
if(!isFinalizer) then
642667
begin
643668
disposedAt = Environment.StackTrace.ToString()
@@ -947,10 +972,10 @@ namespace Harmony.Core.FileIO.Queryable
947972
endmethod
948973

949974
internal method MakeSparse, @Synergex.SynergyDE.Select.Sparse
950-
recData, a
951-
proc
975+
recData, a
976+
proc
952977
mreturn new Synergex.SynergyDE.Select.Sparse(recData)
953-
endmethod
978+
endmethod
954979

955980

956981
internal method SparseObject, @Synergex.SynergyDE.Select.Sparse

HarmonyCore/FileIO/Queryable/QueryBuffer.dbl

+11-6
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ namespace Harmony.Core.FileIO.Queryable
5555
public readwrite property Context, @object
5656
private DrivingBuffers, @List<QueryBuffer.TypeBuffer>
5757
public readwrite property FailedDueToNetwork, boolean, false
58-
58+
public readwrite property Disposed, boolean, false
59+
internal readwrite property MemoryHandleArena, @MemoryHandleArena, new MemoryHandleArena()
5960
public method GetTop, long?
6061
targetBufferIndex, int
6162
proc
@@ -151,6 +152,7 @@ namespace Harmony.Core.FileIO.Queryable
151152
buf, @QueryBuffer.TypeBuffer
152153
dataObjectProvider, @IDataObjectProvider
153154
bufferMap, @Dictionary<QueryBuffer.TypeBuffer, QueryBuffer.TypeBuffer>
155+
handleArena, @MemoryHandleArena
154156
proc
155157
data madeBuffer, @QueryBuffer.TypeBuffer
156158
if(bufferMap != ^null && bufferMap.TryGetValue(buf,madeBuffer)) then
@@ -164,15 +166,15 @@ namespace Harmony.Core.FileIO.Queryable
164166
& ParentFieldName = buf.ParentFieldName,
165167
& DataObjectType = buf.DataObjectType,
166168
& JoinOn = buf.JoinOn,
167-
& JoinedBuffers = buf.JoinedBuffers.Select(lambda(jBuf) { MakeTypeBuffer(jBuf, dataObjectProvider, bufferMap) }).ToList(),
169+
& JoinedBuffers = buf.JoinedBuffers.Select(lambda(jBuf) { MakeTypeBuffer(jBuf, dataObjectProvider, bufferMap, handleArena) }).ToList(),
168170
& IsInnerJoin = buf.IsInnerJoin,
169171
& OrderBy = buf.OrderBy,
170172
& SelectResult = buf.SelectResult,
171173
& Top = buf.Top,
172174
& Skip = buf.Skip
173175
& }
174176

175-
newTypeBuf.MemoryHandle = buf.Metadata.GetBuffer()
177+
newTypeBuf.MemoryHandle = handleArena.GetMemoryHandle(buf.Metadata.RPSStructureSize)
176178
data fileInfo = dataObjectProvider.GetFileInfo(buf.DataObjectType)
177179
;;this is read only, performance is significantly improved by using an input channel
178180
data fileOpenMode = fileInfo.Item2
@@ -209,7 +211,7 @@ namespace Harmony.Core.FileIO.Queryable
209211
if(buf.MemoryHandle != ^null || buf.IOChannel != ^null )
210212
throw new InvalidOperationException("TypeBuffer should not have its MemoryHandle member set during QueryBuffer construction")
211213

212-
TypeBuffers.Add(MakeTypeBuffer(buf, dataObjectProvider, protoBufferMap))
214+
TypeBuffers.Add(MakeTypeBuffer(buf, dataObjectProvider, protoBufferMap, this.MemoryHandleArena))
213215
end
214216
endmethod
215217

@@ -229,6 +231,9 @@ namespace Harmony.Core.FileIO.Queryable
229231
isFinalizer, boolean
230232
endparams
231233
proc
234+
if(Disposed)
235+
mreturn
236+
232237
data buf, @QueryBuffer.TypeBuffer
233238
data allBuffers, @IEnumerable<QueryBuffer.TypeBuffer>, TypeBuffers
234239
if(DrivingBuffers != ^null)
@@ -243,13 +248,12 @@ namespace Harmony.Core.FileIO.Queryable
243248
data ch, int
244249
ch = tempbuf.IOChannel
245250
DataObjectProvider.ChannelManager.AbandonOpenChannels(ch)
251+
tempbuf.IOChannel = ^null
246252
end
247253
end
248254

249255
foreach buf in allBuffers
250256
begin
251-
if(buf.MemoryHandle != ^null)
252-
buf.Metadata.ReturnBuffer(buf.MemoryHandle)
253257
buf.MemoryHandle = ^null
254258
if(DataObjectProvider != ^null && buf.IOChannel != ^null)
255259
begin
@@ -265,6 +269,7 @@ namespace Harmony.Core.FileIO.Queryable
265269
TypeBuffers.Clear()
266270
DataObjectProvider = ^null
267271
DrivingBuffers = ^null
272+
Disposed = true
268273
endmethod
269274

270275
endclass

HarmonyCore/FileIO/Queryable/SelectBuilder.dbl

+2-2
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ namespace Harmony.Core.FileIO.Queryable
460460
startsWith, boolean
461461
endrecord
462462
proc
463-
disposable data memPnt, @MemoryHandle, MemoryHandle.GetMemoryHandle(fieldDetails.ElementSize)
463+
data memPnt, @MemoryHandle, mQueryBuffer.MemoryHandleArena.GetMemoryHandle(fieldDetails.ElementSize)
464464
theOperator = theOperatorParam
465465
if(theValues.Length > 0)
466466
memPnt.Handle.Store(theValues[1])
@@ -871,7 +871,7 @@ namespace Harmony.Core.FileIO.Queryable
871871
bNum ,d3
872872
endrecord
873873
proc
874-
disposable data memPnt, @MemoryHandle, MemoryHandle.GetMemoryHandle(fieldDetails.ElementSize)
874+
data memPnt, @MemoryHandle, mQueryBuffer.MemoryHandleArena.GetMemoryHandle(fieldDetails.ElementSize)
875875
memPnt.Handle = theValue[1]
876876

877877
data caseInsensitive = theOperator == WhereClauseOperator.NoCaseEqualTo ||

HarmonyCore/Utility/MemoryHandle.dbl

+37-9
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import System.Text
55

66
namespace Harmony.Core.Utility
77

8-
public class MemoryHandle implements IDisposable
8+
public class MemoryHandle
99
public Handle, @Synergex.SynergyDE.AlphaDesc
1010

11-
1211
static MakeNew, @System.Reflection.ConstructorInvoker
1312
static method MemoryHandle
1413
proc
@@ -27,15 +26,17 @@ namespace Harmony.Core.Utility
2726
public static method GetMemoryHandle, @MemoryHandle
2827
size, int
2928
proc
30-
mreturn new MemoryHandle() { Handle = (@Synergex.SynergyDE.AlphaDesc)MakeNew.Invoke(size) }
29+
data result = new MemoryHandle() { Handle = (@Synergex.SynergyDE.AlphaDesc)MakeNew.Invoke(size) }
30+
result.Handle.Clear()
31+
mreturn result
3132
endmethod
3233

3334
public method Range, @Synergex.SynergyDE.AlphaDesc
3435
start, int
3536
length, int
3637
proc
3738
if(Handle == ^null)
38-
throw new System.InvalidOperationException("MemoryHandle is not initialized or already disposed")
39+
throw new System.InvalidOperationException("MemoryHandle is not initialized")
3940

4041
mreturn (@Synergex.SynergyDE.AlphaDesc)Handle.AbsRange(start, (start -1) + length)
4142
endmethod
@@ -44,17 +45,44 @@ namespace Harmony.Core.Utility
4445
length, int
4546
proc
4647
if(Handle == ^null)
47-
throw new System.InvalidOperationException("MemoryHandle is not initialized or already disposed")
48+
throw new System.InvalidOperationException("MemoryHandle is not initialized")
4849

4950
mreturn (@Synergex.SynergyDE.AlphaDesc)Handle.AbsRange(1, length)
5051
endmethod
5152

52-
public method Dispose, void
53+
endclass
54+
55+
public class MemoryHandleArena
56+
private _arenaBlocks, @System.Collections.Generic.List<MemoryHandle>
57+
private _blockPosition, int
58+
private _blockSize, int
59+
public method MemoryHandleArena
5360
proc
54-
;;not actually anything to do here right now
55-
Handle = ^null
61+
_arenaBlocks = new System.Collections.Generic.List<MemoryHandle>()
62+
_blockPosition = 0
63+
_blockSize = 64 * 1024
5664
endmethod
5765

58-
endclass
66+
public method GetMemoryHandle, @MemoryHandle
67+
size, int
68+
proc
69+
if(_arenaBlocks.Count == 0)
70+
begin
71+
_arenaBlocks.Add(MemoryHandle.GetMemoryHandle(_blockSize))
72+
end
73+
74+
data block = _arenaBlocks[_arenaBlocks.Count - 1]
75+
if(_blockPosition + size >= _blockSize)
76+
begin
77+
block = MemoryHandle.GetMemoryHandle(_blockSize)
78+
_arenaBlocks.Add(block)
79+
end
80+
81+
data result = block.Range(_blockPosition + 1, size)
82+
_blockPosition += size
83+
mreturn new MemoryHandle() { Handle = result }
84+
endmethod
85+
endclass
86+
5987

6088
endnamespace

Services.Test.CS/UnitTest1.cs

+38
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
using Microsoft.VisualStudio.TestTools.UnitTesting;
44
using Services.Models;
55
using System;
6+
using System.Collections.Generic;
67
using System.Data;
78
using System.Linq;
9+
using System.Threading;
10+
using System.Threading.Tasks;
811

912
namespace Services.Test.CS
1013
{
@@ -216,6 +219,41 @@ public void IncludeWhere()
216219
}
217220
}
218221

222+
[TestMethod]
223+
public void IncludeDeep()
224+
{
225+
var tasks = new List<Task>();
226+
for (int taskN = 0; taskN < 20; taskN++)
227+
{
228+
var task = Task.Run(() =>
229+
{
230+
for (int i = 0; i < 10000; i++)
231+
{
232+
using (var sp = BaseServiceProvider.Services)
233+
{
234+
using (var context = sp.ServiceProvider.GetService<Services.Models.DbContext>())
235+
{
236+
var customersQuerable = context.Customers.AsNoTracking().Include(customer => customer.REL_CustomerFavoriteItem).ThenInclude(itm => itm.REL_OrderItems).ThenInclude(orderItem => orderItem.REL_Order);
237+
var customers = customersQuerable.ToList();
238+
239+
Assert.AreEqual(customers.Count, 38);
240+
}
241+
}
242+
}
243+
});
244+
tasks.Add(task);
245+
}
246+
247+
248+
var allResult = Task.WhenAll(tasks.ToArray());
249+
while(allResult.IsCompleted == false)
250+
{
251+
GC.Collect(1, GCCollectionMode.Forced, false, false);
252+
GC.WaitForPendingFinalizers();
253+
}
254+
255+
}
256+
219257
[TestMethod]
220258
public void IncludeFirstOrDefault()
221259
{

0 commit comments

Comments
 (0)