Skip to content

Commit a46b2d5

Browse files
Clean-up of TypeFactory (#1483)
* Removing undue global thread sync and concurrency checks, the concurrent dictionary handles already that. * Removing useless singleton pattern, likely a remnant of Java port. * Enabling thread safety test of type factory.
1 parent b20002b commit a46b2d5

File tree

7 files changed

+199
-453
lines changed

7 files changed

+199
-453
lines changed

src/NHibernate.Test/Async/SystemTransactions/DistributedSystemTransactionFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -780,4 +780,4 @@ protected override void Configure(Configuration configuration)
780780
protected override bool AppliesTo(ISessionFactoryImplementor factory)
781781
=> base.AppliesTo(factory) && factory.ConnectionProvider.Driver.SupportsEnlistmentWhenAutoEnlistmentIsDisabled;
782782
}
783-
}
783+
}
Lines changed: 83 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,117 @@
11
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Linq;
25
using System.Threading;
36

47
namespace NHibernate.Test
58
{
69
public class MultiThreadRunner<T>
710
{
811
public delegate void ExecuteAction(T subject);
9-
private readonly int numThreads;
10-
private readonly ExecuteAction[] actions;
11-
private readonly Random rnd = new Random();
12-
private bool running;
13-
private int timeout = 1000;
14-
private int timeoutBetweenThreadStart = 30;
1512

16-
public MultiThreadRunner(int numThreads, ExecuteAction[] actions)
13+
private readonly int _numThreads;
14+
private readonly ExecuteAction[] _actions;
15+
private readonly Random _rnd = new Random();
16+
private volatile bool _running;
17+
private ConcurrentQueue<Exception> _errors = new ConcurrentQueue<Exception>();
18+
19+
public MultiThreadRunner(int numThreads, params ExecuteAction[] actions)
1720
{
18-
if(numThreads < 1)
21+
if (numThreads < 1)
1922
{
20-
throw new ArgumentOutOfRangeException("numThreads",numThreads,"Must be GT 1");
23+
throw new ArgumentOutOfRangeException(nameof(numThreads), numThreads, "Must be GTE 1");
2124
}
2225
if (actions == null || actions.Length == 0)
2326
{
24-
throw new ArgumentNullException("actions");
27+
throw new ArgumentNullException(nameof(actions));
2528
}
26-
foreach (ExecuteAction action in actions)
29+
if (actions.Any(action => action == null))
2730
{
28-
if(action==null)
29-
throw new ArgumentNullException("actions", "null delegate");
31+
throw new ArgumentNullException(nameof(actions), "null delegate");
3032
}
31-
this.numThreads = numThreads;
32-
this.actions = actions;
33+
_numThreads = numThreads;
34+
_actions = actions;
3335
}
3436

35-
public int EndTimeout
36-
{
37-
get { return timeout; }
38-
set { timeout = value; }
39-
}
37+
public int EndTimeout { get; set; } = 1000;
4038

41-
public int TimeoutBetweenThreadStart
42-
{
43-
get { return timeoutBetweenThreadStart; }
44-
set { timeoutBetweenThreadStart = value; }
45-
}
39+
public int TimeoutBetweenThreadStart { get; set; } = 30;
4640

47-
public void Run(T subjectInstance)
48-
{
49-
running = true;
50-
Thread[] t = new Thread[numThreads];
51-
for (int i = 0; i < numThreads; i++)
52-
{
53-
t[i] = new Thread(ThreadProc);
54-
t[i].Name = i.ToString();
55-
t[i].Start(subjectInstance);
56-
if (i > 2)
57-
Thread.Sleep(timeoutBetweenThreadStart);
58-
}
41+
public Exception[] GetErrors() => _errors.ToArray();
42+
public void ClearErrors() => _errors = new ConcurrentQueue<Exception>();
5943

60-
Thread.Sleep(timeout);
44+
public int Run(T subjectInstance)
45+
{
46+
var allThreads = new List<ThreadHolder<T>>();
6147

62-
// Tell the threads to shut down, then wait until they all
63-
// finish.
64-
running = false;
65-
for (int i = 0; i < numThreads; i++)
48+
var launcher = new Thread(
49+
() =>
50+
{
51+
try
52+
{
53+
for (var i = 0; i < _numThreads; i++)
54+
{
55+
var threadHolder = new ThreadHolder<T>
56+
{
57+
Thread = new Thread(ThreadProc) { Name = i.ToString() },
58+
Subject = subjectInstance
59+
};
60+
threadHolder.Thread.Start(threadHolder);
61+
allThreads.Add(threadHolder);
62+
if (i > 2 && TimeoutBetweenThreadStart > 0)
63+
Thread.Sleep(TimeoutBetweenThreadStart);
64+
}
65+
}
66+
catch (Exception e)
67+
{
68+
_errors.Enqueue(e);
69+
throw;
70+
}
71+
});
72+
var totalLoops = 0;
73+
_running = true;
74+
// Use a separated thread for launching in case too many threads are asked: the inner Start will freeze
75+
// but would be able to resume once _running would have been set to false, causing first threads to stop.
76+
launcher.Start();
77+
// Sleep for the required timeout, taking into account the start delay (if all threads are launchable without
78+
// having to wait due to thread starvation).
79+
Thread.Sleep(TimeoutBetweenThreadStart * _numThreads + EndTimeout);
80+
// Tell the threads to shut down, then wait until they all finish.
81+
_running = false;
82+
launcher.Join();
83+
foreach (var threadHolder in allThreads.Where(t => t != null))
6684
{
67-
t[i].Join();
85+
threadHolder.Thread.Join();
86+
totalLoops += threadHolder.LoopsDone;
6887
}
88+
return totalLoops;
6989
}
7090

7191
private void ThreadProc(object arg)
7292
{
73-
T subjectInstance = (T) arg;
74-
while (running)
93+
try
94+
{
95+
var holder = (ThreadHolder<T>) arg;
96+
while (_running)
97+
{
98+
var actionIdx = _rnd.Next(0, _actions.Length);
99+
_actions[actionIdx](holder.Subject);
100+
holder.LoopsDone++;
101+
}
102+
}
103+
catch (Exception e)
75104
{
76-
int actionIdx = rnd.Next(0, actions.Length);
77-
actions[actionIdx](subjectInstance);
105+
_errors.Enqueue(e);
106+
throw;
78107
}
79108
}
109+
110+
private class ThreadHolder<TH>
111+
{
112+
public Thread Thread { get; set; }
113+
public int LoopsDone { get; set; }
114+
public TH Subject { get; set; }
115+
}
80116
}
81117
}

src/NHibernate.Test/SystemTransactions/DistributedSystemTransactionFixture.cs

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -345,44 +345,27 @@ public void TransactionInsertLoadWithRollBackTask(bool explicitFlush)
345345
}
346346
}
347347

