From 89783d43acc3f15a92dbe377cab50cf15254425f Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 3 Jun 2022 09:00:20 -0700 Subject: [PATCH 01/12] Implement resource.getrlimit --- Src/IronPython.Modules/resource.cs | 179 ++++++++++++++++++ Tests/modules/system_related/test_resource.py | 23 +++ 2 files changed, 202 insertions(+) create mode 100644 Src/IronPython.Modules/resource.cs create mode 100644 Tests/modules/system_related/test_resource.py diff --git a/Src/IronPython.Modules/resource.cs b/Src/IronPython.Modules/resource.cs new file mode 100644 index 000000000..2f8b1e757 --- /dev/null +++ b/Src/IronPython.Modules/resource.cs @@ -0,0 +1,179 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +using Microsoft.Scripting.Runtime; +using IronPython.Runtime; +using IronPython.Runtime.Exceptions; +using IronPython.Runtime.Operations; +using IronPython.Runtime.Types; + +[assembly: PythonModule("resource", typeof(IronPython.Modules.PythonResourceModule), PlatformsAttribute.PlatformFamily.Unix)] +namespace IronPython.Modules; + +[SupportedOSPlatform("linux")] +[SupportedOSPlatform("macos")] +public static class PythonResourceModule { + public const string __doc__ = "Provides basic mechanisms for measuring and controlling system resources utilized by a program. Unix only."; + + [Obsolete("Deprecated in favor of OSError.")] + public static PythonType error => PythonExceptions.OSError; + + #region Constants + + public static BigInteger RLIM_INFINITY = ulong.MaxValue; // CPython/macOS convention and consistent with values returned by this implementation + + public static int RLIMIT_CPU + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)macos__rlimit_resource.RLIMIT_CPU : (int)linux__rlimit_resource.RLIMIT_CPU; + + public static int RLIMIT_FSIZE + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)macos__rlimit_resource.RLIMIT_FSIZE : (int)linux__rlimit_resource.RLIMIT_FSIZE; + + public static int RLIMIT_DATA + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)macos__rlimit_resource.RLIMIT_DATA : (int)linux__rlimit_resource.RLIMIT_DATA; + + public static int RLIMIT_STACK + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)macos__rlimit_resource.RLIMIT_STACK : (int)linux__rlimit_resource.RLIMIT_STACK; + + public static int RLIMIT_CORE + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)macos__rlimit_resource.RLIMIT_CORE : (int)linux__rlimit_resource.RLIMIT_CORE; + + public static int RLIMIT_RSS + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)macos__rlimit_resource.RLIMIT_RSS : (int)linux__rlimit_resource.RLIMIT_RSS; + + public static int RLIMIT_AS + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)macos__rlimit_resource.RLIMIT_AS : (int)linux__rlimit_resource.RLIMIT_AS; + + public static int RLIMIT_MEMLOCK + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)macos__rlimit_resource.RLIMIT_MEMLOCK : (int)linux__rlimit_resource.RLIMIT_MEMLOCK; + + public static int RLIMIT_NPROC + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)macos__rlimit_resource.RLIMIT_NPROC : (int)linux__rlimit_resource.RLIMIT_NPROC; + + public static int RLIMIT_NOFILE + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)macos__rlimit_resource.RLIMIT_NOFILE : (int)linux__rlimit_resource.RLIMIT_NOFILE; + + [PythonHidden(PlatformID.MacOSX)] + public static int RLIMIT_OFILE => RLIMIT_NOFILE; + + [PythonHidden(PlatformID.MacOSX)] + public static int RLIMIT_LOCKS => (int)linux__rlimit_resource.RLIMIT_LOCKS; + + [PythonHidden(PlatformID.MacOSX)] + public static int RLIMIT_SIGPENDING => (int)linux__rlimit_resource.RLIMIT_SIGPENDING; + + [PythonHidden(PlatformID.MacOSX)] + public static int RLIMIT_MSGQUEUE => (int)linux__rlimit_resource.RLIMIT_MSGQUEUE; + + [PythonHidden(PlatformID.MacOSX)] + public static int RLIMIT_NICE => (int)linux__rlimit_resource.RLIMIT_NICE; + + [PythonHidden(PlatformID.MacOSX)] + public static int RLIMIT_RTPRIO => (int)linux__rlimit_resource.RLIMIT_RTPRIO; + + [PythonHidden(PlatformID.MacOSX)] + public static int RLIMIT_RTTIME => (int)linux__rlimit_resource.RLIMIT_RTTIME; + + private static int RLIM_NLIMITS + => Environment.OSVersion.Platform == PlatformID.MacOSX ? + (int)(macos__rlimit_resource.RLIM_NLIMITS) : (int)(linux__rlimit_resource.RLIM_NLIMITS); + + #endregion + + public static PythonTuple getrlimit(int resource) { + if (resource < 0 || resource >= RLIM_NLIMITS) { + throw PythonOps.ValueError("invalid resource specified"); + } + + IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); + try { + int err = getrlimit_linux(resource, ptr); + ThrowIfError(err); + rlimit res = Marshal.PtrToStructure(ptr); + + return PythonTuple.MakeTuple(res.rlim_cur.ToPythonInt(), res.rlim_max.ToPythonInt()); + } finally { + Marshal.FreeHGlobal(ptr); + } + } + + private static void ThrowIfError(int err) { + if (err != 0) { +#if NET60_OR_GREATER + int errno = Marshal.GetLastPInvokeError(); +#else + int errno = Marshal.GetLastWin32Error(); +#endif + throw PythonOps.OSError(errno, PythonNT.strerror(errno)); + } + } + + private static object ToPythonInt(this ulong value) + => value <= (ulong)int.MaxValue ? (int)value : (BigInteger)value; + + +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + private struct rlimit { + public ulong rlim_cur; + public ulong rlim_max; + } +#pragma warning restore CS0649 + + [DllImport("libc", SetLastError = true, EntryPoint = "getrlimit")] + private static extern int getrlimit_linux(int __resource, IntPtr __rlimits); + + enum macos__rlimit_resource { + RLIMIT_CPU = 0, + RLIMIT_FSIZE = 1, + RLIMIT_DATA = 2, + RLIMIT_STACK = 3, + RLIMIT_CORE = 4, + RLIMIT_RSS = 5, + RLIMIT_AS = 5, + RLIMIT_MEMLOCK = 6, + RLIMIT_NPROC = 7, + RLIMIT_NOFILE = 8, + + RLIM_NLIMITS + } + + enum linux__rlimit_resource { + // /usr/include/x86_64-linux-gnu/sys/resource.h + + RLIMIT_CPU = 0, // Per-process CPU limit + RLIMIT_FSIZE = 1, // Maximum filesize + RLIMIT_DATA = 2, // Maximum size of data segment + RLIMIT_STACK = 3, // Maximum size of stack segment + RLIMIT_CORE = 4, // Maximum size of core file + RLIMIT_RSS = 5, // Largest resident set size + RLIMIT_NPROC = 6, // Maximum number of processes + RLIMIT_NOFILE = 7, // Maximum number of open files + RLIMIT_MEMLOCK = 8, // Maximum locked-in-memory address space + RLIMIT_AS = 9, // Address space limit + RLIMIT_LOCKS = 10, // Maximum number of file locks + RLIMIT_SIGPENDING = 11, // Maximum number of pending signals + RLIMIT_MSGQUEUE = 12, // Maximum bytes in POSIX message queues + RLIMIT_NICE = 13, // Maximum nice prio allowed to raise to (20 added to get a non-negative number) + RLIMIT_RTPRIO = 14, // Maximum realtime priority + RLIMIT_RTTIME = 15, // Time slice in us + + RLIM_NLIMITS + }; +} diff --git a/Tests/modules/system_related/test_resource.py b/Tests/modules/system_related/test_resource.py new file mode 100644 index 000000000..d08cee8df --- /dev/null +++ b/Tests/modules/system_related/test_resource.py @@ -0,0 +1,23 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the Apache 2.0 License. +# See the LICENSE file in the project root for more information. + +import os +import sys +import unittest +import resource + +from iptest import IronPythonTestCase, is_cli, is_linux, path_modifier, run_test, skipUnlessIronPython + +class ResourceTest(IronPythonTestCase): + def test_getrlimit(self): + RLIM_NLIMITS = 16 if is_linux else 9 + + for r in range(RLIM_NLIMITS): + lims = resource.getrlimit(r) + self.assertIsInstance(lims, tuple) + self.assertEqual(len(lims), 2) + self.assertIsInstance(lims[0], int) + self.assertIsInstance(lims[1], int) + +run_test(__name__) From d6c5b4d3732b8e8b05e05c1052d1976289cbd7cf Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 3 Jun 2022 10:57:43 -0700 Subject: [PATCH 02/12] Implement resource.setrlimit --- Src/IronPython.Modules/resource.cs | 75 ++++++++++++++----- .../Cases/IronPythonCasesManifest.ini | 3 + Tests/modules/system_related/test_resource.py | 71 +++++++++++++++++- 3 files changed, 126 insertions(+), 23 deletions(-) diff --git a/Src/IronPython.Modules/resource.cs b/Src/IronPython.Modules/resource.cs index 2f8b1e757..8300be7dd 100644 --- a/Src/IronPython.Modules/resource.cs +++ b/Src/IronPython.Modules/resource.cs @@ -5,9 +5,11 @@ #nullable enable using System; +using System.Collections; using System.Numerics; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using static System.Environment; using Microsoft.Scripting.Runtime; using IronPython.Runtime; @@ -28,46 +30,48 @@ public static class PythonResourceModule { #region Constants - public static BigInteger RLIM_INFINITY = ulong.MaxValue; // CPython/macOS convention and consistent with values returned by this implementation + public static BigInteger RLIM_INFINITY + => OSVersion.Platform == PlatformID.MacOSX ? + (BigInteger)long.MaxValue : BigInteger.MinusOne; public static int RLIMIT_CPU - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)macos__rlimit_resource.RLIMIT_CPU : (int)linux__rlimit_resource.RLIMIT_CPU; public static int RLIMIT_FSIZE - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)macos__rlimit_resource.RLIMIT_FSIZE : (int)linux__rlimit_resource.RLIMIT_FSIZE; public static int RLIMIT_DATA - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)macos__rlimit_resource.RLIMIT_DATA : (int)linux__rlimit_resource.RLIMIT_DATA; public static int RLIMIT_STACK - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)macos__rlimit_resource.RLIMIT_STACK : (int)linux__rlimit_resource.RLIMIT_STACK; public static int RLIMIT_CORE - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)macos__rlimit_resource.RLIMIT_CORE : (int)linux__rlimit_resource.RLIMIT_CORE; public static int RLIMIT_RSS - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)macos__rlimit_resource.RLIMIT_RSS : (int)linux__rlimit_resource.RLIMIT_RSS; public static int RLIMIT_AS - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)macos__rlimit_resource.RLIMIT_AS : (int)linux__rlimit_resource.RLIMIT_AS; public static int RLIMIT_MEMLOCK - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)macos__rlimit_resource.RLIMIT_MEMLOCK : (int)linux__rlimit_resource.RLIMIT_MEMLOCK; public static int RLIMIT_NPROC - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)macos__rlimit_resource.RLIMIT_NPROC : (int)linux__rlimit_resource.RLIMIT_NPROC; public static int RLIMIT_NOFILE - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)macos__rlimit_resource.RLIMIT_NOFILE : (int)linux__rlimit_resource.RLIMIT_NOFILE; [PythonHidden(PlatformID.MacOSX)] @@ -92,7 +96,7 @@ public static int RLIMIT_NOFILE public static int RLIMIT_RTTIME => (int)linux__rlimit_resource.RLIMIT_RTTIME; private static int RLIM_NLIMITS - => Environment.OSVersion.Platform == PlatformID.MacOSX ? + => OSVersion.Platform == PlatformID.MacOSX ? (int)(macos__rlimit_resource.RLIM_NLIMITS) : (int)(linux__rlimit_resource.RLIM_NLIMITS); #endregion @@ -114,6 +118,39 @@ public static PythonTuple getrlimit(int resource) { } } + public static void setrlimit(int resource, [NotNone] IEnumerable limits) { + if (resource < 0 || resource >= RLIM_NLIMITS) { + throw PythonOps.ValueError("invalid resource specified"); + } + + rlimit data; + var cursor = limits.GetEnumerator(); + data.rlim_cur = GetLimitValue(cursor); + data.rlim_max = GetLimitValue(cursor); + if (cursor.MoveNext()) ThrowValueError(); + + IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); + try { + Marshal.StructureToPtr(data, ptr, fDeleteOld: false); + int err = setrlimit_linux(resource, ptr); + ThrowIfError(err); + } finally { + Marshal.FreeHGlobal(ptr); + } + + static long GetLimitValue(IEnumerator cursor) { + if (!cursor.MoveNext() || !PythonOps.TryToIndex(cursor.Current, out BigInteger lim)) + ThrowValueError(); + + long rlim = checked((long)lim); + if (rlim < 0 && OSVersion.Platform == PlatformID.MacOSX) + rlim -= long.MinValue; + return rlim; + } + + static void ThrowValueError() => throw PythonOps.ValueError("expected a tuple of 2 integers"); + } + private static void ThrowIfError(int err) { if (err != 0) { #if NET60_OR_GREATER @@ -125,20 +162,20 @@ private static void ThrowIfError(int err) { } } - private static object ToPythonInt(this ulong value) - => value <= (ulong)int.MaxValue ? (int)value : (BigInteger)value; + private static object ToPythonInt(this long value) + => value is <= int.MaxValue and >= int.MinValue ? (int)value : (BigInteger)value; - -#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value private struct rlimit { - public ulong rlim_cur; - public ulong rlim_max; + public long rlim_cur; + public long rlim_max; } -#pragma warning restore CS0649 [DllImport("libc", SetLastError = true, EntryPoint = "getrlimit")] private static extern int getrlimit_linux(int __resource, IntPtr __rlimits); + [DllImport("libc", SetLastError = true, EntryPoint = "setrlimit")] + private static extern int setrlimit_linux(int __resource, IntPtr __rlimits); + enum macos__rlimit_resource { RLIMIT_CPU = 0, RLIMIT_FSIZE = 1, diff --git a/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini b/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini index 86411e169..bb483604d 100644 --- a/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini +++ b/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini @@ -159,6 +159,9 @@ NotParallelSafe=true # Uses a temporary module with a fixed name [IronPython.modules.type_related.test_bitfields_ctypes_stdlib] RunCondition=NOT $(IS_OSX) # ctypes tests not prepared for macOS +[IronPython.modules.system_related.test_resource] +RunCondition=$(IS_POSIX) # Posix-only module + [IronPython.modules.system_related.test_sys_getframe] IsolationLevel=PROCESS # https://github.com/IronLanguages/ironpython3/issues/489 FullFrames=true diff --git a/Tests/modules/system_related/test_resource.py b/Tests/modules/system_related/test_resource.py index d08cee8df..e083e7a46 100644 --- a/Tests/modules/system_related/test_resource.py +++ b/Tests/modules/system_related/test_resource.py @@ -2,22 +2,85 @@ # The .NET Foundation licenses this file to you under the Apache 2.0 License. # See the LICENSE file in the project root for more information. +# tests fro module 'resource' (Posix only) + import os import sys import unittest import resource -from iptest import IronPythonTestCase, is_cli, is_linux, path_modifier, run_test, skipUnlessIronPython +from iptest import IronPythonTestCase, is_cli, is_linux, is_osx, run_test, skipUnlessIronPython class ResourceTest(IronPythonTestCase): - def test_getrlimit(self): - RLIM_NLIMITS = 16 if is_linux else 9 + def setUp(self): + self.RLIM_NLIMITS = 16 if is_linux else 9 - for r in range(RLIM_NLIMITS): + def test_infinity(self): + if is_osx: + self.assertEqual(resource.RLIM_INFINITY, (1<<63)-1) + else: + self.assertEqual(resource.RLIM_INFINITY, -1) + + def test_getrlimit(self): + for r in range(self.RLIM_NLIMITS): lims = resource.getrlimit(r) self.assertIsInstance(lims, tuple) self.assertEqual(len(lims), 2) self.assertIsInstance(lims[0], int) self.assertIsInstance(lims[1], int) + self.assertRaises(TypeError, resource.getrlimit, None) + self.assertRaises(TypeError, resource.getrlimit, "abc") + self.assertRaises(TypeError, resource.getrlimit, 4.0) + self.assertRaises(ValueError, resource.getrlimit, -1) + self.assertRaises(ValueError, resource.getrlimit, self.RLIM_NLIMITS) + + def test_setrlimit(self): + r = resource.RLIMIT_CORE + lims = resource.getrlimit(r) + rlim_max = lims[1] + # usually max core size is unlimited so a good resource limit to test setting + if (rlim_max == resource.RLIM_INFINITY): + try: + resource.setrlimit(r, (0, rlim_max)) + self.assertEqual(resource.getrlimit(r), (0, rlim_max) ) + + resource.setrlimit(r, (10, rlim_max)) + self.assertEqual(resource.getrlimit(r), (10, rlim_max) ) + + resource.setrlimit(r, [0, rlim_max]) # using a list + self.assertEqual(resource.getrlimit(r), (0, rlim_max) ) + + resource.setrlimit(r, (resource.RLIM_INFINITY, rlim_max)) + self.assertEqual(resource.getrlimit(r), (resource.RLIM_INFINITY, rlim_max) ) + + resource.setrlimit(r, (-1, rlim_max)) + self.assertEqual(resource.getrlimit(r), (resource.RLIM_INFINITY, rlim_max) ) + + resource.setrlimit(r, (-2, rlim_max)) + self.assertEqual(resource.getrlimit(r), (resource.RLIM_INFINITY-1, rlim_max) ) + + resource.setrlimit(r, ((1<<63)-1, rlim_max)) + self.assertEqual(resource.getrlimit(r), ((1<<63)-1, rlim_max) ) + + resource.setrlimit(r, (-(1<<63), rlim_max)) + if is_osx: + self.assertEqual(resource.getrlimit(r), (0, rlim_max) ) + else: + self.assertEqual(resource.getrlimit(r), (-(1<<63), rlim_max) ) + + finally: + resource.setrlimit(r, lims) + + def test_setrlimit_error(self): + self.assertRaises(TypeError, resource.setrlimit, None, (0, 0)) + self.assertRaises(TypeError, resource.setrlimit, "abc", (0, 0)) + self.assertRaises(TypeError, resource.setrlimit, 4.0, (0, 0)) + self.assertRaises(ValueError, resource.setrlimit, -1, (0, 0)) + self.assertRaises(ValueError, resource.setrlimit, self.RLIM_NLIMITS, (0, 0)) + self.assertRaises(ValueError, resource.setrlimit, 0, (0,)) + self.assertRaises(ValueError, resource.setrlimit, 0, (0, 0, 0)) + self.assertRaises(ValueError, resource.setrlimit, 0, (2.3, 0, 0)) + self.assertRaises(TypeError, resource.setrlimit, 0, None) + run_test(__name__) From 8519ff54cfe36c9fd38a450e30084ff0e8e56e34 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 3 Jun 2022 10:58:25 -0700 Subject: [PATCH 03/12] Implement resource.getpagesize --- Src/IronPython.Modules/resource.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Src/IronPython.Modules/resource.cs b/Src/IronPython.Modules/resource.cs index 8300be7dd..788b4ccdf 100644 --- a/Src/IronPython.Modules/resource.cs +++ b/Src/IronPython.Modules/resource.cs @@ -99,6 +99,13 @@ private static int RLIM_NLIMITS => OSVersion.Platform == PlatformID.MacOSX ? (int)(macos__rlimit_resource.RLIM_NLIMITS) : (int)(linux__rlimit_resource.RLIM_NLIMITS); + public static int RUSAGE_SELF => 0; + + public static int RUSAGE_CHILDREN => -1; + + [PythonHidden(PlatformID.MacOSX)] + public static int RUSAGE_THREAD => 1; + #endregion public static PythonTuple getrlimit(int resource) { @@ -151,13 +158,13 @@ static long GetLimitValue(IEnumerator cursor) { static void ThrowValueError() => throw PythonOps.ValueError("expected a tuple of 2 integers"); } + public static BigInteger getpagesize() { + return Mono.Unix.Native.Syscall.sysconf(Mono.Unix.Native.SysconfName._SC_PAGESIZE); + } + private static void ThrowIfError(int err) { if (err != 0) { -#if NET60_OR_GREATER - int errno = Marshal.GetLastPInvokeError(); -#else - int errno = Marshal.GetLastWin32Error(); -#endif + int errno = Marshal.GetLastWin32Error(); // despite its name, on Posix it retrieves errno set by the last p/Invoke call throw PythonOps.OSError(errno, PythonNT.strerror(errno)); } } From 7ba86edc5fc2dad0dd5e2955e34637a743434846 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 3 Jun 2022 12:54:09 -0700 Subject: [PATCH 04/12] Implement resource.getrusage --- Src/IronPython.Modules/resource.cs | 153 ++++++++++++++++-- Tests/modules/system_related/test_resource.py | 43 ++++- 2 files changed, 184 insertions(+), 12 deletions(-) diff --git a/Src/IronPython.Modules/resource.cs b/Src/IronPython.Modules/resource.cs index 788b4ccdf..848db8b1e 100644 --- a/Src/IronPython.Modules/resource.cs +++ b/Src/IronPython.Modules/resource.cs @@ -2,21 +2,27 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +// TODO: use LightThrowing +// TODO: use RuntimeInformation.IsOSPlatform(OSPlatform.OSX) +// TODO: Port to maxOS + #nullable enable using System; -using System.Collections; +using System.Collections.Generic; using System.Numerics; +using System.Linq; using System.Runtime.InteropServices; using System.Runtime.Versioning; -using static System.Environment; +using static System.Environment; // TODO: remove -using Microsoft.Scripting.Runtime; +using IronPython; using IronPython.Runtime; using IronPython.Runtime.Exceptions; using IronPython.Runtime.Operations; using IronPython.Runtime.Types; + [assembly: PythonModule("resource", typeof(IronPython.Modules.PythonResourceModule), PlatformsAttribute.PlatformFamily.Unix)] namespace IronPython.Modules; @@ -125,13 +131,13 @@ public static PythonTuple getrlimit(int resource) { } } - public static void setrlimit(int resource, [NotNone] IEnumerable limits) { + public static void setrlimit(int resource, [NotNone] object limits) { if (resource < 0 || resource >= RLIM_NLIMITS) { throw PythonOps.ValueError("invalid resource specified"); } rlimit data; - var cursor = limits.GetEnumerator(); + var cursor = PythonOps.GetEnumerator(limits); data.rlim_cur = GetLimitValue(cursor); data.rlim_max = GetLimitValue(cursor); if (cursor.MoveNext()) ThrowValueError(); @@ -145,7 +151,7 @@ public static void setrlimit(int resource, [NotNone] IEnumerable limits) { Marshal.FreeHGlobal(ptr); } - static long GetLimitValue(IEnumerator cursor) { + static long GetLimitValue(System.Collections.IEnumerator cursor) { if (!cursor.MoveNext() || !PythonOps.TryToIndex(cursor.Current, out BigInteger lim)) ThrowValueError(); @@ -158,8 +164,107 @@ static long GetLimitValue(IEnumerator cursor) { static void ThrowValueError() => throw PythonOps.ValueError("expected a tuple of 2 integers"); } - public static BigInteger getpagesize() { - return Mono.Unix.Native.Syscall.sysconf(Mono.Unix.Native.SysconfName._SC_PAGESIZE); + public static object getrusage(int who) { + int maxWho = OSVersion.Platform == PlatformID.MacOSX ? 0 : 1; + if (who < -1 || who > maxWho) throw PythonOps.ValueError("invalid who parameter"); + + IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); + try { + int err = getrusage_linux(who, ptr); + ThrowIfError(err); + rusage res = Marshal.PtrToStructure(ptr); + + return new struct_rusage(res); + } finally { + Marshal.FreeHGlobal(ptr); + } + + throw new NotImplementedException(); + } + + public static object getpagesize() { + return Mono.Unix.Native.Syscall.sysconf(Mono.Unix.Native.SysconfName._SC_PAGESIZE).ToPythonInt(); + } + + [PythonType] + public sealed class struct_rusage : PythonTuple { + public const int n_fields = 16; + public const int n_sequence_fields = 16; + public const int n_unnamed_fields = 0; + + private struct_rusage(object?[] sequence) : base(sequence) { + if (__len__() != n_sequence_fields) + throw PythonOps.TypeError("resource.struct_rusage() takes a {0}-sequence ({1}-sequence given)", n_sequence_fields, __len__()); + } + + private struct_rusage(object o) : base(o) { + if (__len__() != n_sequence_fields) + throw PythonOps.TypeError("resource.struct_rusage() takes a {0}-sequence ({1}-sequence given)", n_sequence_fields, __len__()); + } + + internal struct_rusage(rusage data) + : this( + new object[n_sequence_fields] { + data.ru_utime.tv_sec + data.ru_utime.tv_usec * 1e-6, + data.ru_stime.tv_sec + data.ru_stime.tv_usec * 1e-6, + data.ru_maxrss.ToPythonInt(), + data.ru_ixrss.ToPythonInt(), + data.ru_idrss.ToPythonInt(), + data.ru_isrss.ToPythonInt(), + data.ru_minflt.ToPythonInt(), + data.ru_majflt.ToPythonInt(), + data.ru_nswap.ToPythonInt(), + data.ru_inblock.ToPythonInt(), + data.ru_oublock.ToPythonInt(), + data.ru_msgsnd.ToPythonInt(), + data.ru_msgrcv.ToPythonInt(), + data.ru_nsignals.ToPythonInt(), + data.ru_nvcsw.ToPythonInt(), + data.ru_nivcsw.ToPythonInt(), + } + ) { } + + public static struct_rusage __new__([NotNone] PythonType cls, [NotNone] object sequence, PythonDictionary? dict = null) + => new struct_rusage(sequence); + + public object? ru_utime => this[0]; + public object? ru_stime => this[1]; + public object? ru_maxrss => this[2]; + public object? ru_ixrss => this[3]; + public object? ru_idrss => this[4]; + public object? ru_isrss => this[5]; + public object? ru_minflt => this[6]; + public object? ru_majflt => this[7]; + public object? ru_nswap => this[8]; + public object? ru_inblock => this[9]; + public object? ru_oublock => this[10]; + public object? ru_msgsnd => this[11]; + public object? ru_msgrcv => this[12]; + public object? ru_nsignals => this[13]; + public object? ru_nvcsw => this[14]; + public object? ru_nivcsw => this[15]; + + public override string __repr__(CodeContext/*!*/ context) { + return string.Format("resource.struct_rusage(" + + "ru_utime={0}, " + + "ru_stime={1}, " + + "ru_maxrss={2}, " + + "ru_ixrss={3}, " + + "ru_idrss={4}, " + + "ru_isrss={5}, " + + "ru_minflt={6}, " + + "ru_majflt={7}, " + + "ru_nswap={8}, " + + "ru_inblock={9}, " + + "ru_oublock={10}, " + + "ru_msgsnd={11}, " + + "ru_msgrcv={12}, " + + "ru_nsignals={13}, " + + "ru_nvcsw={14}, " + + "ru_nivcsw={15})", + this.Select(v => PythonOps.Repr(context, v)).ToArray()); + } + } private static void ThrowIfError(int err) { @@ -173,17 +278,43 @@ private static object ToPythonInt(this long value) => value is <= int.MaxValue and >= int.MinValue ? (int)value : (BigInteger)value; private struct rlimit { + // /usr/include/x86_64-linux-gnu/bits/resource.h public long rlim_cur; public long rlim_max; } +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + internal struct rusage { + // /usr/include/x86_64-linux-gnu/bits/types/struct_rusage.h + public Mono.Unix.Native.Timeval ru_utime; /* user CPU time used */ + public Mono.Unix.Native.Timeval ru_stime; /* system CPU time used */ + public long ru_maxrss; /* maximum resident set size */ + public long ru_ixrss; /* integral shared memory size */ + public long ru_idrss; /* integral unshared data size */ + public long ru_isrss; /* integral unshared stack size */ + public long ru_minflt; /* page reclaims (soft page faults) */ + public long ru_majflt; /* page faults (hard page faults) */ + public long ru_nswap; /* swaps */ + public long ru_inblock; /* block input operations */ + public long ru_oublock; /* block output operations */ + public long ru_msgsnd; /* IPC messages sent */ + public long ru_msgrcv; /* IPC messages received */ + public long ru_nsignals; /* signals received */ + public long ru_nvcsw; /* voluntary context switches */ + public long ru_nivcsw; /* involuntary context switches */ + } +#pragma warning restore CS0649 + [DllImport("libc", SetLastError = true, EntryPoint = "getrlimit")] private static extern int getrlimit_linux(int __resource, IntPtr __rlimits); [DllImport("libc", SetLastError = true, EntryPoint = "setrlimit")] private static extern int setrlimit_linux(int __resource, IntPtr __rlimits); - enum macos__rlimit_resource { + [DllImport("libc", SetLastError = true, EntryPoint = "getrusage")] + private static extern int getrusage_linux(int __who, IntPtr __usage); + + private enum macos__rlimit_resource { RLIMIT_CPU = 0, RLIMIT_FSIZE = 1, RLIMIT_DATA = 2, @@ -198,8 +329,8 @@ enum macos__rlimit_resource { RLIM_NLIMITS } - enum linux__rlimit_resource { - // /usr/include/x86_64-linux-gnu/sys/resource.h + private enum linux__rlimit_resource { + // /usr/include/x86_64-linux-gnu/sys/resource.h RLIMIT_CPU = 0, // Per-process CPU limit RLIMIT_FSIZE = 1, // Maximum filesize diff --git a/Tests/modules/system_related/test_resource.py b/Tests/modules/system_related/test_resource.py index e083e7a46..0ef4c3360 100644 --- a/Tests/modules/system_related/test_resource.py +++ b/Tests/modules/system_related/test_resource.py @@ -38,6 +38,7 @@ def test_getrlimit(self): def test_setrlimit(self): r = resource.RLIMIT_CORE lims = resource.getrlimit(r) + rlim_cur = lims[0] rlim_max = lims[1] # usually max core size is unlimited so a good resource limit to test setting if (rlim_max == resource.RLIM_INFINITY): @@ -69,8 +70,10 @@ def test_setrlimit(self): else: self.assertEqual(resource.getrlimit(r), (-(1<<63), rlim_max) ) + resource.setrlimit(resource.RLIMIT_CORE, (0, rlim_max-1)) # lower + # resource.setrlimit(resource.RLIMIT_CORE, (0, rlim_max)) # TODO: expeced ValueError, got OSError finally: - resource.setrlimit(r, lims) + resource.setrlimit(r, (rlim_cur, rlim_max-1)) def test_setrlimit_error(self): self.assertRaises(TypeError, resource.setrlimit, None, (0, 0)) @@ -83,4 +86,42 @@ def test_setrlimit_error(self): self.assertRaises(ValueError, resource.setrlimit, 0, (2.3, 0, 0)) self.assertRaises(TypeError, resource.setrlimit, 0, None) + def test_pagesize(self): + ps = resource.getpagesize() + self.assertIsInstance(ps, int) + self.assertTrue(ps > 0) + self.assertTrue((ps & (ps-1) == 0)) # ps is power of 2 + + def test_getrusage(self): + self.assertEqual(resource.struct_rusage.n_fields, 16) + self.assertEqual(resource.struct_rusage.n_sequence_fields, 16) + self.assertEqual(resource.struct_rusage.n_unnamed_fields, 0) + + ru = resource.getrusage(resource.RUSAGE_SELF) + self.assertIsInstance(ru, resource.struct_rusage) + self.assertEqual(len(ru), resource.struct_rusage.n_fields) + self.assertIsInstance(ru[0], float) + self.assertIsInstance(ru[1], float) + for i in range(2, resource.struct_rusage.n_fields): + self.assertIsInstance(ru[i], int) + + ru2 = resource.struct_rusage(ru) + self.assertEqual(ru, ru2) + + ru2 = resource.struct_rusage(ru, {}) + self.assertEqual(ru, ru2) + + ru2 = resource.struct_rusage(ru, {'ru_utime': 0.0, 'foo': 'bar'}) # dict is ignored + self.assertEqual(ru, ru2) + + self.assertRaises(TypeError, resource.struct_rusage) + self.assertRaises(TypeError, resource.struct_rusage, 0) + self.assertRaises(TypeError, resource.struct_rusage, range(15)) + self.assertRaises(TypeError, resource.struct_rusage, range(17)) + self.assertRaises(TypeError, resource.struct_rusage, range(16), 0) + + ru2 = resource.struct_rusage(range(resource.struct_rusage.n_sequence_fields)) + self.assertEqual(ru2[15], 15) + self.assertEqual(ru2.ru_nivcsw, 15) + run_test(__name__) From c725914330bfc30d4df57cfa3a436625486cc3e5 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 3 Jun 2022 13:03:48 -0700 Subject: [PATCH 05/12] Implement resources.prlimit --- Src/IronPython.Modules/resource.cs | 69 ++++++++++++++++++- .../Cases/IronPythonCasesManifest.ini | 3 - Tests/modules/system_related/test_resource.py | 23 +++++-- 3 files changed, 84 insertions(+), 11 deletions(-) diff --git a/Src/IronPython.Modules/resource.cs b/Src/IronPython.Modules/resource.cs index 848db8b1e..aaef85102 100644 --- a/Src/IronPython.Modules/resource.cs +++ b/Src/IronPython.Modules/resource.cs @@ -141,6 +141,7 @@ public static void setrlimit(int resource, [NotNone] object limits) { data.rlim_cur = GetLimitValue(cursor); data.rlim_max = GetLimitValue(cursor); if (cursor.MoveNext()) ThrowValueError(); + if ((ulong)data.rlim_cur > (ulong)data.rlim_max) throw PythonOps.ValueError("current limit exceed maximum limit"); IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { @@ -164,6 +165,64 @@ static long GetLimitValue(System.Collections.IEnumerator cursor) { static void ThrowValueError() => throw PythonOps.ValueError("expected a tuple of 2 integers"); } + public static PythonTuple prlimit(int pid, int resource) { + if (resource < 0 || resource >= RLIM_NLIMITS) { + throw PythonOps.ValueError("invalid resource specified"); + } + + IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); + try { + int err = prlimit_linux(pid, resource, IntPtr.Zero, ptr); + ThrowIfError(err); + rlimit res = Marshal.PtrToStructure(ptr); + + return PythonTuple.MakeTuple(res.rlim_cur.ToPythonInt(), res.rlim_max.ToPythonInt()); + } finally { + Marshal.FreeHGlobal(ptr); + } + } + + public static PythonTuple prlimit(int pid, int resource, [NotNone] object limits) { + if (resource < 0 || resource >= RLIM_NLIMITS) { + throw PythonOps.ValueError("invalid resource specified"); + } + + rlimit data; + var cursor = PythonOps.GetEnumerator(limits); + data.rlim_cur = GetLimitValue(cursor); + data.rlim_max = GetLimitValue(cursor); + if (cursor.MoveNext()) ThrowValueError(); + if ((ulong)data.rlim_cur > (ulong)data.rlim_max) throw PythonOps.ValueError("current limit exceed maximum limit"); + + IntPtr ptr_new = IntPtr.Zero; + IntPtr ptr_old = IntPtr.Zero; + try { + ptr_new = Marshal.AllocHGlobal(Marshal.SizeOf()); + ptr_old = Marshal.AllocHGlobal(Marshal.SizeOf()); + Marshal.StructureToPtr(data, ptr_new, fDeleteOld: false); + int err = prlimit_linux(pid, resource, ptr_new, ptr_old); + ThrowIfError(err); + rlimit res = Marshal.PtrToStructure(ptr_old); + + return PythonTuple.MakeTuple(res.rlim_cur.ToPythonInt(), res.rlim_max.ToPythonInt()); + } finally { + if (ptr_new != IntPtr.Zero) Marshal.FreeHGlobal(ptr_new); + if (ptr_old != IntPtr.Zero) Marshal.FreeHGlobal(ptr_old); + } + + static long GetLimitValue(System.Collections.IEnumerator cursor) { + if (!cursor.MoveNext() || !PythonOps.TryToIndex(cursor.Current, out BigInteger lim)) + ThrowValueError(); + + long rlim = checked((long)lim); + if (rlim < 0 && OSVersion.Platform == PlatformID.MacOSX) + rlim -= long.MinValue; + return rlim; + } + + static void ThrowValueError() => throw PythonOps.ValueError("expected a tuple of 2 integers"); + } + public static object getrusage(int who) { int maxWho = OSVersion.Platform == PlatformID.MacOSX ? 0 : 1; if (who < -1 || who > maxWho) throw PythonOps.ValueError("invalid who parameter"); @@ -306,13 +365,17 @@ internal struct rusage { #pragma warning restore CS0649 [DllImport("libc", SetLastError = true, EntryPoint = "getrlimit")] - private static extern int getrlimit_linux(int __resource, IntPtr __rlimits); + private static extern int getrlimit_linux(int resource, /*rlimit*/ IntPtr rlimits); [DllImport("libc", SetLastError = true, EntryPoint = "setrlimit")] - private static extern int setrlimit_linux(int __resource, IntPtr __rlimits); + private static extern int setrlimit_linux(int resource, /*const rlimit*/ IntPtr rlimits); + + [DllImport("libc", SetLastError = true, EntryPoint = "prlimit")] + private static extern int prlimit_linux(int pid, int resource, /*const rlimit*/ IntPtr new_limit, /*rlimit*/ IntPtr old_limit); [DllImport("libc", SetLastError = true, EntryPoint = "getrusage")] - private static extern int getrusage_linux(int __who, IntPtr __usage); + private static extern int getrusage_linux(int who, /*rusage*/ IntPtr usage); + private enum macos__rlimit_resource { RLIMIT_CPU = 0, diff --git a/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini b/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini index bb483604d..86411e169 100644 --- a/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini +++ b/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini @@ -159,9 +159,6 @@ NotParallelSafe=true # Uses a temporary module with a fixed name [IronPython.modules.type_related.test_bitfields_ctypes_stdlib] RunCondition=NOT $(IS_OSX) # ctypes tests not prepared for macOS -[IronPython.modules.system_related.test_resource] -RunCondition=$(IS_POSIX) # Posix-only module - [IronPython.modules.system_related.test_sys_getframe] IsolationLevel=PROCESS # https://github.com/IronLanguages/ironpython3/issues/489 FullFrames=true diff --git a/Tests/modules/system_related/test_resource.py b/Tests/modules/system_related/test_resource.py index 0ef4c3360..1762502ac 100644 --- a/Tests/modules/system_related/test_resource.py +++ b/Tests/modules/system_related/test_resource.py @@ -4,23 +4,32 @@ # tests fro module 'resource' (Posix only) -import os -import sys import unittest -import resource -from iptest import IronPythonTestCase, is_cli, is_linux, is_osx, run_test, skipUnlessIronPython +from iptest import IronPythonTestCase, is_cli, is_posix, is_linux, is_osx, run_test, skipUnlessIronPython + +if is_posix: + import resource +else: + try: + import resource + except ImportError: + pass + else: + raise AssertionError("There should be no module resource on Windows") class ResourceTest(IronPythonTestCase): def setUp(self): self.RLIM_NLIMITS = 16 if is_linux else 9 + @unittest.skipUnless(is_posix, "Posix-specific test") def test_infinity(self): if is_osx: self.assertEqual(resource.RLIM_INFINITY, (1<<63)-1) else: self.assertEqual(resource.RLIM_INFINITY, -1) + @unittest.skipUnless(is_posix, "Posix-specific test") def test_getrlimit(self): for r in range(self.RLIM_NLIMITS): lims = resource.getrlimit(r) @@ -35,6 +44,7 @@ def test_getrlimit(self): self.assertRaises(ValueError, resource.getrlimit, -1) self.assertRaises(ValueError, resource.getrlimit, self.RLIM_NLIMITS) + @unittest.skipUnless(is_posix, "Posix-specific test") def test_setrlimit(self): r = resource.RLIMIT_CORE lims = resource.getrlimit(r) @@ -71,10 +81,11 @@ def test_setrlimit(self): self.assertEqual(resource.getrlimit(r), (-(1<<63), rlim_max) ) resource.setrlimit(resource.RLIMIT_CORE, (0, rlim_max-1)) # lower - # resource.setrlimit(resource.RLIMIT_CORE, (0, rlim_max)) # TODO: expeced ValueError, got OSError + # resource.setrlimit(resource.RLIMIT_CORE, (0, rlim_max)) # TODO: expected ValueError, got OSError finally: resource.setrlimit(r, (rlim_cur, rlim_max-1)) + @unittest.skipUnless(is_posix, "Posix-specific test") def test_setrlimit_error(self): self.assertRaises(TypeError, resource.setrlimit, None, (0, 0)) self.assertRaises(TypeError, resource.setrlimit, "abc", (0, 0)) @@ -86,12 +97,14 @@ def test_setrlimit_error(self): self.assertRaises(ValueError, resource.setrlimit, 0, (2.3, 0, 0)) self.assertRaises(TypeError, resource.setrlimit, 0, None) + @unittest.skipUnless(is_posix, "Posix-specific test") def test_pagesize(self): ps = resource.getpagesize() self.assertIsInstance(ps, int) self.assertTrue(ps > 0) self.assertTrue((ps & (ps-1) == 0)) # ps is power of 2 + @unittest.skipUnless(is_posix, "Posix-specific test") def test_getrusage(self): self.assertEqual(resource.struct_rusage.n_fields, 16) self.assertEqual(resource.struct_rusage.n_sequence_fields, 16) From 15cd2cfab9229d4aea00367465fff6b42dda5cf5 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 3 Jun 2022 13:06:05 -0700 Subject: [PATCH 06/12] Use RuntimeInformation.IsOSPlatform --- Src/IronPython.Modules/resource.cs | 34 +++++++++++++----------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/Src/IronPython.Modules/resource.cs b/Src/IronPython.Modules/resource.cs index aaef85102..d173578a0 100644 --- a/Src/IronPython.Modules/resource.cs +++ b/Src/IronPython.Modules/resource.cs @@ -3,20 +3,16 @@ // See the LICENSE file in the project root for more information. // TODO: use LightThrowing -// TODO: use RuntimeInformation.IsOSPlatform(OSPlatform.OSX) // TODO: Port to maxOS #nullable enable using System; -using System.Collections.Generic; using System.Numerics; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.Versioning; -using static System.Environment; // TODO: remove -using IronPython; using IronPython.Runtime; using IronPython.Runtime.Exceptions; using IronPython.Runtime.Operations; @@ -37,47 +33,47 @@ public static class PythonResourceModule { #region Constants public static BigInteger RLIM_INFINITY - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (BigInteger)long.MaxValue : BigInteger.MinusOne; public static int RLIMIT_CPU - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)macos__rlimit_resource.RLIMIT_CPU : (int)linux__rlimit_resource.RLIMIT_CPU; public static int RLIMIT_FSIZE - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)macos__rlimit_resource.RLIMIT_FSIZE : (int)linux__rlimit_resource.RLIMIT_FSIZE; public static int RLIMIT_DATA - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)macos__rlimit_resource.RLIMIT_DATA : (int)linux__rlimit_resource.RLIMIT_DATA; public static int RLIMIT_STACK - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)macos__rlimit_resource.RLIMIT_STACK : (int)linux__rlimit_resource.RLIMIT_STACK; public static int RLIMIT_CORE - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)macos__rlimit_resource.RLIMIT_CORE : (int)linux__rlimit_resource.RLIMIT_CORE; public static int RLIMIT_RSS - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)macos__rlimit_resource.RLIMIT_RSS : (int)linux__rlimit_resource.RLIMIT_RSS; public static int RLIMIT_AS - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)macos__rlimit_resource.RLIMIT_AS : (int)linux__rlimit_resource.RLIMIT_AS; public static int RLIMIT_MEMLOCK - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)macos__rlimit_resource.RLIMIT_MEMLOCK : (int)linux__rlimit_resource.RLIMIT_MEMLOCK; public static int RLIMIT_NPROC - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)macos__rlimit_resource.RLIMIT_NPROC : (int)linux__rlimit_resource.RLIMIT_NPROC; public static int RLIMIT_NOFILE - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)macos__rlimit_resource.RLIMIT_NOFILE : (int)linux__rlimit_resource.RLIMIT_NOFILE; [PythonHidden(PlatformID.MacOSX)] @@ -102,7 +98,7 @@ public static int RLIMIT_NOFILE public static int RLIMIT_RTTIME => (int)linux__rlimit_resource.RLIMIT_RTTIME; private static int RLIM_NLIMITS - => OSVersion.Platform == PlatformID.MacOSX ? + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? (int)(macos__rlimit_resource.RLIM_NLIMITS) : (int)(linux__rlimit_resource.RLIM_NLIMITS); public static int RUSAGE_SELF => 0; @@ -157,7 +153,7 @@ static long GetLimitValue(System.Collections.IEnumerator cursor) { ThrowValueError(); long rlim = checked((long)lim); - if (rlim < 0 && OSVersion.Platform == PlatformID.MacOSX) + if (rlim < 0 && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) rlim -= long.MinValue; return rlim; } @@ -215,7 +211,7 @@ static long GetLimitValue(System.Collections.IEnumerator cursor) { ThrowValueError(); long rlim = checked((long)lim); - if (rlim < 0 && OSVersion.Platform == PlatformID.MacOSX) + if (rlim < 0 && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) rlim -= long.MinValue; return rlim; } @@ -224,7 +220,7 @@ static long GetLimitValue(System.Collections.IEnumerator cursor) { } public static object getrusage(int who) { - int maxWho = OSVersion.Platform == PlatformID.MacOSX ? 0 : 1; + int maxWho = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0 : 1; if (who < -1 || who > maxWho) throw PythonOps.ValueError("invalid who parameter"); IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); From 22449391fa5122877b3f03b3d41ed4d594bead9b Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 3 Jun 2022 13:52:16 -0700 Subject: [PATCH 07/12] Additional tests --- Tests/modules/system_related/test_resource.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Tests/modules/system_related/test_resource.py b/Tests/modules/system_related/test_resource.py index 1762502ac..7b7840119 100644 --- a/Tests/modules/system_related/test_resource.py +++ b/Tests/modules/system_related/test_resource.py @@ -80,10 +80,9 @@ def test_setrlimit(self): else: self.assertEqual(resource.getrlimit(r), (-(1<<63), rlim_max) ) - resource.setrlimit(resource.RLIMIT_CORE, (0, rlim_max-1)) # lower - # resource.setrlimit(resource.RLIMIT_CORE, (0, rlim_max)) # TODO: expected ValueError, got OSError + self.assertRaises(ValueError, resource.setrlimit, r, (rlim_max, rlim_max-1)) finally: - resource.setrlimit(r, (rlim_cur, rlim_max-1)) + resource.setrlimit(r, lims) @unittest.skipUnless(is_posix, "Posix-specific test") def test_setrlimit_error(self): @@ -97,6 +96,21 @@ def test_setrlimit_error(self): self.assertRaises(ValueError, resource.setrlimit, 0, (2.3, 0, 0)) self.assertRaises(TypeError, resource.setrlimit, 0, None) + @unittest.skipUnless(is_posix, "Posix-specific test") + def test_prlimit(self): + r = resource.RLIMIT_CORE + lims = resource.getrlimit(r) + self.assertEqual(resource.prlimit(0, r), lims) + rlim_cur = lims[0] + rlim_max = lims[1] + # usually max core size is unlimited so a good resource limit to test setting + if (rlim_max == resource.RLIM_INFINITY): + try: + resource.prlimit(0, r, (1024, rlim_max)) + self.assertEqual(resource.getrlimit(r), (1024, rlim_max) ) + finally: + resource.prlimit(0, r, lims) + @unittest.skipUnless(is_posix, "Posix-specific test") def test_pagesize(self): ps = resource.getpagesize() From e875e8b38882f8eb60a301bf3372b7b7770c304a Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Sat, 4 Jun 2022 11:13:24 -0700 Subject: [PATCH 08/12] Use light throwing --- Src/IronPython.Modules/resource.cs | 212 +++++++++++++++-------------- 1 file changed, 107 insertions(+), 105 deletions(-) diff --git a/Src/IronPython.Modules/resource.cs b/Src/IronPython.Modules/resource.cs index d173578a0..86d0ae6ae 100644 --- a/Src/IronPython.Modules/resource.cs +++ b/Src/IronPython.Modules/resource.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -// TODO: use LightThrowing // TODO: Port to maxOS #nullable enable @@ -13,15 +12,16 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; +using Microsoft.Scripting.Runtime; using IronPython.Runtime; using IronPython.Runtime.Exceptions; using IronPython.Runtime.Operations; using IronPython.Runtime.Types; - [assembly: PythonModule("resource", typeof(IronPython.Modules.PythonResourceModule), PlatformsAttribute.PlatformFamily.Unix)] namespace IronPython.Modules; + [SupportedOSPlatform("linux")] [SupportedOSPlatform("macos")] public static class PythonResourceModule { @@ -110,85 +110,86 @@ private static int RLIM_NLIMITS #endregion - public static PythonTuple getrlimit(int resource) { + [LightThrowing] + public static object getrlimit(int resource) { if (resource < 0 || resource >= RLIM_NLIMITS) { - throw PythonOps.ValueError("invalid resource specified"); + return LightExceptions.Throw(PythonOps.ValueError("invalid resource specified")); } IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { - int err = getrlimit_linux(resource, ptr); - ThrowIfError(err); - rlimit res = Marshal.PtrToStructure(ptr); + int retval = getrlimit_linux(resource, ptr); + if (retval != 0) return GetPInvokeError(); + rlimit res = Marshal.PtrToStructure(ptr); return PythonTuple.MakeTuple(res.rlim_cur.ToPythonInt(), res.rlim_max.ToPythonInt()); } finally { Marshal.FreeHGlobal(ptr); } } - public static void setrlimit(int resource, [NotNone] object limits) { + [LightThrowing] + public static object? setrlimit(int resource, [NotNone] object limits) { if (resource < 0 || resource >= RLIM_NLIMITS) { - throw PythonOps.ValueError("invalid resource specified"); + return LightExceptions.Throw(PythonOps.ValueError("invalid resource specified")); } rlimit data; var cursor = PythonOps.GetEnumerator(limits); - data.rlim_cur = GetLimitValue(cursor); - data.rlim_max = GetLimitValue(cursor); - if (cursor.MoveNext()) ThrowValueError(); - if ((ulong)data.rlim_cur > (ulong)data.rlim_max) throw PythonOps.ValueError("current limit exceed maximum limit"); + if (GetLimitValue(cursor) is not long rlim_cur) return LimitsArgError(); + data.rlim_cur = unchecked((ulong)rlim_cur); + + if (GetLimitValue(cursor) is not long rlim_max) return LimitsArgError(); + data.rlim_max = unchecked((ulong)rlim_max); + + if (cursor.MoveNext()) return LimitsArgError(); + if (data.rlim_cur > data.rlim_max) return LightExceptions.Throw(PythonOps.ValueError("current limit exceed maximum limit")); IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { Marshal.StructureToPtr(data, ptr, fDeleteOld: false); - int err = setrlimit_linux(resource, ptr); - ThrowIfError(err); + int retval = setrlimit_linux(resource, ptr); + if (retval != 0) return GetPInvokeError(); } finally { Marshal.FreeHGlobal(ptr); } - - static long GetLimitValue(System.Collections.IEnumerator cursor) { - if (!cursor.MoveNext() || !PythonOps.TryToIndex(cursor.Current, out BigInteger lim)) - ThrowValueError(); - - long rlim = checked((long)lim); - if (rlim < 0 && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - rlim -= long.MinValue; - return rlim; - } - - static void ThrowValueError() => throw PythonOps.ValueError("expected a tuple of 2 integers"); + return null; } - public static PythonTuple prlimit(int pid, int resource) { + [LightThrowing] + public static object prlimit(int pid, int resource) { if (resource < 0 || resource >= RLIM_NLIMITS) { - throw PythonOps.ValueError("invalid resource specified"); + return LightExceptions.Throw(PythonOps.ValueError("invalid resource specified")); } IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { - int err = prlimit_linux(pid, resource, IntPtr.Zero, ptr); - ThrowIfError(err); - rlimit res = Marshal.PtrToStructure(ptr); + int retval = prlimit_linux(pid, resource, IntPtr.Zero, ptr); + if (retval != 0) return GetPInvokeError(); + rlimit res = Marshal.PtrToStructure(ptr); return PythonTuple.MakeTuple(res.rlim_cur.ToPythonInt(), res.rlim_max.ToPythonInt()); } finally { Marshal.FreeHGlobal(ptr); } } - public static PythonTuple prlimit(int pid, int resource, [NotNone] object limits) { + [LightThrowing] + public static object prlimit(int pid, int resource, [NotNone] object limits) { if (resource < 0 || resource >= RLIM_NLIMITS) { - throw PythonOps.ValueError("invalid resource specified"); + return LightExceptions.Throw(PythonOps.ValueError("invalid resource specified")); } rlimit data; var cursor = PythonOps.GetEnumerator(limits); - data.rlim_cur = GetLimitValue(cursor); - data.rlim_max = GetLimitValue(cursor); - if (cursor.MoveNext()) ThrowValueError(); - if ((ulong)data.rlim_cur > (ulong)data.rlim_max) throw PythonOps.ValueError("current limit exceed maximum limit"); + if (GetLimitValue(cursor) is not long rlim_cur) return LimitsArgError(); + data.rlim_cur = unchecked((ulong)rlim_cur); + + if (GetLimitValue(cursor) is not long rlim_max) return LimitsArgError(); + data.rlim_max = unchecked((ulong)rlim_max); + + if (cursor.MoveNext()) return LimitsArgError(); + if (data.rlim_cur > data.rlim_max) return LightExceptions.Throw(PythonOps.ValueError("current limit exceed maximum limit")); IntPtr ptr_new = IntPtr.Zero; IntPtr ptr_old = IntPtr.Zero; @@ -196,45 +197,32 @@ public static PythonTuple prlimit(int pid, int resource, [NotNone] object limits ptr_new = Marshal.AllocHGlobal(Marshal.SizeOf()); ptr_old = Marshal.AllocHGlobal(Marshal.SizeOf()); Marshal.StructureToPtr(data, ptr_new, fDeleteOld: false); - int err = prlimit_linux(pid, resource, ptr_new, ptr_old); - ThrowIfError(err); - rlimit res = Marshal.PtrToStructure(ptr_old); + int retval = prlimit_linux(pid, resource, ptr_new, ptr_old); + if (retval != 0) return GetPInvokeError(); + rlimit res = Marshal.PtrToStructure(ptr_old); return PythonTuple.MakeTuple(res.rlim_cur.ToPythonInt(), res.rlim_max.ToPythonInt()); } finally { if (ptr_new != IntPtr.Zero) Marshal.FreeHGlobal(ptr_new); if (ptr_old != IntPtr.Zero) Marshal.FreeHGlobal(ptr_old); } - - static long GetLimitValue(System.Collections.IEnumerator cursor) { - if (!cursor.MoveNext() || !PythonOps.TryToIndex(cursor.Current, out BigInteger lim)) - ThrowValueError(); - - long rlim = checked((long)lim); - if (rlim < 0 && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - rlim -= long.MinValue; - return rlim; - } - - static void ThrowValueError() => throw PythonOps.ValueError("expected a tuple of 2 integers"); } + [LightThrowing] public static object getrusage(int who) { int maxWho = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0 : 1; - if (who < -1 || who > maxWho) throw PythonOps.ValueError("invalid who parameter"); + if (who < -1 || who > maxWho) return LightExceptions.Throw(PythonOps.ValueError("invalid who parameter")); IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { - int err = getrusage_linux(who, ptr); - ThrowIfError(err); - rusage res = Marshal.PtrToStructure(ptr); + int retval = getrusage_linux(who, ptr); + if (retval != 0) return GetPInvokeError(); + rusage res = Marshal.PtrToStructure(ptr); return new struct_rusage(res); } finally { Marshal.FreeHGlobal(ptr); } - - throw new NotImplementedException(); } public static object getpagesize() { @@ -322,41 +310,55 @@ public override string __repr__(CodeContext/*!*/ context) { } - private static void ThrowIfError(int err) { - if (err != 0) { - int errno = Marshal.GetLastWin32Error(); // despite its name, on Posix it retrieves errno set by the last p/Invoke call - throw PythonOps.OSError(errno, PythonNT.strerror(errno)); - } + private static long? GetLimitValue(System.Collections.IEnumerator cursor) { + if (!cursor.MoveNext() || !PythonOps.TryToIndex(cursor.Current, out BigInteger lim)) + return null; + + long rlim = checked((long)lim); + if (rlim < 0 && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + rlim -= long.MinValue; + return rlim; + } + + private static object LimitsArgError() + => LightExceptions.Throw(PythonOps.ValueError("expected a tuple of 2 integers")); + + private static object GetPInvokeError() { + int errno = Marshal.GetLastWin32Error(); // despite its name, on Posix it retrieves errno set by the last p/Invoke call + return LightExceptions.Throw(PythonOps.OSError(errno, PythonNT.strerror(errno))); } + private static object ToPythonInt(this ulong value) + => unchecked((long)value).ToPythonInt(); + private static object ToPythonInt(this long value) => value is <= int.MaxValue and >= int.MinValue ? (int)value : (BigInteger)value; private struct rlimit { // /usr/include/x86_64-linux-gnu/bits/resource.h - public long rlim_cur; - public long rlim_max; + public ulong rlim_cur; + public ulong rlim_max; } #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value internal struct rusage { // /usr/include/x86_64-linux-gnu/bits/types/struct_rusage.h - public Mono.Unix.Native.Timeval ru_utime; /* user CPU time used */ - public Mono.Unix.Native.Timeval ru_stime; /* system CPU time used */ - public long ru_maxrss; /* maximum resident set size */ - public long ru_ixrss; /* integral shared memory size */ - public long ru_idrss; /* integral unshared data size */ - public long ru_isrss; /* integral unshared stack size */ - public long ru_minflt; /* page reclaims (soft page faults) */ - public long ru_majflt; /* page faults (hard page faults) */ - public long ru_nswap; /* swaps */ - public long ru_inblock; /* block input operations */ - public long ru_oublock; /* block output operations */ - public long ru_msgsnd; /* IPC messages sent */ - public long ru_msgrcv; /* IPC messages received */ - public long ru_nsignals; /* signals received */ - public long ru_nvcsw; /* voluntary context switches */ - public long ru_nivcsw; /* involuntary context switches */ + public Mono.Unix.Native.Timeval ru_utime; // user CPU time used + public Mono.Unix.Native.Timeval ru_stime; // system CPU time used + public long ru_maxrss; // maximum resident set size + public long ru_ixrss; // integral shared memory size + public long ru_idrss; // integral unshared data size + public long ru_isrss; // integral unshared stack size + public long ru_minflt; // page reclaims (soft page faults) + public long ru_majflt; // page faults (hard page faults) + public long ru_nswap; // swaps + public long ru_inblock; // block input operations + public long ru_oublock; // block output operations + public long ru_msgsnd; // IPC messages sent + public long ru_msgrcv; // IPC messages received + public long ru_nsignals; // signals received + public long ru_nvcsw; // voluntary context switches + public long ru_nivcsw; // involuntary context switches } #pragma warning restore CS0649 @@ -373,6 +375,29 @@ internal struct rusage { private static extern int getrusage_linux(int who, /*rusage*/ IntPtr usage); + private enum linux__rlimit_resource { + // /usr/include/x86_64-linux-gnu/sys/resource.h + + RLIMIT_CPU = 0, // Per-process CPU limit + RLIMIT_FSIZE = 1, // Maximum filesize + RLIMIT_DATA = 2, // Maximum size of data segment + RLIMIT_STACK = 3, // Maximum size of stack segment + RLIMIT_CORE = 4, // Maximum size of core file + RLIMIT_RSS = 5, // Largest resident set size + RLIMIT_NPROC = 6, // Maximum number of processes + RLIMIT_NOFILE = 7, // Maximum number of open files + RLIMIT_MEMLOCK = 8, // Maximum locked-in-memory address space + RLIMIT_AS = 9, // Address space limit + RLIMIT_LOCKS = 10, // Maximum number of file locks + RLIMIT_SIGPENDING = 11, // Maximum number of pending signals + RLIMIT_MSGQUEUE = 12, // Maximum bytes in POSIX message queues + RLIMIT_NICE = 13, // Maximum nice prio allowed to raise to (20 added to get a non-negative number) + RLIMIT_RTPRIO = 14, // Maximum realtime priority + RLIMIT_RTTIME = 15, // Time slice in us + + RLIM_NLIMITS + }; + private enum macos__rlimit_resource { RLIMIT_CPU = 0, RLIMIT_FSIZE = 1, @@ -387,27 +412,4 @@ private enum macos__rlimit_resource { RLIM_NLIMITS } - - private enum linux__rlimit_resource { - // /usr/include/x86_64-linux-gnu/sys/resource.h - - RLIMIT_CPU = 0, // Per-process CPU limit - RLIMIT_FSIZE = 1, // Maximum filesize - RLIMIT_DATA = 2, // Maximum size of data segment - RLIMIT_STACK = 3, // Maximum size of stack segment - RLIMIT_CORE = 4, // Maximum size of core file - RLIMIT_RSS = 5, // Largest resident set size - RLIMIT_NPROC = 6, // Maximum number of processes - RLIMIT_NOFILE = 7, // Maximum number of open files - RLIMIT_MEMLOCK = 8, // Maximum locked-in-memory address space - RLIMIT_AS = 9, // Address space limit - RLIMIT_LOCKS = 10, // Maximum number of file locks - RLIMIT_SIGPENDING = 11, // Maximum number of pending signals - RLIMIT_MSGQUEUE = 12, // Maximum bytes in POSIX message queues - RLIMIT_NICE = 13, // Maximum nice prio allowed to raise to (20 added to get a non-negative number) - RLIMIT_RTPRIO = 14, // Maximum realtime priority - RLIMIT_RTTIME = 15, // Time slice in us - - RLIM_NLIMITS - }; } From 7320880c57d376731cbcbc39de34508260f621dc Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 10 Jun 2022 06:55:22 -0700 Subject: [PATCH 09/12] Adapt to macOS --- Src/IronPython.Modules/resource.cs | 42 ++++++++++++------- Src/IronPython/Runtime/PlatformsAttribute.cs | 8 +++- .../Cases/CPythonCasesManifest.ini | 4 +- Tests/modules/system_related/test_resource.py | 1 + .../system_related/test_resource_stdlib.py | 34 +++++++++++++++ 5 files changed, 70 insertions(+), 19 deletions(-) create mode 100644 Tests/modules/system_related/test_resource_stdlib.py diff --git a/Src/IronPython.Modules/resource.cs b/Src/IronPython.Modules/resource.cs index 86d0ae6ae..4430f4cfc 100644 --- a/Src/IronPython.Modules/resource.cs +++ b/Src/IronPython.Modules/resource.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -// TODO: Port to maxOS - #nullable enable using System; @@ -13,6 +11,7 @@ using System.Runtime.Versioning; using Microsoft.Scripting.Runtime; + using IronPython.Runtime; using IronPython.Runtime.Exceptions; using IronPython.Runtime.Operations; @@ -118,7 +117,7 @@ public static object getrlimit(int resource) { IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { - int retval = getrlimit_linux(resource, ptr); + int retval = getrlimit(resource, ptr); if (retval != 0) return GetPInvokeError(); rlimit res = Marshal.PtrToStructure(ptr); @@ -148,7 +147,8 @@ public static object getrlimit(int resource) { IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { Marshal.StructureToPtr(data, ptr, fDeleteOld: false); - int retval = setrlimit_linux(resource, ptr); + int retval = setrlimit(resource, ptr); + // TODO: for full CPython compliance, return ValueError iso OSError if non-superuser tries to raise max limit if (retval != 0) return GetPInvokeError(); } finally { Marshal.FreeHGlobal(ptr); @@ -156,6 +156,8 @@ public static object getrlimit(int resource) { return null; } + [PythonHidden(PlatformID.MacOSX)] + [SupportedOSPlatform("linux")] [LightThrowing] public static object prlimit(int pid, int resource) { if (resource < 0 || resource >= RLIM_NLIMITS) { @@ -164,7 +166,7 @@ public static object prlimit(int pid, int resource) { IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { - int retval = prlimit_linux(pid, resource, IntPtr.Zero, ptr); + int retval = prlimit(pid, resource, IntPtr.Zero, ptr); if (retval != 0) return GetPInvokeError(); rlimit res = Marshal.PtrToStructure(ptr); @@ -174,6 +176,8 @@ public static object prlimit(int pid, int resource) { } } + [PythonHidden(PlatformID.MacOSX)] + [SupportedOSPlatform("linux")] [LightThrowing] public static object prlimit(int pid, int resource, [NotNone] object limits) { if (resource < 0 || resource >= RLIM_NLIMITS) { @@ -197,7 +201,8 @@ public static object prlimit(int pid, int resource, [NotNone] object limits) { ptr_new = Marshal.AllocHGlobal(Marshal.SizeOf()); ptr_old = Marshal.AllocHGlobal(Marshal.SizeOf()); Marshal.StructureToPtr(data, ptr_new, fDeleteOld: false); - int retval = prlimit_linux(pid, resource, ptr_new, ptr_old); + int retval = prlimit(pid, resource, ptr_new, ptr_old); + // TODO: for full CPython compliance, return ValueError iso OSError if non-superuser tries to raise max limit if (retval != 0) return GetPInvokeError(); rlimit res = Marshal.PtrToStructure(ptr_old); @@ -215,7 +220,7 @@ public static object getrusage(int who) { IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { - int retval = getrusage_linux(who, ptr); + int retval = getrusage(who, ptr); if (retval != 0) return GetPInvokeError(); rusage res = Marshal.PtrToStructure(ptr); @@ -336,6 +341,8 @@ private static object ToPythonInt(this long value) private struct rlimit { // /usr/include/x86_64-linux-gnu/bits/resource.h + // /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/resource.h + public ulong rlim_cur; public ulong rlim_max; } @@ -343,6 +350,8 @@ private struct rlimit { #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value internal struct rusage { // /usr/include/x86_64-linux-gnu/bits/types/struct_rusage.h + // /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/resource.h + public Mono.Unix.Native.Timeval ru_utime; // user CPU time used public Mono.Unix.Native.Timeval ru_stime; // system CPU time used public long ru_maxrss; // maximum resident set size @@ -362,17 +371,18 @@ internal struct rusage { } #pragma warning restore CS0649 - [DllImport("libc", SetLastError = true, EntryPoint = "getrlimit")] - private static extern int getrlimit_linux(int resource, /*rlimit*/ IntPtr rlimits); + [DllImport("libc", SetLastError = true)] + private static extern int getrlimit(int resource, /*rlimit*/ IntPtr rlimits); - [DllImport("libc", SetLastError = true, EntryPoint = "setrlimit")] - private static extern int setrlimit_linux(int resource, /*const rlimit*/ IntPtr rlimits); + [DllImport("libc", SetLastError = true)] + private static extern int setrlimit(int resource, /*const rlimit*/ IntPtr rlimits); - [DllImport("libc", SetLastError = true, EntryPoint = "prlimit")] - private static extern int prlimit_linux(int pid, int resource, /*const rlimit*/ IntPtr new_limit, /*rlimit*/ IntPtr old_limit); + [DllImport("libc", SetLastError = true)] + [SupportedOSPlatform("linux")] + private static extern int prlimit(int pid, int resource, /*const rlimit*/ IntPtr new_limit, /*rlimit*/ IntPtr old_limit); - [DllImport("libc", SetLastError = true, EntryPoint = "getrusage")] - private static extern int getrusage_linux(int who, /*rusage*/ IntPtr usage); + [DllImport("libc", SetLastError = true)] + private static extern int getrusage(int who, /*rusage*/ IntPtr usage); private enum linux__rlimit_resource { @@ -399,6 +409,8 @@ private enum linux__rlimit_resource { }; private enum macos__rlimit_resource { + // /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/resource.h + RLIMIT_CPU = 0, RLIMIT_FSIZE = 1, RLIMIT_DATA = 2, diff --git a/Src/IronPython/Runtime/PlatformsAttribute.cs b/Src/IronPython/Runtime/PlatformsAttribute.cs index a42220336..2c5ac6d84 100644 --- a/Src/IronPython/Runtime/PlatformsAttribute.cs +++ b/Src/IronPython/Runtime/PlatformsAttribute.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Runtime.InteropServices; namespace IronPython.Runtime { public class PlatformsAttribute : Attribute { @@ -16,7 +17,12 @@ public enum PlatformFamily { public PlatformID[] ValidPlatforms { get; protected set; } - public bool IsPlatformValid => ValidPlatforms == null || ValidPlatforms.Length == 0 || Array.IndexOf(ValidPlatforms, Environment.OSVersion.Platform) >= 0; + public bool IsPlatformValid => ValidPlatforms == null || ValidPlatforms.Length == 0 || Array.IndexOf(ValidPlatforms, ActualPlatform) >= 0; + + private static PlatformID ActualPlatform + => RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? PlatformID.Unix : + RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? PlatformID.MacOSX : + Environment.OSVersion.Platform; protected void SetValidPlatforms(PlatformFamily validPlatformFamily) { switch (validPlatformFamily) { diff --git a/Src/IronPythonTest/Cases/CPythonCasesManifest.ini b/Src/IronPythonTest/Cases/CPythonCasesManifest.ini index ed104a85f..b5fdcc69f 100644 --- a/Src/IronPythonTest/Cases/CPythonCasesManifest.ini +++ b/Src/IronPythonTest/Cases/CPythonCasesManifest.ini @@ -773,10 +773,8 @@ IsolationLevel=PROCESS [CPython.test_reprlib] Ignore=true -[CPython.test_resource] -RunCondition=$(IS_POSIX) +[CPython.test_resource] # IronPython.test_resource_stdlib Ignore=true -Reason=unittest.case.SkipTest: No module named 'resource' [CPython.test_robotparser] Ignore=true diff --git a/Tests/modules/system_related/test_resource.py b/Tests/modules/system_related/test_resource.py index 7b7840119..55bec7ca0 100644 --- a/Tests/modules/system_related/test_resource.py +++ b/Tests/modules/system_related/test_resource.py @@ -97,6 +97,7 @@ def test_setrlimit_error(self): self.assertRaises(TypeError, resource.setrlimit, 0, None) @unittest.skipUnless(is_posix, "Posix-specific test") + @unittest.skipUnless(not is_osx, "prlimit not available on macOS") def test_prlimit(self): r = resource.RLIMIT_CORE lims = resource.getrlimit(r) diff --git a/Tests/modules/system_related/test_resource_stdlib.py b/Tests/modules/system_related/test_resource_stdlib.py new file mode 100644 index 000000000..d238885f1 --- /dev/null +++ b/Tests/modules/system_related/test_resource_stdlib.py @@ -0,0 +1,34 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the Apache 2.0 License. +# See the LICENSE file in the project root for more information. + +## +## Run selected tests from test_resource from StdLib +## + +import unittest +import sys + +from iptest import run_test + +import test.test_resource + +def load_tests(loader, standard_tests, pattern): + if sys.implementation.name == 'ironpython': + suite = unittest.TestSuite() + suite.addTest(test.test_resource.ResourceTest('test_args')) + suite.addTest(test.test_resource.ResourceTest('test_freebsd_contants')) + #suite.addTest(test.test_resource.ResourceTest('test_fsize_enforced')) # TODO: handle SIGXFSZ + suite.addTest(test.test_resource.ResourceTest('test_fsize_ismax')) + suite.addTest(test.test_resource.ResourceTest('test_fsize_toobig')) + suite.addTest(test.test_resource.ResourceTest('test_getrusage')) + suite.addTest(test.test_resource.ResourceTest('test_linux_constants')) + suite.addTest(test.test_resource.ResourceTest('test_pagesize')) + suite.addTest(test.test_resource.ResourceTest('test_prlimit')) + suite.addTest(test.test_resource.ResourceTest('test_setrusage_refcount')) + return suite + + else: + return loader.loadTestsFromModule(test.test_resource, pattern) + +run_test(__name__) From de71274411e9a148487d955fd1472ccfd5399be4 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 10 Jun 2022 08:14:38 -0700 Subject: [PATCH 10/12] Disable test on Windows --- .../Cases/IronPythonCasesManifest.ini | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini b/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini index 86411e169..02e23cc1c 100644 --- a/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini +++ b/Src/IronPythonTest/Cases/IronPythonCasesManifest.ini @@ -143,6 +143,9 @@ IsolationLevel=PROCESS # causes a failure in IronPython.test_attrinjector [IronPython.interop.net.type.test_reachtype] IsolationLevel=PROCESS # causes failures in IronPython.test_ast and IronPython.scripts.test_cgcheck +[IronPython.modules.io_related.test_cPickle] +NotParallelSafe=true # Uses a temporary module with a fixed name + [IronPython.modules.misc.test__weakref] RetryCount=2 @@ -153,16 +156,16 @@ NotParallelSafe=true # test_data.gz RunCondition=NOT $(IS_POSIX) NotParallelSafe=true # Uses fixed file, directory, and environment variable names -[IronPython.modules.io_related.test_cPickle] -NotParallelSafe=true # Uses a temporary module with a fixed name - -[IronPython.modules.type_related.test_bitfields_ctypes_stdlib] -RunCondition=NOT $(IS_OSX) # ctypes tests not prepared for macOS +[IronPython.modules.system_related.test_resource_stdlib] +RunCondition=$(IS_POSIX) # Module resource is Posix-specific [IronPython.modules.system_related.test_sys_getframe] IsolationLevel=PROCESS # https://github.com/IronLanguages/ironpython3/issues/489 FullFrames=true +[IronPython.modules.type_related.test_bitfields_ctypes_stdlib] +RunCondition=NOT $(IS_OSX) # ctypes tests not prepared for macOS + [IronPython.scripts.test_builder] Ignore=true From 6dcb69fc4e7231794346ba8ac919022b023c972e Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Tue, 14 Jun 2022 18:37:11 -0700 Subject: [PATCH 11/12] Replace Mono.Unix calls and types --- Src/IronPython.Modules/resource.cs | 53 +++++++++++++++++++----------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/Src/IronPython.Modules/resource.cs b/Src/IronPython.Modules/resource.cs index 4430f4cfc..f83898a1c 100644 --- a/Src/IronPython.Modules/resource.cs +++ b/Src/IronPython.Modules/resource.cs @@ -230,8 +230,8 @@ public static object getrusage(int who) { } } - public static object getpagesize() { - return Mono.Unix.Native.Syscall.sysconf(Mono.Unix.Native.SysconfName._SC_PAGESIZE).ToPythonInt(); + public static int getpagesize() { + return Environment.SystemPageSize; } [PythonType] @@ -253,8 +253,8 @@ private struct_rusage(object o) : base(o) { internal struct_rusage(rusage data) : this( new object[n_sequence_fields] { - data.ru_utime.tv_sec + data.ru_utime.tv_usec * 1e-6, - data.ru_stime.tv_sec + data.ru_stime.tv_usec * 1e-6, + data.ru_utime.GetTime(), + data.ru_stime.GetTime(), data.ru_maxrss.ToPythonInt(), data.ru_ixrss.ToPythonInt(), data.ru_idrss.ToPythonInt(), @@ -348,26 +348,39 @@ private struct rlimit { } #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + internal struct timeval { + // /usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h, .../bits/types.h + // /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/_types/_timeval.h, .../i386/_types.h, .../arm64/_types.h + + public long tv_sec; + public long tv_usec; // long on Linux but int on Darwin + + public double GetTime() + => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + tv_sec + (tv_usec & 0xFFFF_FFFF) * 1e-6 : + tv_sec + tv_usec * 1e-6; + } + internal struct rusage { // /usr/include/x86_64-linux-gnu/bits/types/struct_rusage.h // /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/resource.h - public Mono.Unix.Native.Timeval ru_utime; // user CPU time used - public Mono.Unix.Native.Timeval ru_stime; // system CPU time used - public long ru_maxrss; // maximum resident set size - public long ru_ixrss; // integral shared memory size - public long ru_idrss; // integral unshared data size - public long ru_isrss; // integral unshared stack size - public long ru_minflt; // page reclaims (soft page faults) - public long ru_majflt; // page faults (hard page faults) - public long ru_nswap; // swaps - public long ru_inblock; // block input operations - public long ru_oublock; // block output operations - public long ru_msgsnd; // IPC messages sent - public long ru_msgrcv; // IPC messages received - public long ru_nsignals; // signals received - public long ru_nvcsw; // voluntary context switches - public long ru_nivcsw; // involuntary context switches + public timeval ru_utime; // user CPU time used + public timeval ru_stime; // system CPU time used + public long ru_maxrss; // maximum resident set size + public long ru_ixrss; // integral shared memory size + public long ru_idrss; // integral unshared data size + public long ru_isrss; // integral unshared stack size + public long ru_minflt; // page reclaims (soft page faults) + public long ru_majflt; // page faults (hard page faults) + public long ru_nswap; // swaps + public long ru_inblock; // block input operations + public long ru_oublock; // block output operations + public long ru_msgsnd; // IPC messages sent + public long ru_msgrcv; // IPC messages received + public long ru_nsignals; // signals received + public long ru_nvcsw; // voluntary context switches + public long ru_nivcsw; // involuntary context switches } #pragma warning restore CS0649 From 64677bfb43f045608b7072885510daa34f199475 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Tue, 14 Jun 2022 18:37:28 -0700 Subject: [PATCH 12/12] Simplify tests --- Tests/modules/system_related/test_resource.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Tests/modules/system_related/test_resource.py b/Tests/modules/system_related/test_resource.py index 55bec7ca0..1c6dabd61 100644 --- a/Tests/modules/system_related/test_resource.py +++ b/Tests/modules/system_related/test_resource.py @@ -2,11 +2,11 @@ # The .NET Foundation licenses this file to you under the Apache 2.0 License. # See the LICENSE file in the project root for more information. -# tests fro module 'resource' (Posix only) +# tests for module 'resource' (Posix only) import unittest -from iptest import IronPythonTestCase, is_cli, is_posix, is_linux, is_osx, run_test, skipUnlessIronPython +from iptest import IronPythonTestCase, is_posix, is_linux, is_osx, run_test if is_posix: import resource @@ -18,18 +18,17 @@ else: raise AssertionError("There should be no module resource on Windows") +@unittest.skipUnless(is_posix, "Posix-specific test") class ResourceTest(IronPythonTestCase): def setUp(self): self.RLIM_NLIMITS = 16 if is_linux else 9 - @unittest.skipUnless(is_posix, "Posix-specific test") def test_infinity(self): if is_osx: self.assertEqual(resource.RLIM_INFINITY, (1<<63)-1) else: self.assertEqual(resource.RLIM_INFINITY, -1) - @unittest.skipUnless(is_posix, "Posix-specific test") def test_getrlimit(self): for r in range(self.RLIM_NLIMITS): lims = resource.getrlimit(r) @@ -44,7 +43,6 @@ def test_getrlimit(self): self.assertRaises(ValueError, resource.getrlimit, -1) self.assertRaises(ValueError, resource.getrlimit, self.RLIM_NLIMITS) - @unittest.skipUnless(is_posix, "Posix-specific test") def test_setrlimit(self): r = resource.RLIMIT_CORE lims = resource.getrlimit(r) @@ -84,7 +82,6 @@ def test_setrlimit(self): finally: resource.setrlimit(r, lims) - @unittest.skipUnless(is_posix, "Posix-specific test") def test_setrlimit_error(self): self.assertRaises(TypeError, resource.setrlimit, None, (0, 0)) self.assertRaises(TypeError, resource.setrlimit, "abc", (0, 0)) @@ -96,7 +93,6 @@ def test_setrlimit_error(self): self.assertRaises(ValueError, resource.setrlimit, 0, (2.3, 0, 0)) self.assertRaises(TypeError, resource.setrlimit, 0, None) - @unittest.skipUnless(is_posix, "Posix-specific test") @unittest.skipUnless(not is_osx, "prlimit not available on macOS") def test_prlimit(self): r = resource.RLIMIT_CORE @@ -112,14 +108,12 @@ def test_prlimit(self): finally: resource.prlimit(0, r, lims) - @unittest.skipUnless(is_posix, "Posix-specific test") def test_pagesize(self): ps = resource.getpagesize() self.assertIsInstance(ps, int) self.assertTrue(ps > 0) self.assertTrue((ps & (ps-1) == 0)) # ps is power of 2 - @unittest.skipUnless(is_posix, "Posix-specific test") def test_getrusage(self): self.assertEqual(resource.struct_rusage.n_fields, 16) self.assertEqual(resource.struct_rusage.n_sequence_fields, 16)