Skip to content

Commit 8c8d8b8

Browse files
authored
Merge pull request #344 from servicetitan/upstream/OpenSystemLogicOnlyRegion
Optimize IDisposable implementations
2 parents b0a738a + 2b4d0d4 commit 8c8d8b8

File tree

11 files changed

+72
-71
lines changed

11 files changed

+72
-71
lines changed

Orm/Xtensive.Orm.Tests/Storage/Prefetch/PrefetchManagerBasicTest.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System;
88
using System.Collections.Generic;
99
using System.Linq;
10+
using System.Runtime.CompilerServices;
1011
using System.Threading;
1112
using NUnit.Framework;
1213
using Xtensive.Core;
@@ -27,16 +28,14 @@ namespace Xtensive.Orm.Tests.Storage.Prefetch
2728
[TestFixture]
2829
public class PrefetchManagerBasicTest : PrefetchManagerTestBase
2930
{
31+
private const int Iterations = 10;
3032
private volatile static int instanceCount;
3133

3234
#region Nested class
3335

3436
public class MemoryLeakTester
3537
{
36-
~MemoryLeakTester()
37-
{
38-
instanceCount--;
39-
}
38+
~MemoryLeakTester() => Interlocked.Decrement(ref instanceCount);
4039
}
4140

4241
#endregion
@@ -932,17 +931,18 @@ public void RemoveTest()
932931
[Test]
933932
public void ReferenceToSessionIsNotPreservedInCacheTest()
934933
{
935-
// Use separate method for session related processing
934+
// Use separate method with [MethodImpl(MethodImplOptions.NoInlining)] attribute for session related processing
936935
// to make sure we don't hold session reference somewhere on stack
937936
OpenSessionsAndRunPrefetches();
938937
TestHelper.CollectGarbage(true);
939938
Assert.That(instanceCount, Is.EqualTo(0));
940939
}
941940

941+
[MethodImpl(MethodImplOptions.NoInlining)]
942942
private void OpenSessionsAndRunPrefetches()
943943
{
944-
instanceCount = 10;
945-
for (int i = 0; i < instanceCount; i++) {
944+
instanceCount = Iterations;
945+
for (int i = 0; i < Iterations; i++) {
946946
using (var session = Domain.OpenSession())
947947
using (var t = session.OpenTransaction()) {
948948
session.Extensions.Set(new MemoryLeakTester());

Orm/Xtensive.Orm/Collections/BindingCollection.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,7 @@ public virtual BindingScope Add(TKey key, TValue value)
112112
public virtual void PermanentAdd(TKey key, TValue value)
113113
{
114114
bindings[key] = value;
115-
if (!permanentBindings.Contains(key)) {
116-
permanentBindings.Add(key);
117-
}
115+
permanentBindings.Add(key);
118116
}
119117

120118
/// <summary>

Orm/Xtensive.Orm/Collections/Graphs/Graph.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ public Graph<Node<TNode>, Edge<TEdge>> CreateMutableCopy()
5656
foreach (var rNode in copy.Nodes) {
5757
var node = rNode.Value;
5858
foreach (var edge in node.Edges) {
59-
if (!processedEdges.Contains(edge)) {
59+
if (processedEdges.Add(edge)) {
6060
var rEdge = new Edge<TEdge>(nodeMap[edge.Source], nodeMap[edge.Target], (TEdge) edge);
61-
processedEdges.Add(edge);
6261
}
6362
}
6463
}

Orm/Xtensive.Orm/Orm/Entity.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,7 @@ public VersionInfo VersionInfo {
138138
List<PrefetchFieldDescriptor> columnsToPrefetch = null;
139139
foreach (var columnInfo in versionColumns) {
140140
if (!tuple.GetFieldState(columnInfo.Field.MappingInfo.Offset).IsAvailable()) {
141-
if (columnsToPrefetch==null)
142-
columnsToPrefetch = new List<PrefetchFieldDescriptor>();
141+
columnsToPrefetch ??= new List<PrefetchFieldDescriptor>(1);
143142
columnsToPrefetch.Add(new PrefetchFieldDescriptor(columnInfo.Field));
144143
}
145144
}

Orm/Xtensive.Orm/Orm/Internals/Pinner.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ namespace Xtensive.Orm.Internals
1414
{
1515
internal sealed class Pinner : SessionBound
1616
{
17+
private class DisposableRemover : IDisposable
18+
{
19+
public Pinner Pinner { get; init; }
20+
public EntityState State { get; init; }
21+
22+
public void Dispose() => Pinner.roots.Remove(State);
23+
}
24+
1725
private readonly HashSet<EntityState> roots = new HashSet<EntityState>();
1826

1927
private EntityChangeRegistry activeRegistry;
@@ -24,15 +32,8 @@ internal sealed class Pinner : SessionBound
2432
public EntityChangeRegistry PinnedItems { get; private set; }
2533
public EntityChangeRegistry PersistableItems { get; private set; }
2634

27-
public IDisposable RegisterRoot(EntityState state)
28-
{
29-
if (roots.Contains(state))
30-
return null;
31-
roots.Add(state);
32-
return new Disposable<Pinner, EntityState>(
33-
this, state,
34-
(disposing, _this, _state) => _this.roots.Remove(_state));
35-
}
35+
public IDisposable RegisterRoot(EntityState state) =>
36+
roots.Add(state) ? new DisposableRemover { Pinner = this, State = state } : null;
3637

3738
public void ClearRoots()
3839
{

Orm/Xtensive.Orm/Orm/Model/FullTextIndexInfoCollection.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ public bool TryGetValue(TypeInfo typeInfo, out FullTextIndexInfo fullTextIndexIn
5454
public void Add(TypeInfo typeInfo, FullTextIndexInfo fullTextIndexInfo)
5555
{
5656
EnsureNotLocked();
57-
if (!container.Contains(fullTextIndexInfo))
58-
container.Add(fullTextIndexInfo);
57+
container.Add(fullTextIndexInfo);
5958
indexMap.Add(typeInfo, fullTextIndexInfo);
6059
}
6160

Orm/Xtensive.Orm/Orm/OperationLog.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public KeyMapping Replay(Session session)
6161
KeyMapping keyMapping;
6262

6363
using (session.Activate()) {
64-
using (isSystemOperationLog ? session.OpenSystemLogicOnlyRegion() : null)
64+
using (isSystemOperationLog ? (IDisposable) session.OpenSystemLogicOnlyRegion() : null)
6565
using (var tx = session.OpenTransaction(TransactionOpenMode.New)) {
6666

6767
foreach (var operation in operations)

Orm/Xtensive.Orm/Orm/Operations/OperationRegistry.cs

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,24 @@ namespace Xtensive.Orm.Operations
1616
/// </summary>
1717
public sealed class OperationRegistry
1818
{
19+
public readonly struct SystemOperationRegistrationScope : IDisposable
20+
{
21+
private readonly OperationRegistry registry;
22+
private readonly bool prevIsSystemOperationRegistrationEnabled;
23+
24+
public SystemOperationRegistrationScope(OperationRegistry registry, bool enable)
25+
{
26+
this.registry = registry;
27+
prevIsSystemOperationRegistrationEnabled = registry.IsSystemOperationRegistrationEnabled;
28+
registry.IsSystemOperationRegistrationEnabled = enable;
29+
}
30+
31+
public void Dispose() => registry.IsSystemOperationRegistrationEnabled = prevIsSystemOperationRegistrationEnabled;
32+
}
33+
1934
private readonly ICompletableScope blockingScope;
2035
private bool isOperationRegistrationEnabled = true;
2136
private bool isUndoOperationRegistrationEnabled = true;
22-
private bool isSystemOperationRegistrationEnabled = true;
2337
private Collections.Deque<ICompletableScope> scopes = new Collections.Deque<ICompletableScope>();
2438

2539
/// <summary>
@@ -41,10 +55,7 @@ public bool IsRegistrationEnabled {
4155
/// <summary>
4256
/// Gets a value indicating whether system operation registration is enabled.
4357
/// </summary>
44-
public bool IsSystemOperationRegistrationEnabled {
45-
get { return isSystemOperationRegistrationEnabled; }
46-
internal set { isSystemOperationRegistrationEnabled = value; }
47-
}
58+
public bool IsSystemOperationRegistrationEnabled { get; internal set; } = true;
4859

4960
/// <summary>
5061
/// Gets a value indicating whether this instance can register operation
@@ -219,29 +230,13 @@ public IDisposable DisableUndoOperationRegistration()
219230
/// Temporarily disables system operation logging.
220231
/// </summary>
221232
/// <returns>An <see cref="IDisposable"/> object enabling the logging back on its disposal.</returns>
222-
public IDisposable DisableSystemOperationRegistration()
223-
{
224-
if (!isSystemOperationRegistrationEnabled)
225-
return null;
226-
var result = new Disposable<OperationRegistry, bool>(this, isSystemOperationRegistrationEnabled,
227-
(disposing, _this, previousState) => _this.isSystemOperationRegistrationEnabled = previousState);
228-
isSystemOperationRegistrationEnabled = false;
229-
return result;
230-
}
233+
public SystemOperationRegistrationScope DisableSystemOperationRegistration() => new(this, false);
231234

232235
/// <summary>
233236
/// Temporarily enables system operation logging.
234237
/// </summary>
235238
/// <returns>An <see cref="IDisposable"/> object disabling the logging back on its disposal.</returns>
236-
public IDisposable EnableSystemOperationRegistration()
237-
{
238-
if (isSystemOperationRegistrationEnabled)
239-
return null;
240-
var result = new Disposable<OperationRegistry, bool>(this, isSystemOperationRegistrationEnabled,
241-
(disposing, _this, previousState) => _this.isSystemOperationRegistrationEnabled = previousState);
242-
isSystemOperationRegistrationEnabled = true;
243-
return result;
244-
}
239+
public SystemOperationRegistrationScope EnableSystemOperationRegistration() => new(this, true);
245240

246241
/// <summary>
247242
/// Registers the operation.

Orm/Xtensive.Orm/Orm/Services/DirectSessionAccessor.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ public sealed class DirectSessionAccessor : SessionBound,
2828
/// disposal will restore previous state of
2929
/// <see cref="Session.IsSystemLogicOnly"/> property.
3030
/// </returns>
31-
public IDisposable OpenSystemLogicOnlyRegion()
32-
{
33-
return Session.OpenSystemLogicOnlyRegion();
34-
}
31+
public Session.SystemLogicOnlyRegionScope OpenSystemLogicOnlyRegion() => Session.OpenSystemLogicOnlyRegion();
3532

3633
/// <summary>
3734
/// Changes the value of <see cref="Session.Handler"/>.

Orm/Xtensive.Orm/Orm/Session.Persist.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ private async ValueTask Persist(PersistReason reason, bool isAsync, Cancellation
224224
}
225225

226226
/// <summary>
227-
/// Temporarily disables all save changes operations (both explicit ant automatic)
227+
/// Temporarily disables all save changes operations (both explicit ant automatic)
228228
/// for specified <paramref name="target"/>.
229229
/// Such entity is prevented from being persisted to the database,
230230
/// when <see cref="SaveChanges"/> is called or query is executed.
@@ -234,16 +234,20 @@ private async ValueTask Persist(PersistReason reason, bool isAsync, Cancellation
234234
/// all entities that reference <paramref name="target"/> are also pinned automatically.
235235
/// </summary>
236236
/// <param name="target">The entity to disable persisting.</param>
237-
/// <returns>A special object that controls lifetime of such behavior if <paramref name="target"/> was not previously processed by the method,
238-
/// otherwise <see langword="null"/>.</returns>
237+
/// <returns>
238+
/// A special object that controls lifetime of such behavior if <paramref name="target"/> was not previously processed by the method
239+
/// and automatic saving of changes is enabled (<see cref="SessionOptions.AutoSaveChanges"/>),
240+
/// otherwise <see langword="null"/>.
241+
/// </returns>
239242
public IDisposable DisableSaveChanges(IEntity target)
240243
{
241244
EnsureNotDisposed();
242245
ArgumentValidator.EnsureArgumentNotNull(target, "target");
246+
if (!Configuration.Supports(SessionOptions.AutoSaveChanges))
247+
return null; // No need to pin in this case
248+
243249
var targetEntity = (Entity) target;
244250
targetEntity.EnsureNotRemoved();
245-
if (!Configuration.Supports(SessionOptions.AutoSaveChanges))
246-
return new Disposable(b => {return;}); // No need to pin in this case
247251
return pinner.RegisterRoot(targetEntity.State);
248252
}
249253

@@ -252,15 +256,15 @@ public IDisposable DisableSaveChanges(IEntity target)
252256
/// Explicit call of <see cref="SaveChanges"/> will lead to flush changes anyway.
253257
/// If save changes is to be performed due to starting a nested transaction or committing a transaction,
254258
/// active disabling save changes scope will lead to failure.
255-
/// <returns>A special object that controls lifetime of such behavior if there is no active scope,
259+
/// <returns>A special object that controls lifetime of such behavior if there is no active scope
260+
/// and automatic saving of changes is enabled (<see cref="SessionOptions.AutoSaveChanges"/>),
256261
/// otherwise <see langword="null"/>.</returns>
257262
/// </summary>
258263
public IDisposable DisableSaveChanges()
259264
{
260-
if (!Configuration.Supports(SessionOptions.AutoSaveChanges))
261-
return new Disposable(b => { return; }); // No need to pin in this case
262-
if (disableAutoSaveChanges)
263-
return null;
265+
if (!Configuration.Supports(SessionOptions.AutoSaveChanges) || disableAutoSaveChanges) {
266+
return null; // No need to pin in these cases
267+
}
264268

265269
disableAutoSaveChanges = true;
266270
return new Disposable(_ => {
@@ -317,7 +321,7 @@ private void CancelEntitiesChanges()
317321
newEntity.Update(null);
318322
newEntity.PersistenceState = PersistenceState.Removed;
319323
}
320-
324+
321325
foreach (var modifiedEntity in EntityChangeRegistry.GetItems(PersistenceState.Modified)) {
322326
modifiedEntity.RollbackDifference();
323327
modifiedEntity.PersistenceState = PersistenceState.Synchronized;

Orm/Xtensive.Orm/Orm/Session.SystemLogic.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,26 @@ namespace Xtensive.Orm
1111
{
1212
public partial class Session
1313
{
14+
public readonly struct SystemLogicOnlyRegionScope : IDisposable
15+
{
16+
private readonly Session session;
17+
private readonly bool prevIsSystemLogicOnly;
18+
19+
internal SystemLogicOnlyRegionScope(Session session)
20+
{
21+
this.session = session;
22+
prevIsSystemLogicOnly = session.IsSystemLogicOnly;
23+
session.IsSystemLogicOnly = true;
24+
}
25+
26+
public void Dispose() => session.IsSystemLogicOnly = prevIsSystemLogicOnly;
27+
}
28+
1429
/// <summary>
1530
/// Gets a value indicating whether only a system logic is enabled.
1631
/// </summary>
1732
internal bool IsSystemLogicOnly { get; set; }
1833

19-
internal IDisposable OpenSystemLogicOnlyRegion()
20-
{
21-
var result = new Disposable<Session, bool>(this, IsSystemLogicOnly,
22-
(disposing, session, previousState) => session.IsSystemLogicOnly = previousState);
23-
IsSystemLogicOnly = true;
24-
return result;
25-
}
34+
internal SystemLogicOnlyRegionScope OpenSystemLogicOnlyRegion() => new(this);
2635
}
2736
}

0 commit comments

Comments
 (0)