348-
private int _totalCall;
349-
350-
[Test, Explicit("Test added for NH-1709 (trying to recreate the issue... without luck). If one thread break the test, you can see the result in the console.")]
348+
[Test, Explicit("Test added for NH-1709 (trying to recreate the issue... without luck).")]
351349
public void MultiThreadedTransaction()
352350
{
353351
// Test added for NH-1709 (trying to recreate the issue... without luck)
354-
// If one thread break the test, you can see the result in the console.
355-
((Logger)_log.Logger).Level = log4net.Core.Level.Debug;
356-
var actions = new MultiThreadRunner<object>.ExecuteAction[]
357-
{
358-
delegate
359-
{
360-
CanRollbackTransaction(false);
361-
_totalCall++;
362-
},
363-
delegate
364-
{
365-
RollbackOutsideNh(false);
366-
_totalCall++;
367-
},
368-
delegate
369-
{
370-
TransactionInsertWithRollBackTask(false);
371-
_totalCall++;
372-
},
373-
delegate
374-
{
375-
TransactionInsertLoadWithRollBackTask(false);
376-
_totalCall++;
377-
},
378-
};
379-
var mtr = new MultiThreadRunner<object>(20, actions)
352+
var mtr = new MultiThreadRunner<object>(
353+
20,
354+
o => CanRollbackTransaction(false),
355+
o => RollbackOutsideNh(false),
356+
o => TransactionInsertWithRollBackTask(false),
357+
o => TransactionInsertLoadWithRollBackTask(false))
380358
{
381359
EndTimeout = 5000,
382360
TimeoutBetweenThreadStart = 5
383361
};
384-
mtr.Run(null);
385-
_log.DebugFormat("{0} calls", _totalCall);
362+
var totalCalls = mtr.Run(null);
363+
_log.DebugFormat("{0} calls", totalCalls);
364+
var errors = mtr.GetErrors();
365+
if (errors.Length > 0)
366+
{
367+
Assert.Fail("One or more thread failed, found {0} errors. First exception: {1}", errors.Length, errors[0]);
368+
}
386369
}
387370

