From aa6edbdffdd3118f1e541d472893ce154c32a706 Mon Sep 17 00:00:00 2001 From: vseanreesermsft <78103370+vseanreesermsft@users.noreply.github.com> Date: Thu, 8 May 2025 12:04:19 -0700 Subject: [PATCH 01/12] Update branding to 9.0.6 (#115385) --- eng/Versions.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 1d2850cf8ad0b9..f8ad0bed44fda2 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,11 +1,11 @@ - 9.0.5 + 9.0.6 9 0 - 5 + 6 9.0.100 8.0.$([MSBuild]::Add($(PatchVersion),11)) 7.0.20 From 7e47914cc72ae79c1cd32e7056237483aa117713 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 11:05:33 -0500 Subject: [PATCH 02/12] [release/9.0] Update dependencies from dotnet/emsdk (#114702) * Update dependencies from https://github.com/dotnet/emsdk build 20250415.2 Microsoft.SourceBuild.Intermediate.emsdk , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100 , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100.Transport From Version 9.0.5-servicing.25212.1 -> To Version 9.0.5-servicing.25215.2 * Update dependencies from https://github.com/dotnet/emsdk build 20250419.3 Microsoft.SourceBuild.Intermediate.emsdk , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100 , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100.Transport From Version 9.0.5-servicing.25212.1 -> To Version 9.0.5-servicing.25219.3 * Update dependencies from https://github.com/dotnet/emsdk build 20250423.3 Microsoft.SourceBuild.Intermediate.emsdk , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100 , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100.Transport From Version 9.0.5-servicing.25212.1 -> To Version 9.0.5-servicing.25223.3 * Update dependencies from https://github.com/dotnet/emsdk build 20250428.1 Microsoft.SourceBuild.Intermediate.emsdk , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100 , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100.Transport From Version 9.0.5-servicing.25212.1 -> To Version 9.0.5-servicing.25228.1 * Update dependencies from https://github.com/dotnet/emsdk build 20250505.2 Microsoft.SourceBuild.Intermediate.emsdk , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100 , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100.Transport From Version 9.0.5-servicing.25212.1 -> To Version 9.0.5-servicing.25255.2 * Update dependencies from https://github.com/dotnet/emsdk build 20250508.2 Microsoft.SourceBuild.Intermediate.emsdk , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100 , Microsoft.NET.Workload.Emscripten.Current.Manifest-9.0.100.Transport From Version 9.0.5-servicing.25212.1 -> To Version 9.0.6-servicing.25258.2 --------- Co-authored-by: dotnet-maestro[bot] --- NuGet.config | 2 +- eng/Version.Details.xml | 12 ++++++------ eng/Versions.props | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/NuGet.config b/NuGet.config index b6cfbd7189b7aa..f904309ba4b44e 100644 --- a/NuGet.config +++ b/NuGet.config @@ -9,7 +9,7 @@ - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 798b64f9f13415..07c3131752bd43 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -64,18 +64,18 @@ a8336269316c42f8164fe7bf45972dd8a81e52dc - + https://github.com/dotnet/emsdk - 78f6f07d38e8755e573039a8aa04e131d3e59b76 + e3d8e8ea6df192d864698cdd76984d74135d0d13 - + https://github.com/dotnet/emsdk - 78f6f07d38e8755e573039a8aa04e131d3e59b76 + e3d8e8ea6df192d864698cdd76984d74135d0d13 - + https://github.com/dotnet/emsdk - 78f6f07d38e8755e573039a8aa04e131d3e59b76 + e3d8e8ea6df192d864698cdd76984d74135d0d13 diff --git a/eng/Versions.props b/eng/Versions.props index f8ad0bed44fda2..ffeee755a3715f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -243,8 +243,8 @@ Note: when the name is updated, make sure to update dependency name in eng/pipelines/common/xplat-setup.yml like - DarcDependenciesChanged.Microsoft_NET_Workload_Emscripten_Current_Manifest-9_0_100_Transport --> - 9.0.5-servicing.25212.1 - 9.0.5 + 9.0.6-servicing.25258.2 + 9.0.6 $(MicrosoftNETWorkloadEmscriptenCurrentManifest90100Version) 1.1.87-gba258badda From dda82fffb11580334b2db48807d4d93531a5bfed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 09:49:39 +0200 Subject: [PATCH 03/12] update macos signing to use pme (#115634) Co-authored-by: Oleksandr.Didyk --- .../common/macos-sign-with-entitlements.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/eng/pipelines/common/macos-sign-with-entitlements.yml b/eng/pipelines/common/macos-sign-with-entitlements.yml index 72a03b90f340d6..6a20a31481eb9d 100644 --- a/eng/pipelines/common/macos-sign-with-entitlements.yml +++ b/eng/pipelines/common/macos-sign-with-entitlements.yml @@ -30,12 +30,13 @@ steps: - task: EsrpCodeSigning@5 displayName: 'ESRP CodeSigning' inputs: - ConnectedServiceName: 'DotNet-Engineering-Services_KeyVault' - AppRegistrationClientId: '28ec6507-2167-4eaa-a294-34408cf5dd0e' - AppRegistrationTenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47' - AuthAKVName: 'EngKeyVault' - AuthCertName: 'DotNetCore-ESRP-AuthCert' - AuthSignCertName: 'DotNetCore-ESRP-AuthSignCert' + ConnectedServiceName: 'DotNetBuildESRP' + UseMSIAuthentication: true + EsrpClientId: '28ec6507-2167-4eaa-a294-34408cf5dd0e' + AppRegistrationClientId: '0ecbcdb7-8451-4cbe-940a-4ed97b08b955' + AppRegistrationTenantId: '975f013f-7f24-47e8-a7d3-abc4752bf346' + AuthAKVName: 'DotNetEngKeyVault' + AuthSignCertName: 'DotNet-ESRP-AuthSignCert' FolderPath: '$(Build.ArtifactStagingDirectory)/' Pattern: 'mac_entitled_to_sign.zip' UseMinimatch: true From df01702501508335f2f56bf9509d552b3132e574 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 10:43:23 -0700 Subject: [PATCH 04/12] [release/9.0] [Mono] Fix c11 ARM64 atomics to issue full memory barrier. (#115635) * Fix C11 atomic seq cst on ARM64. * Add library regression test. * Add functional regression test. * Adjust functional test to work under NAOT. * Add comment to C11_MEMORY_ORDER_SEQ_CST + align atomic_load barrier to JIT. --------- Co-authored-by: lateralusX Co-authored-by: Rahul Bhandari --- .../tests/ParallelForEachAsyncTests.cs | 26 ++++++ src/mono/mono/utils/atomic.h | 81 +++++++++++++++---- .../Device/ParallelForEachAsync/Program.cs | 50 ++++++++++++ ...OS.Device.ParallelForEachAsync.Test.csproj | 22 +++++ 4 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 src/tests/FunctionalTests/iOS/Device/ParallelForEachAsync/Program.cs create mode 100644 src/tests/FunctionalTests/iOS/Device/ParallelForEachAsync/iOS.Device.ParallelForEachAsync.Test.csproj diff --git a/src/libraries/System.Threading.Tasks.Parallel/tests/ParallelForEachAsyncTests.cs b/src/libraries/System.Threading.Tasks.Parallel/tests/ParallelForEachAsyncTests.cs index 77c16ddd72c928..093d35bb55e0e9 100644 --- a/src/libraries/System.Threading.Tasks.Parallel/tests/ParallelForEachAsyncTests.cs +++ b/src/libraries/System.Threading.Tasks.Parallel/tests/ParallelForEachAsyncTests.cs @@ -1393,6 +1393,32 @@ static async IAsyncEnumerable Iterate() await t; } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public async Task GitHubIssue_114262_Async() + { + var options = new ParallelOptions + { + MaxDegreeOfParallelism = 5, + CancellationToken = new CancellationTokenSource().Token + }; + + var range = Enumerable.Range(1, 1000); + + for (int i = 0; i < 100; i++) + { + await Parallel.ForEachAsync(range, options, async (data, token) => + { + for (int i = 0; i < 5; i++) + { + await Task.Yield(); + var buffer = new byte[10_000]; + await Task.Run(() => {var _ = buffer[0];} ); + await Task.Yield(); + } + }); + } + } + private static async IAsyncEnumerable EnumerableRangeAsync(int start, int count, bool yield = true) { for (int i = start; i < start + count; i++) diff --git a/src/mono/mono/utils/atomic.h b/src/mono/mono/utils/atomic.h index 3f0a01e6beb428..9d4459d932fb1a 100644 --- a/src/mono/mono/utils/atomic.h +++ b/src/mono/mono/utils/atomic.h @@ -95,11 +95,25 @@ Apple targets have historically being problematic, xcode 4.6 would miscompile th #include +#if defined(HOST_ARM64) +// C11 atomics on ARM64 offers a weaker version of sequential consistent, not expected by mono atomics operations. +// C11 seq_cst on ARM64 corresponds to acquire/release semantics, but mono expects these functions to emit a full memory +// barrier preventing any kind of reordering around the atomic operation. GCC atomics on ARM64 had similar limitations, +// see comments on GCC atomics below and mono injected full memory barriers around GCC atomic functions to mitigate this. +// Since mono GCC atomics implementation ended up even stronger (full memory barrier before/after), the C11 atomics +// implementation is still a little weaker, but should correspond to the exact same semantics as implemented by JIT +// compiler for sequential consistent atomic load/store/add/exchange/cas op codes on ARM64. +#define C11_MEMORY_ORDER_SEQ_CST() atomic_thread_fence (memory_order_seq_cst) +#else +#define C11_MEMORY_ORDER_SEQ_CST() +#endif + static inline guint8 mono_atomic_cas_u8 (volatile guint8 *dest, guint8 exch, guint8 comp) { g_static_assert (sizeof (atomic_char) == sizeof (*dest) && ATOMIC_CHAR_LOCK_FREE == 2); (void)atomic_compare_exchange_strong ((volatile atomic_char *)dest, (char*)&comp, exch); + C11_MEMORY_ORDER_SEQ_CST (); return comp; } @@ -108,6 +122,7 @@ mono_atomic_cas_u16 (volatile guint16 *dest, guint16 exch, guint16 comp) { g_static_assert (sizeof (atomic_short) == sizeof (*dest) && ATOMIC_SHORT_LOCK_FREE == 2); (void)atomic_compare_exchange_strong ((volatile atomic_short *)dest, (short*)&comp, exch); + C11_MEMORY_ORDER_SEQ_CST (); return comp; } @@ -116,6 +131,7 @@ mono_atomic_cas_i32 (volatile gint32 *dest, gint32 exch, gint32 comp) { g_static_assert (sizeof (atomic_int) == sizeof (*dest) && ATOMIC_INT_LOCK_FREE == 2); (void)atomic_compare_exchange_strong ((volatile atomic_int *)dest, &comp, exch); + C11_MEMORY_ORDER_SEQ_CST (); return comp; } @@ -125,14 +141,14 @@ mono_atomic_cas_i64 (volatile gint64 *dest, gint64 exch, gint64 comp) #if SIZEOF_LONG == 8 g_static_assert (sizeof (atomic_long) == sizeof (*dest) && ATOMIC_LONG_LOCK_FREE == 2); (void)atomic_compare_exchange_strong ((volatile atomic_long *)dest, (long*)&comp, exch); - return comp; #elif SIZEOF_LONG_LONG == 8 g_static_assert (sizeof (atomic_llong) == sizeof (*dest) && ATOMIC_LLONG_LOCK_FREE == 2); (void)atomic_compare_exchange_strong ((volatile atomic_llong *)dest, (long long*)&comp, exch); - return comp; #else #error "gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC" #endif + C11_MEMORY_ORDER_SEQ_CST (); + return comp; } static inline gpointer @@ -140,6 +156,7 @@ mono_atomic_cas_ptr (volatile gpointer *dest, gpointer exch, gpointer comp) { g_static_assert(ATOMIC_POINTER_LOCK_FREE == 2); (void)atomic_compare_exchange_strong ((volatile _Atomic(gpointer) *)dest, &comp, exch); + C11_MEMORY_ORDER_SEQ_CST (); return comp; } @@ -191,21 +208,27 @@ static inline guint8 mono_atomic_xchg_u8 (volatile guint8 *dest, guint8 exch) { g_static_assert (sizeof (atomic_char) == sizeof (*dest) && ATOMIC_CHAR_LOCK_FREE == 2); - return atomic_exchange ((volatile atomic_char *)dest, exch); + guint8 old = atomic_exchange ((volatile atomic_char *)dest, exch); + C11_MEMORY_ORDER_SEQ_CST (); + return old; } static inline guint16 mono_atomic_xchg_u16 (volatile guint16 *dest, guint16 exch) { g_static_assert (sizeof (atomic_short) == sizeof (*dest) && ATOMIC_SHORT_LOCK_FREE == 2); - return atomic_exchange ((volatile atomic_short *)dest, exch); + guint16 old = atomic_exchange ((volatile atomic_short *)dest, exch); + C11_MEMORY_ORDER_SEQ_CST (); + return old; } static inline gint32 mono_atomic_xchg_i32 (volatile gint32 *dest, gint32 exch) { g_static_assert (sizeof (atomic_int) == sizeof (*dest) && ATOMIC_INT_LOCK_FREE == 2); - return atomic_exchange ((volatile atomic_int *)dest, exch); + gint32 old = atomic_exchange ((volatile atomic_int *)dest, exch); + C11_MEMORY_ORDER_SEQ_CST (); + return old; } static inline gint64 @@ -213,27 +236,33 @@ mono_atomic_xchg_i64 (volatile gint64 *dest, gint64 exch) { #if SIZEOF_LONG == 8 g_static_assert (sizeof (atomic_long) == sizeof (*dest) && ATOMIC_LONG_LOCK_FREE == 2); - return atomic_exchange ((volatile atomic_long *)dest, exch); + gint64 old = atomic_exchange ((volatile atomic_long *)dest, exch); #elif SIZEOF_LONG_LONG == 8 g_static_assert (sizeof (atomic_llong) == sizeof (*dest) && ATOMIC_LLONG_LOCK_FREE == 2); - return atomic_exchange ((volatile atomic_llong *)dest, exch); + gint64 old = atomic_exchange ((volatile atomic_llong *)dest, exch); #else #error "gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC" #endif + C11_MEMORY_ORDER_SEQ_CST (); + return old; } static inline gpointer mono_atomic_xchg_ptr (volatile gpointer *dest, gpointer exch) { g_static_assert (ATOMIC_POINTER_LOCK_FREE == 2); - return atomic_exchange ((volatile _Atomic(gpointer) *)dest, exch); + gpointer old = atomic_exchange ((volatile _Atomic(gpointer) *)dest, exch); + C11_MEMORY_ORDER_SEQ_CST (); + return old; } static inline gint32 mono_atomic_fetch_add_i32 (volatile gint32 *dest, gint32 add) { g_static_assert (sizeof (atomic_int) == sizeof (*dest) && ATOMIC_INT_LOCK_FREE == 2); - return atomic_fetch_add ((volatile atomic_int *)dest, add); + gint32 old = atomic_fetch_add ((volatile atomic_int *)dest, add); + C11_MEMORY_ORDER_SEQ_CST (); + return old; } static inline gint64 @@ -241,33 +270,41 @@ mono_atomic_fetch_add_i64 (volatile gint64 *dest, gint64 add) { #if SIZEOF_LONG == 8 g_static_assert (sizeof (atomic_long) == sizeof (*dest) && ATOMIC_LONG_LOCK_FREE == 2); - return atomic_fetch_add ((volatile atomic_long *)dest, add); + gint64 old = atomic_fetch_add ((volatile atomic_long *)dest, add); #elif SIZEOF_LONG_LONG == 8 g_static_assert (sizeof (atomic_llong) == sizeof (*dest) && ATOMIC_LLONG_LOCK_FREE == 2); - return atomic_fetch_add ((volatile atomic_llong *)dest, add); + gint64 old = atomic_fetch_add ((volatile atomic_llong *)dest, add); #else #error "gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC" #endif + C11_MEMORY_ORDER_SEQ_CST (); + return old; } static inline gint8 mono_atomic_load_i8 (volatile gint8 *src) { g_static_assert (sizeof (atomic_char) == sizeof (*src) && ATOMIC_CHAR_LOCK_FREE == 2); - return atomic_load ((volatile atomic_char *)src); + C11_MEMORY_ORDER_SEQ_CST (); + gint8 val = atomic_load ((volatile atomic_char *)src); + return val; } static inline gint16 mono_atomic_load_i16 (volatile gint16 *src) { g_static_assert (sizeof (atomic_short) == sizeof (*src) && ATOMIC_SHORT_LOCK_FREE == 2); - return atomic_load ((volatile atomic_short *)src); + C11_MEMORY_ORDER_SEQ_CST (); + gint16 val = atomic_load ((volatile atomic_short *)src); + return val; } static inline gint32 mono_atomic_load_i32 (volatile gint32 *src) { g_static_assert (sizeof (atomic_int) == sizeof (*src) && ATOMIC_INT_LOCK_FREE == 2); - return atomic_load ((volatile atomic_int *)src); + C11_MEMORY_ORDER_SEQ_CST (); + gint32 val = atomic_load ((volatile atomic_int *)src); + return val; } static inline gint64 @@ -275,20 +312,25 @@ mono_atomic_load_i64 (volatile gint64 *src) { #if SIZEOF_LONG == 8 g_static_assert (sizeof (atomic_long) == sizeof (*src) && ATOMIC_LONG_LOCK_FREE == 2); - return atomic_load ((volatile atomic_long *)src); + C11_MEMORY_ORDER_SEQ_CST (); + gint64 val = atomic_load ((volatile atomic_long *)src); #elif SIZEOF_LONG_LONG == 8 g_static_assert (sizeof (atomic_llong) == sizeof (*src) && ATOMIC_LLONG_LOCK_FREE == 2); - return atomic_load ((volatile atomic_llong *)src); + C11_MEMORY_ORDER_SEQ_CST (); + gint64 val = atomic_load ((volatile atomic_llong *)src); #else #error "gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC" #endif + return val; } static inline gpointer mono_atomic_load_ptr (volatile gpointer *src) { g_static_assert (ATOMIC_POINTER_LOCK_FREE == 2); - return atomic_load ((volatile _Atomic(gpointer) *)src); + C11_MEMORY_ORDER_SEQ_CST (); + gpointer val = atomic_load ((volatile _Atomic(gpointer) *)src); + return val; } static inline void @@ -296,6 +338,7 @@ mono_atomic_store_i8 (volatile gint8 *dst, gint8 val) { g_static_assert (sizeof (atomic_char) == sizeof (*dst) && ATOMIC_CHAR_LOCK_FREE == 2); atomic_store ((volatile atomic_char *)dst, val); + C11_MEMORY_ORDER_SEQ_CST (); } static inline void @@ -303,6 +346,7 @@ mono_atomic_store_i16 (volatile gint16 *dst, gint16 val) { g_static_assert (sizeof (atomic_short) == sizeof (*dst) && ATOMIC_SHORT_LOCK_FREE == 2); atomic_store ((volatile atomic_short *)dst, val); + C11_MEMORY_ORDER_SEQ_CST (); } static inline void @@ -310,6 +354,7 @@ mono_atomic_store_i32 (volatile gint32 *dst, gint32 val) { g_static_assert (sizeof (atomic_int) == sizeof (*dst) && ATOMIC_INT_LOCK_FREE == 2); atomic_store ((atomic_int *)dst, val); + C11_MEMORY_ORDER_SEQ_CST (); } static inline void @@ -324,6 +369,7 @@ mono_atomic_store_i64 (volatile gint64 *dst, gint64 val) #else #error "gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC" #endif + C11_MEMORY_ORDER_SEQ_CST (); } static inline void @@ -331,6 +377,7 @@ mono_atomic_store_ptr (volatile gpointer *dst, gpointer val) { g_static_assert (ATOMIC_POINTER_LOCK_FREE == 2); atomic_store ((volatile _Atomic(gpointer) *)dst, val); + C11_MEMORY_ORDER_SEQ_CST (); } #elif defined(MONO_USE_WIN32_ATOMIC) diff --git a/src/tests/FunctionalTests/iOS/Device/ParallelForEachAsync/Program.cs b/src/tests/FunctionalTests/iOS/Device/ParallelForEachAsync/Program.cs new file mode 100644 index 00000000000000..493efaae51f91a --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Device/ParallelForEachAsync/Program.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +using System.Linq; + +public static class Program +{ + [DllImport("__Internal")] + public static extern void mono_ios_set_summary (string value); + + private static async Task GitHubIssue_114262_Async() + { + var options = new ParallelOptions + { + MaxDegreeOfParallelism = 5, + CancellationToken = new CancellationTokenSource().Token + }; + + var range = Enumerable.Range(1, 1000); + + for (int i = 0; i < 100; i++) + { + await Parallel.ForEachAsync(range, options, async (data, token) => + { + for (int i = 0; i < 5; i++) + { + await Task.Yield(); + var buffer = new byte[10_000]; + await Task.Run(() => {var _ = buffer[0];} ); + await Task.Yield(); + } + }); + } + } + + public static async Task Main(string[] args) + { + mono_ios_set_summary($"Starting functional test"); + + await GitHubIssue_114262_Async(); + + Console.WriteLine("Done!"); + + return 42; + } +} diff --git a/src/tests/FunctionalTests/iOS/Device/ParallelForEachAsync/iOS.Device.ParallelForEachAsync.Test.csproj b/src/tests/FunctionalTests/iOS/Device/ParallelForEachAsync/iOS.Device.ParallelForEachAsync.Test.csproj new file mode 100644 index 00000000000000..ae748949616933 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Device/ParallelForEachAsync/iOS.Device.ParallelForEachAsync.Test.csproj @@ -0,0 +1,22 @@ + + + Exe + true + $(NetCoreAppCurrent) + ios + arm64 + false + 42 + true + + + + true + false + iOS.Device.ParallelForEachAsync.Test.dll + + + + + + From 40579d17b74cb2be5103713893957e43db709a37 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 2 May 2025 00:24:14 -0700 Subject: [PATCH 05/12] Revert change to follow symlinks of dotnet host https://github.com/dotnet/runtime/pull/99576 changed the host to first resolve symlinks before resolving the application directory. This means that relative loads happen relative to the pointed-at file, not the symbolic link. This was a breaking change made to match the symbolic link behavior on all platforms. Unfortunately, it seems a number of users have taken a dependency on the Windows-specific behavior. This PR reverts the change and puts back in place the old Windows behavior. --- src/native/corehost/corehost.cpp | 2 +- src/native/corehost/fxr_resolver.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/corehost/corehost.cpp b/src/native/corehost/corehost.cpp index c8a94312c5d6d4..c233a52237a9fb 100644 --- a/src/native/corehost/corehost.cpp +++ b/src/native/corehost/corehost.cpp @@ -115,7 +115,7 @@ int exe_start(const int argc, const pal::char_t* argv[]) // Use realpath to find the path of the host, resolving any symlinks. // hostfxr (for dotnet) and the app dll (for apphost) are found relative to the host. pal::string_t host_path; - if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path)) + if (!pal::get_own_executable_path(&host_path) || !pal::fullpath(&host_path)) { trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path.c_str()); return StatusCode::CoreHostCurHostFindFailure; diff --git a/src/native/corehost/fxr_resolver.cpp b/src/native/corehost/fxr_resolver.cpp index a69bf7ced896a5..0d1cbddf9deb5c 100644 --- a/src/native/corehost/fxr_resolver.cpp +++ b/src/native/corehost/fxr_resolver.cpp @@ -95,7 +95,7 @@ bool fxr_resolver::try_get_path( bool search_global = (search & search_location_global) != 0; pal::string_t default_install_location; pal::string_t dotnet_root_env_var_name; - if (search_app_relative && pal::realpath(app_relative_dotnet_root)) + if (search_app_relative && pal::fullpath(app_relative_dotnet_root)) { trace::info(_X("Using app-relative location [%s] as runtime location."), app_relative_dotnet_root->c_str()); out_dotnet_root->assign(*app_relative_dotnet_root); From 9763cdd856797285849fc7129a25f0828243fabd Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 5 May 2025 15:38:11 -0700 Subject: [PATCH 06/12] Adapt symbolic link tests --- .../tests/HostActivation.Tests/SymbolicLinks.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs index d440d3eae2caa2..8e7fe336f8190f 100644 --- a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs +++ b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs @@ -37,8 +37,8 @@ public void Run_apphost_behind_symlink(string symlinkRelativePath) .CaptureStdErr() .CaptureStdOut() .Execute() - .Should().Pass() - .And.HaveStdOutContaining("Hello World"); + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); } } @@ -67,8 +67,8 @@ public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePa .CaptureStdErr() .CaptureStdOut() .Execute() - .Should().Pass() - .And.HaveStdOutContaining("Hello World"); + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); } } @@ -91,8 +91,8 @@ public void Run_framework_dependent_app_behind_symlink(string symlinkRelativePat .CaptureStdOut() .DotNetRoot(TestContext.BuiltDotNet.BinPath) .Execute() - .Should().Pass() - .And.HaveStdOutContaining("Hello World"); + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); } } @@ -148,8 +148,8 @@ public void Put_dotnet_behind_symlink() .CaptureStdErr() .CaptureStdOut() .Execute() - .Should().Pass() - .And.HaveStdOutContaining("Hello World"); + .Should().Fail() + .And.HaveStdErrContaining($"[{Path.Combine(testDir.Location, "host", "fxr")}] does not exist"); } } From 586355f5177e21fea60d891061868d8a48137054 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 6 May 2025 13:59:04 -0700 Subject: [PATCH 07/12] Conditional behavior based on OS --- .../HostActivation.Tests/SymbolicLinks.cs | 77 +++++++++++++++---- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs index 8e7fe336f8190f..90d1c9a7c6046a 100644 --- a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs +++ b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using FluentAssertions; using Microsoft.DotNet.Cli.Build.Framework; @@ -33,12 +34,23 @@ public void Run_apphost_behind_symlink(string symlinkRelativePath) var symlinkFullPath = Path.Combine(testDir.Location, symlinkRelativePath); using var symlink = new SymLink(symlinkFullPath, sharedTestState.SelfContainedApp.AppExe); - Command.Create(symlinkFullPath) + var result = Command.Create(symlinkFullPath) .CaptureStdErr() .CaptureStdOut() - .Execute() - .Should().Fail() - .And.HaveStdErrContaining("The application to execute does not exist"); + .Execute(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + result + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); + } + else + { + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } } } @@ -63,12 +75,23 @@ public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePa Directory.CreateDirectory(Path.GetDirectoryName(symlink1Path)); using var symlink1 = new SymLink(symlink1Path, symlink2Path); - Command.Create(symlink1.SrcPath) + var result = Command.Create(symlink1.SrcPath) .CaptureStdErr() .CaptureStdOut() - .Execute() - .Should().Fail() - .And.HaveStdErrContaining("The application to execute does not exist"); + .Execute(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + result + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); + } + else + { + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } } } @@ -86,13 +109,24 @@ public void Run_framework_dependent_app_behind_symlink(string symlinkRelativePat Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath))); using var symlink = new SymLink(Path.Combine(testDir.Location, symlinkRelativePath), sharedTestState.FrameworkDependentApp.AppExe); - Command.Create(symlink.SrcPath) + var result = Command.Create(symlink.SrcPath) .CaptureStdErr() .CaptureStdOut() .DotNetRoot(TestContext.BuiltDotNet.BinPath) - .Execute() - .Should().Fail() - .And.HaveStdErrContaining("The application to execute does not exist"); + .Execute(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + result + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); + } + else + { + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } } } @@ -144,12 +178,23 @@ public void Put_dotnet_behind_symlink() var dotnetSymlink = Path.Combine(testDir.Location, Binaries.DotNet.FileName); using var symlink = new SymLink(dotnetSymlink, TestContext.BuiltDotNet.DotnetExecutablePath); - Command.Create(symlink.SrcPath, sharedTestState.SelfContainedApp.AppDll) + var result = Command.Create(symlink.SrcPath, sharedTestState.SelfContainedApp.AppDll) .CaptureStdErr() .CaptureStdOut() - .Execute() - .Should().Fail() - .And.HaveStdErrContaining($"[{Path.Combine(testDir.Location, "host", "fxr")}] does not exist"); + .Execute(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + result + .Should().Fail() + .And.HaveStdErrContaining($"[{Path.Combine(testDir.Location, "host", "fxr")}] does not exist"); + } + else + { + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } } } From a30fb3776e648b9770ce75d2aa3af60d59cb0b67 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 12 May 2025 20:35:58 -0700 Subject: [PATCH 08/12] Add unit test for success with symlinks --- .../HostActivation.Tests/SymbolicLinks.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs index 90d1c9a7c6046a..7b735896140baf 100644 --- a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs +++ b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -22,6 +23,48 @@ public SymbolicLinks(SymbolicLinks.SharedTestState fixture) sharedTestState = fixture; } + [Theory] + [InlineData("a/b/SymlinkToFrameworkDependentApp")] + [InlineData("a/SymlinkToFrameworkDependentApp")] + public void Symlink_all_files(string symlinkRelativePath) + { + using var testDir = TestArtifact.Create("symlink"); + Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath))); + + // Symlink every file in the app directory + var symlinks = new List(); + try + { + foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location)) + { + var fileName = Path.GetFileName(file); + var symlinkPath = Path.Combine(testDir.Location, symlinkRelativePath, fileName); + Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath)); + symlinks.Add(new SymLink(symlinkPath, file)); + } + + var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe))) + .CaptureStdErr() + .CaptureStdOut() + .DotNetRoot(TestContext.BuiltDotNet.BinPath) + .Execute(); + + // This should succeed on all platforms, but for different reasons: + // * Windows: The apphost will look next to the symlink for the app dll and find the symlinked dll + // * Unix: The apphost will look next to the resolved apphost for the app dll and find the real thing + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } + finally + { + foreach (var symlink in symlinks) + { + symlink.Dispose(); + } + } + } + [Theory] [InlineData ("a/b/SymlinkToApphost")] [InlineData ("a/SymlinkToApphost")] From ea6384a4de08f7fd8f0f3abc8621d1ac41e6c2d2 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 12 May 2025 20:37:05 -0700 Subject: [PATCH 09/12] Add self-contained test as well --- .../HostActivation.Tests/SymbolicLinks.cs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs index 7b735896140baf..8ca17401307838 100644 --- a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs +++ b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs @@ -26,7 +26,7 @@ public SymbolicLinks(SymbolicLinks.SharedTestState fixture) [Theory] [InlineData("a/b/SymlinkToFrameworkDependentApp")] [InlineData("a/SymlinkToFrameworkDependentApp")] - public void Symlink_all_files(string symlinkRelativePath) + public void Symlink_all_files_fx(string symlinkRelativePath) { using var testDir = TestArtifact.Create("symlink"); Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath))); @@ -65,6 +65,48 @@ public void Symlink_all_files(string symlinkRelativePath) } } + [Theory] + [InlineData("a/b/SymlinkToFrameworkDependentApp")] + [InlineData("a/SymlinkToFrameworkDependentApp")] + public void Symlink_all_files_self_contained(string symlinkRelativePath) + { + using var testDir = TestArtifact.Create("symlink"); + Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath))); + + // Symlink every file in the app directory + var symlinks = new List(); + try + { + foreach (var file in Directory.EnumerateFiles(sharedTestState.SelfContainedApp.Location)) + { + var fileName = Path.GetFileName(file); + var symlinkPath = Path.Combine(testDir.Location, symlinkRelativePath, fileName); + Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath)); + symlinks.Add(new SymLink(symlinkPath, file)); + } + + var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe))) + .CaptureStdErr() + .CaptureStdOut() + .DotNetRoot(TestContext.BuiltDotNet.BinPath) + .Execute(); + + // This should succeed on all platforms, but for different reasons: + // * Windows: The apphost will look next to the symlink for the files and find the symlinks + // * Unix: The apphost will look next to the resolved apphost for the files and find the real thing + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } + finally + { + foreach (var symlink in symlinks) + { + symlink.Dispose(); + } + } + } + [Theory] [InlineData ("a/b/SymlinkToApphost")] [InlineData ("a/SymlinkToApphost")] From 9ccf540df0f557056b89f4b3754bf3a54da32b03 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Wed, 28 May 2025 14:48:22 -0700 Subject: [PATCH 10/12] Add test with split files --- .../HostActivation.Tests/SymbolicLinks.cs | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs index 8ca17401307838..2dcb929f70a528 100644 --- a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs +++ b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs @@ -65,6 +65,79 @@ public void Symlink_all_files_fx(string symlinkRelativePath) } } + [Theory] + [InlineData("a/b/SymlinkToFrameworkDependentApp")] + [InlineData("a/SymlinkToFrameworkDependentApp")] + public void Symlink_split_files_fx(string symlinkRelativePath) + { + using var testDir = TestArtifact.Create("symlink"); + + // Split the app into two directories, one for the apphost and one for the rest of the files + var appHostDir = Path.Combine(testDir.Location, "apphost"); + var appFilesDir = Path.Combine(testDir.Location, "appfiles"); + Directory.CreateDirectory(appHostDir); + Directory.CreateDirectory(appFilesDir); + + File.Copy( + Path.Combine(sharedTestState.FrameworkDependentApp.Location, sharedTestState.FrameworkDependentApp.AppExe), + Path.Combine(appHostDir, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe))); + + foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location)) + { + var fileName = Path.GetFileName(file); + if (fileName != sharedTestState.FrameworkDependentApp.AppExe) + { + File.Copy(file, Path.Combine(appFilesDir, fileName)); + } + } + + // Symlink all of the above into a single directory + var targetPath = Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)); + Directory.CreateDirectory(targetPath); + var symlinks = new List(); + try + { + foreach (var file in Directory.EnumerateFiles(appFilesDir)) + { + var fileName = Path.GetFileName(file); + var symlinkPath = Path.Combine(targetPath, symlinkRelativePath, fileName); + Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath)); + symlinks.Add(new SymLink(file, symlinkPath)); + } + symlinks.Add(new SymLink( + Path.Combine(appHostDir, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)), + Path.Combine(targetPath, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)))); + + var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe))) + .CaptureStdErr() + .CaptureStdOut() + .DotNetRoot(TestContext.BuiltDotNet.BinPath) + .Execute(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // On Windows, the apphost will look next to the symlink for the app dll and find the symlinks + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } + else + { + // On Unix, the apphost will not find the app files next to the symlink + result + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); + } + } + finally + { + foreach (var symlink in symlinks) + { + symlink.Dispose(); + } + } + } + [Theory] [InlineData("a/b/SymlinkToFrameworkDependentApp")] [InlineData("a/SymlinkToFrameworkDependentApp")] From dad79163a99b6e771216e786883651419ab7fd46 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Wed, 28 May 2025 14:56:52 -0700 Subject: [PATCH 11/12] Simplify dir handling --- src/installer/tests/HostActivation.Tests/SymbolicLinks.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs index 2dcb929f70a528..ecd9280c834458 100644 --- a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs +++ b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs @@ -92,7 +92,7 @@ public void Symlink_split_files_fx(string symlinkRelativePath) } // Symlink all of the above into a single directory - var targetPath = Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)); + var targetPath = Path.Combine(testDir.Location, symlinkRelativePath); Directory.CreateDirectory(targetPath); var symlinks = new List(); try @@ -100,15 +100,15 @@ public void Symlink_split_files_fx(string symlinkRelativePath) foreach (var file in Directory.EnumerateFiles(appFilesDir)) { var fileName = Path.GetFileName(file); - var symlinkPath = Path.Combine(targetPath, symlinkRelativePath, fileName); + var symlinkPath = Path.Combine(targetPath, fileName); Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath)); symlinks.Add(new SymLink(file, symlinkPath)); } symlinks.Add(new SymLink( Path.Combine(appHostDir, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)), - Path.Combine(targetPath, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)))); + Path.Combine(targetPath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)))); - var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe))) + var result = Command.Create(Path.Combine(targetPath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe))) .CaptureStdErr() .CaptureStdOut() .DotNetRoot(TestContext.BuiltDotNet.BinPath) From bcf5e93723f6a7250fe7a79a47e3fe8c7a08c631 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 2 Jun 2025 19:55:46 -0700 Subject: [PATCH 12/12] Simplify test code --- .../tests/HostActivation.Tests/SymbolicLinks.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs index ecd9280c834458..0ae2fa863dc7aa 100644 --- a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs +++ b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs @@ -78,14 +78,16 @@ public void Symlink_split_files_fx(string symlinkRelativePath) Directory.CreateDirectory(appHostDir); Directory.CreateDirectory(appFilesDir); + var appHostName = Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe); + File.Copy( - Path.Combine(sharedTestState.FrameworkDependentApp.Location, sharedTestState.FrameworkDependentApp.AppExe), - Path.Combine(appHostDir, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe))); + sharedTestState.FrameworkDependentApp.AppExe, + Path.Combine(appHostDir, appHostName)); foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location)) { var fileName = Path.GetFileName(file); - if (fileName != sharedTestState.FrameworkDependentApp.AppExe) + if (fileName != appHostName) { File.Copy(file, Path.Combine(appFilesDir, fileName)); } @@ -102,13 +104,14 @@ public void Symlink_split_files_fx(string symlinkRelativePath) var fileName = Path.GetFileName(file); var symlinkPath = Path.Combine(targetPath, fileName); Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath)); - symlinks.Add(new SymLink(file, symlinkPath)); + symlinks.Add(new SymLink(symlinkPath, file)); } symlinks.Add(new SymLink( - Path.Combine(appHostDir, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)), - Path.Combine(targetPath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)))); + Path.Combine(targetPath, appHostName), + Path.Combine(appHostDir, appHostName))); - var result = Command.Create(Path.Combine(targetPath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe))) + Console.ReadLine(); + var result = Command.Create(Path.Combine(targetPath, appHostName)) .CaptureStdErr() .CaptureStdOut() .DotNetRoot(TestContext.BuiltDotNet.BinPath)