Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 3769e15

Browse files
committed
Merge pull request #2185 from stephentoub/process_osx_fix
Fix Process bugs on OSX
2 parents c946bef + 180b029 commit 3769e15

File tree

5 files changed

+105
-55
lines changed

5 files changed

+105
-55
lines changed

src/Common/src/Interop/OSX/Interop.libproc.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ internal static partial class libproc
2525
private const int PROC_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN;
2626
private static int PROC_PIDLISTFD_SIZE = Marshal.SizeOf<proc_fdinfo>();
2727
private static int PROC_PIDLISTTHREADS_SIZE = (Marshal.SizeOf<uint>() * 2);
28-
28+
2929
// Constants from sys\resource.h
3030
private const int RUSAGE_SELF = 0;
3131

@@ -364,6 +364,7 @@ private static unsafe extern int proc_pidinfo(
364364
int result = 0;
365365
int size = 20; // start assuming 20 threads is enough
366366
ulong[] threadIds = null;
367+
var threads = new List<KeyValuePair<ulong, proc_threadinfo?>>();
367368

368369
// We have no way of knowning how many threads the process has (and therefore how big our buffer should be)
369370
// so while the return value of the function is the same as our buffer size (meaning it completely filled
@@ -378,7 +379,10 @@ private static unsafe extern int proc_pidinfo(
378379

379380
if (result <= 0)
380381
{
381-
throw new Win32Exception();
382+
// If we were unable to access the information, just return the empty list.
383+
// This is likely to happen for privileged processes, if the process went away
384+
// by the time we tried to query it, etc.
385+
return threads;
382386
}
383387
else
384388
{
@@ -394,7 +398,7 @@ private static unsafe extern int proc_pidinfo(
394398

395399
// Loop over each thread and get the thread info
396400
int count = (int)(result / Marshal.SizeOf<ulong>());
397-
List<KeyValuePair<ulong, proc_threadinfo?>> threads = new List<KeyValuePair<ulong, proc_threadinfo?>>(count);
401+
threads.Capacity = count;
398402
for (int i = 0; i < count; i++)
399403
{
400404
threads.Add(new KeyValuePair<ulong, proc_threadinfo?>(threadIds[i], GetThreadInfoById(pid, threadIds[i])));
@@ -406,7 +410,7 @@ private static unsafe extern int proc_pidinfo(
406410
/// <summary>
407411
/// Retrieves the number of open file descriptors for the specified pid
408412
/// </summary>
409-
/// <returns>Returns an array of open File Descriptors for this process</returns>
413+
/// <returns>A count of file descriptors for this process.</returns>
410414
/// <remarks>
411415
/// This function doesn't use the helper since it seems to allow passing NULL
412416
/// values in to the buffer and length parameters to get back an estimation
@@ -426,11 +430,14 @@ internal static unsafe int GetFileDescriptorCountForPid(int pid)
426430
int result = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, (proc_fdinfo*)null, 0);
427431
if (result <= 0)
428432
{
429-
throw new Win32Exception();
433+
// If we were unable to access the information, just return the empty list.
434+
// This is likely to happen for privileged processes, if the process went away
435+
// by the time we tried to query it, etc.
436+
return 0;
430437
}
431438

432439
proc_fdinfo[] fds;
433-
int size = (int)(result / Marshal.SizeOf<proc_fdinfo>());
440+
int size = (int)(result / Marshal.SizeOf<proc_fdinfo>()) + 1;
434441

435442
// Just in case the app opened a ton of handles between when we asked and now,
436443
// make sure we retry if our buffer is filled
@@ -444,7 +451,10 @@ internal static unsafe int GetFileDescriptorCountForPid(int pid)
444451

445452
if (result <= 0)
446453
{
447-
throw new Win32Exception();
454+
// If we were unable to access the information, just return the empty list.
455+
// This is likely to happen for privileged processes, if the process went away
456+
// by the time we tried to query it, etc.
457+
return 0;
448458
}
449459
else
450460
{

src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.OSX.cs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Collections.Generic;
5+
using System.Runtime.InteropServices;
56

67
namespace System.Diagnostics
78
{
@@ -17,33 +18,29 @@ public static int[] GetProcessIds()
1718
// ---- PAL layer ends here ----
1819
// -----------------------------
1920

20-
private unsafe static ProcessInfo CreateProcessInfo(int pid)
21+
private static ProcessInfo CreateProcessInfo(int pid)
2122
{
2223
// Negative PIDs aren't valid
2324
if (pid < 0)
2425
{
2526
throw new ArgumentOutOfRangeException("pid");
2627
}
2728

28-
ProcessInfo procInfo = new ProcessInfo();
29+
ProcessInfo procInfo = new ProcessInfo()
30+
{
31+
ProcessId = pid
32+
};
2933

3034
// Try to get the task info. This can fail if the user permissions don't permit
3135
// this user context to query the specified process
3236
Interop.libproc.proc_taskallinfo? info = Interop.libproc.GetProcessInfoById(pid);
3337
if (info.HasValue)
3438
{
35-
// We need to convert the byte pointer to an IntPtr
36-
// that we can pass to the Marshal.PtrToStringAnsi call
37-
// but the nullable struct type makes it difficult to inline,
38-
// so make a temp variable to remove the nullable and get the pointer
39-
Interop.libproc.proc_taskallinfo temp = info.Value;
40-
IntPtr ptrString = new IntPtr(temp.pbsd.pbi_comm);
41-
4239
// Set the values we have; all the other values don't have meaning or don't exist on OSX
40+
Interop.libproc.proc_taskallinfo temp = info.Value;
41+
unsafe { procInfo.ProcessName = Marshal.PtrToStringAnsi(new IntPtr(temp.pbsd.pbi_comm)); }
4342
procInfo.BasePriority = temp.ptinfo.pti_priority;
4443
procInfo.HandleCount = Interop.libproc.GetFileDescriptorCountForPid(pid);
45-
procInfo.ProcessId = pid;
46-
procInfo.ProcessName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptrString);
4744
procInfo.VirtualBytes = (long)temp.ptinfo.pti_virtual_size;
4845
procInfo.WorkingSet = (long)temp.ptinfo.pti_resident_size;
4946
}
@@ -52,19 +49,23 @@ private unsafe static ProcessInfo CreateProcessInfo(int pid)
5249
List<KeyValuePair<ulong, Interop.libproc.proc_threadinfo?>> lstThreads = Interop.libproc.GetAllThreadsInProcess(pid);
5350
foreach (KeyValuePair<ulong, Interop.libproc.proc_threadinfo?> t in lstThreads)
5451
{
52+
var ti = new ThreadInfo()
53+
{
54+
_processId = pid,
55+
_threadId = (int)t.Key, // The OS X thread ID is 64-bits, but we're forced to truncate due to the public API signature
56+
_basePriority = 0,
57+
_startAddress = IntPtr.Zero
58+
};
59+
60+
// Fill in additional info if we were able to retrieve such data about the thread
5561
if (t.Value.HasValue)
5662
{
57-
procInfo._threadInfoList.Add(new ThreadInfo()
58-
{
59-
_basePriority = 0,
60-
_currentPriority = t.Value.Value.pth_curpri,
61-
_processId = pid,
62-
_startAddress = IntPtr.Zero, // We don't have this info
63-
_threadId = Convert.ToInt32(t.Key),
64-
_threadState = ConvertOsxThreadRunStateToThreadState((Interop.libproc.ThreadRunState)t.Value.Value.pth_run_state),
65-
_threadWaitReason = ConvertOsxThreadFlagsToWaitReason((Interop.libproc.ThreadFlags)t.Value.Value.pth_flags)
66-
});
63+
ti._currentPriority = t.Value.Value.pth_curpri;
64+
ti._threadState = ConvertOsxThreadRunStateToThreadState((Interop.libproc.ThreadRunState)t.Value.Value.pth_run_state);
65+
ti._threadWaitReason = ConvertOsxThreadFlagsToWaitReason((Interop.libproc.ThreadFlags)t.Value.Value.pth_flags);
6766
}
67+
68+
procInfo._threadInfoList.Add(ti);
6869
}
6970

7071
return procInfo;

src/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests/ProcessTest.cs

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public void SetAndCheckBasePriority(ProcessPriorityClass exPriorityClass, int pr
7979
Assert.Equal(priority, _process.BasePriority);
8080
}
8181

82-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
82+
[Fact]
8383
public void Process_BasePriority()
8484
{
8585
ProcessPriorityClass originalPriority = _process.PriorityClass;
@@ -119,7 +119,7 @@ public void StartAndKillProcessWithDelay(Process p)
119119
Assert.True(p.WaitForExit(WaitInMS));
120120
}
121121

122-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
122+
[Fact]
123123
public void Process_EnableRaiseEvents()
124124
{
125125
{
@@ -156,7 +156,7 @@ public void Process_EnableRaiseEvents()
156156
}
157157
}
158158

159-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
159+
[Fact]
160160
public void Process_ExitCode()
161161
{
162162
{
@@ -261,11 +261,17 @@ public void Process_MainModule()
261261
}
262262
}
263263

264-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
264+
[Fact]
265265
public void Process_MaxWorkingSet()
266266
{
267-
IntPtr min, max;
268-
uint flags;
267+
using (Process p = Process.GetCurrentProcess())
268+
{
269+
Assert.True((long)p.MaxWorkingSet > 0);
270+
Assert.True((long)p.MinWorkingSet >= 0);
271+
}
272+
273+
if (global::Interop.IsOSX)
274+
return; // doesn't support getting/setting working set for other processes
269275

270276
long curValue = (long)_process.MaxWorkingSet;
271277
Assert.True(curValue >= 0);
@@ -275,6 +281,9 @@ public void Process_MaxWorkingSet()
275281
try
276282
{
277283
_process.MaxWorkingSet = (IntPtr)((int)curValue + 1024);
284+
285+
IntPtr min, max;
286+
uint flags;
278287
Interop.GetProcessWorkingSetSizeEx(_process.SafeHandle, out min, out max, out flags);
279288
curValue = (int)max;
280289
_process.Refresh();
@@ -287,9 +296,18 @@ public void Process_MaxWorkingSet()
287296
}
288297
}
289298

290-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
299+
[Fact]
291300
public void Process_MinWorkingSet()
292301
{
302+
using (Process p = Process.GetCurrentProcess())
303+
{
304+
Assert.True((long)p.MaxWorkingSet > 0);
305+
Assert.True((long)p.MinWorkingSet >= 0);
306+
}
307+
308+
if (global::Interop.IsOSX)
309+
return; // doesn't support getting/setting working set for other processes
310+
293311
long curValue = (long)_process.MinWorkingSet;
294312
Assert.True(curValue >= 0);
295313

@@ -341,57 +359,59 @@ private void AssertNonZeroWindowsZeroUnix(long value)
341359
}
342360
}
343361

344-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
362+
[Fact]
345363
public void Process_NonpagedSystemMemorySize64()
346364
{
347365
AssertNonZeroWindowsZeroUnix(_process.NonpagedSystemMemorySize64);
348366
}
349367

350-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
368+
[Fact]
351369
public void Process_PagedMemorySize64()
352370
{
353371
AssertNonZeroWindowsZeroUnix(_process.PagedMemorySize64);
354372
}
355373

356-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
374+
[Fact]
357375
public void Process_PagedSystemMemorySize64()
358376
{
359377
AssertNonZeroWindowsZeroUnix(_process.PagedSystemMemorySize64);
360378
}
361379

362-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
380+
[Fact]
363381
public void Process_PeakPagedMemorySize64()
364382
{
365383
AssertNonZeroWindowsZeroUnix(_process.PeakPagedMemorySize64);
366384
}
367385

368-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
386+
[Fact]
369387
public void Process_PeakVirtualMemorySize64()
370388
{
371389
AssertNonZeroWindowsZeroUnix(_process.PeakVirtualMemorySize64);
372390
}
373391

374-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
392+
[Fact]
375393
public void Process_PeakWorkingSet64()
376394
{
377395
AssertNonZeroWindowsZeroUnix(_process.PeakWorkingSet64);
378396
}
379397

380-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
398+
[Fact]
381399
public void Process_PrivateMemorySize64()
382400
{
383401
AssertNonZeroWindowsZeroUnix(_process.PrivateMemorySize64);
384402
}
385403

386-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
387-
public void Process_PrivilegedProcessorTime()
404+
[Fact]
405+
[ActiveIssue(2184, PlatformID.OSX)]
406+
public void Process_ProcessorTime()
388407
{
389408
Assert.True(_process.UserProcessorTime.TotalSeconds >= 0);
390409
Assert.True(_process.PrivilegedProcessorTime.TotalSeconds >= 0);
391410
Assert.True(_process.TotalProcessorTime.TotalSeconds >= 0);
392411
}
393412

394-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
413+
[Fact]
414+
[PlatformSpecific(~PlatformID.OSX)] // getting/setting affinity not supported on OSX
395415
public void Process_ProcessorAffinity()
396416
{
397417
IntPtr curProcessorAffinity = _process.ProcessorAffinity;
@@ -449,7 +469,7 @@ public void Process_InvalidPriorityClass()
449469
Assert.Throws<ArgumentException>(() => { p.PriorityClass = ProcessPriorityClass.Normal | ProcessPriorityClass.Idle; });
450470
}
451471

452-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
472+
[Fact]
453473
public void ProcessProcessName()
454474
{
455475
Assert.Equal(_process.ProcessName, CoreRunName, StringComparer.OrdinalIgnoreCase);
@@ -462,7 +482,7 @@ public void ProcessProcessName()
462482
[DllImport("libc")]
463483
internal static extern int getpid();
464484

465-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
485+
[Fact]
466486
public void Process_GetCurrentProcess()
467487
{
468488
Process current = Process.GetCurrentProcess();
@@ -476,7 +496,7 @@ public void Process_GetCurrentProcess()
476496
Assert.Equal(Process.GetProcessById(currentProcessId).ProcessName, Process.GetCurrentProcess().ProcessName);
477497
}
478498

479-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
499+
[Fact]
480500
public void Process_GetProcesses()
481501
{
482502
// Get all the processes running on the machine.
@@ -494,7 +514,7 @@ public void Process_GetProcesses()
494514
Assert.True(foundCurrentProcess, "Process_GetProcesses002 failed");
495515
}
496516

497-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
517+
[Fact]
498518
public void Process_GetProcessesByName()
499519
{
500520
// Get the current process using its name
@@ -760,5 +780,25 @@ public void Process_IPC()
760780
}
761781
}
762782

783+
[Fact]
784+
public void ThreadCount()
785+
{
786+
Assert.True(_process.Threads.Count > 0);
787+
using (Process p = Process.GetCurrentProcess())
788+
{
789+
Assert.True(p.Threads.Count > 0);
790+
}
791+
}
792+
793+
// [Fact] // uncomment for diagnostic purposes to list processes to console
794+
public void ConsoleWriteLineProcesses()
795+
{
796+
foreach (var p in Process.GetProcesses().OrderBy(p => p.Id))
797+
{
798+
Console.WriteLine("{0} : \"{1}\" (Threads: {2})", p.Id, p.ProcessName, p.Threads.Count);
799+
p.Dispose();
800+
}
801+
}
802+
763803
}
764804
}

src/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests/Process_EncodingTest.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Text;
5-
using System.Diagnostics;
65
using Xunit;
76

87
namespace System.Diagnostics.ProcessTests
@@ -23,7 +22,7 @@ public partial class ProcessTest
2322

2423
private const int s_ConsoleEncoding = 437;
2524

26-
[Fact, ActiveIssue(1538, PlatformID.OSX)]
25+
[Fact]
2726
public void Process_EncodingBeforeProvider()
2827
{
2928
Action<int> run = expectedCodePage =>

0 commit comments

Comments
 (0)