388371
[Theory]
@@ -820,4 +803,4 @@ public void SessionIsNotEnlisted()
820803
}
821804
}
822805
}
823-
}
806+
}

src/NHibernate.Test/TypesTest/TypeFactoryFixture.cs

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -73,42 +73,28 @@ public long? GenericInt64
7373
}
7474

7575
private readonly Random rnd = new Random();
76-
private int totalCall;
7776

78-
[Test, Explicit]
77+
[Test]
7978
public void MultiThreadAccess()
8079
{
8180
// Test added for NH-1251
82-
// If one thread break the test you can see the result in the console.
83-
((Logger) log.Logger).Level = log4net.Core.Level.Debug;
84-
MultiThreadRunner<object>.ExecuteAction[] actions = new MultiThreadRunner<object>.ExecuteAction[]
81+
var mtr = new MultiThreadRunner<object>(
82+
100,
83+
o => TypeFactory.GetStringType(rnd.Next(1, 50)),
84+
o => TypeFactory.GetBinaryType(rnd.Next(1, 50)),
85+
o => TypeFactory.GetSerializableType(rnd.Next(1, 50)),
86+
o => TypeFactory.GetTypeType(rnd.Next(1, 20)))
8587
{
86-
delegate(object o)
87-
{
88-
TypeFactory.GetStringType(rnd.Next(1, 50));
89-
totalCall++;
90-
},
91-
delegate(object o)
92-
{
93-
TypeFactory.GetBinaryType(rnd.Next(1, 50));
94-
totalCall++;
95-
},
96-
delegate(object o)
97-
{
98-
TypeFactory.GetSerializableType(rnd.Next(1, 50));
99-
totalCall++;
100-
},
101-
delegate(object o)
102-
{
103-
TypeFactory.GetTypeType(rnd.Next(1, 20));
104-
totalCall++;
105-
},
88+
EndTimeout = 2000,
89+
TimeoutBetweenThreadStart = 2
10690
};
107-
MultiThreadRunner<object> mtr = new MultiThreadRunner<object>(100, actions);
108-
mtr.EndTimeout = 2000;
109-
mtr.TimeoutBetweenThreadStart = 2;
110-
mtr.Run(null);
111-
log.DebugFormat("{0} calls", totalCall);
91+
var totalCalls = mtr.Run(null);
92+
log.DebugFormat("{0} calls", totalCalls);
93+
var errors = mtr.GetErrors();
94+
if (errors.Length > 0)
95+
{
96+
Assert.Fail("One or more thread failed, found {0} errors. First exception: {1}", errors.Length, errors[0]);
97+
}
11298
}
11399

114100
[Test]

0 commit comments

Comments
 (0)