1
- using System . Collections . Generic ;
1
+ using System ;
2
+ using System . Collections . Generic ;
2
3
using System . Diagnostics ;
3
4
using System . IO ;
4
5
using System . IO . Pipes ;
6
+ using System . Threading ;
7
+ using System . Threading . Tasks ;
5
8
using BenchmarkDotNet . Diagnosers ;
6
9
using BenchmarkDotNet . Engines ;
7
10
using BenchmarkDotNet . Running ;
@@ -11,28 +14,66 @@ namespace BenchmarkDotNet.Loggers
11
14
internal class Broker
12
15
{
13
16
private readonly ILogger logger ;
17
+ private readonly Process process ;
14
18
private readonly IDiagnoser diagnoser ;
15
19
private readonly AnonymousPipeServerStream inputFromBenchmark , acknowledgments ;
16
20
private readonly DiagnoserActionParameters diagnoserActionParameters ;
21
+ private readonly ManualResetEvent finished ;
17
22
18
23
public Broker ( ILogger logger , Process process , IDiagnoser diagnoser ,
19
24
BenchmarkCase benchmarkCase , BenchmarkId benchmarkId , AnonymousPipeServerStream inputFromBenchmark , AnonymousPipeServerStream acknowledgments )
20
25
{
21
26
this . logger = logger ;
27
+ this . process = process ;
22
28
this . diagnoser = diagnoser ;
23
29
this . inputFromBenchmark = inputFromBenchmark ;
24
30
this . acknowledgments = acknowledgments ;
25
31
diagnoserActionParameters = new DiagnoserActionParameters ( process , benchmarkCase , benchmarkId ) ;
32
+ finished = new ManualResetEvent ( false ) ;
26
33
27
34
Results = new List < string > ( ) ;
28
35
PrefixedOutput = new List < string > ( ) ;
36
+
37
+ process . EnableRaisingEvents = true ;
38
+ process . Exited += OnProcessExited ;
29
39
}
30
40
31
41
internal List < string > Results { get ; }
32
42
33
43
internal List < string > PrefixedOutput { get ; }
34
44
35
45
internal void ProcessData ( )
46
+ {
47
+ // When the process fails to start, there is no pipe to read from.
48
+ // If we try to read from such pipe, the read blocks and BDN hangs.
49
+ // We can't use async methods with cancellation tokens because Anonymous Pipes don't support async IO.
50
+
51
+ // Usually, this property is not set yet.
52
+ if ( process . HasExited )
53
+ {
54
+ return ;
55
+ }
56
+
57
+ Task . Run ( ProcessDataBlocking ) ;
58
+
59
+ finished . WaitOne ( ) ;
60
+ finished . Dispose ( ) ;
61
+ }
62
+
63
+ private void OnProcessExited ( object sender , EventArgs e )
64
+ {
65
+ process . Exited -= OnProcessExited ;
66
+
67
+ // Dispose all the pipes to let reading from pipe finish with EOF and avoid a reasource leak.
68
+ inputFromBenchmark . DisposeLocalCopyOfClientHandle ( ) ;
69
+ inputFromBenchmark . Dispose ( ) ;
70
+ acknowledgments . DisposeLocalCopyOfClientHandle ( ) ;
71
+ acknowledgments . Dispose ( ) ;
72
+
73
+ finished . Set ( ) ;
74
+ }
75
+
76
+ private void ProcessDataBlocking ( )
36
77
{
37
78
using StreamReader reader = new ( inputFromBenchmark , AnonymousPipesHost . UTF8NoBOM , detectEncodingFromByteOrderMarks : false ) ;
38
79
using StreamWriter writer = new ( acknowledgments , AnonymousPipesHost . UTF8NoBOM , bufferSize : 1 ) ;
@@ -67,6 +108,8 @@ internal void ProcessData()
67
108
{
68
109
// we have received the last signal so we can stop reading from the pipe
69
110
// if the process won't exit after this, its hung and needs to be killed
111
+ process . Exited -= OnProcessExited ;
112
+ finished . Set ( ) ;
70
113
return ;
71
114
}
72
115
}
0 commit comments