From f0eaff8839360a9570c5c73f7e9551a6bc485d3c Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 15 Apr 2019 12:31:32 -0700 Subject: [PATCH 01/24] Native hosting - add host context-based entry points --- src/corehost/cli/comhost/comhost.cpp | 18 +- src/corehost/cli/context_contract.h | 49 ++ src/corehost/cli/fxr/corehost_init.cpp | 6 + src/corehost/cli/fxr/corehost_init.h | 2 + src/corehost/cli/fxr/fx_muxer.cpp | 264 ++++++++-- src/corehost/cli/fxr/fx_muxer.h | 28 +- src/corehost/cli/fxr/host_context.h | 34 ++ src/corehost/cli/fxr/hostfxr.cpp | 461 ++++++++++++++++-- src/corehost/cli/fxr/hostpolicy_resolver.cpp | 12 +- src/corehost/cli/fxr/hostpolicy_resolver.h | 10 + src/corehost/cli/fxr_resolver.h | 35 +- src/corehost/cli/hostfxr.h | 48 +- src/corehost/cli/hostpolicy/args.cpp | 4 +- src/corehost/cli/hostpolicy/hostpolicy.cpp | 231 +++++++-- .../cli/hostpolicy/hostpolicy_context.cpp | 1 + .../cli/hostpolicy/hostpolicy_context.h | 1 + src/corehost/cli/ijwhost/ijwhost.cpp | 10 +- src/corehost/cli/winrthost/winrthost.cpp | 18 +- src/corehost/error_codes.h | 4 + 19 files changed, 1065 insertions(+), 171 deletions(-) create mode 100644 src/corehost/cli/context_contract.h create mode 100644 src/corehost/cli/fxr/host_context.h diff --git a/src/corehost/cli/comhost/comhost.cpp b/src/corehost/cli/comhost/comhost.cpp index 171a4c84b2..63c56c9c8f 100644 --- a/src/corehost/cli/comhost/comhost.cpp +++ b/src/corehost/cli/comhost/comhost.cpp @@ -50,21 +50,23 @@ namespace { return load_fxr_and_get_delegate( hostfxr_delegate_type::com_activation, - [](const pal::string_t& host_path, pal::string_t* app_path_out) + [app_path](const pal::string_t& host_path, pal::string_t* config_path_out) { - pal::string_t app_path_local{ host_path }; - - // Strip the comhost suffix to get the 'app' - size_t idx = app_path_local.rfind(_X(".comhost.dll")); + // Strip the comhost suffix to get the 'app' and config + size_t idx = host_path.rfind(_X(".comhost.dll")); assert(idx != pal::string_t::npos); + + pal::string_t app_path_local{ host_path }; app_path_local.replace(app_path_local.begin() + idx, app_path_local.end(), _X(".dll")); + *app_path = std::move(app_path_local); - *app_path_out = std::move(app_path_local); + pal::string_t config_path_local { host_path }; + config_path_local.replace(config_path_local.begin() + idx, config_path_local.end(), _X(".runtimeconfig.json")); + *config_path_out = std::move(config_path_local); return StatusCode::Success; }, - delegate, - app_path + delegate ); } diff --git a/src/corehost/cli/context_contract.h b/src/corehost/cli/context_contract.h new file mode 100644 index 0000000000..3f28c125af --- /dev/null +++ b/src/corehost/cli/context_contract.h @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __CONTEXT_CONTRACT_H__ +#define __CONTEXT_CONTRACT_H__ + +#include + +typedef void* context_handle; + +enum class coreclr_delegate_type +{ + invalid, + com_activation, + load_in_memory_assembly, + winrt_activation +}; + +struct corehost_context_contract +{ + size_t version; + context_handle instance; + int (*get_property_value)( + const context_handle instance, + const pal::char_t* key, + const pal::char_t** value); + int (*set_property_value)( + const context_handle instance, + const pal::char_t* key, + const pal::char_t* value); + int (*get_properties)( + const context_handle instance, + size_t *count, + const pal::char_t** keys, + const pal::char_t** values); + int (*load_runtime)( + const context_handle instance); + int (*run_app)( + const context_handle instance, + const int argc, + const pal::char_t* argv[]); + int (*get_runtime_delegate)( + const context_handle instance, + coreclr_delegate_type type, + void** delegate); +}; + +#endif // __CONTEXT_CONTRACT_H__ \ No newline at end of file diff --git a/src/corehost/cli/fxr/corehost_init.cpp b/src/corehost/cli/fxr/corehost_init.cpp index 4a26793b81..1e2ff2efdd 100644 --- a/src/corehost/cli/fxr/corehost_init.cpp +++ b/src/corehost/cli/fxr/corehost_init.cpp @@ -129,3 +129,9 @@ const host_interface_t& corehost_init_t::get_host_init_data() return hi; } + +void corehost_init_t::get_runtime_properties(std::unordered_map &out_properties) +{ + for (int i = 0; i < m_clr_keys.size(); ++i) + out_properties[m_clr_keys[i]] = m_clr_values[i]; +} \ No newline at end of file diff --git a/src/corehost/cli/fxr/corehost_init.h b/src/corehost/cli/fxr/corehost_init.h index d4eb972625..92117a0206 100644 --- a/src/corehost/cli/fxr/corehost_init.h +++ b/src/corehost/cli/fxr/corehost_init.h @@ -47,6 +47,8 @@ class corehost_init_t const fx_definition_vector_t& fx_definitions); const host_interface_t& get_host_init_data(); + + void get_runtime_properties(std::unordered_map &out_properties); }; #endif // __COREHOST_INIT_H__ \ No newline at end of file diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index 1b1828a35e..79d49c0304 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#include #include +#include #include #include #include @@ -11,6 +13,7 @@ #include #include "corehost_init.h" +#include "context_contract.h" #include "deps_format.h" #include "framework_info.h" #include "fx_definition.h" @@ -26,9 +29,16 @@ #include "roll_fwd_on_no_candidate_fx_option.h" using corehost_main_fn = int(*) (const int argc, const pal::char_t* argv[]); -using corehost_get_delegate_fn = int(*)(coreclr_delegate_type type, void** delegate); using corehost_main_with_output_buffer_fn = int(*) (const int argc, const pal::char_t* argv[], pal::char_t buffer[], int32_t buffer_size, int32_t* required_buffer_size); +namespace +{ + std::mutex g_context_lock; + std::condition_variable g_context_cv; + std::unique_ptr g_active_host_context; + std::atomic g_context_initializing(false); +} + template int load_hostpolicy( const pal::string_t& lib_dir, @@ -68,6 +78,13 @@ static int execute_app( if (code != StatusCode::Success) return code; + { + // Track an empty 'active' context so that host context-based APIs can work properly when + // the runtime is loaded through non-host context-based APIs. + std::lock_guard lock{ g_context_lock }; + g_active_host_context.reset(new host_context_t()); + } + { propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer); @@ -480,7 +497,7 @@ namespace runtime_config_t::settings_t override_settings; - // `Roll forward` is set to Minor (2) (roll_forward_option::Minor) by default. + // `Roll forward` is set to Minor (2) (roll_forward_option::Minor) by default. // For backward compatibility there are two settings: // - rollForward (the new one) which has more possible values // - rollForwardOnNoCandidateFx (the old one) with only 0-Off, 1-Minor, 2-Major @@ -702,37 +719,6 @@ int fx_muxer_t::execute( namespace { - int get_delegate_from_runtime( - const pal::string_t& impl_dll_dir, - corehost_init_t* init, - coreclr_delegate_type type, - void** delegate) - { - pal::dll_t corehost; - hostpolicy_contract host_contract{}; - corehost_get_delegate_fn coreclr_delegate = nullptr; - - int code = load_hostpolicy(impl_dll_dir, &corehost, host_contract, "corehost_get_coreclr_delegate", &coreclr_delegate); - if (code != StatusCode::Success) - { - trace::error(_X("This component must target .NET Core 3.0 or a higher version.")); - return code; - } - - { - propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer); - - const host_interface_t& intf = init->get_host_init_data(); - - if ((code = host_contract.load(&intf)) == StatusCode::Success) - { - code = coreclr_delegate(type, delegate); - } - } - - return code; - } - int get_init_info_for_component( const host_startup_info_t &host_info, host_mode_t mode, @@ -777,24 +763,222 @@ namespace return StatusCode::Success; } + + int initialize_context(const pal::string_t impl_dir, hostpolicy_contract &host_contract, const host_interface_t &host_interface, corehost_context_contract *context_contract) + { + pal::dll_t corehost; + int rc = hostpolicy_resolver::load(impl_dir, &corehost, host_contract); + if (rc != StatusCode::Success) + { + trace::error(_X("An error occurred while loading required library %s from [%s]"), LIBHOSTPOLICY_NAME, impl_dir.c_str()); + return rc; + } + + if (host_contract.init_context == nullptr) + { + trace::error(_X("This component must target .NET Core 3.0 or a higher version.")); + return StatusCode::HostApiUnsupportedVersion; + } + + { + propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer); + if ((rc = host_contract.load(&host_interface)) == StatusCode::Success) + { + rc = host_contract.init_context(&host_interface, context_contract); + } + } + + return rc; + } } -int fx_muxer_t::load_runtime_and_get_delegate( +int fx_muxer_t::initialize_for_app( const host_startup_info_t& host_info, - host_mode_t mode, - coreclr_delegate_type delegate_type, - void** delegate) + int argc, + const pal::char_t* argv[], + void** host_context_handle) +{ + { + std::unique_lock lock{ g_context_lock }; + g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); + + if (g_active_host_context != nullptr) + return StatusCode::HostInvalidState; + + g_context_initializing.store(true); + } + + host_mode_t mode = host_mode_t::apphost; + opt_map_t opts; + pal::string_t impl_dir; + std::unique_ptr init; + int rc = get_init_info_for_app( + pal::string_t{} /*host_command*/, + host_info, + host_info.app_path, + opts, + mode, + impl_dir, + init); + if (rc != StatusCode::Success) + return rc; + + const host_interface_t &intf = init->get_host_init_data(); + hostpolicy_contract host_contract{}; + std::unique_ptr context(new host_context_t()); + rc = initialize_context(impl_dir, host_contract, intf, &context->context_contract); + if (rc != StatusCode::Success) + return rc; + + context->type = host_context_type::initialized; + context->host_contract = host_contract; + context->is_app = true; + for (int i = 0; i < argc; ++i) + context->argv.push_back(argv[i]); + + *host_context_handle = context.release(); + return rc; +} + +int fx_muxer_t::initialize_for_runtime_config( + const host_startup_info_t& host_info, + const pal::char_t * runtime_config_path, + void** host_context_handle) { - assert(host_info.is_valid(mode)); + bool already_initialized = false; + { + std::unique_lock lock{ g_context_lock }; + g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); + + already_initialized = g_active_host_context != nullptr; + if (!already_initialized) + { + g_context_initializing.store(true); + } + else if (g_active_host_context->type == host_context_type::invalid) + { + return StatusCode::HostInvalidState; + } + } - pal::string_t runtime_config = _X(""); + host_mode_t mode = host_mode_t::libhost; + pal::string_t runtime_config = runtime_config_path; pal::string_t impl_dir; std::unique_ptr init; int rc = get_init_info_for_component(host_info, mode, runtime_config, impl_dir, init); if (rc != StatusCode::Success) return rc; - rc = get_delegate_from_runtime(impl_dir, init.get(), delegate_type, delegate); + const host_interface_t &intf = init->get_host_init_data(); + hostpolicy_contract host_contract{}; + std::unique_ptr context(new host_context_t()); + rc = initialize_context(impl_dir, host_contract, intf, &context->context_contract); + if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) + return rc; + + context->host_contract = host_contract; + context->is_app = false; + if (already_initialized || rc == StatusCode::CoreHostAlreadyInitialized) + { + context->type = host_context_type::secondary; + init->get_runtime_properties(context->config_properties); + } + else + { + context->type = host_context_type::initialized; + } + + *host_context_handle = context.release(); + return rc; +} + +namespace +{ + int load_runtime(host_context_t *context) + { + assert(context->type == host_context_type::initialized || context->type == host_context_type::active); + if (context->type == host_context_type::active) + return StatusCode::Success; + + const corehost_context_contract &contract = context->context_contract; + int rc = contract.load_runtime(contract.instance); + + // Mark the context as active or invalid + context->type = rc == StatusCode::Success ? host_context_type::active : host_context_type::invalid; + + { + std::lock_guard lock{ g_context_lock }; + g_active_host_context.reset(context); + g_context_initializing.store(false); + } + + g_context_cv.notify_all(); + return rc; + } +} + +int fx_muxer_t::run_app(host_context_t *context) +{ + if (!context->is_app || context->type == host_context_type::invalid) + return StatusCode::InvalidArgFailure; + + int argc = context->argv.size(); + std::vector argv; + argv.reserve(argc); + for (const auto& str : context->argv) + argv.push_back(str.c_str()); + + const corehost_context_contract &contract = context->context_contract; + { + propagate_error_writer_t propagate_error_writer_to_corehost(context->host_contract.set_error_writer); + + int rc = load_runtime(context); + if (rc != StatusCode::Success) + return rc; + + return contract.run_app(contract.instance, argc, argv.data()); + } +} + +int fx_muxer_t::get_runtime_delegate(host_context_t *context, coreclr_delegate_type type, void **delegate) +{ + if (context->is_app || context->type == host_context_type::invalid) + return StatusCode::InvalidArgFailure; + + const corehost_context_contract &contract = context->context_contract; + { + propagate_error_writer_t propagate_error_writer_to_corehost(context->host_contract.set_error_writer); + + if (context->type != host_context_type::secondary) + { + int rc = load_runtime(context); + if (rc != StatusCode::Success) + return rc; + } + + return contract.get_runtime_delegate(contract.instance, type, delegate); + } +} + +int fx_muxer_t::close_host_context(const host_context_t *context) +{ + const hostpolicy_contract &host_contract = context->host_contract; + if (host_contract.close_context == nullptr) + { + trace::error(_X("This component must target .NET Core 3.0 or a higher version.")); + return StatusCode::HostApiUnsupportedVersion; + } + + int rc; + { + propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer); + rc = host_contract.close_context(context->context_contract); + } + + // Do not delete the active context. + if (context->type != host_context_type::active) + delete context; + return rc; } diff --git a/src/corehost/cli/fxr/fx_muxer.h b/src/corehost/cli/fxr/fx_muxer.h index 6c62c78cb0..df26eaaffb 100644 --- a/src/corehost/cli/fxr/fx_muxer.h +++ b/src/corehost/cli/fxr/fx_muxer.h @@ -4,22 +4,16 @@ class corehost_init_t; class runtime_config_t; -class fx_definition_t; +class fx_definition_t; struct fx_ver_t; struct host_startup_info_t; +#include +#include "error_codes.h" #include "fx_definition.h" +#include "host_context.h" #include "host_interface.h" #include "host_startup_info.h" -#include "error_codes.h" - -enum class coreclr_delegate_type -{ - invalid, - com_activation, - load_in_memory_assembly, - winrt_activation -}; class fx_muxer_t { @@ -32,11 +26,21 @@ class fx_muxer_t pal::char_t result_buffer[], int32_t buffer_size, int32_t* required_buffer_size); - static int load_runtime_and_get_delegate( + static int initialize_for_app( const host_startup_info_t& host_info, - host_mode_t mode, + int argc, + const pal::char_t* argv[], + void** host_context_handle); + static int initialize_for_runtime_config( + const host_startup_info_t& host_info, + const pal::char_t * runtime_config_path, + void** host_context_handle); + static int run_app(host_context_t *context); + static int get_runtime_delegate( + host_context_t *context, coreclr_delegate_type delegate_type, void** delegate); + static int close_host_context(const host_context_t *context); private: static int parse_args( const host_startup_info_t& host_info, diff --git a/src/corehost/cli/fxr/host_context.h b/src/corehost/cli/fxr/host_context.h new file mode 100644 index 0000000000..ac67837e6b --- /dev/null +++ b/src/corehost/cli/fxr/host_context.h @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __HOST_CONTEXT_H__ +#define __HOST_CONTEXT_H__ + +#include + +#include +#include "hostpolicy_resolver.h" + +enum class host_context_type +{ + initialized, // Created, but not active (runtime not loaded) + active, // Runtime loaded for this context + secondary, // Created after runtime was loaded using another context + invalid, // Failed on loading runtime +}; + +struct host_context_t +{ + host_context_type type; + + hostpolicy_contract host_contract; + corehost_context_contract context_contract; + + bool is_app; + std::vector argv; + + std::unordered_map config_properties; +}; + +#endif // __HOST_CONTEXT_H__ \ No newline at end of file diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index 3b54d6bbe5..7a1cac3c40 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -13,6 +13,7 @@ #include "sdk_info.h" #include "sdk_resolver.h" #include "hostfxr.h" +#include "host_context.h" SHARED_API int hostfxr_main_startupinfo(const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path) { @@ -51,13 +52,13 @@ SHARED_API int hostfxr_main(const int argc, const pal::char_t* argv[]) // sub-folders. Pass the directory of a dotnet executable to // mimic how that executable would search in its own directory. // It is also valid to pass nullptr or empty, in which case -// multi-level lookup can still search other locations if +// multi-level lookup can still search other locations if // it has not been disabled by the user's environment. // // working_dir // The directory where the search for global.json (which can // control the resolved SDK version) starts and proceeds -// upwards. +// upwards. // // buffer // The buffer where the resolved SDK path will be written. @@ -156,18 +157,18 @@ typedef void (*hostfxr_resolve_sdk2_result_fn)( // sub-folders. Pass the directory of a dotnet executable to // mimic how that executable would search in its own directory. // It is also valid to pass nullptr or empty, in which case -// multi-level lookup can still search other locations if +// multi-level lookup can still search other locations if // it has not been disabled by the user's environment. // // working_dir // The directory where the search for global.json (which can // control the resolved SDK version) starts and proceeds -// upwards. +// upwards. // // flags // Bitwise flags that influence resolution. // disallow_prerelease (0x1) -// do not allow resolution to return a prerelease SDK version +// do not allow resolution to return a prerelease SDK version // unless prerelease version was specified via global.json. // // result @@ -190,7 +191,7 @@ typedef void (*hostfxr_resolve_sdk2_result_fn)( // Return value: // 0 on success, otherwise failure // 0x8000809b - SDK could not be resolved (SdkResolverResolveFailure) -// +// // String encoding: // Windows - UTF-16 (pal::char_t is 2 byte wchar_t) // Unix - UTF-8 (pal::char_t is 1 byte char) @@ -219,7 +220,7 @@ SHARED_API int32_t hostfxr_resolve_sdk2( pal::string_t global_json_path; bool success = sdk_resolver_t::resolve_sdk_dotnet_path( - exe_dir, + exe_dir, working_dir, &resolved_sdk_dir, (flags & hostfxr_resolve_sdk2_flags_t::disallow_prerelease) != 0, @@ -240,7 +241,7 @@ SHARED_API int32_t hostfxr_resolve_sdk2( } return success - ? StatusCode::Success + ? StatusCode::Success : StatusCode::SdkResolverResolveFailure; } @@ -303,7 +304,7 @@ SHARED_API int32_t hostfxr_get_available_sdks( result(sdk_dirs.size(), &sdk_dirs[0]); } - + return StatusCode::Success; } @@ -372,18 +373,18 @@ typedef void(*hostfxr_error_writer_fn)(const pal::char_t* message); // Sets a callback which is to be used to write errors to. // // Parameters: -// error_writer +// error_writer // A callback function which will be invoked every time an error is to be reported. // Or nullptr to unregister previously registered callback and return to the default behavior. // Return value: // The previously registered callback (which is now unregistered), or nullptr if no previous callback // was registered -// +// // The error writer is registered per-thread, so the registration is thread-local. On each thread // only one callback can be registered. Subsequent registrations overwrite the previous ones. -// +// // By default no callback is registered in which case the errors are written to stderr. -// +// // Each call to the error writer is sort of like writing a single line (the EOL character is omitted). // Multiple calls to the error writer may occure for one failure. // @@ -396,55 +397,431 @@ SHARED_API hostfxr_error_writer_fn hostfxr_set_error_writer(hostfxr_error_writer return trace::set_error_writer(error_writer); } -coreclr_delegate_type hostfxr_delegate_to_coreclr_delegate(hostfxr_delegate_type type) +namespace +{ + int populate_startup_info(const hostfxr_initialize_parameters *parameters, host_startup_info_t &startup_info) + { + if (parameters != nullptr) + { + if (parameters->host_path != nullptr) + startup_info.host_path = parameters->host_path; + + if (parameters->dotnet_root != nullptr) + startup_info.dotnet_root = parameters->dotnet_root; + } + + if (startup_info.host_path.empty()) + { + if (!pal::get_own_executable_path(&startup_info.host_path) || !pal::realpath(&startup_info.host_path)) + { + trace::error(_X("Failed to resolve full path of the current host [%s]"), startup_info.host_path.c_str()); + return StatusCode::CoreHostCurHostFindFailure; + } + } + + if (startup_info.dotnet_root.empty()) + { + pal::string_t mod_path; + if (!pal::get_own_module_path(&mod_path)) + return StatusCode::CoreHostCurHostFindFailure; + + pal::string_t fxr_root = get_directory(get_directory(mod_path)); + startup_info.dotnet_root = get_directory(get_directory(fxr_root)); + if (!pal::realpath(&startup_info.dotnet_root)) + { + trace::error(_X("Failed to resolve full path of dotnet root [%s]"), startup_info.dotnet_root.c_str()); + return StatusCode::CoreHostCurHostFindFailure; + } + } + + return StatusCode::Success; + } +} + +// +// Initializes the hosting components for running an application +// +// Parameters: +// argc +// Number of argv arguments +// argv +// Arguments for the application. If argc is 0, this is ignored. +// app_path +// Path to the managed application. If this is nullptr, the first argument in argv is assumed to be +// the application path. +// parameters +// Optional. Additional parameters for initialization +// host_context_handle +// On success, this will be populated with an opaque value representing the initalized host context +// +// Return value: +// Success - Hosting components were successfully initialized +// HostInvalidState - Hosting components are already initialized +// +// The app_path will be used to find the corresponding .runtimeconfig.json and .deps.json with which to +// resolve frameworks and dependencies and prepare everything needed to load the runtime. +// +// This function does not load the runtime. +// +SHARED_API int32_t __cdecl hostfxr_initialize_for_app( + int argc, + const pal::char_t *argv[], + const pal::char_t *app_path, + const hostfxr_initialize_parameters * parameters, + hostfxr_handle * host_context_handle) { - switch (type) + if (host_context_handle == nullptr || (argv == nullptr && argc != 0) || (app_path == nullptr && argc == 0)) + return StatusCode::InvalidArgFailure; + + *host_context_handle = nullptr; + + trace::setup(); + trace::info(_X("--- Invoked hostfxr_initialize_for_app [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + + host_startup_info_t startup_info{}; + int new_argc; + const pal::char_t **new_argv; + if (app_path != nullptr) + { + startup_info.app_path = app_path; + new_argc = argc; + new_argv = argv; + } + else { - case hostfxr_delegate_type::com_activation: - return coreclr_delegate_type::com_activation; - case hostfxr_delegate_type::load_in_memory_assembly: - return coreclr_delegate_type::load_in_memory_assembly; - case hostfxr_delegate_type::winrt_activation: - return coreclr_delegate_type::winrt_activation; + // Take the first argument as the app path + startup_info.app_path = argv[0]; + new_argc = argc-1; + new_argv = argc > 0 ? &argv[1] : nullptr; + } + + int rc = populate_startup_info(parameters, startup_info); + if (rc != StatusCode::Success) + return rc; + + return fx_muxer_t::initialize_for_app( + startup_info, + new_argc, + new_argv, + host_context_handle); +} + +// +// Initializes the hosting components using a .runtimeconfig.json file +// +// Parameters: +// runtime_config_path +// Path to the .runtimeconfig.json file +// parameters +// Optional. Additional parameters for initialization +// host_context_handle +// On success, this will be populated with an opaque value representing the initalized host context +// +// Return value: +// Success - Hosting components were successfully initialized +// CoreHostAlreadyInitialized - Config is compatible with already initialized hosting components +// [TODO] +// CoreHostIncompatibleConfig - Config is incompatible with already initialized hosting components +// CoreHostDifferentProperties - Config has runtime properties that differ from already initialized hosting components +// +// This function will process the .runtimeconfig.json to resolve frameworks and prepare everything needed +// to load the runtime. It will only process the .deps.json from frameworks (not any app/component that +// may be next to the .runtimeconfig.json). +// +// This function does not load the runtime. +// +// If called when the runtime has already been loaded, this function will check if the specified runtime +// config is compatible with the existing runtime. +// +SHARED_API int32_t __cdecl hostfxr_initialize_for_runtime_config( + const pal::char_t *runtime_config_path, + const hostfxr_initialize_parameters *parameters, + hostfxr_handle *host_context_handle) +{ + if (runtime_config_path == nullptr || host_context_handle == nullptr) + return StatusCode::InvalidArgFailure; + + *host_context_handle = nullptr; + + trace::setup(); + trace::info(_X("--- Invoked hostfxr_initialize_for_runtime_config [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + + host_startup_info_t startup_info{}; + int rc = populate_startup_info(parameters, startup_info); + if (rc != StatusCode::Success) + return rc; + + return fx_muxer_t::initialize_for_runtime_config( + startup_info, + runtime_config_path, + host_context_handle); +} + +// +// Load CoreCLR and run the application for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// +// Return value: +// The error code result. +// +// The host_context_handle must have been initialized using hostfxr_initialize_for_app. +// +// This function will not return until the managed application exits. +// +SHARED_API int32_t __cdecl hostfxr_run_app(const hostfxr_handle host_context_handle) +{ + if (host_context_handle == nullptr) + return StatusCode::InvalidArgFailure; + + trace::setup(); + trace::info(_X("--- Invoked hostfxr_run_app [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + + host_context_t *context = static_cast(host_context_handle); + return fx_muxer_t::run_app(context); +} + +namespace +{ + coreclr_delegate_type hostfxr_delegate_to_coreclr_delegate(hostfxr_delegate_type type) + { + switch (type) + { + case hostfxr_delegate_type::com_activation: + return coreclr_delegate_type::com_activation; + case hostfxr_delegate_type::load_in_memory_assembly: + return coreclr_delegate_type::load_in_memory_assembly; + case hostfxr_delegate_type::winrt_activation: + return coreclr_delegate_type::winrt_activation; + } + return coreclr_delegate_type::invalid; } - return coreclr_delegate_type::invalid; } // // Gets a typed delegate from the currently loaded CoreCLR or from a newly created one. // // Parameters: -// libhost_path -// Absolute path of the entry hosting library -// dotnet_root -// app_path +// host_context_handle +// Handle to the initialized host context +// type +// Type of runtime delegate requested // delegate -// An out parameter that will be assigned the delegate. +// An out parameter that will be assigned the delegate. +// // Return value: // The error code result. // -// A new CoreCLR instance will be created or reused if the existing instance can satisfy the configuration -// requirements supplied by the runtimeconfig.json file. +// The host_context_handle must have been initialized using hostfxr_initialize_for_runtime_config. // -SHARED_API int32_t hostfxr_get_runtime_delegate( - const pal::char_t* host_path, - const pal::char_t* dotnet_root, - const pal::char_t* app_path, +SHARED_API int32_t __cdecl hostfxr_get_runtime_delegate( + const hostfxr_handle host_context_handle, hostfxr_delegate_type type, - void** delegate) + void **delegate) { - if (host_path == nullptr || dotnet_root == nullptr || delegate == nullptr) + if (host_context_handle == nullptr || delegate == nullptr) return StatusCode::InvalidArgFailure; trace::setup(); - trace::info(_X("--- Invoked hostfxr_get_runtime_delegate [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - host_startup_info_t startup_info{ host_path, dotnet_root, app_path }; + host_context_t *context = static_cast(host_context_handle); + return fx_muxer_t::get_runtime_delegate(context, hostfxr_delegate_to_coreclr_delegate(type), delegate); +} - return fx_muxer_t::load_runtime_and_get_delegate( - startup_info, - host_mode_t::libhost, - hostfxr_delegate_to_coreclr_delegate(type), - delegate); +// +// Gets the runtime property value for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// name +// Runtime property name +// value +// Out parameter. Pointer to a buffer with the property value. +// +// Return value: +// The error code result. +// +// The buffer pointed to by value is owned by the host context. The lifetime of the buffer is only +// guaranteed until any of the below occur: +// - a 'run' method is called for the host context +// - properties are changed via hostfxr_set_runtime_property_value +// - the host context is closed via 'hostfxr_close' +// +// If host_context_handle is nullptr and an active host context exists, this function will get the +// property value for the active host context. +// +SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value( + const hostfxr_handle host_context_handle, + const pal::char_t *name, + const pal::char_t **value) +{ + if (name == nullptr || value == nullptr) + return StatusCode::InvalidArgFailure; + + trace::setup(); + trace::info(_X("--- Invoked hostfxr_get_runtime_property_value [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + + if (host_context_handle == nullptr) + { + // TODO: get property for active context + return StatusCode::InvalidArgFailure; + } + + host_context_t *context = static_cast(host_context_handle); + if (context->type == host_context_type::invalid) + return StatusCode::InvalidArgFailure; + + if (context->type == host_context_type::secondary) + { + const std::unordered_map &properties = context->config_properties; + auto iter = properties.find(name); + if (iter == properties.cend()) + return StatusCode::InvalidArgFailure; + + *value = (*iter).second.c_str(); + return S_OK; + } + + assert(context->type == host_context_type::initialized || context->type == host_context_type::active); + const corehost_context_contract contract = context->context_contract; + return contract.get_property_value(contract.instance, name, value); +} + +// +// Sets the value of a runtime property for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// name +// Runtime property name +// value +// Value to set +// +// Return value: +// The error code result. +// +// Setting properties is only supported for the first host context, before the runtime has been loaded. +// +// If the property already exists in the host context, it will be overwritten. If value is nullptr, the +// property will be removed. +// +SHARED_API int32_t __cdecl hostfxr_set_runtime_property_value( + const hostfxr_handle host_context_handle, + const pal::char_t *name, + const pal::char_t *value) +{ + if (name == nullptr) + return StatusCode::InvalidArgFailure; + + trace::setup(); + trace::info(_X("--- Invoked hostfxr_set_runtime_property_value [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + + host_context_t *context = static_cast(host_context_handle); + if (context->type != host_context_type::initialized) + { + trace::error(_X("Setting properties is not allowed once runtime has been loaded.")); + return StatusCode::InvalidArgFailure; + } + + const corehost_context_contract &contract = context->context_contract; + return contract.set_property_value(contract.instance, name, value); } + +// +// Gets all the runtime properties for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// count +// [in] Size of the keys and values buffers +// [out] Number of properties returned (size of keys/values buffers used). If the input value is too +// small or keys/values is nullptr, this is populated with the number of available properties +// keys +// Array of pointers to buffers with runtime property keys +// values +// Array of pointers to buffers with runtime property values +// +// Return value: +// The error code result. +// +// The buffers pointed to by keys and values are owned by the host context. The lifetime of the buffers is only +// guaranteed until any of the below occur: +// - a 'run' method is called for the host context +// - properties are changed via hostfxr_set_runtime_property_value +// - the host context is closed via 'hostfxr_close' +// +// If host_context_handle is nullptr and an active host context exists, this function will get the +// properties for the active host context. +// +SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( + const hostfxr_handle host_context_handle, + size_t * count, + const pal::char_t **keys, + const pal::char_t **values) +{ + if (count == nullptr) + return StatusCode::InvalidArgFailure; + + trace::setup(); + trace::info(_X("--- Invoked hostfxr_get_runtime_properties [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + + if (host_context_handle == nullptr) + { + // TODO: get properties for active context + return StatusCode::InvalidArgFailure; + } + + host_context_t *context = static_cast(host_context_handle); + if (context->type == host_context_type::invalid) + return StatusCode::InvalidArgFailure; + + if (context->type == host_context_type::secondary) + { + const std::unordered_map &properties = context->config_properties; + size_t actualCount = properties.size(); + if (*count < actualCount || keys == nullptr || values == nullptr) + { + *count = actualCount; + return StatusCode::HostApiBufferTooSmall; + } + + int i = 0; + for (const auto& kv : properties) + { + keys[i] = kv.first.data(); + values[i] = kv.second.data(); + ++i; + } + + return StatusCode::Success; + } + + assert(context->type == host_context_type::initialized || context->type == host_context_type::active); + const corehost_context_contract &contract = context->context_contract; + return contract.get_properties(contract.instance, count, keys, values); +} + +// +// Closes an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// +// Return value: +// The error code result. +// +SHARED_API int32_t __cdecl hostfxr_close(const hostfxr_handle host_context_handle) +{ + trace::setup(); + trace::info(_X("--- Invoked hostfxr_close [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + + host_context_t *context = static_cast(host_context_handle); + return fx_muxer_t::close_host_context(context); +} \ No newline at end of file diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.cpp b/src/corehost/cli/fxr/hostpolicy_resolver.cpp index 504171f868..8ffbf66127 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.cpp +++ b/src/corehost/cli/fxr/hostpolicy_resolver.cpp @@ -215,10 +215,14 @@ int hostpolicy_resolver::load( return StatusCode::CoreHostEntryPointFailure; g_hostpolicy_contract.set_error_writer = (corehost_set_error_writer_fn)pal::get_symbol(g_hostpolicy, "corehost_set_error_writer"); - - // It's possible to not have corehost_set_error_writer, since this was only introduced in 3.0 - // so 2.0 hostpolicy would not have the export. In this case we will not propagate the error writer - // and errors will still be reported to stderr. + g_hostpolicy_contract.init_context = (corehost_initialize_context_fn)pal::get_symbol(g_hostpolicy, "corehost_initialize_context"); + g_hostpolicy_contract.close_context = (corehost_close_context_fn)pal::get_symbol(g_hostpolicy, "corehost_close_context"); + + // It's possible to not have corehost_set_error_writer, corehost_initialize_context, and + // corehost_close_context. These were introduced in 3.0 so 2.0 hostpolicy would not have + // the export. In this case we will not propagate the error writer and errors will still + // be reported to stderr. Callers are responsible for checking that the function pointers + // are not null before using them. } // Return global values diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.h b/src/corehost/cli/fxr/hostpolicy_resolver.h index ec9156ad0e..8ab777d337 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.h +++ b/src/corehost/cli/fxr/hostpolicy_resolver.h @@ -2,14 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#ifndef __HOSTPOLICY_RESOLVER_H__ +#define __HOSTPOLICY_RESOLVER_H__ + #include #include #include +#include using corehost_load_fn = int(*) (const host_interface_t* init); using corehost_unload_fn = int(*) (); using corehost_error_writer_fn = void(*) (const pal::char_t* message); using corehost_set_error_writer_fn = corehost_error_writer_fn(*) (corehost_error_writer_fn error_writer); +using corehost_initialize_context_fn = int(*)(const host_interface_t* init, corehost_context_contract* handle); +using corehost_close_context_fn = int(*)(const corehost_context_contract handle); struct hostpolicy_contract { @@ -19,6 +25,8 @@ struct hostpolicy_contract // 3.0+ contracts corehost_set_error_writer_fn set_error_writer; + corehost_initialize_context_fn init_context; + corehost_close_context_fn close_context; }; namespace hostpolicy_resolver @@ -36,3 +44,5 @@ namespace hostpolicy_resolver const std::vector& probe_realpaths, pal::string_t* impl_dir); }; + +#endif // __HOSTPOLICY_RESOLVER_H__ \ No newline at end of file diff --git a/src/corehost/cli/fxr_resolver.h b/src/corehost/cli/fxr_resolver.h index f84365f8da..c39e4b63c9 100644 --- a/src/corehost/cli/fxr_resolver.h +++ b/src/corehost/cli/fxr_resolver.h @@ -18,8 +18,8 @@ namespace fxr_resolver pal::string_t dotnet_root_from_fxr_path(const pal::string_t &fxr_path); } -template -int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostNameToAppNameCallback host_path_to_app_path, TDelegate* delegate, pal::string_t* out_app_path = nullptr) +template +int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallback host_path_to_config_path, TDelegate* delegate) { pal::dll_t fxr; @@ -56,22 +56,37 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostNameToAppNameCall // Leak fxr - auto get_delegate_from_hostfxr = (hostfxr_get_delegate_fn)pal::get_symbol(fxr, "hostfxr_get_runtime_delegate"); - if (get_delegate_from_hostfxr == nullptr) + auto hostfxr_init_context = (hostfxr_initialize_for_runtime_config_fn)pal::get_symbol(fxr, "hostfxr_initialize_for_runtime_config"); + auto hostfxr_get_delegate = (hostfxr_get_runtime_delegate_fn)pal::get_symbol(fxr, "hostfxr_get_runtime_delegate"); + auto hostfxr_close = (hostfxr_close_fn)pal::get_symbol(fxr, "hostfxr_close"); + if (hostfxr_init_context == nullptr || hostfxr_get_delegate == nullptr || hostfxr_close == nullptr) return StatusCode::CoreHostEntryPointFailure; - pal::string_t app_path; - - pal::string_t* app_path_to_use = out_app_path != nullptr ? out_app_path : &app_path; - - pal::hresult_t status = host_path_to_app_path(host_path, app_path_to_use); + pal::string_t config_path; + pal::hresult_t status = host_path_to_config_path(host_path, &config_path); if (status != StatusCode::Success) { return status; } - return get_delegate_from_hostfxr(host_path.c_str(), dotnet_root.c_str(), app_path_to_use->c_str(), type, (void**)delegate); + hostfxr_initialize_parameters parameters { + sizeof(hostfxr_initialize_parameters), + host_path.c_str(), + dotnet_root.c_str() + }; + + hostfxr_handle context; + int rc = hostfxr_init_context(config_path.c_str(), ¶meters, &context); + if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) + return rc; + + rc = hostfxr_get_delegate(context, type, (void**)delegate); + + int rcClose = hostfxr_close(context); + assert(rcClose == StatusCode::Success); + + return rc; } #endif //_COREHOST_CLI_FXR_RESOLVER_H_ diff --git a/src/corehost/cli/hostfxr.h b/src/corehost/cli/hostfxr.h index 759a322898..759fa4c480 100644 --- a/src/corehost/cli/hostfxr.h +++ b/src/corehost/cli/hostfxr.h @@ -23,13 +23,6 @@ enum class hostfxr_delegate_type winrt_activation }; -using hostfxr_get_delegate_fn = int32_t(*)( - const pal::char_t* host_path, - const pal::char_t* dotnet_root, - const pal::char_t* app_path, - hostfxr_delegate_type type, - void** delegate); - using hostfxr_main_fn = int32_t(*)(const int argc, const pal::char_t* argv[]); using hostfxr_main_startupinfo_fn = int32_t(*)( const int argc, @@ -40,4 +33,45 @@ using hostfxr_main_startupinfo_fn = int32_t(*)( using hostfxr_error_writer_fn = void(*)(const pal::char_t* message); using hostfxr_set_error_writer_fn = hostfxr_error_writer_fn(*)(hostfxr_error_writer_fn error_writer); +using hostfxr_handle = void*; +struct hostfxr_initialize_parameters +{ + size_t size; + const pal::char_t *host_path; + const pal::char_t *dotnet_root; +}; + +using hostfxr_initialize_for_app_fn = int32_t(__cdecl *)( + int argc, + const pal::char_t *argv[], + const pal::char_t *app_path, + const hostfxr_initialize_parameters *parameters, + hostfxr_handle *host_context_handle); +using hostfxr_initialize_for_runtime_config_fn = int32_t(__cdecl *)( + const pal::char_t *runtime_config_path, + const hostfxr_initialize_parameters*parameters, + hostfxr_handle *host_context_handle); + +using hostfxr_get_runtime_property_value_fn = int32_t(__cdecl *)( + const hostfxr_handle host_context_handle, + const pal::char_t *name, + const pal::char_t **value); +using hostfxr_set_runtime_property_value_fn = int32_t(__cdecl *)( + const hostfxr_handle host_context_handle, + const pal::char_t *name, + const pal::char_t *value); +using hostfxr_get_runtime_properties_fn = int32_t(__cdecl *)( + const hostfxr_handle host_context_handle, + size_t * count, + const pal::char_t **keys, + const pal::char_t **values); + +using hostfxr_run_app_fn = int32_t(__cdecl *)(const hostfxr_handle host_context_handle); +using hostfxr_get_runtime_delegate_fn = int32_t(__cdecl *)( + const hostfxr_handle host_context_handle, + hostfxr_delegate_type type, + void **delegate); + +using hostfxr_close_fn = int32_t(__cdecl *)(const hostfxr_handle host_context_handle); + #endif //_COREHOST_CLI_HOSTFXR_H_ diff --git a/src/corehost/cli/hostpolicy/args.cpp b/src/corehost/cli/hostpolicy/args.cpp index 26cafe6258..8b629ab55c 100644 --- a/src/corehost/cli/hostpolicy/args.cpp +++ b/src/corehost/cli/hostpolicy/args.cpp @@ -57,7 +57,7 @@ void setup_shared_store_paths(const pal::string_t& tfm, host_mode_t host_mode,co bool parse_arguments( const hostpolicy_init_t& init, - const int argc, const pal::char_t* argv[], + const int argc, const pal::char_t* argv[], arguments_t& args) { pal::string_t managed_application_path; @@ -116,7 +116,7 @@ bool init_arguments( args.additional_deps_serialized = additional_deps_serialized; args.managed_application = managed_application_path; - if (!pal::realpath(&args.managed_application)) + if (!args.managed_application.empty() && !pal::realpath(&args.managed_application)) { trace::error(_X("Failed to locate managed application [%s]"), args.managed_application.c_str()); return false; diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index ceed0116c2..ce9445c80f 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#include +#include #include #include #include "args.h" @@ -14,6 +16,7 @@ #include #include "breadcrumbs.h" #include +#include #include "hostpolicy_context.h" namespace @@ -27,7 +30,12 @@ namespace std::mutex g_lib_lock; std::weak_ptr g_lib_coreclr; - int create_coreclr(const hostpolicy_context_t &context, host_mode_t mode, std::unique_ptr &coreclr) + std::mutex g_context_lock; + std::condition_variable g_context_cv; + std::unique_ptr g_context; + std::atomic g_context_initializing(false); + + int create_coreclr(const hostpolicy_context_t &context, std::unique_ptr &coreclr) { // Verbose logging if (trace::is_enabled()) @@ -38,7 +46,7 @@ namespace std::vector host_path; pal::pal_clrstring(context.host_path, &host_path); - const char *app_domain_friendly_name = mode == host_mode_t::libhost ? "clr_libhost" : "clrhost"; + const char *app_domain_friendly_name = context.host_mode == host_mode_t::libhost ? "clr_libhost" : "clrhost"; // Create a CoreCLR instance trace::verbose(_X("CoreCLR path = '%s', CoreCLR dir = '%s'"), context.clr_path.c_str(), context.clr_dir.c_str()); @@ -58,30 +66,45 @@ namespace return StatusCode::Success; } - int create_hostpolicy_context( + int get_or_create_hostpolicy_context( hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool breadcrumbs_enabled, - std::shared_ptr &context) + hostpolicy_context_t **context) { + hostpolicy_context_t *existing_context; + { + std::unique_lock lock{ g_context_lock }; + g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); + + existing_context = g_context.get(); + if (existing_context == nullptr) + g_context_initializing.store(true); + } + + if (existing_context != nullptr) + { + // [TODO] Validate the current context is acceptable for this request + trace::info(_X("Host context has already been initialized")); + *context = existing_context; + return StatusCode::CoreHostAlreadyInitialized; + } std::unique_ptr context_local(new hostpolicy_context_t()); int rc = context_local->initialize(hostpolicy_init, args, breadcrumbs_enabled); if (rc != StatusCode::Success) return rc; - context = std::move(context_local); - + *context = context_local.release(); return StatusCode::Success; } } int get_or_create_coreclr( - hostpolicy_init_t &hostpolicy_init, - const arguments_t &args, - host_mode_t mode, + hostpolicy_context_t *context, std::shared_ptr &coreclr) { + assert(context != nullptr); coreclr = g_lib_coreclr.lock(); if (coreclr != nullptr) { @@ -100,19 +123,26 @@ int get_or_create_coreclr( return StatusCode::Success; } - hostpolicy_context_t context {}; - int rc = context.initialize(hostpolicy_init, args, false /* enable_breadcrumbs */); - if (rc != StatusCode::Success) - return rc; - std::unique_ptr coreclr_local; - rc = create_coreclr(context, mode, coreclr_local); + int rc = create_coreclr(*context, coreclr_local); + + { + std::lock_guard context_lock { g_context_lock }; + if (rc == StatusCode::Success) + g_context.reset(context); + + g_context_initializing.store(false); + } + + g_context_cv.notify_all(); + if (rc != StatusCode::Success) return rc; assert(g_coreclr == nullptr); g_coreclr = std::move(coreclr_local); g_lib_coreclr = g_coreclr; + } coreclr = g_coreclr; @@ -320,25 +350,18 @@ SHARED_API int corehost_main(const int argc, const pal::char_t* argv[]) if (rc != StatusCode::Success) return rc; - std::shared_ptr context; - rc = create_hostpolicy_context(g_init, args, true /* breadcrumbs_enabled */, context); + assert(g_context == nullptr); + hostpolicy_context_t *context; + rc = get_or_create_hostpolicy_context(g_init, args, true /* breadcrumbs_enabled */, &context); if (rc != StatusCode::Success) return rc; - std::unique_ptr coreclr; - rc = create_coreclr(*context, g_init.host_mode, coreclr); + std::shared_ptr coreclr; + rc = get_or_create_coreclr(context, coreclr); if (rc != StatusCode::Success) return rc; - assert(g_coreclr == nullptr); - g_coreclr = std::move(coreclr); - - { - std::lock_guard lock{ g_lib_lock }; - g_lib_coreclr = g_coreclr; - } - - rc = run_as_app(g_coreclr, *context, args.app_argc, args.app_argv); + rc = run_as_app(coreclr, *context, args.app_argc, args.app_argv); return rc; } @@ -392,8 +415,58 @@ int corehost_libhost_init(hostpolicy_init_t &hostpolicy_init, const pal::string_ namespace { - int get_coreclr_delegate(const std::shared_ptr &coreclr, coreclr_delegate_type type, void** delegate) + int load_runtime_for_context(const context_handle instance) { + if (instance == nullptr) + return StatusCode::InvalidArgFailure; + + hostpolicy_context_t *context = static_cast(instance); + std::shared_ptr coreclr; + return get_or_create_coreclr(context, coreclr); + } + + int run_app_for_context( + const context_handle instance, + const int argc, + const pal::char_t *argv[]) + { + if (instance == nullptr) + return StatusCode::InvalidArgFailure; + + hostpolicy_context_t *context = static_cast(instance); + std::shared_ptr coreclr; + int rc = get_or_create_coreclr(context, coreclr); + if (rc != StatusCode::Success) + return rc; + + return run_as_app(coreclr, *context, argc, argv); + } + + int get_delegate_for_context( + const context_handle instance, + coreclr_delegate_type type, + void **delegate) + { + if (delegate == nullptr) + return StatusCode::InvalidArgFailure; + + std::shared_ptr coreclr; + if (instance == nullptr) + { + // Allow 'attach' to existing coreclr + std::lock_guard lock{ g_lib_lock }; + coreclr = g_coreclr; + if (coreclr == nullptr) + return StatusCode::HostInvalidState; + } + else + { + hostpolicy_context_t *context = static_cast(instance); + int rc = get_or_create_coreclr(context, coreclr); + if (rc != StatusCode::Success) + return rc; + } + switch (type) { case coreclr_delegate_type::com_activation: @@ -418,22 +491,108 @@ namespace return StatusCode::LibHostInvalidArgs; } } + + int get_property( + const context_handle instance, + const pal::char_t *key, + const pal::char_t **value) + { + if (instance == nullptr || key == nullptr) + return StatusCode::InvalidArgFailure; + + hostpolicy_context_t *context = static_cast(instance); + if (!context->coreclr_properties.try_get(key, value)) + return StatusCode::InvalidArgFailure; + + return StatusCode::Success; + } + + int set_property( + const context_handle instance, + const pal::char_t *key, + const pal::char_t *value) + { + if (instance == nullptr || key == nullptr) + return StatusCode::InvalidArgFailure; + + hostpolicy_context_t *context = static_cast(instance); + if (value != nullptr) + { + context->coreclr_properties.add(key, value); + } + else + { + context->coreclr_properties.remove(key); + } + + return StatusCode::Success; + } + + int get_properties( + const context_handle instance, + size_t * count, + const pal::char_t **keys, + const pal::char_t **values) + { + if (instance == nullptr || count == nullptr) + return StatusCode::InvalidArgFailure; + + hostpolicy_context_t *context = static_cast(instance); + size_t actualCount = context->coreclr_properties.count(); + if (*count < actualCount || keys == nullptr || values == nullptr) + { + *count = actualCount; + return StatusCode::HostApiBufferTooSmall; + } + + int index = 0; + std::function callback = [&] (const pal::string_t& key, const pal::string_t& value) + { + keys[index] = key.data(); + values[index] = value.data(); + ++index; + }; + context->coreclr_properties.enumerate(callback); + + return StatusCode::Success; + } } -SHARED_API int corehost_get_coreclr_delegate(coreclr_delegate_type type, void** delegate) +SHARED_API int __cdecl corehost_initialize_context(const host_interface_t *init, corehost_context_contract *context_contract) { - arguments_t args; + if (init == nullptr || context_contract == nullptr) + return StatusCode::InvalidArgFailure; - int rc = corehost_libhost_init(g_init, _X("corehost_get_coreclr_delegate"), args); + arguments_t args; + int rc = corehost_libhost_init(g_init, _X("corehost_initialize_context"), args); if (rc != StatusCode::Success) return rc; - std::shared_ptr coreclr; - rc = get_or_create_coreclr(g_init, args, g_init.host_mode, coreclr); - if (rc != StatusCode::Success) + hostpolicy_context_t *context; + rc = get_or_create_hostpolicy_context(g_init, args, g_init.host_mode != host_mode_t::libhost, &context); + if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) return rc; - return get_coreclr_delegate(coreclr, type, delegate); + context_contract->version = sizeof(corehost_context_contract); + context_contract->instance = rc == StatusCode::CoreHostAlreadyInitialized ? nullptr : context; + context_contract->get_property_value = get_property; + context_contract->set_property_value = set_property; + context_contract->get_properties = get_properties; + context_contract->load_runtime = load_runtime_for_context; + context_contract->run_app = run_app_for_context; + context_contract->get_runtime_delegate = get_delegate_for_context; + + return rc; +} + +SHARED_API int __cdecl corehost_close_context(corehost_context_contract context_contract) +{ + hostpolicy_context_t *context = static_cast(context_contract.instance); + std::lock_guard context_lock{ g_context_lock }; + if (context != g_context.get()) + delete context; + + return StatusCode::Success; } SHARED_API int corehost_unload() diff --git a/src/corehost/cli/hostpolicy/hostpolicy_context.cpp b/src/corehost/cli/hostpolicy/hostpolicy_context.cpp index aa933d79e7..ed1ad39649 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy_context.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy_context.cpp @@ -20,6 +20,7 @@ namespace int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs) { application = args.managed_application; + host_mode = hostpolicy_init.host_mode; host_path = args.host_path; breadcrumbs_enabled = enable_breadcrumbs; diff --git a/src/corehost/cli/hostpolicy/hostpolicy_context.h b/src/corehost/cli/hostpolicy/hostpolicy_context.h index 2508f023a8..9736f2c20c 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy_context.h +++ b/src/corehost/cli/hostpolicy/hostpolicy_context.h @@ -17,6 +17,7 @@ struct hostpolicy_context_t pal::string_t application; pal::string_t clr_dir; pal::string_t clr_path; + host_mode_t host_mode; pal::string_t host_path; bool breadcrumbs_enabled; diff --git a/src/corehost/cli/ijwhost/ijwhost.cpp b/src/corehost/cli/ijwhost/ijwhost.cpp index d2a5728959..c4f7002cb4 100644 --- a/src/corehost/cli/ijwhost/ijwhost.cpp +++ b/src/corehost/cli/ijwhost/ijwhost.cpp @@ -25,13 +25,19 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_m { return load_fxr_and_get_delegate( hostfxr_delegate_type::load_in_memory_assembly, - [handle](const pal::string_t& host_path, pal::string_t* app_path_out) + [handle](const pal::string_t& host_path, pal::string_t* config_path_out) { - if (!pal::get_module_path(handle, app_path_out)) + pal::string_t mod_path; + if (!pal::get_module_path(handle, &mod_path)) { trace::error(_X("Failed to resolve full path of the current mixed-mode module [%s]"), host_path.c_str()); return StatusCode::LibHostCurExeFindFailure; } + + pal::string_t config_path_local { strip_file_ext(mod_path) }; + config_path_local.append(_X(".runtimeconfig.json")); + *config_path_out = std::move(config_path_local); + return StatusCode::Success; }, delegate diff --git a/src/corehost/cli/winrthost/winrthost.cpp b/src/corehost/cli/winrthost/winrthost.cpp index d57f04dfb6..4c5a2f7db9 100644 --- a/src/corehost/cli/winrthost/winrthost.cpp +++ b/src/corehost/cli/winrthost/winrthost.cpp @@ -35,21 +35,23 @@ namespace { return load_fxr_and_get_delegate( hostfxr_delegate_type::winrt_activation, - [](const pal::string_t& host_path, pal::string_t* app_path_out) + [app_path](const pal::string_t& host_path, pal::string_t* config_path_out) { - pal::string_t app_path_local{ host_path }; - - // Change the extension to get the 'app' - size_t idx = app_path_local.rfind(_X(".dll")); + // Change the extension to get the 'app' and config + size_t idx = host_path.rfind(_X(".dll")); assert(idx != pal::string_t::npos); + + pal::string_t app_path_local{ host_path }; app_path_local.replace(app_path_local.begin() + idx, app_path_local.end(), _X(".winmd")); + *app_path = std::move(app_path_local); - *app_path_out = std::move(app_path_local); + pal::string_t config_path_local { host_path }; + config_path_local.replace(config_path_local.begin() + idx, config_path_local.end(), _X(".runtimeconfig.json")); + *config_path_out = std::move(config_path_local); return StatusCode::Success; }, - delegate, - app_path + delegate ); } } diff --git a/src/corehost/error_codes.h b/src/corehost/error_codes.h index e86e651e6e..1662af34f2 100644 --- a/src/corehost/error_codes.h +++ b/src/corehost/error_codes.h @@ -40,5 +40,9 @@ enum StatusCode BundleExtractionFailure = 0x8000809f, BundleExtractionIOError = 0x800080a0, LibHostDuplicateProperty = 0x800080a1, + HostApiUnsupportedVersion = 0x800080a2, + HostInvalidState = 0x800080a3, + + CoreHostAlreadyInitialized = 0x00000001, }; #endif // __ERROR_CODES_H__ From 9cc62b839fe2c4fd14b091243af49957db5c1a40 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 15 Apr 2019 12:34:44 -0700 Subject: [PATCH 02/24] Update native host test exe to use host context entry points --- .../cli/test/nativehost/CMakeLists.txt | 1 + .../cli/test/nativehost/host_context_test.cpp | 329 ++++++++++++++++++ .../cli/test/nativehost/host_context_test.h | 49 +++ .../cli/test/nativehost/nativehost.cpp | 64 ++++ 4 files changed, 443 insertions(+) create mode 100644 src/corehost/cli/test/nativehost/host_context_test.cpp create mode 100644 src/corehost/cli/test/nativehost/host_context_test.h diff --git a/src/corehost/cli/test/nativehost/CMakeLists.txt b/src/corehost/cli/test/nativehost/CMakeLists.txt index ab6abb6ac8..68508be4d8 100644 --- a/src/corehost/cli/test/nativehost/CMakeLists.txt +++ b/src/corehost/cli/test/nativehost/CMakeLists.txt @@ -18,6 +18,7 @@ endif() include_directories(${CMAKE_CURRENT_LIST_DIR}/../../nethost) set(SOURCES + ./host_context_test.cpp ./nativehost.cpp ) diff --git a/src/corehost/cli/test/nativehost/host_context_test.cpp b/src/corehost/cli/test/nativehost/host_context_test.cpp new file mode 100644 index 0000000000..46c55af894 --- /dev/null +++ b/src/corehost/cli/test/nativehost/host_context_test.cpp @@ -0,0 +1,329 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include +#include +#include +#include +#include +#include "host_context_test.h" + +namespace +{ + std::vector to_str(const pal::string_t &value) + { + std::vector vect; + pal::pal_utf8string(value, &vect); + return vect; + } + + class hostfxr_exports + { + public: + hostfxr_initialize_for_app_fn init_app; + hostfxr_run_app_fn run_app; + + hostfxr_initialize_for_runtime_config_fn init_config; + hostfxr_get_runtime_delegate_fn get_delegate; + + hostfxr_get_runtime_property_value_fn get_prop_value; + hostfxr_set_runtime_property_value_fn set_prop_value; + hostfxr_get_runtime_properties_fn get_properties; + + hostfxr_close_fn close; + + public: + hostfxr_exports(const pal::string_t &hostfxr_path) + { + if (!pal::load_library(&hostfxr_path, &_dll)) + { + std::cout << "Load library of hostfxr failed" << std::endl; + throw StatusCode::CoreHostLibLoadFailure; + } + + init_app = (hostfxr_initialize_for_app_fn)pal::get_symbol(_dll, "hostfxr_initialize_for_app"); + run_app = (hostfxr_run_app_fn)pal::get_symbol(_dll, "hostfxr_run_app"); + + init_config = (hostfxr_initialize_for_runtime_config_fn)pal::get_symbol(_dll, "hostfxr_initialize_for_runtime_config"); + get_delegate = (hostfxr_get_runtime_delegate_fn)pal::get_symbol(_dll, "hostfxr_get_runtime_delegate"); + + get_prop_value = (hostfxr_get_runtime_property_value_fn)pal::get_symbol(_dll, "hostfxr_get_runtime_property_value"); + set_prop_value = (hostfxr_set_runtime_property_value_fn)pal::get_symbol(_dll, "hostfxr_set_runtime_property_value"); + get_properties = (hostfxr_get_runtime_properties_fn)pal::get_symbol(_dll, "hostfxr_get_runtime_properties"); + + close = (hostfxr_close_fn)pal::get_symbol(_dll, "hostfxr_close"); + + if (init_app == nullptr || run_app == nullptr + || init_config == nullptr || get_delegate == nullptr + || get_prop_value == nullptr || set_prop_value == nullptr + || get_properties == nullptr || close == nullptr) + { + std::cout << "Failed to get hostfxr entry points" << std::endl; + throw StatusCode::CoreHostEntryPointFailure; + } + } + + ~hostfxr_exports() + { + pal::unload_library(_dll); + } + + private: + pal::dll_t _dll; + }; + + void get_property_value( + const hostfxr_exports &hostfxr, + hostfxr_handle handle, + int property_count, + const pal::char_t *property_keys[], + const char *log_prefix) + { + for (int i = 0; i < property_count; ++i) + { + const pal::char_t *key = property_keys[i]; + const pal::char_t *value; + int rc = hostfxr.get_prop_value(handle, key, &value); + if (rc == StatusCode::Success) + { + std::cout << log_prefix << "hostfxr_get_runtime_property_value succeeded for property: " + << to_str(key).data() << "=" << to_str(value).data() << std::endl; + } + else + { + std::cout << log_prefix << "hostfxr_get_runtime_property_value failed for property: " << to_str(key).data() << std::endl; + } + } + } + + void set_property_value( + const hostfxr_exports &hostfxr, + hostfxr_handle handle, + int property_count, + const pal::char_t *property_keys[], + bool remove, + const char *log_prefix) + { + for (int i = 0; i < property_count; ++i) + { + const pal::char_t *key = property_keys[i]; + const pal::char_t *value = remove ? nullptr : _X("VALUE_FROM_HOST"); + int rc = hostfxr.set_prop_value(handle, key, value); + if (rc == StatusCode::Success) + { + std::cout << log_prefix << "hostfxr_set_runtime_property_value succeeded for property: " << to_str(key).data() << std::endl; + } + else + { + std::cout << log_prefix << "hostfxr_get_runtime_property_value failed for property: " << to_str(key).data() + << " - " << std::hex << std::showbase << rc << std::endl; + } + } + } + + void get_properties( + const hostfxr_exports &hostfxr, + hostfxr_handle handle, + const char *log_prefix) + { + size_t count = 0; + std::vector keys; + std::vector values; + int rc = hostfxr.get_properties(handle, &count, nullptr, nullptr); + if (rc == StatusCode::HostApiBufferTooSmall) + { + keys.resize(count); + values.resize(count); + rc = hostfxr.get_properties(handle, &count, keys.data(), values.data()); + } + + if (rc != StatusCode::Success) + { + std::cout << log_prefix << "hostfxr_get_runtime_properties failed: " + << std::hex << std::showbase << rc << std::endl; + return; + } + + std::cout << log_prefix << "hostfxr_get_runtime_properties succeeded." << std::endl; + for (int i = 0; i < keys.size(); ++i) + { + std::cout << log_prefix << "hostfxr_get_runtime_properties: " + << to_str(keys[i]).data() << "=" << to_str(values[i]).data() << std::endl; + } + } + + void inspect_modify_properties( + host_context_test::check_properties scenario, + const hostfxr_exports &hostfxr, + hostfxr_handle handle, + int key_count, + const pal::char_t *keys[], + const char *log_prefix) + { + switch (scenario) + { + case host_context_test::check_properties::get: + get_property_value(hostfxr, handle, key_count, keys, log_prefix); + break; + case host_context_test::check_properties::set: + set_property_value(hostfxr, handle, key_count, keys, false /*remove*/, log_prefix); + break; + case host_context_test::check_properties::remove: + set_property_value(hostfxr, handle, key_count, keys, true /*remove*/, log_prefix); + break; + case host_context_test::check_properties::get_all: + get_properties(hostfxr, handle, log_prefix); + break; + case host_context_test::check_properties::none: + default: + break; + } + } + + bool config_test( + const hostfxr_exports &hostfxr, + host_context_test::check_properties check_properties, + const pal::char_t *config_path, + int argc, + const pal::char_t *argv[], + const char *log_prefix) + { + hostfxr_handle handle; + int rc = hostfxr.init_config(config_path, nullptr, &handle); + if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) + { + std::cout << log_prefix << "hostfxr_initialize_for_runtime_config failed: " << std::hex << std::showbase << rc << std::endl; + return false; + } + + inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, log_prefix); + + void *delegate; + rc = hostfxr.get_delegate(handle, hostfxr_delegate_type::com_activation, &delegate); + if (rc != StatusCode::Success) + std::cout << log_prefix << "hostfxr_get_runtime_delegate failed: " << std::hex << std::showbase << rc << std::endl; + + int rcClose = hostfxr.close(handle); + if (rcClose != StatusCode::Success) + std::cout << log_prefix << "hostfxr_close failed: " << std::hex << std::showbase << rc << std::endl; + + return rc == StatusCode::Success && rcClose == StatusCode::Success; + } +} + +host_context_test::check_properties host_context_test::check_properties_from_string(const pal::char_t *str) +{ + if (pal::strcmp(str, _X("get")) == 0) + { + return host_context_test::check_properties::get; + } + else if (pal::strcmp(str, _X("set")) == 0) + { + return host_context_test::check_properties::set; + } + else if (pal::strcmp(str, _X("remove")) == 0) + { + return host_context_test::check_properties::remove; + } + else if (pal::strcmp(str, _X("get_all")) == 0) + { + return host_context_test::check_properties::get_all; + } + + return host_context_test::check_properties::none; +} + +bool host_context_test::app( + check_properties check_properties, + const pal::string_t &hostfxr_path, + const pal::char_t *app_path, + int argc, + const pal::char_t *argv[]) +{ + hostfxr_exports hostfxr { hostfxr_path }; + + hostfxr_handle handle; + int rc = hostfxr.init_app(argc, argv, app_path, nullptr, &handle); + if (rc != StatusCode::Success) + { + std::cout << "hostfxr_initialize_for_app failed: " << std::hex << std::showbase << rc << std::endl; + return false; + } + + inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, "app - "); + + rc = hostfxr.run_app(handle); + if (rc != StatusCode::Success) + std::cout << "hostfxr_run_app failed: " << std::hex << std::showbase << rc << std::endl; + + int rcClose = hostfxr.close(handle); + if (rcClose != StatusCode::Success) + std::cout << "hostfxr_close failed: " << std::hex << std::showbase << rc << std::endl; + + return rc == StatusCode::Success && rcClose == StatusCode::Success; +} + +bool host_context_test::config( + check_properties check_properties, + const pal::string_t &hostfxr_path, + const pal::char_t *config_path, + int argc, + const pal::char_t *argv[]) +{ + hostfxr_exports hostfxr { hostfxr_path }; + + return config_test(hostfxr, check_properties, config_path, argc, argv, "config - "); +} + +bool host_context_test::config_multiple( + check_properties check_properties, + const pal::string_t &hostfxr_path, + const pal::char_t *config_path, + const pal::char_t *secondary_config_path, + int argc, + const pal::char_t *argv[]) +{ + hostfxr_exports hostfxr { hostfxr_path }; + + if (!config_test(hostfxr, check_properties, config_path, argc, argv, "config - ")) + return false; + + return config_test(hostfxr, check_properties, secondary_config_path, argc, argv, "secondary config - "); +} + +bool host_context_test::mixed( + check_properties check_properties, + const pal::string_t &hostfxr_path, + const pal::char_t *app_path, + const pal::char_t *config_path, + int argc, + const pal::char_t *argv[]) +{ + hostfxr_exports hostfxr { hostfxr_path }; + + hostfxr_handle handle; + int rc = hostfxr.init_app(argc, argv, app_path, nullptr, &handle); + if (rc != StatusCode::Success) + { + std::cout << "hostfxr_initialize_for_app failed: " << std::hex << std::showbase << rc << std::endl; + return false; + } + + inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, "app - "); + + auto run_app = [&]{ + int rc = hostfxr.run_app(handle); + if (rc != StatusCode::Success) + std::cout << "hostfxr_run_app failed: " << std::hex << std::showbase << rc << std::endl; + + int rcClose = hostfxr.close(handle); + if (rcClose != StatusCode::Success) + std::cout << "hostfxr_close failed: " << std::hex << std::showbase << rc << std::endl; + }; + std::thread app_start = std::thread(run_app); + + bool success = config_test(hostfxr, check_properties, config_path, argc, argv, "config - "); + app_start.join(); + return success; +} \ No newline at end of file diff --git a/src/corehost/cli/test/nativehost/host_context_test.h b/src/corehost/cli/test/nativehost/host_context_test.h new file mode 100644 index 0000000000..304aea56d4 --- /dev/null +++ b/src/corehost/cli/test/nativehost/host_context_test.h @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include +#include +#include +#include + +namespace host_context_test +{ + enum check_properties + { + none, + get, + set, + remove, + get_all + }; + + check_properties check_properties_from_string(const pal::char_t *str); + + bool app( + check_properties scenario, + const pal::string_t &hostfxr_path, + const pal::char_t *app_path, + int argc, + const pal::char_t *argv[]); + bool config( + check_properties scenario, + const pal::string_t &hostfxr_path, + const pal::char_t *config_path, + int argc, + const pal::char_t *argv[]); + bool config_multiple( + check_properties scenario, + const pal::string_t &hostfxr_path, + const pal::char_t *config_path, + const pal::char_t *secondary_config_path, + int argc, + const pal::char_t *argv[]); + bool mixed( + check_properties scenario, + const pal::string_t &hostfxr_path, + const pal::char_t *app_path, + const pal::char_t *config_path, + int argc, + const pal::char_t *argv[]); +} \ No newline at end of file diff --git a/src/corehost/cli/test/nativehost/nativehost.cpp b/src/corehost/cli/test/nativehost/nativehost.cpp index 352c80e596..ee2c4e552e 100644 --- a/src/corehost/cli/test/nativehost/nativehost.cpp +++ b/src/corehost/cli/test/nativehost/nativehost.cpp @@ -7,6 +7,8 @@ #include #include #include "comhost_test.h" +#include +#include "host_context_test.h" namespace { @@ -80,6 +82,68 @@ int main(const int argc, const pal::char_t *argv[]) return EXIT_FAILURE; } } + else if (pal::strcmp(command, _X("host_context")) == 0) + { + // args: ... + const int min_argc = 6; + if (argc < min_argc) + { + std::cerr << "Invalid arguments" << std::endl; + return -1; + } + + const pal::char_t *scenario = argv[2]; + const pal::char_t *check_properties_str = argv[3]; + const pal::string_t hostfxr_path = argv[4]; + const pal::char_t *app_or_config_path = argv[5]; + + int remaining_argc = argc - min_argc; + const pal::char_t **remaining_argv = nullptr; + if (argc > min_argc) + remaining_argv = &argv[min_argc]; + + auto check_properties = host_context_test::check_properties_from_string(check_properties_str); + + bool success = false; + if (pal::strcmp(scenario, _X("app")) == 0) + { + success = host_context_test::app(check_properties, hostfxr_path, app_or_config_path, remaining_argc, remaining_argv); + } + else if (pal::strcmp(scenario, _X("config")) == 0) + { + success = host_context_test::config(check_properties, hostfxr_path, app_or_config_path, remaining_argc, remaining_argv); + } + else if (pal::strcmp(scenario, _X("config_multiple")) == 0) + { + if (argc < min_argc + 1) + { + std::cerr << "Invalid arguments" << std::endl; + return -1; + } + + const pal::char_t *secondary_config_path = argv[6]; + --remaining_argc; + ++remaining_argv; + + success = host_context_test::config_multiple(check_properties, hostfxr_path, app_or_config_path, secondary_config_path, remaining_argc, remaining_argv); + } + else if (pal::strcmp(scenario, _X("mixed")) == 0) + { + if (argc < min_argc + 1) + { + std::cerr << "Invalid arguments" << std::endl; + return -1; + } + + const pal::char_t *config_path = argv[6]; + --remaining_argc; + ++remaining_argv; + + success = host_context_test::mixed(check_properties, hostfxr_path, app_or_config_path, config_path, remaining_argc, remaining_argv); + } + + return success ? EXIT_SUCCESS : EXIT_FAILURE; + } #if defined(_WIN32) else if (pal::strcmp(command, _X("comhost")) == 0) { From d0f90546c02a00cb4aad37009c900b4d40234790 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 18 Apr 2019 11:44:56 -0700 Subject: [PATCH 03/24] Allow getting properties of active context without context handle --- src/corehost/cli/fxr/fx_muxer.cpp | 8 ++++++- src/corehost/cli/fxr/fx_muxer.h | 1 + src/corehost/cli/fxr/host_context.h | 5 +++++ src/corehost/cli/fxr/hostfxr.cpp | 34 +++++++++++++++++++++++------ 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index 79d49c0304..bb27aeae0b 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -35,7 +35,7 @@ namespace { std::mutex g_context_lock; std::condition_variable g_context_cv; - std::unique_ptr g_active_host_context; + std::unique_ptr g_active_host_context; std::atomic g_context_initializing(false); } @@ -960,6 +960,12 @@ int fx_muxer_t::get_runtime_delegate(host_context_t *context, coreclr_delegate_t } } +const host_context_t* fx_muxer_t::get_active_host_context() +{ + std::lock_guard lock{ g_context_lock }; + return g_active_host_context.get(); +} + int fx_muxer_t::close_host_context(const host_context_t *context) { const hostpolicy_contract &host_contract = context->host_contract; diff --git a/src/corehost/cli/fxr/fx_muxer.h b/src/corehost/cli/fxr/fx_muxer.h index df26eaaffb..416e94ca45 100644 --- a/src/corehost/cli/fxr/fx_muxer.h +++ b/src/corehost/cli/fxr/fx_muxer.h @@ -40,6 +40,7 @@ class fx_muxer_t host_context_t *context, coreclr_delegate_type delegate_type, void** delegate); + static const host_context_t* get_active_host_context(); static int close_host_context(const host_context_t *context); private: static int parse_args( diff --git a/src/corehost/cli/fxr/host_context.h b/src/corehost/cli/fxr/host_context.h index ac67837e6b..674e993c41 100644 --- a/src/corehost/cli/fxr/host_context.h +++ b/src/corehost/cli/fxr/host_context.h @@ -12,6 +12,7 @@ enum class host_context_type { + empty, // Not populated, cannot be used for context-based operations initialized, // Created, but not active (runtime not loaded) active, // Runtime loaded for this context secondary, // Created after runtime was loaded using another context @@ -29,6 +30,10 @@ struct host_context_t std::vector argv; std::unordered_map config_properties; + + host_context_t() + : type { host_context_type::empty } + { } }; #endif // __HOST_CONTEXT_H__ \ No newline at end of file diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index 7a1cac3c40..d974a2bb82 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -665,13 +665,23 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value( trace::setup(); trace::info(_X("--- Invoked hostfxr_get_runtime_property_value [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + const host_context_t *context; if (host_context_handle == nullptr) { - // TODO: get property for active context - return StatusCode::InvalidArgFailure; + const host_context_t *context_maybe = fx_muxer_t::get_active_host_context(); + if (context_maybe == nullptr || context_maybe->type != host_context_type::active) + { + trace::error(_X("Hosting components context has not been initialized. Cannot get runtime properties.")); + return StatusCode::HostInvalidState; + } + + context = context_maybe; + } + else + { + context = static_cast(host_context_handle); } - host_context_t *context = static_cast(host_context_handle); if (context->type == host_context_type::invalid) return StatusCode::InvalidArgFailure; @@ -715,7 +725,7 @@ SHARED_API int32_t __cdecl hostfxr_set_runtime_property_value( const pal::char_t *name, const pal::char_t *value) { - if (name == nullptr) + if (host_context_handle == nullptr || name == nullptr) return StatusCode::InvalidArgFailure; trace::setup(); @@ -771,13 +781,23 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( trace::setup(); trace::info(_X("--- Invoked hostfxr_get_runtime_properties [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + const host_context_t *context; if (host_context_handle == nullptr) { - // TODO: get properties for active context - return StatusCode::InvalidArgFailure; + const host_context_t *context_maybe = fx_muxer_t::get_active_host_context(); + if (context_maybe == nullptr || context_maybe->type != host_context_type::active) + { + trace::error(_X("Hosting components context has not been initialized. Cannot get runtime properties.")); + return StatusCode::HostInvalidState; + } + + context = context_maybe; + } + else + { + context = static_cast(host_context_handle); } - host_context_t *context = static_cast(host_context_handle); if (context->type == host_context_type::invalid) return StatusCode::InvalidArgFailure; From 43b8ea902a42c45313b4eea9947ef9a092f2fb3f Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 18 Apr 2019 15:50:29 -0700 Subject: [PATCH 04/24] Check for initializing / already initialized context when running with non-context-based APIs --- src/corehost/cli/fxr/fx_muxer.cpp | 59 ++++++++++++++++++++-- src/corehost/cli/fxr/hostfxr.cpp | 3 ++ src/corehost/cli/hostpolicy/hostpolicy.cpp | 16 ++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index bb27aeae0b..3371f40315 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -70,6 +70,16 @@ static int execute_app( const int argc, const pal::char_t* argv[]) { + { + std::unique_lock lock{ g_context_lock }; + g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); + + if (g_active_host_context != nullptr) + return StatusCode::HostInvalidState; + + g_context_initializing.store(true); + } + pal::dll_t corehost; hostpolicy_contract host_contract{}; corehost_main_fn host_main = nullptr; @@ -82,7 +92,9 @@ static int execute_app( // Track an empty 'active' context so that host context-based APIs can work properly when // the runtime is loaded through non-host context-based APIs. std::lock_guard lock{ g_context_lock }; + assert(g_active_host_context == nullptr); g_active_host_context.reset(new host_context_t()); + g_context_initializing.store(false); } { @@ -96,6 +108,7 @@ static int execute_app( } } + g_context_cv.notify_all(); return code; } @@ -790,6 +803,19 @@ namespace return rc; } + + void handle_initialize_failure(hostpolicy_contract *hostpolicy_contract = nullptr) + { + { + std::lock_guard lock{ g_context_lock }; + g_context_initializing.store(false); + } + + if (hostpolicy_contract != nullptr && hostpolicy_contract->unload != nullptr) + hostpolicy_contract->unload(); + + g_context_cv.notify_all(); + } } int fx_muxer_t::initialize_for_app( @@ -803,7 +829,10 @@ int fx_muxer_t::initialize_for_app( g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); if (g_active_host_context != nullptr) + { + trace::error(_X("Hosting components are already initialized. Re-initialization for an app is not allowed.")); return StatusCode::HostInvalidState; + } g_context_initializing.store(true); } @@ -821,15 +850,22 @@ int fx_muxer_t::initialize_for_app( impl_dir, init); if (rc != StatusCode::Success) + { + handle_initialize_failure(); return rc; + } const host_interface_t &intf = init->get_host_init_data(); hostpolicy_contract host_contract{}; std::unique_ptr context(new host_context_t()); rc = initialize_context(impl_dir, host_contract, intf, &context->context_contract); if (rc != StatusCode::Success) + { + handle_initialize_failure(&host_contract); return rc; + } + trace::verbose(_X("Initialized context for app: %s"), host_info.app_path.c_str()); context->type = host_context_type::initialized; context->host_contract = host_contract; context->is_app = true; @@ -867,24 +903,37 @@ int fx_muxer_t::initialize_for_runtime_config( std::unique_ptr init; int rc = get_init_info_for_component(host_info, mode, runtime_config, impl_dir, init); if (rc != StatusCode::Success) + { + if (!already_initialized) + handle_initialize_failure(); + return rc; + } const host_interface_t &intf = init->get_host_init_data(); hostpolicy_contract host_contract{}; std::unique_ptr context(new host_context_t()); rc = initialize_context(impl_dir, host_contract, intf, &context->context_contract); + already_initialized |= rc == StatusCode::CoreHostAlreadyInitialized; if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) + { + if (!already_initialized) + handle_initialize_failure(&host_contract); + return rc; + } context->host_contract = host_contract; context->is_app = false; - if (already_initialized || rc == StatusCode::CoreHostAlreadyInitialized) + if (already_initialized) { + trace::verbose(_X("Initialized secondary context for config: %s"), runtime_config_path); context->type = host_context_type::secondary; init->get_runtime_properties(context->config_properties); } else { + trace::verbose(_X("Initialized context for config: %s"), runtime_config_path); context->type = host_context_type::initialized; } @@ -908,6 +957,7 @@ namespace { std::lock_guard lock{ g_context_lock }; + assert(g_active_host_context == nullptr); g_active_host_context.reset(context); g_context_initializing.store(false); } @@ -982,8 +1032,11 @@ int fx_muxer_t::close_host_context(const host_context_t *context) } // Do not delete the active context. - if (context->type != host_context_type::active) - delete context; + { + std::lock_guard lock{ g_context_lock }; + if (context != g_active_host_context.get()) + delete context; + } return rc; } diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index d974a2bb82..14e0282776 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -839,6 +839,9 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( // SHARED_API int32_t __cdecl hostfxr_close(const hostfxr_handle host_context_handle) { + if (host_context_handle == nullptr) + return StatusCode::InvalidArgFailure; + trace::setup(); trace::info(_X("--- Invoked hostfxr_close [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index ce9445c80f..4c556f5065 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -93,7 +93,15 @@ namespace std::unique_ptr context_local(new hostpolicy_context_t()); int rc = context_local->initialize(hostpolicy_init, args, breadcrumbs_enabled); if (rc != StatusCode::Success) + { + { + std::lock_guard lock{ g_context_lock }; + g_context_initializing.store(false); + } + + g_context_cv.notify_all(); return rc; + } *context = context_local.release(); return StatusCode::Success; @@ -597,6 +605,14 @@ SHARED_API int __cdecl corehost_close_context(corehost_context_contract context_ SHARED_API int corehost_unload() { + std::lock_guard lock{ g_lib_lock }; + if (g_coreclr != nullptr) + return StatusCode::Success; + + // Allow re-initializing if runtime has not been loaded + std::lock_guard init_lock{ g_init_lock }; + g_init_done = false; + return StatusCode::Success; } From 5df40885407d3f4bc02e6bf4d0cb98ec6a12e07e Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 22 Apr 2019 10:52:21 -0700 Subject: [PATCH 05/24] Use specific error code for getting runtime property that doesn't exist --- src/corehost/cli/fxr/hostfxr.cpp | 2 +- src/corehost/cli/hostpolicy/coreclr.cpp | 1 + src/corehost/cli/hostpolicy/hostpolicy.cpp | 2 +- src/corehost/error_codes.h | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index 14e0282776..3bc0660305 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -690,7 +690,7 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value( const std::unordered_map &properties = context->config_properties; auto iter = properties.find(name); if (iter == properties.cend()) - return StatusCode::InvalidArgFailure; + return StatusCode::HostPropertyNotFound; *value = (*iter).second.c_str(); return S_OK; diff --git a/src/corehost/cli/hostpolicy/coreclr.cpp b/src/corehost/cli/hostpolicy/coreclr.cpp index db900195da..9c70addc57 100644 --- a/src/corehost/cli/hostpolicy/coreclr.cpp +++ b/src/corehost/cli/hostpolicy/coreclr.cpp @@ -280,6 +280,7 @@ void coreclr_property_bag_t::remove(const pal::char_t *key) if (iter == _properties.cend()) return; + trace::verbose(_X("Removing property %s. Old value: '%s'."), key, (*iter).second.c_str()); _properties.erase(iter); } diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index 4c556f5065..7e93fcd6d0 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -510,7 +510,7 @@ namespace hostpolicy_context_t *context = static_cast(instance); if (!context->coreclr_properties.try_get(key, value)) - return StatusCode::InvalidArgFailure; + return StatusCode::HostPropertyNotFound; return StatusCode::Success; } diff --git a/src/corehost/error_codes.h b/src/corehost/error_codes.h index 1662af34f2..3a2cd1a2c3 100644 --- a/src/corehost/error_codes.h +++ b/src/corehost/error_codes.h @@ -42,6 +42,7 @@ enum StatusCode LibHostDuplicateProperty = 0x800080a1, HostApiUnsupportedVersion = 0x800080a2, HostInvalidState = 0x800080a3, + HostPropertyNotFound = 0x800080a4, CoreHostAlreadyInitialized = 0x00000001, }; From 478e2cdaced4330744d71bb95906b93ed6d325f1 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 22 Apr 2019 12:24:03 -0700 Subject: [PATCH 06/24] Update COM-activation.md --- Documentation/design-docs/COM-activation.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/design-docs/COM-activation.md b/Documentation/design-docs/COM-activation.md index a08fc1941c..a0c71f6be5 100644 --- a/Documentation/design-docs/COM-activation.md +++ b/Documentation/design-docs/COM-activation.md @@ -68,9 +68,9 @@ When `DllGetClassObject()` is called in a COM activation scenario, the following * If a [`.runtimeconfig.json`](https://github.com/dotnet/cli/blob/master/Documentation/specs/runtime-configuration-file.md) file exists adjacent to the target managed assembly (`.runtimeconfig.json`), that file is used to describe the target framework and CLR configuration. The documentation for the `.runtimeconfig.json` format defines under what circumstances this file may be optional. 1) The `DllGetClassObject()` function verifies the `CLSID` mapping has a mapping for the `CLSID`. * If the `CLSID` is unknown in the mapping the traditional `CLASS_E_CLASSNOTAVAILABLE` is returned. -1) The shim attempts to load the latest version of the `hostfxr` library and retrieves the `hostfxr_get_com_activation_delegate()` export. +1) The shim attempts to load the latest version of the `hostfxr` library and retrieves the `hostfxr_initialize_for_runtime_config()` and `hostfxr_get_runtime_delegate()` exports. 1) The target assembly name is computed by stripping off the `.comhost.dll` prefix and replacing it with `.dll`. Using the name of the target assembly, the path to the `.runtimeconfig.json` file is then computed. -1) The `hostfxr_get_com_activation_delegate()` export is called. +1) The `hostfxr_initialize_for_runtime_config()` export is called. 1) Based on the `.runtimeconfig.json` the [framework](https://docs.microsoft.com/dotnet/core/packages#frameworks) to use can be determined and the appropriate `hostpolicy` library path is computed. 1) The `hostpolicy` library is loaded and various exports are retrieved. * If a `hostpolicy` instance is already loaded, the one presently loaded is re-used. @@ -78,8 +78,8 @@ When `DllGetClassObject()` is called in a COM activation scenario, the following * **At 3.0 GA** If a CLR is active within the process, the requested CLR version will be validated against that CLR. If version satisfiability fails, activation will fail. 1) The `corehost_load()` export is called to initialize `hostpolicy`. - Prior to .NET Core 3.0, during application activation the `corehost_load()` export would always initialize `hostpolicy` regardless if initialization had already been performed. For .NET Core 3.0, calling the function again will not re-initialize `hostpolicy`, but simply return. -1) The `corehost_get_com_activation_delegate()` export from `hostpolicy` is called. -1) The `corehost_get_com_activation_delegate()` export determines if the associated `coreclr` library has been loaded and if so, uses the existing activated CLR instance. If a CLR instance is not available, `hostpolicy` will load `coreclr` and activate a new CLR instance. +1) The `hostfxr_get_runtime_delegate()` export is called +1) The `hostfxr_get_runtime_delegate()` export calls into `hostpolicy` and determines if the associated `coreclr` library has been loaded and if so, uses the existing activated CLR instance. If a CLR instance is not available, `hostpolicy` will load `coreclr` and activate a new CLR instance. * **Prior to 3.0 GA** No validation is done to determine if the current running coreclr instance can satisfy the current assembly's `.runtimeconfig.json`. * **At 3.0 GA** If a CLR is active within the process, the requested CLR version will be validated against that CLR. If version satisfiability fails, activation will fail. 1) A request to the CLR is made to create a managed delegate to a static "activation" method. The delegate is returned to the shim to attempt activation of the requested class. From 30f5772e597eeb72c9d09b7e4ea55b330f00aebd Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 22 Apr 2019 14:43:43 -0700 Subject: [PATCH 07/24] PR feedback --- ...contract.h => corehost_context_contract.h} | 28 +++++++++---------- src/corehost/cli/fxr/fx_muxer.cpp | 2 +- src/corehost/cli/fxr/fx_muxer.h | 2 +- src/corehost/cli/fxr/host_context.h | 2 +- src/corehost/cli/fxr/hostfxr.cpp | 14 +++++----- src/corehost/cli/fxr/hostpolicy_resolver.h | 2 +- src/corehost/cli/fxr_resolver.h | 10 +++---- src/corehost/cli/hostfxr.h | 14 +++++----- src/corehost/cli/hostpolicy/hostpolicy.cpp | 4 +-- src/corehost/error_codes.h | 6 ++-- 10 files changed, 43 insertions(+), 41 deletions(-) rename src/corehost/cli/{context_contract.h => corehost_context_contract.h} (62%) diff --git a/src/corehost/cli/context_contract.h b/src/corehost/cli/corehost_context_contract.h similarity index 62% rename from src/corehost/cli/context_contract.h rename to src/corehost/cli/corehost_context_contract.h index 3f28c125af..5c6f98ea6b 100644 --- a/src/corehost/cli/context_contract.h +++ b/src/corehost/cli/corehost_context_contract.h @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#ifndef __CONTEXT_CONTRACT_H__ -#define __CONTEXT_CONTRACT_H__ +#ifndef __COREHOST_CONTEXT_CONTRACT_H__ +#define __COREHOST_CONTEXT_CONTRACT_H__ #include @@ -21,29 +21,29 @@ struct corehost_context_contract { size_t version; context_handle instance; - int (*get_property_value)( + int (__cdecl *get_property_value)( const context_handle instance, const pal::char_t* key, - const pal::char_t** value); - int (*set_property_value)( + /*out*/ const pal::char_t** value); + int (__cdecl *set_property_value)( const context_handle instance, const pal::char_t* key, const pal::char_t* value); - int (*get_properties)( + int (__cdecl *get_properties)( const context_handle instance, - size_t *count, - const pal::char_t** keys, - const pal::char_t** values); - int (*load_runtime)( + /*inout*/ size_t *count, + /*out*/ const pal::char_t** keys, + /*out*/ const pal::char_t** values); + int (__cdecl *load_runtime)( const context_handle instance); - int (*run_app)( + int (__cdecl *run_app)( const context_handle instance, const int argc, const pal::char_t* argv[]); - int (*get_runtime_delegate)( + int (__cdecl *get_runtime_delegate)( const context_handle instance, coreclr_delegate_type type, - void** delegate); + /*out*/ void** delegate); }; -#endif // __CONTEXT_CONTRACT_H__ \ No newline at end of file +#endif // __COREHOST_CONTEXT_CONTRACT_H__ \ No newline at end of file diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index 3371f40315..a0c997a5c7 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -12,8 +12,8 @@ #include #include +#include #include "corehost_init.h" -#include "context_contract.h" #include "deps_format.h" #include "framework_info.h" #include "fx_definition.h" diff --git a/src/corehost/cli/fxr/fx_muxer.h b/src/corehost/cli/fxr/fx_muxer.h index 416e94ca45..51a6770c7c 100644 --- a/src/corehost/cli/fxr/fx_muxer.h +++ b/src/corehost/cli/fxr/fx_muxer.h @@ -8,7 +8,7 @@ class fx_definition_t; struct fx_ver_t; struct host_startup_info_t; -#include +#include #include "error_codes.h" #include "fx_definition.h" #include "host_context.h" diff --git a/src/corehost/cli/fxr/host_context.h b/src/corehost/cli/fxr/host_context.h index 674e993c41..fde948a9de 100644 --- a/src/corehost/cli/fxr/host_context.h +++ b/src/corehost/cli/fxr/host_context.h @@ -7,7 +7,7 @@ #include -#include +#include #include "hostpolicy_resolver.h" enum class host_context_type diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index 3bc0660305..e5ec09f9b6 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -468,7 +468,7 @@ SHARED_API int32_t __cdecl hostfxr_initialize_for_app( const pal::char_t *argv[], const pal::char_t *app_path, const hostfxr_initialize_parameters * parameters, - hostfxr_handle * host_context_handle) + /*out*/ hostfxr_handle * host_context_handle) { if (host_context_handle == nullptr || (argv == nullptr && argc != 0) || (app_path == nullptr && argc == 0)) return StatusCode::InvalidArgFailure; @@ -536,7 +536,7 @@ SHARED_API int32_t __cdecl hostfxr_initialize_for_app( SHARED_API int32_t __cdecl hostfxr_initialize_for_runtime_config( const pal::char_t *runtime_config_path, const hostfxr_initialize_parameters *parameters, - hostfxr_handle *host_context_handle) + /*out*/ hostfxr_handle *host_context_handle) { if (runtime_config_path == nullptr || host_context_handle == nullptr) return StatusCode::InvalidArgFailure; @@ -619,7 +619,7 @@ namespace SHARED_API int32_t __cdecl hostfxr_get_runtime_delegate( const hostfxr_handle host_context_handle, hostfxr_delegate_type type, - void **delegate) + /*out*/ void **delegate) { if (host_context_handle == nullptr || delegate == nullptr) return StatusCode::InvalidArgFailure; @@ -657,7 +657,7 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_delegate( SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value( const hostfxr_handle host_context_handle, const pal::char_t *name, - const pal::char_t **value) + /*out*/ const pal::char_t **value) { if (name == nullptr || value == nullptr) return StatusCode::InvalidArgFailure; @@ -771,9 +771,9 @@ SHARED_API int32_t __cdecl hostfxr_set_runtime_property_value( // SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( const hostfxr_handle host_context_handle, - size_t * count, - const pal::char_t **keys, - const pal::char_t **values) + /*inout*/ size_t * count, + /*out*/ const pal::char_t **keys, + /*out*/ const pal::char_t **values) { if (count == nullptr) return StatusCode::InvalidArgFailure; diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.h b/src/corehost/cli/fxr/hostpolicy_resolver.h index 8ab777d337..45805ba7e9 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.h +++ b/src/corehost/cli/fxr/hostpolicy_resolver.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include using corehost_load_fn = int(*) (const host_interface_t* init); using corehost_unload_fn = int(*) (); diff --git a/src/corehost/cli/fxr_resolver.h b/src/corehost/cli/fxr_resolver.h index c39e4b63c9..b0944a727a 100644 --- a/src/corehost/cli/fxr_resolver.h +++ b/src/corehost/cli/fxr_resolver.h @@ -56,10 +56,10 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallb // Leak fxr - auto hostfxr_init_context = (hostfxr_initialize_for_runtime_config_fn)pal::get_symbol(fxr, "hostfxr_initialize_for_runtime_config"); - auto hostfxr_get_delegate = (hostfxr_get_runtime_delegate_fn)pal::get_symbol(fxr, "hostfxr_get_runtime_delegate"); + auto hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)pal::get_symbol(fxr, "hostfxr_initialize_for_runtime_config"); + auto hostfxr_get_runtime_delegate = (hostfxr_get_runtime_delegate_fn)pal::get_symbol(fxr, "hostfxr_get_runtime_delegate"); auto hostfxr_close = (hostfxr_close_fn)pal::get_symbol(fxr, "hostfxr_close"); - if (hostfxr_init_context == nullptr || hostfxr_get_delegate == nullptr || hostfxr_close == nullptr) + if (hostfxr_initialize_for_runtime_config == nullptr || hostfxr_get_runtime_delegate == nullptr || hostfxr_close == nullptr) return StatusCode::CoreHostEntryPointFailure; pal::string_t config_path; @@ -77,11 +77,11 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallb }; hostfxr_handle context; - int rc = hostfxr_init_context(config_path.c_str(), ¶meters, &context); + int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), ¶meters, &context); if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) return rc; - rc = hostfxr_get_delegate(context, type, (void**)delegate); + rc = hostfxr_get_runtime_delegate(context, type, (void**)delegate); int rcClose = hostfxr_close(context); assert(rcClose == StatusCode::Success); diff --git a/src/corehost/cli/hostfxr.h b/src/corehost/cli/hostfxr.h index 759fa4c480..bc4ab70a24 100644 --- a/src/corehost/cli/hostfxr.h +++ b/src/corehost/cli/hostfxr.h @@ -46,31 +46,31 @@ using hostfxr_initialize_for_app_fn = int32_t(__cdecl *)( const pal::char_t *argv[], const pal::char_t *app_path, const hostfxr_initialize_parameters *parameters, - hostfxr_handle *host_context_handle); + /*out*/ hostfxr_handle *host_context_handle); using hostfxr_initialize_for_runtime_config_fn = int32_t(__cdecl *)( const pal::char_t *runtime_config_path, const hostfxr_initialize_parameters*parameters, - hostfxr_handle *host_context_handle); + /*out*/ hostfxr_handle *host_context_handle); using hostfxr_get_runtime_property_value_fn = int32_t(__cdecl *)( const hostfxr_handle host_context_handle, const pal::char_t *name, - const pal::char_t **value); + /*out*/ const pal::char_t **value); using hostfxr_set_runtime_property_value_fn = int32_t(__cdecl *)( const hostfxr_handle host_context_handle, const pal::char_t *name, const pal::char_t *value); using hostfxr_get_runtime_properties_fn = int32_t(__cdecl *)( const hostfxr_handle host_context_handle, - size_t * count, - const pal::char_t **keys, - const pal::char_t **values); + /*inout*/ size_t * count, + /*out*/ const pal::char_t **keys, + /*out*/ const pal::char_t **values); using hostfxr_run_app_fn = int32_t(__cdecl *)(const hostfxr_handle host_context_handle); using hostfxr_get_runtime_delegate_fn = int32_t(__cdecl *)( const hostfxr_handle host_context_handle, hostfxr_delegate_type type, - void **delegate); + /*out*/ void **delegate); using hostfxr_close_fn = int32_t(__cdecl *)(const hostfxr_handle host_context_handle); diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index 7e93fcd6d0..9403479361 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -16,7 +16,7 @@ #include #include "breadcrumbs.h" #include -#include +#include #include "hostpolicy_context.h" namespace @@ -566,7 +566,7 @@ namespace } } -SHARED_API int __cdecl corehost_initialize_context(const host_interface_t *init, corehost_context_contract *context_contract) +SHARED_API int __cdecl corehost_initialize_context(const host_interface_t *init, /*out*/ corehost_context_contract *context_contract) { if (init == nullptr || context_contract == nullptr) return StatusCode::InvalidArgFailure; diff --git a/src/corehost/error_codes.h b/src/corehost/error_codes.h index 3a2cd1a2c3..1ef23bc4f9 100644 --- a/src/corehost/error_codes.h +++ b/src/corehost/error_codes.h @@ -6,7 +6,11 @@ #define __ERROR_CODES_H__ enum StatusCode { + // Success Success = 0, + CoreHostAlreadyInitialized = 0x00000001, + + // Failure InvalidArgFailure = 0x80008081, CoreHostLibLoadFailure = 0x80008082, CoreHostLibMissingFailure = 0x80008083, @@ -43,7 +47,5 @@ enum StatusCode HostApiUnsupportedVersion = 0x800080a2, HostInvalidState = 0x800080a3, HostPropertyNotFound = 0x800080a4, - - CoreHostAlreadyInitialized = 0x00000001, }; #endif // __ERROR_CODES_H__ From 418b185ebbc43841abd65d728ad2cc15436ac96b Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 22 Apr 2019 14:45:59 -0700 Subject: [PATCH 08/24] Make native host test exe get properties of active context --- .../cli/test/nativehost/host_context_test.cpp | 70 ++++++++++++++++--- .../cli/test/nativehost/host_context_test.h | 4 +- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/corehost/cli/test/nativehost/host_context_test.cpp b/src/corehost/cli/test/nativehost/host_context_test.cpp index 46c55af894..e5c7c31967 100644 --- a/src/corehost/cli/test/nativehost/host_context_test.cpp +++ b/src/corehost/cli/test/nativehost/host_context_test.cpp @@ -11,6 +11,10 @@ namespace { + const char *app_log_prefix = "[APP] "; + const char *config_log_prefix = "[CONFIG] "; + const char *secondary_log_prefix = "[SECONDARY] "; + std::vector to_str(const pal::string_t &value) { std::vector vect; @@ -92,7 +96,8 @@ namespace } else { - std::cout << log_prefix << "hostfxr_get_runtime_property_value failed for property: " << to_str(key).data() << std::endl; + std::cout << log_prefix << "hostfxr_get_runtime_property_value failed for property: " << to_str(key).data() + << " - " << std::hex << std::showbase << rc << std::endl; } } } @@ -116,7 +121,7 @@ namespace } else { - std::cout << log_prefix << "hostfxr_get_runtime_property_value failed for property: " << to_str(key).data() + std::cout << log_prefix << "hostfxr_set_runtime_property_value failed for property: " << to_str(key).data() << " - " << std::hex << std::showbase << rc << std::endl; } } @@ -140,7 +145,7 @@ namespace if (rc != StatusCode::Success) { - std::cout << log_prefix << "hostfxr_get_runtime_properties failed: " + std::cout << log_prefix << "hostfxr_get_runtime_properties failed - " << std::hex << std::showbase << rc << std::endl; return; } @@ -175,6 +180,12 @@ namespace case host_context_test::check_properties::get_all: get_properties(hostfxr, handle, log_prefix); break; + case host_context_test::check_properties::get_active: + get_property_value(hostfxr, nullptr, key_count, keys, log_prefix); + break; + case host_context_test::check_properties::get_all_active: + get_properties(hostfxr, nullptr, log_prefix); + break; case host_context_test::check_properties::none: default: break; @@ -230,6 +241,14 @@ host_context_test::check_properties host_context_test::check_properties_from_str { return host_context_test::check_properties::get_all; } + else if (pal::strcmp(str, _X("get_active")) == 0) + { + return host_context_test::check_properties::get_active; + } + else if (pal::strcmp(str, _X("get_all_active")) == 0) + { + return host_context_test::check_properties::get_all_active; + } return host_context_test::check_properties::none; } @@ -251,7 +270,7 @@ bool host_context_test::app( return false; } - inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, "app - "); + inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, app_log_prefix); rc = hostfxr.run_app(handle); if (rc != StatusCode::Success) @@ -273,7 +292,7 @@ bool host_context_test::config( { hostfxr_exports hostfxr { hostfxr_path }; - return config_test(hostfxr, check_properties, config_path, argc, argv, "config - "); + return config_test(hostfxr, check_properties, config_path, argc, argv, config_log_prefix); } bool host_context_test::config_multiple( @@ -286,10 +305,40 @@ bool host_context_test::config_multiple( { hostfxr_exports hostfxr { hostfxr_path }; - if (!config_test(hostfxr, check_properties, config_path, argc, argv, "config - ")) + if (!config_test(hostfxr, check_properties, config_path, argc, argv, config_log_prefix)) return false; - return config_test(hostfxr, check_properties, secondary_config_path, argc, argv, "secondary config - "); + return config_test(hostfxr, check_properties, secondary_config_path, argc, argv, secondary_log_prefix); +} + +namespace +{ + class block_mock_execute_assembly + { + public: + block_mock_execute_assembly() + { + if (pal::getenv(_X("TEST_BLOCK_MOCK_EXECUTE_ASSEMBLY"), &_path)) + pal::touch_file(_path); + } + + ~block_mock_execute_assembly() + { + unblock(); + } + + void unblock() + { + if (_path.empty()) + return; + + pal::remove(_path.c_str()); + _path.clear(); + } + + private: + pal::string_t _path; + }; } bool host_context_test::mixed( @@ -310,7 +359,9 @@ bool host_context_test::mixed( return false; } - inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, "app - "); + inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, app_log_prefix); + + block_mock_execute_assembly block_mock; auto run_app = [&]{ int rc = hostfxr.run_app(handle); @@ -323,7 +374,8 @@ bool host_context_test::mixed( }; std::thread app_start = std::thread(run_app); - bool success = config_test(hostfxr, check_properties, config_path, argc, argv, "config - "); + bool success = config_test(hostfxr, check_properties, config_path, argc, argv, secondary_log_prefix); + block_mock.unblock(); app_start.join(); return success; } \ No newline at end of file diff --git a/src/corehost/cli/test/nativehost/host_context_test.h b/src/corehost/cli/test/nativehost/host_context_test.h index 304aea56d4..4f61e11f21 100644 --- a/src/corehost/cli/test/nativehost/host_context_test.h +++ b/src/corehost/cli/test/nativehost/host_context_test.h @@ -15,7 +15,9 @@ namespace host_context_test get, set, remove, - get_all + get_all, + get_active, + get_all_active }; check_properties check_properties_from_string(const pal::char_t *str); From 0912eeb54bd185d0949586912fe65303cf45ce80 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 23 Apr 2019 18:44:11 -0700 Subject: [PATCH 09/24] Add coreclr to hostpolicy context instead of tracking a separate global --- src/corehost/cli/hostpolicy/hostpolicy.cpp | 176 ++++++++---------- .../cli/hostpolicy/hostpolicy_context.h | 2 + 2 files changed, 75 insertions(+), 103 deletions(-) diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index 9403479361..320bc42e85 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -25,45 +25,59 @@ namespace bool g_init_done; hostpolicy_init_t g_init; - std::shared_ptr g_coreclr; - - std::mutex g_lib_lock; - std::weak_ptr g_lib_coreclr; - std::mutex g_context_lock; std::condition_variable g_context_cv; std::unique_ptr g_context; std::atomic g_context_initializing(false); - int create_coreclr(const hostpolicy_context_t &context, std::unique_ptr &coreclr) + int create_coreclr(hostpolicy_context_t *context) { - // Verbose logging - if (trace::is_enabled()) + assert(context != nullptr); + if (context->coreclr != nullptr) + return StatusCode::Success; + + int rc; { - context.coreclr_properties.log_properties(); - } + std::lock_guard context_lock { g_context_lock }; + if (g_context != nullptr) + { + trace::error(_X("CoreClr has already been loaded for a different context")); + return StatusCode::HostInvalidState; + } - std::vector host_path; - pal::pal_clrstring(context.host_path, &host_path); + // Verbose logging + if (trace::is_enabled()) + context->coreclr_properties.log_properties(); - const char *app_domain_friendly_name = context.host_mode == host_mode_t::libhost ? "clr_libhost" : "clrhost"; + std::vector host_path; + pal::pal_clrstring(context->host_path, &host_path); + const char *app_domain_friendly_name = context->host_mode == host_mode_t::libhost ? "clr_libhost" : "clrhost"; - // Create a CoreCLR instance - trace::verbose(_X("CoreCLR path = '%s', CoreCLR dir = '%s'"), context.clr_path.c_str(), context.clr_dir.c_str()); - auto hr = coreclr_t::create( - context.clr_dir, - host_path.data(), - app_domain_friendly_name, - context.coreclr_properties, - coreclr); + // Create a CoreCLR instance + trace::verbose(_X("CoreCLR path = '%s', CoreCLR dir = '%s'"), context->clr_path.c_str(), context->clr_dir.c_str()); + auto hr = coreclr_t::create( + context->clr_dir, + host_path.data(), + app_domain_friendly_name, + context->coreclr_properties, + context->coreclr); - if (!SUCCEEDED(hr)) - { - trace::error(_X("Failed to create CoreCLR, HRESULT: 0x%X"), hr); - return StatusCode::CoreClrInitFailure; + if (!SUCCEEDED(hr)) + { + trace::error(_X("Failed to create CoreCLR, HRESULT: 0x%X"), hr); + rc = StatusCode::CoreClrInitFailure; + } + else + { + g_context.reset(context); + rc = StatusCode::Success; + } + + g_context_initializing.store(false); } - return StatusCode::Success; + g_context_cv.notify_all(); + return rc; } int get_or_create_hostpolicy_context( @@ -86,6 +100,7 @@ namespace { // [TODO] Validate the current context is acceptable for this request trace::info(_X("Host context has already been initialized")); + assert(existing_context->coreclr != nullptr); *context = existing_context; return StatusCode::CoreHostAlreadyInitialized; } @@ -108,55 +123,6 @@ namespace } } -int get_or_create_coreclr( - hostpolicy_context_t *context, - std::shared_ptr &coreclr) -{ - assert(context != nullptr); - coreclr = g_lib_coreclr.lock(); - if (coreclr != nullptr) - { - // [TODO] Validate the current CLR instance is acceptable for this request - - trace::info(_X("Using existing CoreClr instance")); - return StatusCode::Success; - } - - { - std::lock_guard lock{ g_lib_lock }; - coreclr = g_lib_coreclr.lock(); - if (coreclr != nullptr) - { - trace::info(_X("Using existing CoreClr instance")); - return StatusCode::Success; - } - - std::unique_ptr coreclr_local; - int rc = create_coreclr(*context, coreclr_local); - - { - std::lock_guard context_lock { g_context_lock }; - if (rc == StatusCode::Success) - g_context.reset(context); - - g_context_initializing.store(false); - } - - g_context_cv.notify_all(); - - if (rc != StatusCode::Success) - return rc; - - assert(g_coreclr == nullptr); - g_coreclr = std::move(coreclr_local); - g_lib_coreclr = g_coreclr; - - } - - coreclr = g_coreclr; - return StatusCode::Success; -} - int run_host_command( hostpolicy_init_t &hostpolicy_init, const arguments_t &args, @@ -190,11 +156,12 @@ int run_host_command( } int run_as_app( - const std::shared_ptr &coreclr, const hostpolicy_context_t &context, int argc, const pal::char_t **argv) { + assert(context.coreclr != nullptr); + // Initialize clr strings for arguments std::vector> argv_strs(argc); std::vector argv_local(argc); @@ -230,7 +197,7 @@ int run_as_app( // Execute the application unsigned int exit_code; - auto hr = g_coreclr->execute_assembly( + auto hr = context.coreclr->execute_assembly( argv_local.size(), argv_local.data(), managed_app.data(), @@ -245,7 +212,7 @@ int run_as_app( trace::info(_X("Execute managed assembly exit code: 0x%X"), exit_code); // Shut down the CoreCLR - hr = g_coreclr->shutdown((int*)&exit_code); + hr = context.coreclr->shutdown((int*)&exit_code); if (!SUCCEEDED(hr)) { trace::warning(_X("Failed to shut down CoreCLR, HRESULT: 0x%X"), hr); @@ -364,12 +331,11 @@ SHARED_API int corehost_main(const int argc, const pal::char_t* argv[]) if (rc != StatusCode::Success) return rc; - std::shared_ptr coreclr; - rc = get_or_create_coreclr(context, coreclr); + rc = create_coreclr(context); if (rc != StatusCode::Success) return rc; - rc = run_as_app(coreclr, *context, args.app_argc, args.app_argv); + rc = run_as_app(*context, args.app_argc, args.app_argv); return rc; } @@ -429,8 +395,7 @@ namespace return StatusCode::InvalidArgFailure; hostpolicy_context_t *context = static_cast(instance); - std::shared_ptr coreclr; - return get_or_create_coreclr(context, coreclr); + return create_coreclr(context); } int run_app_for_context( @@ -442,12 +407,13 @@ namespace return StatusCode::InvalidArgFailure; hostpolicy_context_t *context = static_cast(instance); - std::shared_ptr coreclr; - int rc = get_or_create_coreclr(context, coreclr); - if (rc != StatusCode::Success) - return rc; + if (context->coreclr == nullptr) + { + trace::error(_X("CoreClr must be loaded before running a app")); + return StatusCode::HostInvalidState; + } - return run_as_app(coreclr, *context, argc, argv); + return run_as_app(*context, argc, argv); } int get_delegate_for_context( @@ -458,23 +424,27 @@ namespace if (delegate == nullptr) return StatusCode::InvalidArgFailure; - std::shared_ptr coreclr; + hostpolicy_context_t *context; if (instance == nullptr) { - // Allow 'attach' to existing coreclr - std::lock_guard lock{ g_lib_lock }; - coreclr = g_coreclr; - if (coreclr == nullptr) + std::lock_guard lock{ g_context_lock }; + if (g_context == nullptr || g_context->coreclr == nullptr) return StatusCode::HostInvalidState; + + context = g_context.get(); } else { - hostpolicy_context_t *context = static_cast(instance); - int rc = get_or_create_coreclr(context, coreclr); - if (rc != StatusCode::Success) - return rc; + context = static_cast(instance); + } + + if (context->coreclr == nullptr) + { + trace::error(_X("CoreClr must be loaded before getting a delegate")); + return StatusCode::HostInvalidState; } + coreclr_t *coreclr = context->coreclr.get(); switch (type) { case coreclr_delegate_type::com_activation: @@ -547,11 +517,11 @@ namespace hostpolicy_context_t *context = static_cast(instance); size_t actualCount = context->coreclr_properties.count(); - if (*count < actualCount || keys == nullptr || values == nullptr) - { - *count = actualCount; + + size_t input_count = *count; + *count = actualCount; + if (input_count < actualCount || keys == nullptr || values == nullptr) return StatusCode::HostApiBufferTooSmall; - } int index = 0; std::function callback = [&] (const pal::string_t& key, const pal::string_t& value) @@ -605,8 +575,8 @@ SHARED_API int __cdecl corehost_close_context(corehost_context_contract context_ SHARED_API int corehost_unload() { - std::lock_guard lock{ g_lib_lock }; - if (g_coreclr != nullptr) + std::lock_guard lock{ g_context_lock }; + if (g_context != nullptr) return StatusCode::Success; // Allow re-initializing if runtime has not been loaded diff --git a/src/corehost/cli/hostpolicy/hostpolicy_context.h b/src/corehost/cli/hostpolicy/hostpolicy_context.h index 9736f2c20c..f77d03857c 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy_context.h +++ b/src/corehost/cli/hostpolicy/hostpolicy_context.h @@ -25,6 +25,8 @@ struct hostpolicy_context_t coreclr_property_bag_t coreclr_properties; + std::unique_ptr coreclr; + int initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs); }; From b94665bd9bb10fc51a094ee65d76f6630cbbe678 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 23 Apr 2019 20:29:02 -0700 Subject: [PATCH 10/24] Add functions for getting context from handle Add marker to context structs to help indicate valid and closed --- src/corehost/cli/fxr/CMakeLists.txt | 2 + src/corehost/cli/fxr/fx_muxer.cpp | 13 +++-- src/corehost/cli/fxr/fx_muxer.h | 2 +- src/corehost/cli/fxr/host_context.cpp | 48 +++++++++++++++ src/corehost/cli/fxr/host_context.h | 13 +++-- src/corehost/cli/fxr/hostfxr.cpp | 44 ++++++++------ src/corehost/cli/hostpolicy/hostpolicy.cpp | 58 ++++++++++++------- .../cli/hostpolicy/hostpolicy_context.cpp | 31 ++++++++++ .../cli/hostpolicy/hostpolicy_context.h | 6 ++ 9 files changed, 168 insertions(+), 49 deletions(-) create mode 100644 src/corehost/cli/fxr/host_context.cpp diff --git a/src/corehost/cli/fxr/CMakeLists.txt b/src/corehost/cli/fxr/CMakeLists.txt index f01169ae09..b158f53656 100644 --- a/src/corehost/cli/fxr/CMakeLists.txt +++ b/src/corehost/cli/fxr/CMakeLists.txt @@ -31,6 +31,7 @@ set(SOURCES ./fx_resolver.cpp ./fx_resolver.messages.cpp ./framework_info.cpp + ./host_context.cpp ./hostpolicy_resolver.cpp ./sdk_info.cpp ./sdk_resolver.cpp @@ -52,6 +53,7 @@ set(HEADERS ./fx_muxer.h ./fx_resolver.h ./framework_info.h + ./host_context.h ./hostpolicy_resolver.h ./sdk_info.h ./sdk_resolver.h diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index a0c997a5c7..fd354b0462 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -97,6 +97,8 @@ static int execute_app( g_context_initializing.store(false); } + g_context_cv.notify_all(); + { propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer); @@ -108,7 +110,6 @@ static int execute_app( } } - g_context_cv.notify_all(); return code; } @@ -969,7 +970,7 @@ namespace int fx_muxer_t::run_app(host_context_t *context) { - if (!context->is_app || context->type == host_context_type::invalid) + if (!context->is_app) return StatusCode::InvalidArgFailure; int argc = context->argv.size(); @@ -992,7 +993,7 @@ int fx_muxer_t::run_app(host_context_t *context) int fx_muxer_t::get_runtime_delegate(host_context_t *context, coreclr_delegate_type type, void **delegate) { - if (context->is_app || context->type == host_context_type::invalid) + if (context->is_app) return StatusCode::InvalidArgFailure; const corehost_context_contract &contract = context->context_contract; @@ -1016,7 +1017,7 @@ const host_context_t* fx_muxer_t::get_active_host_context() return g_active_host_context.get(); } -int fx_muxer_t::close_host_context(const host_context_t *context) +int fx_muxer_t::close_host_context(host_context_t *context) { const hostpolicy_contract &host_contract = context->host_contract; if (host_contract.close_context == nullptr) @@ -1025,7 +1026,9 @@ int fx_muxer_t::close_host_context(const host_context_t *context) return StatusCode::HostApiUnsupportedVersion; } - int rc; + int rc = StatusCode::Success; + context->close(); + if (context->type != host_context_type::secondary) { propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer); rc = host_contract.close_context(context->context_contract); diff --git a/src/corehost/cli/fxr/fx_muxer.h b/src/corehost/cli/fxr/fx_muxer.h index 51a6770c7c..bce6c38230 100644 --- a/src/corehost/cli/fxr/fx_muxer.h +++ b/src/corehost/cli/fxr/fx_muxer.h @@ -41,7 +41,7 @@ class fx_muxer_t coreclr_delegate_type delegate_type, void** delegate); static const host_context_t* get_active_host_context(); - static int close_host_context(const host_context_t *context); + static int close_host_context(host_context_t *context); private: static int parse_args( const host_startup_info_t& host_info, diff --git a/src/corehost/cli/fxr/host_context.cpp b/src/corehost/cli/fxr/host_context.cpp new file mode 100644 index 0000000000..e88e39252c --- /dev/null +++ b/src/corehost/cli/fxr/host_context.cpp @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "host_context.h" +#include + +namespace +{ + const int32_t valid_host_context_marker = 0xabababab; + const int32_t closed_host_context_marker = 0xcdcdcdcd; +} + +host_context_t* host_context_t::from_handle(const hostfxr_handle handle, bool allow_invalid_type) +{ + if (handle == nullptr) + return nullptr; + + host_context_t *context = static_cast(handle); + int32_t marker = context->marker; + if (marker == valid_host_context_marker) + { + if (allow_invalid_type || context->type != host_context_type::invalid) + return context; + + trace::error(_X("Host context is in an invalid state")); + } + else if (marker == closed_host_context_marker) + { + trace::error(_X("Host context has already been closed")); + } + else + { + trace::error(_X("Invalid host context handle marker: 0x%x"), marker); + } + + return nullptr; +} + +host_context_t::host_context_t() + : marker { valid_host_context_marker } + , type { host_context_type::empty } +{ } + +void host_context_t::close() +{ + marker = closed_host_context_marker; +} \ No newline at end of file diff --git a/src/corehost/cli/fxr/host_context.h b/src/corehost/cli/fxr/host_context.h index fde948a9de..5b41910c37 100644 --- a/src/corehost/cli/fxr/host_context.h +++ b/src/corehost/cli/fxr/host_context.h @@ -8,6 +8,7 @@ #include #include +#include #include "hostpolicy_resolver.h" enum class host_context_type @@ -21,8 +22,13 @@ enum class host_context_type struct host_context_t { - host_context_type type; +public: // static + static host_context_t* from_handle(const hostfxr_handle handle, bool allow_invalid_type = false); + +public: + int32_t marker; // used as an indication for validity + host_context_type type; hostpolicy_contract host_contract; corehost_context_contract context_contract; @@ -31,9 +37,8 @@ struct host_context_t std::unordered_map config_properties; - host_context_t() - : type { host_context_type::empty } - { } + host_context_t(); + void close(); }; #endif // __HOST_CONTEXT_H__ \ No newline at end of file diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index e5ec09f9b6..df2437b49a 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -573,13 +573,13 @@ SHARED_API int32_t __cdecl hostfxr_initialize_for_runtime_config( // SHARED_API int32_t __cdecl hostfxr_run_app(const hostfxr_handle host_context_handle) { - if (host_context_handle == nullptr) - return StatusCode::InvalidArgFailure; - trace::setup(); trace::info(_X("--- Invoked hostfxr_run_app [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - host_context_t *context = static_cast(host_context_handle); + host_context_t *context = host_context_t::from_handle(host_context_handle); + if (context == nullptr) + return StatusCode::InvalidArgFailure; + return fx_muxer_t::run_app(context); } @@ -621,13 +621,16 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_delegate( hostfxr_delegate_type type, /*out*/ void **delegate) { - if (host_context_handle == nullptr || delegate == nullptr) + if (delegate == nullptr) return StatusCode::InvalidArgFailure; trace::setup(); trace::info(_X("--- Invoked hostfxr_get_runtime_delegate [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - host_context_t *context = static_cast(host_context_handle); + host_context_t *context = host_context_t::from_handle(host_context_handle); + if (context == nullptr) + return StatusCode::InvalidArgFailure; + return fx_muxer_t::get_runtime_delegate(context, hostfxr_delegate_to_coreclr_delegate(type), delegate); } @@ -679,11 +682,11 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value( } else { - context = static_cast(host_context_handle); + context = host_context_t::from_handle(host_context_handle); + if (context == nullptr) + return StatusCode::InvalidArgFailure; } - if (context->type == host_context_type::invalid) - return StatusCode::InvalidArgFailure; if (context->type == host_context_type::secondary) { @@ -725,13 +728,16 @@ SHARED_API int32_t __cdecl hostfxr_set_runtime_property_value( const pal::char_t *name, const pal::char_t *value) { - if (host_context_handle == nullptr || name == nullptr) + if (name == nullptr) return StatusCode::InvalidArgFailure; trace::setup(); trace::info(_X("--- Invoked hostfxr_set_runtime_property_value [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - host_context_t *context = static_cast(host_context_handle); + host_context_t *context = host_context_t::from_handle(host_context_handle); + if (context == nullptr) + return StatusCode::InvalidArgFailure; + if (context->type != host_context_type::initialized) { trace::error(_X("Setting properties is not allowed once runtime has been loaded.")); @@ -795,12 +801,11 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( } else { - context = static_cast(host_context_handle); + context = host_context_t::from_handle(host_context_handle); + if (context == nullptr) + return StatusCode::InvalidArgFailure; } - if (context->type == host_context_type::invalid) - return StatusCode::InvalidArgFailure; - if (context->type == host_context_type::secondary) { const std::unordered_map &properties = context->config_properties; @@ -839,12 +844,13 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( // SHARED_API int32_t __cdecl hostfxr_close(const hostfxr_handle host_context_handle) { - if (host_context_handle == nullptr) - return StatusCode::InvalidArgFailure; - trace::setup(); trace::info(_X("--- Invoked hostfxr_close [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - host_context_t *context = static_cast(host_context_handle); + // Allow contexts with a type of invalid as we still need to clean them up + host_context_t *context = host_context_t::from_handle(host_context_handle, /*allow_invalid_type*/ true); + if (context == nullptr) + return StatusCode::InvalidArgFailure; + return fx_muxer_t::close_host_context(context); } \ No newline at end of file diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index 320bc42e85..af1813bd0c 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -389,24 +389,24 @@ int corehost_libhost_init(hostpolicy_init_t &hostpolicy_init, const pal::string_ namespace { - int load_runtime_for_context(const context_handle instance) + int load_runtime_for_context(const context_handle handle) { - if (instance == nullptr) + hostpolicy_context_t *context = hostpolicy_context_t::from_handle(handle); + if (context == nullptr) return StatusCode::InvalidArgFailure; - hostpolicy_context_t *context = static_cast(instance); return create_coreclr(context); } int run_app_for_context( - const context_handle instance, + const context_handle handle, const int argc, const pal::char_t *argv[]) { - if (instance == nullptr) + hostpolicy_context_t *context = hostpolicy_context_t::from_handle(handle); + if (context == nullptr) return StatusCode::InvalidArgFailure; - hostpolicy_context_t *context = static_cast(instance); if (context->coreclr == nullptr) { trace::error(_X("CoreClr must be loaded before running a app")); @@ -417,7 +417,7 @@ namespace } int get_delegate_for_context( - const context_handle instance, + const context_handle handle, coreclr_delegate_type type, void **delegate) { @@ -425,7 +425,7 @@ namespace return StatusCode::InvalidArgFailure; hostpolicy_context_t *context; - if (instance == nullptr) + if (handle == nullptr) { std::lock_guard lock{ g_context_lock }; if (g_context == nullptr || g_context->coreclr == nullptr) @@ -435,7 +435,9 @@ namespace } else { - context = static_cast(instance); + context = hostpolicy_context_t::from_handle(handle); + if (context == nullptr) + return StatusCode::InvalidArgFailure; } if (context->coreclr == nullptr) @@ -471,14 +473,17 @@ namespace } int get_property( - const context_handle instance, + const context_handle handle, const pal::char_t *key, const pal::char_t **value) { - if (instance == nullptr || key == nullptr) + if (key == nullptr) + return StatusCode::InvalidArgFailure; + + hostpolicy_context_t *context = hostpolicy_context_t::from_handle(handle); + if (context == nullptr) return StatusCode::InvalidArgFailure; - hostpolicy_context_t *context = static_cast(instance); if (!context->coreclr_properties.try_get(key, value)) return StatusCode::HostPropertyNotFound; @@ -486,14 +491,17 @@ namespace } int set_property( - const context_handle instance, + const context_handle handle, const pal::char_t *key, const pal::char_t *value) { - if (instance == nullptr || key == nullptr) + if (key == nullptr) + return StatusCode::InvalidArgFailure; + + hostpolicy_context_t *context = hostpolicy_context_t::from_handle(handle); + if (context == nullptr) return StatusCode::InvalidArgFailure; - hostpolicy_context_t *context = static_cast(instance); if (value != nullptr) { context->coreclr_properties.add(key, value); @@ -507,17 +515,19 @@ namespace } int get_properties( - const context_handle instance, + const context_handle handle, size_t * count, const pal::char_t **keys, const pal::char_t **values) { - if (instance == nullptr || count == nullptr) + if (count == nullptr) return StatusCode::InvalidArgFailure; - hostpolicy_context_t *context = static_cast(instance); - size_t actualCount = context->coreclr_properties.count(); + hostpolicy_context_t *context = hostpolicy_context_t::from_handle(handle); + if (context == nullptr) + return StatusCode::InvalidArgFailure; + size_t actualCount = context->coreclr_properties.count(); size_t input_count = *count; *count = actualCount; if (input_count < actualCount || keys == nullptr || values == nullptr) @@ -565,10 +575,18 @@ SHARED_API int __cdecl corehost_initialize_context(const host_interface_t *init, SHARED_API int __cdecl corehost_close_context(corehost_context_contract context_contract) { - hostpolicy_context_t *context = static_cast(context_contract.instance); + hostpolicy_context_t *context = hostpolicy_context_t::from_handle(context_contract.instance); + if (context == nullptr) + return StatusCode::InvalidArgFailure; + + // Do not delete the active context or mark it as closed so that inspection of + // the active context is still allowed std::lock_guard context_lock{ g_context_lock }; if (context != g_context.get()) + { + context->close(); delete context; + } return StatusCode::Success; } diff --git a/src/corehost/cli/hostpolicy/hostpolicy_context.cpp b/src/corehost/cli/hostpolicy/hostpolicy_context.cpp index ed1ad39649..c9986029cc 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy_context.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy_context.cpp @@ -10,6 +10,9 @@ namespace { + const int32_t valid_hostpolicy_context_marker = 0xabababab; + const int32_t closed_hostpolicy_context_marker = 0xcdcdcdcd; + void log_duplicate_property_error(const pal::char_t *property_key) { trace::error(_X("Duplicate runtime property found: %s"), property_key); @@ -17,8 +20,31 @@ namespace } } +hostpolicy_context_t* hostpolicy_context_t::from_handle(const context_handle handle) +{ + if (handle == nullptr) + return nullptr; + + hostpolicy_context_t *context = static_cast(handle); + int32_t marker = context->marker; + if (marker == valid_hostpolicy_context_marker) + return context; + + if (marker == closed_hostpolicy_context_marker) + { + trace::error(_X("Hostpolicy context has already been closed")); + } + else + { + trace::error(_X("Invalid hostpolicy context handle marker: 0x%x"), marker); + } + + return nullptr; +} + int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs) { + marker = valid_hostpolicy_context_marker; application = args.managed_application; host_mode = hostpolicy_init.host_mode; host_path = args.host_path; @@ -199,4 +225,9 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a } return StatusCode::Success; +} + +void hostpolicy_context_t::close() +{ + marker = closed_hostpolicy_context_marker; } \ No newline at end of file diff --git a/src/corehost/cli/hostpolicy/hostpolicy_context.h b/src/corehost/cli/hostpolicy/hostpolicy_context.h index f77d03857c..d45129c64e 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy_context.h +++ b/src/corehost/cli/hostpolicy/hostpolicy_context.h @@ -9,11 +9,16 @@ #include "args.h" #include "coreclr.h" +#include #include "hostpolicy_init.h" struct hostpolicy_context_t { +public: // static + static hostpolicy_context_t* from_handle(const context_handle handle); + public: + int32_t marker; // used as an indication for validity pal::string_t application; pal::string_t clr_dir; pal::string_t clr_path; @@ -28,6 +33,7 @@ struct hostpolicy_context_t std::unique_ptr coreclr; int initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs); + void close(); }; #endif // __HOSTPOLICY_CONTEXT_H__ From 377a6263e74f2fbc235324700f5cea95c4d4c961 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 23 Apr 2019 21:06:59 -0700 Subject: [PATCH 11/24] Rename context instance -> handle. Add helper for tracing hostfxr entry. --- src/corehost/cli/corehost_context_contract.h | 14 ++-- src/corehost/cli/fxr/fx_muxer.cpp | 6 +- src/corehost/cli/fxr/hostfxr.cpp | 75 +++++++++----------- src/corehost/cli/hostpolicy/hostpolicy.cpp | 4 +- 4 files changed, 44 insertions(+), 55 deletions(-) diff --git a/src/corehost/cli/corehost_context_contract.h b/src/corehost/cli/corehost_context_contract.h index 5c6f98ea6b..cee93cf026 100644 --- a/src/corehost/cli/corehost_context_contract.h +++ b/src/corehost/cli/corehost_context_contract.h @@ -20,28 +20,28 @@ enum class coreclr_delegate_type struct corehost_context_contract { size_t version; - context_handle instance; + context_handle handle; int (__cdecl *get_property_value)( - const context_handle instance, + const context_handle handle, const pal::char_t* key, /*out*/ const pal::char_t** value); int (__cdecl *set_property_value)( - const context_handle instance, + const context_handle handle, const pal::char_t* key, const pal::char_t* value); int (__cdecl *get_properties)( - const context_handle instance, + const context_handle handle, /*inout*/ size_t *count, /*out*/ const pal::char_t** keys, /*out*/ const pal::char_t** values); int (__cdecl *load_runtime)( - const context_handle instance); + const context_handle handle); int (__cdecl *run_app)( - const context_handle instance, + const context_handle handle, const int argc, const pal::char_t* argv[]); int (__cdecl *get_runtime_delegate)( - const context_handle instance, + const context_handle handle, coreclr_delegate_type type, /*out*/ void** delegate); }; diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index fd354b0462..eb09b284ba 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -951,7 +951,7 @@ namespace return StatusCode::Success; const corehost_context_contract &contract = context->context_contract; - int rc = contract.load_runtime(contract.instance); + int rc = contract.load_runtime(contract.handle); // Mark the context as active or invalid context->type = rc == StatusCode::Success ? host_context_type::active : host_context_type::invalid; @@ -987,7 +987,7 @@ int fx_muxer_t::run_app(host_context_t *context) if (rc != StatusCode::Success) return rc; - return contract.run_app(contract.instance, argc, argv.data()); + return contract.run_app(contract.handle, argc, argv.data()); } } @@ -1007,7 +1007,7 @@ int fx_muxer_t::get_runtime_delegate(host_context_t *context, coreclr_delegate_t return rc; } - return contract.get_runtime_delegate(contract.instance, type, delegate); + return contract.get_runtime_delegate(contract.handle, type, delegate); } } diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index df2437b49a..226d1a51dc 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -15,11 +15,18 @@ #include "hostfxr.h" #include "host_context.h" -SHARED_API int hostfxr_main_startupinfo(const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path) +namespace { - trace::setup(); + void trace_hostfxr_entry_point(const pal::char_t *entry_point) + { + trace::setup(); + trace::info(_X("--- Invoked %s [commit hash: %s]"), entry_point, _STRINGIFY(REPO_COMMIT_HASH)); + } +} - trace::info(_X("--- Invoked hostfxr v2 [commit hash: %s] main"), _STRINGIFY(REPO_COMMIT_HASH)); +SHARED_API int hostfxr_main_startupinfo(const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path) +{ + trace_hostfxr_entry_point(_X("hostfxr_main_startupinfo")); host_startup_info_t startup_info(host_path, dotnet_root, app_path); @@ -28,9 +35,7 @@ SHARED_API int hostfxr_main_startupinfo(const int argc, const pal::char_t* argv[ SHARED_API int hostfxr_main(const int argc, const pal::char_t* argv[]) { - trace::setup(); - - trace::info(_X("--- Invoked hostfxr [commit hash: %s] main"), _STRINGIFY(REPO_COMMIT_HASH)); + trace_hostfxr_entry_point(_X("hostfxr_main")); host_startup_info_t startup_info; startup_info.parse(argc, argv); @@ -87,9 +92,7 @@ SHARED_API int32_t hostfxr_resolve_sdk( pal::char_t buffer[], int32_t buffer_size) { - trace::setup(); - - trace::info(_X("--- Invoked hostfxr [commit hash: %s] hostfxr_resolve_sdk"), _STRINGIFY(REPO_COMMIT_HASH)); + trace_hostfxr_entry_point(_X("hostfxr_resolve_sdk")); if (buffer_size < 0 || (buffer_size > 0 && buffer == nullptr)) { @@ -202,9 +205,7 @@ SHARED_API int32_t hostfxr_resolve_sdk2( int32_t flags, hostfxr_resolve_sdk2_result_fn result) { - trace::setup(); - - trace::info(_X("--- Invoked hostfxr [commit hash: %s] hostfxr_resolve_sdk2"), _STRINGIFY(REPO_COMMIT_HASH)); + trace_hostfxr_entry_point(_X("hostfxr_resolve_sdk2")); if (exe_dir == nullptr) { @@ -276,9 +277,7 @@ SHARED_API int32_t hostfxr_get_available_sdks( const pal::char_t* exe_dir, hostfxr_get_available_sdks_result_fn result) { - trace::setup(); - - trace::info(_X("--- Invoked hostfxr [commit hash: %s] hostfxr_get_available_sdks"), _STRINGIFY(REPO_COMMIT_HASH)); + trace_hostfxr_entry_point(_X("hostfxr_get_available_sdks")); if (exe_dir == nullptr) { @@ -350,9 +349,7 @@ SHARED_API int32_t hostfxr_get_available_sdks( // SHARED_API int32_t hostfxr_get_native_search_directories(const int argc, const pal::char_t* argv[], pal::char_t buffer[], int32_t buffer_size, int32_t* required_buffer_size) { - trace::setup(); - - trace::info(_X("--- Invoked hostfxr_get_native_search_directories [commit hash: %s] main"), _STRINGIFY(REPO_COMMIT_HASH)); + trace_hostfxr_entry_point(_X("hostfxr_get_native_search_directories")); if (buffer_size < 0 || (buffer_size > 0 && buffer == nullptr) || required_buffer_size == nullptr) { @@ -470,14 +467,13 @@ SHARED_API int32_t __cdecl hostfxr_initialize_for_app( const hostfxr_initialize_parameters * parameters, /*out*/ hostfxr_handle * host_context_handle) { + trace_hostfxr_entry_point(_X("hostfxr_initialize_for_app")); + if (host_context_handle == nullptr || (argv == nullptr && argc != 0) || (app_path == nullptr && argc == 0)) return StatusCode::InvalidArgFailure; *host_context_handle = nullptr; - trace::setup(); - trace::info(_X("--- Invoked hostfxr_initialize_for_app [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - host_startup_info_t startup_info{}; int new_argc; const pal::char_t **new_argv; @@ -538,14 +534,13 @@ SHARED_API int32_t __cdecl hostfxr_initialize_for_runtime_config( const hostfxr_initialize_parameters *parameters, /*out*/ hostfxr_handle *host_context_handle) { + trace_hostfxr_entry_point(_X("hostfxr_initialize_for_runtime_config")); + if (runtime_config_path == nullptr || host_context_handle == nullptr) return StatusCode::InvalidArgFailure; *host_context_handle = nullptr; - trace::setup(); - trace::info(_X("--- Invoked hostfxr_initialize_for_runtime_config [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - host_startup_info_t startup_info{}; int rc = populate_startup_info(parameters, startup_info); if (rc != StatusCode::Success) @@ -573,8 +568,7 @@ SHARED_API int32_t __cdecl hostfxr_initialize_for_runtime_config( // SHARED_API int32_t __cdecl hostfxr_run_app(const hostfxr_handle host_context_handle) { - trace::setup(); - trace::info(_X("--- Invoked hostfxr_run_app [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + trace_hostfxr_entry_point(_X("hostfxr_run_app")); host_context_t *context = host_context_t::from_handle(host_context_handle); if (context == nullptr) @@ -621,12 +615,11 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_delegate( hostfxr_delegate_type type, /*out*/ void **delegate) { + trace_hostfxr_entry_point(_X("hostfxr_get_runtime_delegate")); + if (delegate == nullptr) return StatusCode::InvalidArgFailure; - trace::setup(); - trace::info(_X("--- Invoked hostfxr_get_runtime_delegate [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - host_context_t *context = host_context_t::from_handle(host_context_handle); if (context == nullptr) return StatusCode::InvalidArgFailure; @@ -662,12 +655,11 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value( const pal::char_t *name, /*out*/ const pal::char_t **value) { + trace_hostfxr_entry_point(_X("hostfxr_get_runtime_property_value")); + if (name == nullptr || value == nullptr) return StatusCode::InvalidArgFailure; - trace::setup(); - trace::info(_X("--- Invoked hostfxr_get_runtime_property_value [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - const host_context_t *context; if (host_context_handle == nullptr) { @@ -701,7 +693,7 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value( assert(context->type == host_context_type::initialized || context->type == host_context_type::active); const corehost_context_contract contract = context->context_contract; - return contract.get_property_value(contract.instance, name, value); + return contract.get_property_value(contract.handle, name, value); } // @@ -728,12 +720,11 @@ SHARED_API int32_t __cdecl hostfxr_set_runtime_property_value( const pal::char_t *name, const pal::char_t *value) { + trace_hostfxr_entry_point(_X("hostfxr_set_runtime_property_value")); + if (name == nullptr) return StatusCode::InvalidArgFailure; - trace::setup(); - trace::info(_X("--- Invoked hostfxr_set_runtime_property_value [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - host_context_t *context = host_context_t::from_handle(host_context_handle); if (context == nullptr) return StatusCode::InvalidArgFailure; @@ -745,7 +736,7 @@ SHARED_API int32_t __cdecl hostfxr_set_runtime_property_value( } const corehost_context_contract &contract = context->context_contract; - return contract.set_property_value(contract.instance, name, value); + return contract.set_property_value(contract.handle, name, value); } // @@ -781,12 +772,11 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( /*out*/ const pal::char_t **keys, /*out*/ const pal::char_t **values) { + trace_hostfxr_entry_point(_X("hostfxr_get_runtime_properties")); + if (count == nullptr) return StatusCode::InvalidArgFailure; - trace::setup(); - trace::info(_X("--- Invoked hostfxr_get_runtime_properties [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); - const host_context_t *context; if (host_context_handle == nullptr) { @@ -829,7 +819,7 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( assert(context->type == host_context_type::initialized || context->type == host_context_type::active); const corehost_context_contract &contract = context->context_contract; - return contract.get_properties(contract.instance, count, keys, values); + return contract.get_properties(contract.handle, count, keys, values); } // @@ -844,8 +834,7 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( // SHARED_API int32_t __cdecl hostfxr_close(const hostfxr_handle host_context_handle) { - trace::setup(); - trace::info(_X("--- Invoked hostfxr_close [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH)); + trace_hostfxr_entry_point(_X("hostfxr_close")); // Allow contexts with a type of invalid as we still need to clean them up host_context_t *context = host_context_t::from_handle(host_context_handle, /*allow_invalid_type*/ true); diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index af1813bd0c..aab3e7b04c 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -562,7 +562,7 @@ SHARED_API int __cdecl corehost_initialize_context(const host_interface_t *init, return rc; context_contract->version = sizeof(corehost_context_contract); - context_contract->instance = rc == StatusCode::CoreHostAlreadyInitialized ? nullptr : context; + context_contract->handle = rc == StatusCode::CoreHostAlreadyInitialized ? nullptr : context; context_contract->get_property_value = get_property; context_contract->set_property_value = set_property; context_contract->get_properties = get_properties; @@ -575,7 +575,7 @@ SHARED_API int __cdecl corehost_initialize_context(const host_interface_t *init, SHARED_API int __cdecl corehost_close_context(corehost_context_contract context_contract) { - hostpolicy_context_t *context = hostpolicy_context_t::from_handle(context_contract.instance); + hostpolicy_context_t *context = hostpolicy_context_t::from_handle(context_contract.handle); if (context == nullptr) return StatusCode::InvalidArgFailure; From 93b2ac9217a168195969588d23b6ee237db91c8c Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 24 Apr 2019 16:05:45 -0700 Subject: [PATCH 12/24] Separate helper function to create/initialize first vs secondary context Rename for clarity between hostpolicy_contract and hostpolicy_context_contract Comments about static variables --- src/corehost/cli/fxr/fx_muxer.cpp | 190 +++++++++--------- src/corehost/cli/fxr/host_context.cpp | 76 ++++++- src/corehost/cli/fxr/host_context.h | 21 +- src/corehost/cli/fxr/hostfxr.cpp | 6 +- src/corehost/cli/fxr/hostpolicy_resolver.cpp | 6 +- src/corehost/cli/fxr/hostpolicy_resolver.h | 4 +- src/corehost/cli/hostpolicy/hostpolicy.cpp | 11 +- .../cli/hostpolicy/hostpolicy_context.cpp | 7 +- 8 files changed, 208 insertions(+), 113 deletions(-) diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index eb09b284ba..1fbf98daa7 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -33,23 +33,35 @@ using corehost_main_with_output_buffer_fn = int(*) (const int argc, const pal::c namespace { + // hostfxr tracks the context used to load hostpolicy and coreclr as the active host context. This is the first + // context that is successfully created and used to load the runtime. There can only be one active context. + // Secondary contexts can only be created once the first context has fully initialized and been used to load the + // runtime. Any calls to initialize another context while the runtime has not yet been loaded by the first context + // will block until the first context loads the runtime (or fails). std::mutex g_context_lock; - std::condition_variable g_context_cv; + + // Tracks the active host context. This is the context that was used to load and initialize hostpolicy and coreclr. + // It will only be set once both hostpolicy and coreclr are loaded and initialized. Once set, it should not be changed. std::unique_ptr g_active_host_context; + + // Tracks whether the host context is initializing (from creation of the first context to loading the runtime). + // Initialization of other contexts should block if the first context is initializing (i.e. this is true). + // The condition variable is used to block on and signal changes to this state. std::atomic g_context_initializing(false); + std::condition_variable g_context_cv; } template int load_hostpolicy( const pal::string_t& lib_dir, pal::dll_t* h_host, - hostpolicy_contract &host_contract, + hostpolicy_contract_t &hostpolicy_contract, const char *main_entry_symbol, T* main_fn) { assert(main_entry_symbol != nullptr && main_fn != nullptr); - int rc = hostpolicy_resolver::load(lib_dir, h_host, host_contract); + int rc = hostpolicy_resolver::load(lib_dir, h_host, hostpolicy_contract); if (rc != StatusCode::Success) { trace::error(_X("An error occurred while loading required library %s from [%s]"), LIBHOSTPOLICY_NAME, lib_dir.c_str()); @@ -75,16 +87,19 @@ static int execute_app( g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); if (g_active_host_context != nullptr) + { + trace::error(_X("Hosting components are already initialized. Re-initialization to execute an app is not allowed.")); return StatusCode::HostInvalidState; + } g_context_initializing.store(true); } pal::dll_t corehost; - hostpolicy_contract host_contract{}; + hostpolicy_contract_t hostpolicy_contract{}; corehost_main_fn host_main = nullptr; - int code = load_hostpolicy(impl_dll_dir, &corehost, host_contract, "corehost_main", &host_main); + int code = load_hostpolicy(impl_dll_dir, &corehost, hostpolicy_contract, "corehost_main", &host_main); if (code != StatusCode::Success) return code; @@ -93,20 +108,20 @@ static int execute_app( // the runtime is loaded through non-host context-based APIs. std::lock_guard lock{ g_context_lock }; assert(g_active_host_context == nullptr); - g_active_host_context.reset(new host_context_t()); + g_active_host_context.reset(new host_context_t(host_context_type::empty, hostpolicy_contract, {})); g_context_initializing.store(false); } g_context_cv.notify_all(); { - propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer); + propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer); const host_interface_t& intf = init->get_host_init_data(); - if ((code = host_contract.load(&intf)) == StatusCode::Success) + if ((code = hostpolicy_contract.load(&intf)) == StatusCode::Success) { code = host_main(argc, argv); - (void)host_contract.unload(); + (void)hostpolicy_contract.unload(); } } @@ -123,21 +138,21 @@ static int execute_host_command( int32_t* required_buffer_size) { pal::dll_t corehost; - hostpolicy_contract host_contract{}; + hostpolicy_contract_t hostpolicy_contract{}; corehost_main_with_output_buffer_fn host_main = nullptr; - int code = load_hostpolicy(impl_dll_dir, &corehost, host_contract, "corehost_main_with_output_buffer", &host_main); + int code = load_hostpolicy(impl_dll_dir, &corehost, hostpolicy_contract, "corehost_main_with_output_buffer", &host_main); if (code != StatusCode::Success) return code; { - propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer); + propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer); const host_interface_t& intf = init->get_host_init_data(); - if ((code = host_contract.load(&intf)) == StatusCode::Success) + if ((code = hostpolicy_contract.load(&intf)) == StatusCode::Success) { code = host_main(argc, argv, result_buffer, buffer_size, required_buffer_size); - (void)host_contract.unload(); + (void)hostpolicy_contract.unload(); } } @@ -489,8 +504,8 @@ namespace const pal::string_t &app_candidate, const opt_map_t &opts, host_mode_t mode, - pal::string_t &impl_dir, - std::unique_ptr &init) + /*out*/ pal::string_t &hostpolicy_dir, + /*out*/ std::unique_ptr &init) { pal::string_t opts_fx_version = _X("--fx-version"); pal::string_t opts_roll_forward = _X("--roll-forward"); @@ -603,7 +618,7 @@ namespace trace::verbose(_X("Executing as a %s app as per config file [%s]"), (is_framework_dependent ? _X("framework-dependent") : _X("self-contained")), app_config.get_path().c_str()); - if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, app_candidate, deps_file, probe_realpaths, &impl_dir)) + if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, app_candidate, deps_file, probe_realpaths, &hostpolicy_dir)) { return CoreHostLibMissingFailure; } @@ -626,7 +641,7 @@ int fx_muxer_t::read_config_and_execute( int32_t buffer_size, int32_t* required_buffer_size) { - pal::string_t impl_dir; + pal::string_t hostpolicy_dir; std::unique_ptr init; int rc = get_init_info_for_app( host_command, @@ -634,18 +649,18 @@ int fx_muxer_t::read_config_and_execute( app_candidate, opts, mode, - impl_dir, + hostpolicy_dir, init); if (rc != StatusCode::Success) return rc; if (host_command.size() == 0) { - rc = execute_app(impl_dir, init.get(), new_argc, new_argv); + rc = execute_app(hostpolicy_dir, init.get(), new_argc, new_argv); } else { - rc = execute_host_command(impl_dir, init.get(), new_argc, new_argv, out_buffer, buffer_size, required_buffer_size); + rc = execute_host_command(hostpolicy_dir, init.get(), new_argc, new_argv, out_buffer, buffer_size, required_buffer_size); } return rc; @@ -737,8 +752,8 @@ namespace const host_startup_info_t &host_info, host_mode_t mode, pal::string_t &runtime_config_path, - pal::string_t &impl_dir, - std::unique_ptr &init) + /*out*/ pal::string_t &hostpolicy_dir, + /*out*/ std::unique_ptr &init) { // Read config fx_definition_vector_t fx_definitions; @@ -767,7 +782,7 @@ namespace (is_framework_dependent ? _X("framework-dependent") : _X("self-contained")), app_config.get_path().c_str()); pal::string_t deps_file; - if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, host_info.app_path, deps_file, probe_realpaths, &impl_dir)) + if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, host_info.app_path, deps_file, probe_realpaths, &hostpolicy_dir)) { return StatusCode::CoreHostLibMissingFailure; } @@ -778,44 +793,40 @@ namespace return StatusCode::Success; } - int initialize_context(const pal::string_t impl_dir, hostpolicy_contract &host_contract, const host_interface_t &host_interface, corehost_context_contract *context_contract) + void handle_initialize_failure(hostpolicy_contract_t *hostpolicy_contract = nullptr) { - pal::dll_t corehost; - int rc = hostpolicy_resolver::load(impl_dir, &corehost, host_contract); - if (rc != StatusCode::Success) - { - trace::error(_X("An error occurred while loading required library %s from [%s]"), LIBHOSTPOLICY_NAME, impl_dir.c_str()); - return rc; - } - - if (host_contract.init_context == nullptr) { - trace::error(_X("This component must target .NET Core 3.0 or a higher version.")); - return StatusCode::HostApiUnsupportedVersion; + std::lock_guard lock{ g_context_lock }; + g_context_initializing.store(false); } - { - propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer); - if ((rc = host_contract.load(&host_interface)) == StatusCode::Success) - { - rc = host_contract.init_context(&host_interface, context_contract); - } - } + if (hostpolicy_contract != nullptr && hostpolicy_contract->unload != nullptr) + hostpolicy_contract->unload(); - return rc; + g_context_cv.notify_all(); } - void handle_initialize_failure(hostpolicy_contract *hostpolicy_contract = nullptr) + int initialize_context( + const pal::string_t hostpolicy_dir, + corehost_init_t &init, + /*out*/ std::unique_ptr &context) { + pal::dll_t corehost; + hostpolicy_contract_t hostpolicy_contract{}; + int rc = hostpolicy_resolver::load(hostpolicy_dir, &corehost, hostpolicy_contract); + if (rc != StatusCode::Success) { - std::lock_guard lock{ g_context_lock }; - g_context_initializing.store(false); + trace::error(_X("An error occurred while loading required library %s from [%s]"), LIBHOSTPOLICY_NAME, hostpolicy_dir.c_str()); + return rc; } - if (hostpolicy_contract != nullptr && hostpolicy_contract->unload != nullptr) - hostpolicy_contract->unload(); + const host_interface_t &host_interface = init.get_host_init_data(); + corehost_context_contract hostpolicy_context_contract; + rc = host_context_t::create(hostpolicy_contract, init, context); + if (rc != StatusCode::Success) + handle_initialize_failure(&hostpolicy_contract); - g_context_cv.notify_all(); + return rc; } } @@ -840,7 +851,7 @@ int fx_muxer_t::initialize_for_app( host_mode_t mode = host_mode_t::apphost; opt_map_t opts; - pal::string_t impl_dir; + pal::string_t hostpolicy_dir; std::unique_ptr init; int rc = get_init_info_for_app( pal::string_t{} /*host_command*/, @@ -848,7 +859,7 @@ int fx_muxer_t::initialize_for_app( host_info.app_path, opts, mode, - impl_dir, + hostpolicy_dir, init); if (rc != StatusCode::Success) { @@ -856,23 +867,19 @@ int fx_muxer_t::initialize_for_app( return rc; } - const host_interface_t &intf = init->get_host_init_data(); - hostpolicy_contract host_contract{}; - std::unique_ptr context(new host_context_t()); - rc = initialize_context(impl_dir, host_contract, intf, &context->context_contract); + std::unique_ptr context; + rc = initialize_context(hostpolicy_dir, *init, context); if (rc != StatusCode::Success) { - handle_initialize_failure(&host_contract); + trace::error(_X("Failed to initialize context for app: %s. Error code: 0x%x"), host_info.app_path.c_str(), rc); return rc; } - trace::verbose(_X("Initialized context for app: %s"), host_info.app_path.c_str()); - context->type = host_context_type::initialized; - context->host_contract = host_contract; context->is_app = true; for (int i = 0; i < argc; ++i) context->argv.push_back(argv[i]); + trace::verbose(_X("Initialized context for app: %s"), host_info.app_path.c_str()); *host_context_handle = context.release(); return rc; } @@ -882,27 +889,28 @@ int fx_muxer_t::initialize_for_runtime_config( const pal::char_t * runtime_config_path, void** host_context_handle) { - bool already_initialized = false; + const host_context_t *existing_context; { std::unique_lock lock{ g_context_lock }; g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); - already_initialized = g_active_host_context != nullptr; - if (!already_initialized) + existing_context = g_active_host_context.get(); + if (existing_context == nullptr) { g_context_initializing.store(true); } - else if (g_active_host_context->type == host_context_type::invalid) + else if (existing_context->type == host_context_type::invalid) { return StatusCode::HostInvalidState; } } + bool already_initialized = existing_context != nullptr; host_mode_t mode = host_mode_t::libhost; pal::string_t runtime_config = runtime_config_path; - pal::string_t impl_dir; + pal::string_t hostpolicy_dir; std::unique_ptr init; - int rc = get_init_info_for_component(host_info, mode, runtime_config, impl_dir, init); + int rc = get_init_info_for_component(host_info, mode, runtime_config, hostpolicy_dir, init); if (rc != StatusCode::Success) { if (!already_initialized) @@ -911,33 +919,25 @@ int fx_muxer_t::initialize_for_runtime_config( return rc; } - const host_interface_t &intf = init->get_host_init_data(); - hostpolicy_contract host_contract{}; - std::unique_ptr context(new host_context_t()); - rc = initialize_context(impl_dir, host_contract, intf, &context->context_contract); - already_initialized |= rc == StatusCode::CoreHostAlreadyInitialized; - if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) - { - if (!already_initialized) - handle_initialize_failure(&host_contract); - - return rc; - } - - context->host_contract = host_contract; - context->is_app = false; + std::unique_ptr context; if (already_initialized) { - trace::verbose(_X("Initialized secondary context for config: %s"), runtime_config_path); - context->type = host_context_type::secondary; - init->get_runtime_properties(context->config_properties); + rc = host_context_t::create_secondary(existing_context->hostpolicy_contract, *init, context); } else { - trace::verbose(_X("Initialized context for config: %s"), runtime_config_path); - context->type = host_context_type::initialized; + rc = initialize_context(hostpolicy_dir, *init, context); + } + + if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) + { + trace::error(_X("Failed to initialize context for config: %s. Error code: 0x%x"), runtime_config_path, rc); + return rc; } + context->is_app = false; + + trace::verbose(_X("Initialized %s for config: %s"), already_initialized ? _X("secondary context") : _X("context"), runtime_config_path); *host_context_handle = context.release(); return rc; } @@ -950,7 +950,7 @@ namespace if (context->type == host_context_type::active) return StatusCode::Success; - const corehost_context_contract &contract = context->context_contract; + const corehost_context_contract &contract = context->hostpolicy_context_contract; int rc = contract.load_runtime(contract.handle); // Mark the context as active or invalid @@ -979,9 +979,9 @@ int fx_muxer_t::run_app(host_context_t *context) for (const auto& str : context->argv) argv.push_back(str.c_str()); - const corehost_context_contract &contract = context->context_contract; + const corehost_context_contract &contract = context->hostpolicy_context_contract; { - propagate_error_writer_t propagate_error_writer_to_corehost(context->host_contract.set_error_writer); + propagate_error_writer_t propagate_error_writer_to_corehost(context->hostpolicy_contract.set_error_writer); int rc = load_runtime(context); if (rc != StatusCode::Success) @@ -996,9 +996,9 @@ int fx_muxer_t::get_runtime_delegate(host_context_t *context, coreclr_delegate_t if (context->is_app) return StatusCode::InvalidArgFailure; - const corehost_context_contract &contract = context->context_contract; + const corehost_context_contract &contract = context->hostpolicy_context_contract; { - propagate_error_writer_t propagate_error_writer_to_corehost(context->host_contract.set_error_writer); + propagate_error_writer_t propagate_error_writer_to_corehost(context->hostpolicy_contract.set_error_writer); if (context->type != host_context_type::secondary) { @@ -1019,8 +1019,8 @@ const host_context_t* fx_muxer_t::get_active_host_context() int fx_muxer_t::close_host_context(host_context_t *context) { - const hostpolicy_contract &host_contract = context->host_contract; - if (host_contract.close_context == nullptr) + const hostpolicy_contract_t &hostpolicy_contract = context->hostpolicy_contract; + if (hostpolicy_contract.close_context == nullptr) { trace::error(_X("This component must target .NET Core 3.0 or a higher version.")); return StatusCode::HostApiUnsupportedVersion; @@ -1030,8 +1030,8 @@ int fx_muxer_t::close_host_context(host_context_t *context) context->close(); if (context->type != host_context_type::secondary) { - propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer); - rc = host_contract.close_context(context->context_contract); + propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer); + rc = hostpolicy_contract.close_context(context->hostpolicy_context_contract); } // Do not delete the active context. diff --git a/src/corehost/cli/fxr/host_context.cpp b/src/corehost/cli/fxr/host_context.cpp index e88e39252c..fd16a5df85 100644 --- a/src/corehost/cli/fxr/host_context.cpp +++ b/src/corehost/cli/fxr/host_context.cpp @@ -9,20 +9,83 @@ namespace { const int32_t valid_host_context_marker = 0xabababab; const int32_t closed_host_context_marker = 0xcdcdcdcd; + + int create_context_common( + const hostpolicy_contract_t &hostpolicy_contract, + const host_interface_t &host_interface, + bool already_loaded, + /*out*/ corehost_context_contract *hostpolicy_context_contract) + { + if (hostpolicy_contract.init_context == nullptr) + { + trace::error(_X("This component must target .NET Core 3.0 or a higher version.")); + return StatusCode::HostApiUnsupportedVersion; + } + + int rc = StatusCode::Success; + { + propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer); + if (!already_loaded) + rc = hostpolicy_contract.load(&host_interface); + + if (rc == StatusCode::Success) + { + rc = hostpolicy_contract.init_context(&host_interface, hostpolicy_context_contract); + } + } + + return rc; + } +} + +int host_context_t::create( + const hostpolicy_contract_t &hostpolicy_contract, + corehost_init_t &init, + /*out*/ std::unique_ptr &context) +{ + const host_interface_t &host_interface = init.get_host_init_data(); + corehost_context_contract hostpolicy_context_contract; + int rc = create_context_common(hostpolicy_contract, host_interface, /*already_loaded*/ false, &hostpolicy_context_contract); + if (rc == StatusCode::Success) + { + std::unique_ptr context_local(new host_context_t(host_context_type::initialized, hostpolicy_contract, hostpolicy_context_contract)); + context = std::move(context_local); + } + + return rc; +} + +int host_context_t::create_secondary( + const hostpolicy_contract_t &hostpolicy_contract, + corehost_init_t &init, + /*out*/ std::unique_ptr &context) +{ + const host_interface_t &host_interface = init.get_host_init_data(); + corehost_context_contract hostpolicy_context_contract; + int rc = create_context_common(hostpolicy_contract, host_interface, /*already_loaded*/ true, &hostpolicy_context_contract); + if (rc == StatusCode::CoreHostAlreadyInitialized) + { + std::unique_ptr context_local(new host_context_t(host_context_type::secondary, hostpolicy_contract, hostpolicy_context_contract)); + init.get_runtime_properties(context_local->config_properties); + context = std::move(context_local); + } + + assert(rc != StatusCode::Success); + return rc; } host_context_t* host_context_t::from_handle(const hostfxr_handle handle, bool allow_invalid_type) { if (handle == nullptr) return nullptr; - + host_context_t *context = static_cast(handle); int32_t marker = context->marker; if (marker == valid_host_context_marker) { if (allow_invalid_type || context->type != host_context_type::invalid) return context; - + trace::error(_X("Host context is in an invalid state")); } else if (marker == closed_host_context_marker) @@ -37,9 +100,14 @@ host_context_t* host_context_t::from_handle(const hostfxr_handle handle, bool al return nullptr; } -host_context_t::host_context_t() +host_context_t::host_context_t( + host_context_type type, + const hostpolicy_contract_t &hostpolicy_contract, + const corehost_context_contract &hostpolicy_context_contract) : marker { valid_host_context_marker } - , type { host_context_type::empty } + , type { type } + , hostpolicy_contract { hostpolicy_contract } + , hostpolicy_context_contract { hostpolicy_context_contract } { } void host_context_t::close() diff --git a/src/corehost/cli/fxr/host_context.h b/src/corehost/cli/fxr/host_context.h index 5b41910c37..9ca319e0f1 100644 --- a/src/corehost/cli/fxr/host_context.h +++ b/src/corehost/cli/fxr/host_context.h @@ -8,6 +8,7 @@ #include #include +#include "corehost_init.h" #include #include "hostpolicy_resolver.h" @@ -23,21 +24,35 @@ enum class host_context_type struct host_context_t { public: // static + static int create( + const hostpolicy_contract_t &hostpolicy_contract, + corehost_init_t &init, + /*out*/ std::unique_ptr &context); + static int create_secondary( + const hostpolicy_contract_t &hostpolicy_contract, + corehost_init_t &init, + /*out*/ std::unique_ptr &context); static host_context_t* from_handle(const hostfxr_handle handle, bool allow_invalid_type = false); public: int32_t marker; // used as an indication for validity host_context_type type; - hostpolicy_contract host_contract; - corehost_context_contract context_contract; + const hostpolicy_contract_t hostpolicy_contract; + const corehost_context_contract hostpolicy_context_contract; + // Whether or not the context was initialized for an app. argv will be empty for non-app contexts. bool is_app; std::vector argv; + // Config properties for secondary contexts std::unordered_map config_properties; - host_context_t(); + host_context_t( + host_context_type type, + const hostpolicy_contract_t &hostpolicy_contract, + const corehost_context_contract &hostpolicy_context_contract); + void close(); }; diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index 226d1a51dc..f062b5797a 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -692,7 +692,7 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value( } assert(context->type == host_context_type::initialized || context->type == host_context_type::active); - const corehost_context_contract contract = context->context_contract; + const corehost_context_contract &contract = context->hostpolicy_context_contract; return contract.get_property_value(contract.handle, name, value); } @@ -735,7 +735,7 @@ SHARED_API int32_t __cdecl hostfxr_set_runtime_property_value( return StatusCode::InvalidArgFailure; } - const corehost_context_contract &contract = context->context_contract; + const corehost_context_contract &contract = context->hostpolicy_context_contract; return contract.set_property_value(contract.handle, name, value); } @@ -818,7 +818,7 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( } assert(context->type == host_context_type::initialized || context->type == host_context_type::active); - const corehost_context_contract &contract = context->context_contract; + const corehost_context_contract &contract = context->hostpolicy_context_contract; return contract.get_properties(contract.handle, count, keys, values); } diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.cpp b/src/corehost/cli/fxr/hostpolicy_resolver.cpp index 8ffbf66127..3d31a316cf 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.cpp +++ b/src/corehost/cli/fxr/hostpolicy_resolver.cpp @@ -15,7 +15,7 @@ namespace { std::mutex g_hostpolicy_lock; pal::dll_t g_hostpolicy; - hostpolicy_contract g_hostpolicy_contract; + hostpolicy_contract_t g_hostpolicy_contract; /** * Resolve the hostpolicy version from deps. @@ -190,7 +190,7 @@ namespace int hostpolicy_resolver::load( const pal::string_t& lib_dir, pal::dll_t* h_host, - hostpolicy_contract &host_contract) + hostpolicy_contract_t &hostpolicy_contract) { std::lock_guard lock{ g_hostpolicy_lock }; if (g_hostpolicy == nullptr) @@ -227,7 +227,7 @@ int hostpolicy_resolver::load( // Return global values *h_host = g_hostpolicy; - host_contract = g_hostpolicy_contract; + hostpolicy_contract = g_hostpolicy_contract; return StatusCode::Success; } diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.h b/src/corehost/cli/fxr/hostpolicy_resolver.h index 45805ba7e9..229644a704 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.h +++ b/src/corehost/cli/fxr/hostpolicy_resolver.h @@ -17,7 +17,7 @@ using corehost_set_error_writer_fn = corehost_error_writer_fn(*) (corehost_error using corehost_initialize_context_fn = int(*)(const host_interface_t* init, corehost_context_contract* handle); using corehost_close_context_fn = int(*)(const corehost_context_contract handle); -struct hostpolicy_contract +struct hostpolicy_contract_t { // Required API contracts corehost_load_fn load; @@ -34,7 +34,7 @@ namespace hostpolicy_resolver int load( const pal::string_t& lib_dir, pal::dll_t* h_host, - hostpolicy_contract &host_contract); + hostpolicy_contract_t &hostpolicy_contract); bool try_get_dir( host_mode_t mode, const pal::string_t& dotnet_root, diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index aab3e7b04c..cbc79db4a4 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -25,10 +25,19 @@ namespace bool g_init_done; hostpolicy_init_t g_init; + // hostpolicy tracks the context used to load and initialize coreclr. This is the first context that + // is successfully created and used to load the runtime. There can only be one hostpolicy context. std::mutex g_context_lock; - std::condition_variable g_context_cv; + + // Tracks the hostpolicy context. This is the context that was used to load and initialize coreclr. + // It will only be set once coreclr is loaded and initialized. Once set, it should not be changed. std::unique_ptr g_context; + + // Tracks whether the hostpolicy context is initializing (from creation of the first context to loading coreclr). + // Attempts to get/create a context should block if the first context is initializing (i.e. this is true). + // The condition variable is used to block on and signal changes to this state. std::atomic g_context_initializing(false); + std::condition_variable g_context_cv; int create_coreclr(hostpolicy_context_t *context) { diff --git a/src/corehost/cli/hostpolicy/hostpolicy_context.cpp b/src/corehost/cli/hostpolicy/hostpolicy_context.cpp index c9986029cc..02e39e8c52 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy_context.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy_context.cpp @@ -23,13 +23,16 @@ namespace hostpolicy_context_t* hostpolicy_context_t::from_handle(const context_handle handle) { if (handle == nullptr) + { + trace::error(_X("Hostpolicy context handle should not be null")); return nullptr; - + } + hostpolicy_context_t *context = static_cast(handle); int32_t marker = context->marker; if (marker == valid_hostpolicy_context_marker) return context; - + if (marker == closed_hostpolicy_context_marker) { trace::error(_X("Hostpolicy context has already been closed")); From 859e9601bac3c041fb64016e14204a2f069c6161 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 25 Apr 2019 11:24:08 -0700 Subject: [PATCH 13/24] Remove exposed concept of context from hostpolicy hostpolicy internally tracks its context and external functions just operate on the one context and error out if it is not in the expected state for that operation --- src/corehost/cli/corehost_context_contract.h | 9 +- src/corehost/cli/fxr/fx_muxer.cpp | 21 +- src/corehost/cli/fxr/host_context.cpp | 4 +- src/corehost/cli/fxr/hostfxr.cpp | 6 +- src/corehost/cli/fxr/hostpolicy_resolver.cpp | 14 +- src/corehost/cli/fxr/hostpolicy_resolver.h | 6 +- src/corehost/cli/hostpolicy/coreclr.cpp | 4 +- src/corehost/cli/hostpolicy/coreclr.h | 4 +- src/corehost/cli/hostpolicy/hostpolicy.cpp | 236 +++++++----------- .../cli/hostpolicy/hostpolicy_context.cpp | 34 --- .../cli/hostpolicy/hostpolicy_context.h | 5 - 11 files changed, 119 insertions(+), 224 deletions(-) diff --git a/src/corehost/cli/corehost_context_contract.h b/src/corehost/cli/corehost_context_contract.h index cee93cf026..65da9d538e 100644 --- a/src/corehost/cli/corehost_context_contract.h +++ b/src/corehost/cli/corehost_context_contract.h @@ -20,28 +20,21 @@ enum class coreclr_delegate_type struct corehost_context_contract { size_t version; - context_handle handle; int (__cdecl *get_property_value)( - const context_handle handle, const pal::char_t* key, /*out*/ const pal::char_t** value); int (__cdecl *set_property_value)( - const context_handle handle, const pal::char_t* key, const pal::char_t* value); int (__cdecl *get_properties)( - const context_handle handle, /*inout*/ size_t *count, /*out*/ const pal::char_t** keys, /*out*/ const pal::char_t** values); - int (__cdecl *load_runtime)( - const context_handle handle); + int (__cdecl *load_runtime)(); int (__cdecl *run_app)( - const context_handle handle, const int argc, const pal::char_t* argv[]); int (__cdecl *get_runtime_delegate)( - const context_handle handle, coreclr_delegate_type type, /*out*/ void** delegate); }; diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index 1fbf98daa7..c317050aa4 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -951,7 +951,7 @@ namespace return StatusCode::Success; const corehost_context_contract &contract = context->hostpolicy_context_contract; - int rc = contract.load_runtime(contract.handle); + int rc = contract.load_runtime(); // Mark the context as active or invalid context->type = rc == StatusCode::Success ? host_context_type::active : host_context_type::invalid; @@ -987,7 +987,7 @@ int fx_muxer_t::run_app(host_context_t *context) if (rc != StatusCode::Success) return rc; - return contract.run_app(contract.handle, argc, argv.data()); + return contract.run_app(argc, argv.data()); } } @@ -1007,7 +1007,7 @@ int fx_muxer_t::get_runtime_delegate(host_context_t *context, coreclr_delegate_t return rc; } - return contract.get_runtime_delegate(contract.handle, type, delegate); + return contract.get_runtime_delegate(type, delegate); } } @@ -1019,20 +1019,7 @@ const host_context_t* fx_muxer_t::get_active_host_context() int fx_muxer_t::close_host_context(host_context_t *context) { - const hostpolicy_contract_t &hostpolicy_contract = context->hostpolicy_contract; - if (hostpolicy_contract.close_context == nullptr) - { - trace::error(_X("This component must target .NET Core 3.0 or a higher version.")); - return StatusCode::HostApiUnsupportedVersion; - } - - int rc = StatusCode::Success; context->close(); - if (context->type != host_context_type::secondary) - { - propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer); - rc = hostpolicy_contract.close_context(context->hostpolicy_context_contract); - } // Do not delete the active context. { @@ -1041,7 +1028,7 @@ int fx_muxer_t::close_host_context(host_context_t *context) delete context; } - return rc; + return StatusCode::Success; } int fx_muxer_t::handle_exec( diff --git a/src/corehost/cli/fxr/host_context.cpp b/src/corehost/cli/fxr/host_context.cpp index fd16a5df85..d1c7388e40 100644 --- a/src/corehost/cli/fxr/host_context.cpp +++ b/src/corehost/cli/fxr/host_context.cpp @@ -16,7 +16,7 @@ namespace bool already_loaded, /*out*/ corehost_context_contract *hostpolicy_context_contract) { - if (hostpolicy_contract.init_context == nullptr) + if (hostpolicy_contract.initialize == nullptr) { trace::error(_X("This component must target .NET Core 3.0 or a higher version.")); return StatusCode::HostApiUnsupportedVersion; @@ -30,7 +30,7 @@ namespace if (rc == StatusCode::Success) { - rc = hostpolicy_contract.init_context(&host_interface, hostpolicy_context_contract); + rc = hostpolicy_contract.initialize(&host_interface, hostpolicy_context_contract); } } diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index f062b5797a..3d5f6f893f 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -693,7 +693,7 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value( assert(context->type == host_context_type::initialized || context->type == host_context_type::active); const corehost_context_contract &contract = context->hostpolicy_context_contract; - return contract.get_property_value(contract.handle, name, value); + return contract.get_property_value(name, value); } // @@ -736,7 +736,7 @@ SHARED_API int32_t __cdecl hostfxr_set_runtime_property_value( } const corehost_context_contract &contract = context->hostpolicy_context_contract; - return contract.set_property_value(contract.handle, name, value); + return contract.set_property_value(name, value); } // @@ -819,7 +819,7 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( assert(context->type == host_context_type::initialized || context->type == host_context_type::active); const corehost_context_contract &contract = context->hostpolicy_context_contract; - return contract.get_properties(contract.handle, count, keys, values); + return contract.get_properties(count, keys, values); } // diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.cpp b/src/corehost/cli/fxr/hostpolicy_resolver.cpp index 3d31a316cf..3988898ac7 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.cpp +++ b/src/corehost/cli/fxr/hostpolicy_resolver.cpp @@ -215,14 +215,12 @@ int hostpolicy_resolver::load( return StatusCode::CoreHostEntryPointFailure; g_hostpolicy_contract.set_error_writer = (corehost_set_error_writer_fn)pal::get_symbol(g_hostpolicy, "corehost_set_error_writer"); - g_hostpolicy_contract.init_context = (corehost_initialize_context_fn)pal::get_symbol(g_hostpolicy, "corehost_initialize_context"); - g_hostpolicy_contract.close_context = (corehost_close_context_fn)pal::get_symbol(g_hostpolicy, "corehost_close_context"); - - // It's possible to not have corehost_set_error_writer, corehost_initialize_context, and - // corehost_close_context. These were introduced in 3.0 so 2.0 hostpolicy would not have - // the export. In this case we will not propagate the error writer and errors will still - // be reported to stderr. Callers are responsible for checking that the function pointers - // are not null before using them. + g_hostpolicy_contract.initialize = (corehost_initialize_fn)pal::get_symbol(g_hostpolicy, "corehost_initialize"); + + // It's possible to not have corehost_set_error_writer and corehost_initialize. These were + // introduced in 3.0, so 2.0 hostpolicy would not have the exports. In this case, we will + // not propagate the error writer and errors will still be reported to stderr. Callers are + // responsible for checking that the function pointers are not null before using them. } // Return global values diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.h b/src/corehost/cli/fxr/hostpolicy_resolver.h index 229644a704..5405c7a4c5 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.h +++ b/src/corehost/cli/fxr/hostpolicy_resolver.h @@ -14,8 +14,7 @@ using corehost_load_fn = int(*) (const host_interface_t* init); using corehost_unload_fn = int(*) (); using corehost_error_writer_fn = void(*) (const pal::char_t* message); using corehost_set_error_writer_fn = corehost_error_writer_fn(*) (corehost_error_writer_fn error_writer); -using corehost_initialize_context_fn = int(*)(const host_interface_t* init, corehost_context_contract* handle); -using corehost_close_context_fn = int(*)(const corehost_context_contract handle); +using corehost_initialize_fn = int(*)(const host_interface_t* init, corehost_context_contract* handle); struct hostpolicy_contract_t { @@ -25,8 +24,7 @@ struct hostpolicy_contract_t // 3.0+ contracts corehost_set_error_writer_fn set_error_writer; - corehost_initialize_context_fn init_context; - corehost_close_context_fn close_context; + corehost_initialize_fn initialize; }; namespace hostpolicy_resolver diff --git a/src/corehost/cli/hostpolicy/coreclr.cpp b/src/corehost/cli/hostpolicy/coreclr.cpp index 9c70addc57..32097ecc78 100644 --- a/src/corehost/cli/hostpolicy/coreclr.cpp +++ b/src/corehost/cli/hostpolicy/coreclr.cpp @@ -252,7 +252,7 @@ bool coreclr_property_bag_t::add(const pal::char_t *key, const pal::char_t *valu } } -bool coreclr_property_bag_t::try_get(common_property key, const pal::char_t **value) +bool coreclr_property_bag_t::try_get(common_property key, const pal::char_t **value) const { int idx = static_cast(key); assert(0 <= idx && idx < static_cast(common_property::Last)); @@ -260,7 +260,7 @@ bool coreclr_property_bag_t::try_get(common_property key, const pal::char_t **va return try_get(PropertyNameMapping[idx], value); } -bool coreclr_property_bag_t::try_get(const pal::char_t *key, const pal::char_t **value) +bool coreclr_property_bag_t::try_get(const pal::char_t *key, const pal::char_t **value) const { assert(key != nullptr && value != nullptr); auto iter = _properties.find(key); diff --git a/src/corehost/cli/hostpolicy/coreclr.h b/src/corehost/cli/hostpolicy/coreclr.h index 7227eac9fb..9d4711f7fa 100644 --- a/src/corehost/cli/hostpolicy/coreclr.h +++ b/src/corehost/cli/hostpolicy/coreclr.h @@ -86,8 +86,8 @@ class coreclr_property_bag_t bool add(common_property key, const pal::char_t *value); bool add(const pal::char_t *key, const pal::char_t *value); - bool try_get(common_property key, const pal::char_t **value); - bool try_get(const pal::char_t *key, const pal::char_t **value); + bool try_get(common_property key, const pal::char_t **value) const; + bool try_get(const pal::char_t *key, const pal::char_t **value) const; void remove(const pal::char_t *key); diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index cbc79db4a4..a7b21f8e1c 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -29,47 +29,51 @@ namespace // is successfully created and used to load the runtime. There can only be one hostpolicy context. std::mutex g_context_lock; - // Tracks the hostpolicy context. This is the context that was used to load and initialize coreclr. - // It will only be set once coreclr is loaded and initialized. Once set, it should not be changed. + // Tracks the hostpolicy context. This is the context that will be used to load and initialize coreclr. + // It will only be set once a context is initialized and updated to hold coreclr once the runtime is + // loaded. Once set, it should not be unset. std::unique_ptr g_context; - // Tracks whether the hostpolicy context is initializing (from creation of the first context to loading coreclr). + // Tracks whether the hostpolicy context is initializing (from start of creation of the first context + // to loading coreclr). It will be false before initialization starts and after it succeeds or fails. // Attempts to get/create a context should block if the first context is initializing (i.e. this is true). // The condition variable is used to block on and signal changes to this state. std::atomic g_context_initializing(false); std::condition_variable g_context_cv; - int create_coreclr(hostpolicy_context_t *context) + int create_coreclr() { - assert(context != nullptr); - if (context->coreclr != nullptr) - return StatusCode::Success; - int rc; { std::lock_guard context_lock { g_context_lock }; - if (g_context != nullptr) + if (g_context == nullptr) + { + trace::error(_X("Hostpolicy has not been initialized")); + return StatusCode::HostInvalidState; + } + + if (g_context->coreclr != nullptr) { - trace::error(_X("CoreClr has already been loaded for a different context")); + trace::error(_X("CoreClr has already been loaded")); return StatusCode::HostInvalidState; } // Verbose logging if (trace::is_enabled()) - context->coreclr_properties.log_properties(); + g_context->coreclr_properties.log_properties(); std::vector host_path; - pal::pal_clrstring(context->host_path, &host_path); - const char *app_domain_friendly_name = context->host_mode == host_mode_t::libhost ? "clr_libhost" : "clrhost"; + pal::pal_clrstring(g_context->host_path, &host_path); + const char *app_domain_friendly_name = g_context->host_mode == host_mode_t::libhost ? "clr_libhost" : "clrhost"; // Create a CoreCLR instance - trace::verbose(_X("CoreCLR path = '%s', CoreCLR dir = '%s'"), context->clr_path.c_str(), context->clr_dir.c_str()); + trace::verbose(_X("CoreCLR path = '%s', CoreCLR dir = '%s'"), g_context->clr_path.c_str(), g_context->clr_dir.c_str()); auto hr = coreclr_t::create( - context->clr_dir, + g_context->clr_dir, host_path.data(), app_domain_friendly_name, - context->coreclr_properties, - context->coreclr); + g_context->coreclr_properties, + g_context->coreclr); if (!SUCCEEDED(hr)) { @@ -78,7 +82,6 @@ namespace } else { - g_context.reset(context); rc = StatusCode::Success; } @@ -89,29 +92,24 @@ namespace return rc; } - int get_or_create_hostpolicy_context( + int create_hostpolicy_context( hostpolicy_init_t &hostpolicy_init, const arguments_t &args, - bool breadcrumbs_enabled, - hostpolicy_context_t **context) + bool breadcrumbs_enabled) { - hostpolicy_context_t *existing_context; { std::unique_lock lock{ g_context_lock }; g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); - existing_context = g_context.get(); - if (existing_context == nullptr) - g_context_initializing.store(true); - } + const hostpolicy_context_t *existing_context = g_context.get(); + if (existing_context != nullptr) + { + trace::info(_X("Host context has already been initialized")); + assert(existing_context->coreclr != nullptr); + return StatusCode::CoreHostAlreadyInitialized; + } - if (existing_context != nullptr) - { - // [TODO] Validate the current context is acceptable for this request - trace::info(_X("Host context has already been initialized")); - assert(existing_context->coreclr != nullptr); - *context = existing_context; - return StatusCode::CoreHostAlreadyInitialized; + g_context_initializing.store(true); } std::unique_ptr context_local(new hostpolicy_context_t()); @@ -127,9 +125,32 @@ namespace return rc; } - *context = context_local.release(); + { + std::lock_guard lock{ g_context_lock }; + g_context.reset(context_local.release()); + } + return StatusCode::Success; } + + const hostpolicy_context_t* get_hostpolicy_context(bool require_runtime) + { + std::lock_guard lock{ g_context_lock }; + const hostpolicy_context_t *existing_context = g_context.get(); + if (existing_context == nullptr) + { + trace::error(_X("Hostpolicy context has not been created")); + return nullptr; + } + + if (require_runtime && existing_context->coreclr == nullptr) + { + trace::error(_X("Runtime has not been loaded and initialized")); + return nullptr; + } + + return existing_context; + } } int run_host_command( @@ -164,7 +185,7 @@ int run_host_command( return StatusCode::InvalidArgFailure; } -int run_as_app( +int run_app_for_context( const hostpolicy_context_t &context, int argc, const pal::char_t **argv) @@ -232,6 +253,15 @@ int run_as_app( // The breadcrumb destructor will join to the background thread to finish writing } +int run_app(const int argc, const pal::char_t *argv[]) +{ + const hostpolicy_context_t *context = get_hostpolicy_context(/*require_runtime*/ true); + if (context == nullptr) + return StatusCode::HostInvalidState; + + return run_app_for_context(*context, argc, argv); +} + void trace_hostpolicy_entrypoint_invocation(const pal::string_t& entryPointName) { trace::info(_X("--- Invoked hostpolicy [commit hash: %s] [%s,%s,%s][%s] %s = {"), @@ -335,17 +365,15 @@ SHARED_API int corehost_main(const int argc, const pal::char_t* argv[]) return rc; assert(g_context == nullptr); - hostpolicy_context_t *context; - rc = get_or_create_hostpolicy_context(g_init, args, true /* breadcrumbs_enabled */, &context); + rc = create_hostpolicy_context(g_init, args, true /* breadcrumbs_enabled */); if (rc != StatusCode::Success) return rc; - rc = create_coreclr(context); + rc = create_coreclr(); if (rc != StatusCode::Success) return rc; - rc = run_as_app(*context, args.app_argc, args.app_argv); - return rc; + return run_app(args.app_argc, args.app_argv); } SHARED_API int corehost_main_with_output_buffer(const int argc, const pal::char_t* argv[], pal::char_t buffer[], int32_t buffer_size, int32_t* required_buffer_size) @@ -398,62 +426,14 @@ int corehost_libhost_init(hostpolicy_init_t &hostpolicy_init, const pal::string_ namespace { - int load_runtime_for_context(const context_handle handle) - { - hostpolicy_context_t *context = hostpolicy_context_t::from_handle(handle); - if (context == nullptr) - return StatusCode::InvalidArgFailure; - - return create_coreclr(context); - } - - int run_app_for_context( - const context_handle handle, - const int argc, - const pal::char_t *argv[]) - { - hostpolicy_context_t *context = hostpolicy_context_t::from_handle(handle); - if (context == nullptr) - return StatusCode::InvalidArgFailure; - - if (context->coreclr == nullptr) - { - trace::error(_X("CoreClr must be loaded before running a app")); - return StatusCode::HostInvalidState; - } - - return run_as_app(*context, argc, argv); - } - - int get_delegate_for_context( - const context_handle handle, - coreclr_delegate_type type, - void **delegate) + int get_delegate(coreclr_delegate_type type, void **delegate) { if (delegate == nullptr) return StatusCode::InvalidArgFailure; - hostpolicy_context_t *context; - if (handle == nullptr) - { - std::lock_guard lock{ g_context_lock }; - if (g_context == nullptr || g_context->coreclr == nullptr) - return StatusCode::HostInvalidState; - - context = g_context.get(); - } - else - { - context = hostpolicy_context_t::from_handle(handle); - if (context == nullptr) - return StatusCode::InvalidArgFailure; - } - - if (context->coreclr == nullptr) - { - trace::error(_X("CoreClr must be loaded before getting a delegate")); + const hostpolicy_context_t *context = get_hostpolicy_context(/*require_runtime*/ true); + if (context == nullptr) return StatusCode::HostInvalidState; - } coreclr_t *coreclr = context->coreclr.get(); switch (type) @@ -481,17 +461,14 @@ namespace } } - int get_property( - const context_handle handle, - const pal::char_t *key, - const pal::char_t **value) + int get_property(const pal::char_t *key, const pal::char_t **value) { if (key == nullptr) return StatusCode::InvalidArgFailure; - hostpolicy_context_t *context = hostpolicy_context_t::from_handle(handle); + const hostpolicy_context_t *context = get_hostpolicy_context(/*require_runtime*/ false); if (context == nullptr) - return StatusCode::InvalidArgFailure; + return StatusCode::HostInvalidState; if (!context->coreclr_properties.try_get(key, value)) return StatusCode::HostPropertyNotFound; @@ -499,42 +476,38 @@ namespace return StatusCode::Success; } - int set_property( - const context_handle handle, - const pal::char_t *key, - const pal::char_t *value) + int set_property(const pal::char_t *key, const pal::char_t *value) { if (key == nullptr) return StatusCode::InvalidArgFailure; - hostpolicy_context_t *context = hostpolicy_context_t::from_handle(handle); - if (context == nullptr) - return StatusCode::InvalidArgFailure; + std::lock_guard lock{ g_context_lock }; + if (g_context == nullptr || g_context->coreclr != nullptr) + { + trace::error(_X("Setting properties is only allowed before runtime has been loaded and initialized")); + return HostInvalidState; + } if (value != nullptr) { - context->coreclr_properties.add(key, value); + g_context->coreclr_properties.add(key, value); } else { - context->coreclr_properties.remove(key); + g_context->coreclr_properties.remove(key); } return StatusCode::Success; } - int get_properties( - const context_handle handle, - size_t * count, - const pal::char_t **keys, - const pal::char_t **values) + int get_properties(size_t * count, const pal::char_t **keys, const pal::char_t **values) { if (count == nullptr) return StatusCode::InvalidArgFailure; - hostpolicy_context_t *context = hostpolicy_context_t::from_handle(handle); + const hostpolicy_context_t *context = get_hostpolicy_context(/*require_runtime*/ false); if (context == nullptr) - return StatusCode::InvalidArgFailure; + return StatusCode::HostInvalidState; size_t actualCount = context->coreclr_properties.count(); size_t input_count = *count; @@ -555,51 +528,36 @@ namespace } } -SHARED_API int __cdecl corehost_initialize_context(const host_interface_t *init, /*out*/ corehost_context_contract *context_contract) +SHARED_API int __cdecl corehost_initialize(const host_interface_t *init, /*out*/ corehost_context_contract *context_contract) { if (init == nullptr || context_contract == nullptr) return StatusCode::InvalidArgFailure; arguments_t args; - int rc = corehost_libhost_init(g_init, _X("corehost_initialize_context"), args); + int rc = corehost_libhost_init(g_init, _X("corehost_initialize"), args); if (rc != StatusCode::Success) return rc; - hostpolicy_context_t *context; - rc = get_or_create_hostpolicy_context(g_init, args, g_init.host_mode != host_mode_t::libhost, &context); + rc = create_hostpolicy_context(g_init, args, g_init.host_mode != host_mode_t::libhost); if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) return rc; + if (rc == StatusCode::CoreHostAlreadyInitialized) + { + // [TODO] Validate the current context is acceptable for this request + } + context_contract->version = sizeof(corehost_context_contract); - context_contract->handle = rc == StatusCode::CoreHostAlreadyInitialized ? nullptr : context; context_contract->get_property_value = get_property; context_contract->set_property_value = set_property; context_contract->get_properties = get_properties; - context_contract->load_runtime = load_runtime_for_context; - context_contract->run_app = run_app_for_context; - context_contract->get_runtime_delegate = get_delegate_for_context; + context_contract->load_runtime = create_coreclr; + context_contract->run_app = run_app; + context_contract->get_runtime_delegate = get_delegate; return rc; } -SHARED_API int __cdecl corehost_close_context(corehost_context_contract context_contract) -{ - hostpolicy_context_t *context = hostpolicy_context_t::from_handle(context_contract.handle); - if (context == nullptr) - return StatusCode::InvalidArgFailure; - - // Do not delete the active context or mark it as closed so that inspection of - // the active context is still allowed - std::lock_guard context_lock{ g_context_lock }; - if (context != g_context.get()) - { - context->close(); - delete context; - } - - return StatusCode::Success; -} - SHARED_API int corehost_unload() { std::lock_guard lock{ g_context_lock }; diff --git a/src/corehost/cli/hostpolicy/hostpolicy_context.cpp b/src/corehost/cli/hostpolicy/hostpolicy_context.cpp index 02e39e8c52..ed1ad39649 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy_context.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy_context.cpp @@ -10,9 +10,6 @@ namespace { - const int32_t valid_hostpolicy_context_marker = 0xabababab; - const int32_t closed_hostpolicy_context_marker = 0xcdcdcdcd; - void log_duplicate_property_error(const pal::char_t *property_key) { trace::error(_X("Duplicate runtime property found: %s"), property_key); @@ -20,34 +17,8 @@ namespace } } -hostpolicy_context_t* hostpolicy_context_t::from_handle(const context_handle handle) -{ - if (handle == nullptr) - { - trace::error(_X("Hostpolicy context handle should not be null")); - return nullptr; - } - - hostpolicy_context_t *context = static_cast(handle); - int32_t marker = context->marker; - if (marker == valid_hostpolicy_context_marker) - return context; - - if (marker == closed_hostpolicy_context_marker) - { - trace::error(_X("Hostpolicy context has already been closed")); - } - else - { - trace::error(_X("Invalid hostpolicy context handle marker: 0x%x"), marker); - } - - return nullptr; -} - int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs) { - marker = valid_hostpolicy_context_marker; application = args.managed_application; host_mode = hostpolicy_init.host_mode; host_path = args.host_path; @@ -228,9 +199,4 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a } return StatusCode::Success; -} - -void hostpolicy_context_t::close() -{ - marker = closed_hostpolicy_context_marker; } \ No newline at end of file diff --git a/src/corehost/cli/hostpolicy/hostpolicy_context.h b/src/corehost/cli/hostpolicy/hostpolicy_context.h index d45129c64e..26840fbd78 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy_context.h +++ b/src/corehost/cli/hostpolicy/hostpolicy_context.h @@ -14,11 +14,7 @@ struct hostpolicy_context_t { -public: // static - static hostpolicy_context_t* from_handle(const context_handle handle); - public: - int32_t marker; // used as an indication for validity pal::string_t application; pal::string_t clr_dir; pal::string_t clr_path; @@ -33,7 +29,6 @@ struct hostpolicy_context_t std::unique_ptr coreclr; int initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs); - void close(); }; #endif // __HOSTPOLICY_CONTEXT_H__ From 3f9f7725f8d4ad8847bf260fae2e6c10600d61e3 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 26 Apr 2019 16:16:40 -0700 Subject: [PATCH 14/24] Split getting init info for secondary scenario into separate function Placeholder TODOs for compatibility checks Comments --- .../design-docs/hosting-layer-apis.md | 136 ++++++++++++++---- src/corehost/cli/fxr/fx_muxer.cpp | 71 ++++++--- src/corehost/cli/hostpolicy/hostpolicy.cpp | 34 ++++- 3 files changed, 193 insertions(+), 48 deletions(-) diff --git a/Documentation/design-docs/hosting-layer-apis.md b/Documentation/design-docs/hosting-layer-apis.md index c274d4ec3c..94fdd372a6 100644 --- a/Documentation/design-docs/hosting-layer-apis.md +++ b/Documentation/design-docs/hosting-layer-apis.md @@ -138,6 +138,110 @@ The error writer is registered per-thread. On each thread, only one callback can If `hostfxr` invokes functions in `hostpolicy` as part of its operation, the error writer will be propagated to `hostpolicy` for the duration of the call. This means that errors from both `hostfxr` and `hostpolicy` will be reported through the same error writer. +``` C +int hostfxr_initialize_for_app( + int argc, + const char_t *argv[], + const char_t *app_path, + const hostfxr_initialize_parameters *parameters, + hostfxr_handle * host_context_handle +); +``` +Initialize the hosting components for running a managed application. +* `argc` / `argv` - command-line arguments +* `app_path` - path to the application to run +* `parameters` - optional additional parameters +* `host_context_handle` - if initialization is successful, this receives an opaque value which identifies the initialized host context. + +See [Native hosting](native-hosting.md#initialize-host-context) + +``` C +int hostfxr_initialize_for_runtime_config( + const char_t *runtime_config_path, + const hostfxr_initialize_parameters *parameters, + hostfxr_handle *host_context_handle +); +``` +Initialize the hosting components for a runtime configuration (`.runtimeconfig.json`). +* `runtime_config_path` - path to the `.runtimeconfig.json` file to process +* `parameters` - optional additional parameters +* `host_context_handle` - if initialization is successful, this receives an opaque value which identifies the initialized host context. + +See [Native hosting](native-hosting.md#initialize-host-context) + +``` C +int hostfxr_get_runtime_property_value( + const hostfxr_handle host_context_handle, + const char_t *name, + const char_t **value); +``` + +Get the value of a runtime property specified by its name. +* `host_context_handle` - initialized host context. If set to `nullptr` the function will operate on the first host context in the process. +* `name` - name of the runtime property to get +* `value` - returns a pointer to a buffer with the property value + +See [Native hosting](native-hosting.md#runtime-properties) + +``` C +int hostfxr_set_runtime_property_value( + const hostfxr_handle host_context_handle, + const char_t *name, + const char_t *value); +``` + +Set the value of a property. +* `host_context_handle` - initialized host context +* `name` - name of the runtime property to set +* `value` - value of the property to set. If the property already has a value in the host context, this function will overwrite it. When set to `nullptr` and if the property already has a value then the property is removed. + +See [Native hosting](native-hosting.md#runtime-properties) + +``` C +int hostfxr_get_runtime_properties( + const hostfxr_handle host_context_handle, + size_t * count, + const char_t **keys, + const char_t **values); +``` +Get all runtime properties for the specified host context. +* `host_context_handle` - initialized host context. If set to `nullptr` the function will operate on the first host context in the process. +* `count` - in/out parameter which must not be `nullptr`. On input it specifies the size of the the `keys` and `values` buffers. On output it contains the number of entries used from `keys` and `values` buffers - the number of properties returned. +* `keys` - buffer which acts as an array of pointers to buffers with keys for the runtime properties. +* `values` - buffer which acts as an array of pointer to buffers with values for the runtime properties. + +If `count` is less than the minimum required buffer size or `keys` or `values` is `nullptr`, this function will return `HostApiBufferTooSmall` and `keys` and `values` will be unchanged. + +See [Native hosting](native-hosting.md#runtime-properties) + +``` C +int hostfxr_run_app(const hostfxr_handle host_context_handle); +``` +Run the application specified by `hostfxr_initialize_for_app`. +* `host_context_handle` - handle to the initialized host context. + +This function does not return until the application completes execution. It will shutdown CoreCLR after the application executes. + +See [Native hosting](native-hosting.md#runtime-properties) + +``` C +int hostfxr_get_runtime_delegate(const hostfxr_handle host_context_handle, hostfxr_delegate_type type, void ** delegate); +``` +Start the runtime and get a function pointer to specified functionality of the runtime. +* `host_context_handle` - initialized host context +* `type` - type of runtime functionality requested +* `delegate` - on success, this is populated with the native function pointer to the requested runtime functionality + +See [Native hosting](native-hosting.md#getting-a-delegate-for-runtime-functionality) + +``` C +int hostfxr_close(const hostfxr_handle host_context_handle); +``` +Close a host context. +* `host_context_handle` - initialized host context to close. + +See [Native hosting](native-hosting.md#cleanup) + ## Host Policy All exported functions and function pointers in the `hostpolicy` library use the `__cdecl` calling convention on the x86 platform. @@ -188,14 +292,6 @@ If `buffer_size` is less than the minimum required buffer size, this function wi ### .NET Core 3.0+ -``` C -int corehost_get_coreclr_delegate(coreclr_delegate_type type, void **delegate) -``` - -Get a delegate for CoreCLR functionality -* `type` - requested type of runtime functionality -* `delegate` - function pointer to the requested runtime functionality - ``` C typedef void(*corehost_resolve_component_dependencies_result_fn)( const char_t *assembly_paths, @@ -226,14 +322,6 @@ The return value is the previouly registered callback (which is now unregistered The error writer is registered per-thread. On each thread, only one callback can be registered. Subsequent registrations overwrite the previous ones. -### [Proposed] .NET Core 3.0+ - -#### Removal - -`corehost_get_coreclr_delegate` will be removed and the equivalent functionality provided through the proposed additions below. Since this function is new in 3.0, it should not be required for backwards compatibility. - -#### Addition - ``` C typedef void* context_handle; @@ -242,18 +330,16 @@ struct corehost_context_contract size_t version; context_handle instance; int (*get_property_value)( - context_handle instance, const char_t *key, const char_t **value); int (*set_property_value)( - context_handle instance, const char_t *key, const char_t *value); int (*get_properties)( - context_handle instance, size_t *count, const char_t **keys, const char_t **values); + int (*load_runtime)(); int (*run_app)( const context_handle instance, const int argc, @@ -265,7 +351,7 @@ struct corehost_context_contract }; ``` -Contract for performing operations on an initialized host context. +Contract for performing operations on an initialized hostpolicy. * `version` - version of the struct. * `instance` - opaque handle to the `corehost_context_contract` state. * `get_property_value` - function pointer for getting a property on the host context. @@ -278,6 +364,7 @@ Contract for performing operations on an initialized host context. * `count` - size of `keys` and `values`. If the size is too small, it will be populated with the required size. Otherwise, it will be populated with the size used. * `keys` - buffer to populate with the property keys. * `values` - buffer to populate with the property values. +* `load_runtime` - function pointer for loading CoreCLR * `run_app` - function pointer for running an application. * `argc` / `argv` - command-line arguments. * `get_runtime_delegate` - function pointer for getting a delegate for CoreCLR functionality @@ -285,16 +372,9 @@ Contract for performing operations on an initialized host context. * `delegate` - function pointer to the requested runtime functionality ``` C -int corehost_initialize_context(const host_interface_t *init, corehost_context_contract *context_contract) +int corehost_initialize(const host_interface_t *init, corehost_context_contract *context_contract) ``` Initializes the host context. This calculates everything required to start CoreCLR (but does not actually do so). * `init` - struct defining how the host context should be initialized. If the host context is already initialized, this function will check if `init` is compatible with the active context. * `context_contract` - if initialization is successful, this is populated with the contract for operating on the initialized host context. - -``` C -int corehost_close_context(corehost_context_contract *context_contract) -``` - -Closes the host context. -* `context_contract` - contract of the context to close. diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index c317050aa4..4146206cae 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -760,34 +760,67 @@ namespace auto app = new fx_definition_t(); fx_definitions.push_back(std::unique_ptr(app)); - runtime_config_t::settings_t override_settings; + const runtime_config_t::settings_t override_settings; int rc = read_config(*app, host_info.app_path, runtime_config_path, override_settings); if (rc != StatusCode::Success) return rc; - runtime_config_t app_config = app->get_runtime_config(); + const runtime_config_t app_config = app->get_runtime_config(); bool is_framework_dependent = app_config.get_is_framework_dependent(); if (is_framework_dependent) { rc = fx_resolver_t::resolve_frameworks_for_app(host_info, override_settings, app_config, fx_definitions); if (rc != StatusCode::Success) - { return rc; - } } - std::vector probe_realpaths = get_probe_realpaths(fx_definitions, std::vector() /* specified_probing_paths */); + const std::vector probe_realpaths = get_probe_realpaths(fx_definitions, std::vector() /* specified_probing_paths */); trace::verbose(_X("Libhost loading occurring as a %s app as per config file [%s]"), (is_framework_dependent ? _X("framework-dependent") : _X("self-contained")), app_config.get_path().c_str()); - pal::string_t deps_file; + const pal::string_t deps_file; if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, host_info.app_path, deps_file, probe_realpaths, &hostpolicy_dir)) { return StatusCode::CoreHostLibMissingFailure; } - pal::string_t additional_deps_serialized; + const pal::string_t additional_deps_serialized; + init.reset(new corehost_init_t(pal::string_t{}, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions)); + + return StatusCode::Success; + } + + int get_init_info_for_secondary_component( + const host_startup_info_t &host_info, + host_mode_t mode, + pal::string_t &runtime_config_path, + const host_context_t *existing_context, + /*out*/ std::unique_ptr &init) + { + // Read config + fx_definition_vector_t fx_definitions; + auto app = new fx_definition_t(); + fx_definitions.push_back(std::unique_ptr(app)); + + const runtime_config_t::settings_t override_settings; + int rc = read_config(*app, host_info.app_path, runtime_config_path, override_settings); + if (rc != StatusCode::Success) + return rc; + + const runtime_config_t app_config = app->get_runtime_config(); + bool is_framework_dependent = app_config.get_is_framework_dependent(); + if (!app_config.get_is_framework_dependent()) + { + trace::error(_X("Initialization for self-contained components is not supported")); + return StatusCode::InvalidConfigFile; + } + + // [TODO] Validate the current context is acceptable for this request (frameworks) + + const pal::string_t deps_file; + const pal::string_t additional_deps_serialized; + const std::vector probe_realpaths; init.reset(new corehost_init_t(pal::string_t{}, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions)); return StatusCode::Success; @@ -906,26 +939,30 @@ int fx_muxer_t::initialize_for_runtime_config( } bool already_initialized = existing_context != nullptr; + + int rc; host_mode_t mode = host_mode_t::libhost; pal::string_t runtime_config = runtime_config_path; - pal::string_t hostpolicy_dir; std::unique_ptr init; - int rc = get_init_info_for_component(host_info, mode, runtime_config, hostpolicy_dir, init); - if (rc != StatusCode::Success) - { - if (!already_initialized) - handle_initialize_failure(); - - return rc; - } - std::unique_ptr context; if (already_initialized) { + rc = get_init_info_for_secondary_component(host_info, mode, runtime_config, existing_context, init); + if (rc != StatusCode::Success) + return rc; + rc = host_context_t::create_secondary(existing_context->hostpolicy_contract, *init, context); } else { + pal::string_t hostpolicy_dir; + rc = get_init_info_for_component(host_info, mode, runtime_config, hostpolicy_dir, init); + if (rc != StatusCode::Success) + { + handle_initialize_failure(); + return rc; + } + rc = initialize_context(hostpolicy_dir, *init, context); } diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index a7b21f8e1c..6ff4ff9a88 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -21,6 +21,9 @@ namespace { + // Initialization information set through corehost_load. All other entry points assume this has already + // been set and use it to perform the requested operation. Note that this being initialized does not + // indicate that the runtime is loaded or that the runtime will be loaded (e.g. host commands). std::mutex g_init_lock; bool g_init_done; hostpolicy_init_t g_init; @@ -30,8 +33,8 @@ namespace std::mutex g_context_lock; // Tracks the hostpolicy context. This is the context that will be used to load and initialize coreclr. - // It will only be set once a context is initialized and updated to hold coreclr once the runtime is - // loaded. Once set, it should not be unset. + // It will be set once a context is initialized and updated to hold coreclr once the runtime is loaded. + // Once set, it should not be unset. std::unique_ptr g_context; // Tracks whether the hostpolicy context is initializing (from start of creation of the first context @@ -528,6 +531,31 @@ namespace } } +// Initializes hostpolicy. Calculates everything required to start the runtime and creates a context to track +// that information +// +// Parameters: +// init +// struct containing information about the initialization request. If hostpolicy is not yet initialized +// this is ignored. If hostpolicy is already initialized, this function will check this struct for +// compatibility with the way in which hostpolicy was previously initialized. +// context_contract +// [out] If initialization is successful, populated with a contract for performing operations on hostpolicy +// +// Return value: +// Success - Initialization was succesful +// CoreHostAlreadyInitialized - Request is compatible with already initialized hostpolicy +// [TODO] +// CoreHostDifferentProperties - Request has runtime properties that differ from already initialized hostpolicy +// +// This function does not load the runtime +// +// If a previous request to initialize hostpolicy was made, but the runtime not yet loaded, this function will +// block until the runtime is loaded. +// +// This function assumes corehost_load has already been called. It uses the init information set through that +// call - not the struct passed into this function - to create a context. +// SHARED_API int __cdecl corehost_initialize(const host_interface_t *init, /*out*/ corehost_context_contract *context_contract) { if (init == nullptr || context_contract == nullptr) @@ -544,7 +572,7 @@ SHARED_API int __cdecl corehost_initialize(const host_interface_t *init, /*out*/ if (rc == StatusCode::CoreHostAlreadyInitialized) { - // [TODO] Validate the current context is acceptable for this request + // [TODO] Compare the current context with this request (properties) } context_contract->version = sizeof(corehost_context_contract); From 550d090352ceb952629d70eb0d18173135b224dd Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 26 Apr 2019 18:02:42 -0700 Subject: [PATCH 15/24] Add initialization option to block waiting for a different initialization request (which may not yet have started) to complete --- src/corehost/cli/corehost_context_contract.h | 6 +++ src/corehost/cli/fxr/fx_muxer.cpp | 14 ++++-- src/corehost/cli/fxr/host_context.cpp | 9 ++-- src/corehost/cli/fxr/host_context.h | 2 + src/corehost/cli/fxr/hostpolicy_resolver.h | 2 +- src/corehost/cli/hostpolicy/hostpolicy.cpp | 47 +++++++++++++++++--- 6 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/corehost/cli/corehost_context_contract.h b/src/corehost/cli/corehost_context_contract.h index 65da9d538e..0b770adc55 100644 --- a/src/corehost/cli/corehost_context_contract.h +++ b/src/corehost/cli/corehost_context_contract.h @@ -9,6 +9,12 @@ typedef void* context_handle; +enum intialization_options_t : int32_t +{ + none = 0x0, + wait_for_initialized = 0x1, // Wait until initialization through a differnt request is completed +}; + enum class coreclr_delegate_type { invalid, diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index 4146206cae..b06985bdf6 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -842,6 +842,7 @@ namespace int initialize_context( const pal::string_t hostpolicy_dir, corehost_init_t &init, + int32_t initialize_options, /*out*/ std::unique_ptr &context) { pal::dll_t corehost; @@ -855,7 +856,7 @@ namespace const host_interface_t &host_interface = init.get_host_init_data(); corehost_context_contract hostpolicy_context_contract; - rc = host_context_t::create(hostpolicy_contract, init, context); + rc = host_context_t::create(hostpolicy_contract, init, initialize_options, context); if (rc != StatusCode::Success) handle_initialize_failure(&hostpolicy_contract); @@ -901,7 +902,7 @@ int fx_muxer_t::initialize_for_app( } std::unique_ptr context; - rc = initialize_context(hostpolicy_dir, *init, context); + rc = initialize_context(hostpolicy_dir, *init, intialization_options_t::none, context); if (rc != StatusCode::Success) { trace::error(_X("Failed to initialize context for app: %s. Error code: 0x%x"), host_info.app_path.c_str(), rc); @@ -922,6 +923,7 @@ int fx_muxer_t::initialize_for_runtime_config( const pal::char_t * runtime_config_path, void** host_context_handle) { + int32_t initialization_options = intialization_options_t::none; const host_context_t *existing_context; { std::unique_lock lock{ g_context_lock }; @@ -936,6 +938,10 @@ int fx_muxer_t::initialize_for_runtime_config( { return StatusCode::HostInvalidState; } + else if (existing_context->type == host_context_type::empty) + { + initialization_options |= intialization_options_t::wait_for_initialized; + } } bool already_initialized = existing_context != nullptr; @@ -951,7 +957,7 @@ int fx_muxer_t::initialize_for_runtime_config( if (rc != StatusCode::Success) return rc; - rc = host_context_t::create_secondary(existing_context->hostpolicy_contract, *init, context); + rc = host_context_t::create_secondary(existing_context->hostpolicy_contract, *init, initialization_options, context); } else { @@ -963,7 +969,7 @@ int fx_muxer_t::initialize_for_runtime_config( return rc; } - rc = initialize_context(hostpolicy_dir, *init, context); + rc = initialize_context(hostpolicy_dir, *init, initialization_options, context); } if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) diff --git a/src/corehost/cli/fxr/host_context.cpp b/src/corehost/cli/fxr/host_context.cpp index d1c7388e40..c962574a41 100644 --- a/src/corehost/cli/fxr/host_context.cpp +++ b/src/corehost/cli/fxr/host_context.cpp @@ -13,6 +13,7 @@ namespace int create_context_common( const hostpolicy_contract_t &hostpolicy_contract, const host_interface_t &host_interface, + int32_t initialization_options, bool already_loaded, /*out*/ corehost_context_contract *hostpolicy_context_contract) { @@ -30,7 +31,7 @@ namespace if (rc == StatusCode::Success) { - rc = hostpolicy_contract.initialize(&host_interface, hostpolicy_context_contract); + rc = hostpolicy_contract.initialize(&host_interface, initialization_options, hostpolicy_context_contract); } } @@ -41,11 +42,12 @@ namespace int host_context_t::create( const hostpolicy_contract_t &hostpolicy_contract, corehost_init_t &init, + int32_t initialization_options, /*out*/ std::unique_ptr &context) { const host_interface_t &host_interface = init.get_host_init_data(); corehost_context_contract hostpolicy_context_contract; - int rc = create_context_common(hostpolicy_contract, host_interface, /*already_loaded*/ false, &hostpolicy_context_contract); + int rc = create_context_common(hostpolicy_contract, host_interface, initialization_options, /*already_loaded*/ false, &hostpolicy_context_contract); if (rc == StatusCode::Success) { std::unique_ptr context_local(new host_context_t(host_context_type::initialized, hostpolicy_contract, hostpolicy_context_contract)); @@ -58,11 +60,12 @@ int host_context_t::create( int host_context_t::create_secondary( const hostpolicy_contract_t &hostpolicy_contract, corehost_init_t &init, + int32_t initialization_options, /*out*/ std::unique_ptr &context) { const host_interface_t &host_interface = init.get_host_init_data(); corehost_context_contract hostpolicy_context_contract; - int rc = create_context_common(hostpolicy_contract, host_interface, /*already_loaded*/ true, &hostpolicy_context_contract); + int rc = create_context_common(hostpolicy_contract, host_interface, initialization_options, /*already_loaded*/ true, &hostpolicy_context_contract); if (rc == StatusCode::CoreHostAlreadyInitialized) { std::unique_ptr context_local(new host_context_t(host_context_type::secondary, hostpolicy_contract, hostpolicy_context_contract)); diff --git a/src/corehost/cli/fxr/host_context.h b/src/corehost/cli/fxr/host_context.h index 9ca319e0f1..2fd3e8f930 100644 --- a/src/corehost/cli/fxr/host_context.h +++ b/src/corehost/cli/fxr/host_context.h @@ -27,10 +27,12 @@ struct host_context_t static int create( const hostpolicy_contract_t &hostpolicy_contract, corehost_init_t &init, + int32_t initialization_options, /*out*/ std::unique_ptr &context); static int create_secondary( const hostpolicy_contract_t &hostpolicy_contract, corehost_init_t &init, + int32_t initialization_options, /*out*/ std::unique_ptr &context); static host_context_t* from_handle(const hostfxr_handle handle, bool allow_invalid_type = false); diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.h b/src/corehost/cli/fxr/hostpolicy_resolver.h index 5405c7a4c5..d6e141cc7f 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.h +++ b/src/corehost/cli/fxr/hostpolicy_resolver.h @@ -14,7 +14,7 @@ using corehost_load_fn = int(*) (const host_interface_t* init); using corehost_unload_fn = int(*) (); using corehost_error_writer_fn = void(*) (const pal::char_t* message); using corehost_set_error_writer_fn = corehost_error_writer_fn(*) (corehost_error_writer_fn error_writer); -using corehost_initialize_fn = int(*)(const host_interface_t* init, corehost_context_contract* handle); +using corehost_initialize_fn = int(*)(const host_interface_t* init, int32_t options, corehost_context_contract* handle); struct hostpolicy_contract_t { diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index 6ff4ff9a88..354427e234 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -115,6 +115,8 @@ namespace g_context_initializing.store(true); } + g_context_cv.notify_all(); + std::unique_ptr context_local(new hostpolicy_context_t()); int rc = context_local->initialize(hostpolicy_init, args, breadcrumbs_enabled); if (rc != StatusCode::Success) @@ -539,8 +541,10 @@ namespace // struct containing information about the initialization request. If hostpolicy is not yet initialized // this is ignored. If hostpolicy is already initialized, this function will check this struct for // compatibility with the way in which hostpolicy was previously initialized. +// options +// initialization options // context_contract -// [out] If initialization is successful, populated with a contract for performing operations on hostpolicy +// [out] if initialization is successful, populated with a contract for performing operations on hostpolicy // // Return value: // Success - Initialization was succesful @@ -556,19 +560,52 @@ namespace // This function assumes corehost_load has already been called. It uses the init information set through that // call - not the struct passed into this function - to create a context. // -SHARED_API int __cdecl corehost_initialize(const host_interface_t *init, /*out*/ corehost_context_contract *context_contract) +SHARED_API int __cdecl corehost_initialize(const host_interface_t *init, int32_t options, /*out*/ corehost_context_contract *context_contract) { if (init == nullptr || context_contract == nullptr) return StatusCode::InvalidArgFailure; + bool wait_for_initialized = (options & intialization_options_t::wait_for_initialized) != 0; + if (wait_for_initialized) + { + trace::verbose(_X("Initialization option to wait for initialize request is set")); + std::unique_lock lock{ g_context_lock }; + bool alrleady_initializing = g_context_initializing.load(); + + // If we are not already initializing or done initializing, wait until another context initialization has started + if (g_context == nullptr && !alrleady_initializing) + { + trace::info(_X("Waiting for another request to initialize hostpolicy")); + g_context_cv.wait(lock, [&] { return g_context_initializing.load(); }); + } + } + arguments_t args; int rc = corehost_libhost_init(g_init, _X("corehost_initialize"), args); if (rc != StatusCode::Success) return rc; - rc = create_hostpolicy_context(g_init, args, g_init.host_mode != host_mode_t::libhost); - if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) - return rc; + if (wait_for_initialized) + { + // Wait for context initialization to complete + std::unique_lock lock{ g_context_lock }; + g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); + + const hostpolicy_context_t *existing_context = g_context.get(); + if (existing_context == nullptr || existing_context->coreclr == nullptr) + { + trace::info(_X("Option to wait for initialize request was set, but that request did not result in initialization")); + return StatusCode::HostInvalidState; + } + + rc = StatusCode::CoreHostAlreadyInitialized; + } + else + { + rc = create_hostpolicy_context(g_init, args, g_init.host_mode != host_mode_t::libhost); + if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) + return rc; + } if (rc == StatusCode::CoreHostAlreadyInitialized) { From bc5d7991166f01b5362242db1dd6a0cd730ef5ef Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 29 Apr 2019 11:28:39 -0700 Subject: [PATCH 16/24] Fix missed cases of handling initialize failure/abort --- src/corehost/cli/fxr/fx_muxer.cpp | 52 +++++++++++++--------- src/corehost/cli/hostpolicy/hostpolicy.cpp | 15 +++++-- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index b06985bdf6..70f6227c92 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -49,6 +49,19 @@ namespace // The condition variable is used to block on and signal changes to this state. std::atomic g_context_initializing(false); std::condition_variable g_context_cv; + + void handle_initialize_failure_or_abort(const hostpolicy_contract_t *hostpolicy_contract = nullptr) + { + { + std::lock_guard lock{ g_context_lock }; + g_context_initializing.store(false); + } + + if (hostpolicy_contract != nullptr && hostpolicy_contract->unload != nullptr) + hostpolicy_contract->unload(); + + g_context_cv.notify_all(); + } } template @@ -101,7 +114,10 @@ static int execute_app( int code = load_hostpolicy(impl_dll_dir, &corehost, hostpolicy_contract, "corehost_main", &host_main); if (code != StatusCode::Success) + { + handle_initialize_failure_or_abort(); return code; + } { // Track an empty 'active' context so that host context-based APIs can work properly when @@ -826,19 +842,6 @@ namespace return StatusCode::Success; } - void handle_initialize_failure(hostpolicy_contract_t *hostpolicy_contract = nullptr) - { - { - std::lock_guard lock{ g_context_lock }; - g_context_initializing.store(false); - } - - if (hostpolicy_contract != nullptr && hostpolicy_contract->unload != nullptr) - hostpolicy_contract->unload(); - - g_context_cv.notify_all(); - } - int initialize_context( const pal::string_t hostpolicy_dir, corehost_init_t &init, @@ -851,14 +854,16 @@ namespace if (rc != StatusCode::Success) { trace::error(_X("An error occurred while loading required library %s from [%s]"), LIBHOSTPOLICY_NAME, hostpolicy_dir.c_str()); - return rc; + } + else + { + const host_interface_t &host_interface = init.get_host_init_data(); + corehost_context_contract hostpolicy_context_contract; + rc = host_context_t::create(hostpolicy_contract, init, initialize_options, context); } - const host_interface_t &host_interface = init.get_host_init_data(); - corehost_context_contract hostpolicy_context_contract; - rc = host_context_t::create(hostpolicy_contract, init, initialize_options, context); if (rc != StatusCode::Success) - handle_initialize_failure(&hostpolicy_contract); + handle_initialize_failure_or_abort(&hostpolicy_contract); return rc; } @@ -897,7 +902,7 @@ int fx_muxer_t::initialize_for_app( init); if (rc != StatusCode::Success) { - handle_initialize_failure(); + handle_initialize_failure_or_abort(); return rc; } @@ -965,7 +970,7 @@ int fx_muxer_t::initialize_for_runtime_config( rc = get_init_info_for_component(host_info, mode, runtime_config, hostpolicy_dir, init); if (rc != StatusCode::Success) { - handle_initialize_failure(); + handle_initialize_failure_or_abort(); return rc; } @@ -1062,6 +1067,13 @@ const host_context_t* fx_muxer_t::get_active_host_context() int fx_muxer_t::close_host_context(host_context_t *context) { + if (context->type == host_context_type::initialized) + { + // The first context is being closed without being used to start the runtime + assert(g_active_host_context == nullptr); + handle_initialize_failure_or_abort(&context->hostpolicy_contract); + } + context->close(); // Do not delete the active context. diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index 354427e234..937438209b 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -625,11 +625,18 @@ SHARED_API int __cdecl corehost_initialize(const host_interface_t *init, int32_t SHARED_API int corehost_unload() { - std::lock_guard lock{ g_context_lock }; - if (g_context != nullptr) - return StatusCode::Success; + { + std::lock_guard lock{ g_context_lock }; + if (g_context != nullptr && g_context->coreclr != nullptr) + return StatusCode::Success; + + // Allow re-initializing if runtime has not been loaded + g_context.reset(); + g_context_initializing.store(false); + } + + g_context_cv.notify_all(); - // Allow re-initializing if runtime has not been loaded std::lock_guard init_lock{ g_init_lock }; g_init_done = false; From 0100d5bb817e91369b88112b8c030e4b33906377 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 29 Apr 2019 11:48:04 -0700 Subject: [PATCH 17/24] PR feedback --- src/corehost/cli/fxr/CMakeLists.txt | 1 + src/corehost/cli/fxr/fx_muxer.cpp | 4 +++- src/corehost/cli/fxr/hostfxr.cpp | 3 +-- src/corehost/cli/fxr_resolver.cpp | 11 ----------- src/corehost/cli/fxr_resolver.h | 3 +-- src/corehost/cli/hostpolicy/CMakeLists.txt | 1 + src/corehost/cli/hostpolicy/hostpolicy.cpp | 18 ++++++++++-------- src/corehost/common/utils.cpp | 14 +++++++++++++- src/corehost/common/utils.h | 1 + 9 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/corehost/cli/fxr/CMakeLists.txt b/src/corehost/cli/fxr/CMakeLists.txt index b158f53656..077c502393 100644 --- a/src/corehost/cli/fxr/CMakeLists.txt +++ b/src/corehost/cli/fxr/CMakeLists.txt @@ -38,6 +38,7 @@ set(SOURCES ) set(HEADERS + ../corehost_context_contract.h ../deps_format.h ../deps_entry.h ../host_startup_info.h diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index 70f6227c92..00768dbe3e 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -121,7 +121,9 @@ static int execute_app( { // Track an empty 'active' context so that host context-based APIs can work properly when - // the runtime is loaded through non-host context-based APIs. + // the runtime is loaded through non-host context-based APIs. Once set, the context is never + // unset. This means that if any error occurs after this point (e.g. with loading the runtime), + // the process will be in a corrupted state and loading the runtime again will not be allowed. std::lock_guard lock{ g_context_lock }; assert(g_active_host_context == nullptr); g_active_host_context.reset(new host_context_t(host_context_type::empty, hostpolicy_contract, {})); diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index 3d5f6f893f..2d6266678f 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -422,8 +422,7 @@ namespace if (!pal::get_own_module_path(&mod_path)) return StatusCode::CoreHostCurHostFindFailure; - pal::string_t fxr_root = get_directory(get_directory(mod_path)); - startup_info.dotnet_root = get_directory(get_directory(fxr_root)); + startup_info.dotnet_root = get_dotnet_root_from_fxr_path(mod_path); if (!pal::realpath(&startup_info.dotnet_root)) { trace::error(_X("Failed to resolve full path of dotnet root [%s]"), startup_info.dotnet_root.c_str()); diff --git a/src/corehost/cli/fxr_resolver.cpp b/src/corehost/cli/fxr_resolver.cpp index 12e2853ffd..eb85e4ddda 100644 --- a/src/corehost/cli/fxr_resolver.cpp +++ b/src/corehost/cli/fxr_resolver.cpp @@ -139,15 +139,4 @@ bool fxr_resolver::try_get_existing_fxr(pal::dll_t *out_fxr, pal::string_t *out_ trace::verbose(_X("Found previously loaded library %s [%s]."), LIBFXR_NAME, out_fxr_path->c_str()); return true; -} - -pal::string_t fxr_resolver::dotnet_root_from_fxr_path(const pal::string_t &fxr_path) -{ - // If coreclr exists next to hostfxr, assume everything is local (e.g. self-contained) - if (coreclr_exists_in_dir(fxr_path)) - return get_directory(fxr_path); - - // Path to hostfxr is: /host/fxr// - pal::string_t fxr_root = get_directory(get_directory(fxr_path)); - return get_directory(get_directory(fxr_root)); } \ No newline at end of file diff --git a/src/corehost/cli/fxr_resolver.h b/src/corehost/cli/fxr_resolver.h index b0944a727a..6586ac9ea3 100644 --- a/src/corehost/cli/fxr_resolver.h +++ b/src/corehost/cli/fxr_resolver.h @@ -15,7 +15,6 @@ namespace fxr_resolver { bool try_get_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path); bool try_get_existing_fxr(pal::dll_t *out_fxr, pal::string_t *out_fxr_path); - pal::string_t dotnet_root_from_fxr_path(const pal::string_t &fxr_path); } template @@ -34,7 +33,7 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallb pal::string_t fxr_path; if (fxr_resolver::try_get_existing_fxr(&fxr, &fxr_path)) { - dotnet_root = fxr_resolver::dotnet_root_from_fxr_path(fxr_path); + dotnet_root = get_dotnet_root_from_fxr_path(fxr_path); trace::verbose(_X("The library %s was already loaded. Reusing the previously loaded library [%s]."), LIBFXR_NAME, fxr_path.c_str()); } else diff --git a/src/corehost/cli/hostpolicy/CMakeLists.txt b/src/corehost/cli/hostpolicy/CMakeLists.txt index 6baf0a3a9f..ad279fab46 100644 --- a/src/corehost/cli/hostpolicy/CMakeLists.txt +++ b/src/corehost/cli/hostpolicy/CMakeLists.txt @@ -42,6 +42,7 @@ set(HEADERS ./deps_resolver.h ./hostpolicy_context.h ./hostpolicy_init.h + ../corehost_context_contract.h ../runtime_config.h ../json/casablanca/include/cpprest/json.h ../fxr/fx_ver.h diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index 937438209b..749306b16e 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -32,9 +32,9 @@ namespace // is successfully created and used to load the runtime. There can only be one hostpolicy context. std::mutex g_context_lock; - // Tracks the hostpolicy context. This is the context that will be used to load and initialize coreclr. - // It will be set once a context is initialized and updated to hold coreclr once the runtime is loaded. - // Once set, it should not be unset. + // Tracks the hostpolicy context. This is the one and only hostpolicy context. It represents the information + // that hostpolicy will use or has already used to load and initialize coreclr. It will be set once a context + //is initialized and updated to hold coreclr once the runtime is loaded. std::unique_ptr g_context; // Tracks whether the hostpolicy context is initializing (from start of creation of the first context @@ -313,7 +313,7 @@ SHARED_API int corehost_load(host_interface_t* init) } int corehost_init( - hostpolicy_init_t &hostpolicy_init, + const hostpolicy_init_t &hostpolicy_init, const int argc, const pal::char_t* argv[], const pal::string_t& location, @@ -421,7 +421,7 @@ SHARED_API int corehost_main_with_output_buffer(const int argc, const pal::char_ return rc; } -int corehost_libhost_init(hostpolicy_init_t &hostpolicy_init, const pal::string_t& location, arguments_t& args) +int corehost_libhost_init(const hostpolicy_init_t &hostpolicy_init, const pal::string_t& location, arguments_t& args) { // Host info should always be valid in the delegate scenario assert(hostpolicy_init.host_info.is_valid(host_mode_t::libhost)); @@ -554,7 +554,7 @@ namespace // // This function does not load the runtime // -// If a previous request to initialize hostpolicy was made, but the runtime not yet loaded, this function will +// If a previous request to initialize hostpolicy was made, but the runtime was not yet loaded, this function will // block until the runtime is loaded. // // This function assumes corehost_load has already been called. It uses the init information set through that @@ -570,16 +570,18 @@ SHARED_API int __cdecl corehost_initialize(const host_interface_t *init, int32_t { trace::verbose(_X("Initialization option to wait for initialize request is set")); std::unique_lock lock{ g_context_lock }; - bool alrleady_initializing = g_context_initializing.load(); + bool already_initializing = g_context_initializing.load(); // If we are not already initializing or done initializing, wait until another context initialization has started - if (g_context == nullptr && !alrleady_initializing) + if (g_context == nullptr && !already_initializing) { trace::info(_X("Waiting for another request to initialize hostpolicy")); g_context_cv.wait(lock, [&] { return g_context_initializing.load(); }); } } + // Trace entry point information and initialize args using previously set init information. + // This function does not modify any global state. arguments_t args; int rc = corehost_libhost_init(g_init, _X("corehost_initialize"), args); if (rc != StatusCode::Success) diff --git a/src/corehost/common/utils.cpp b/src/corehost/common/utils.cpp index 2a1c5cf104..e83e32e816 100644 --- a/src/corehost/common/utils.cpp +++ b/src/corehost/common/utils.cpp @@ -270,7 +270,7 @@ bool skip_utf8_bom(pal::istream_t* stream) unsigned char bytes[3]; stream->read((char*) bytes, 3); if ((stream->gcount() < 3) || - (bytes[1] != 0xBB) || + (bytes[1] != 0xBB) || (bytes[2] != 0xBF)) { // Reset to 0 if returning false. @@ -450,4 +450,16 @@ void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& na dev_cfg->assign(dev_json_path); trace::verbose(_X("Runtime config is cfg=%s dev=%s"), json_path.c_str(), dev_json_path.c_str()); +} + +pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path) +{ + // If coreclr exists next to hostfxr, assume everything is local (e.g. self-contained) + pal::string_t fxr_dir = get_directory(fxr_path); + if (coreclr_exists_in_dir(fxr_dir)) + return fxr_dir; + + // Path to hostfxr is: /host/fxr// + pal::string_t fxr_root = get_directory(fxr_dir); + return get_directory(get_directory(fxr_root)); } \ No newline at end of file diff --git a/src/corehost/common/utils.h b/src/corehost/common/utils.h index c555a06b6f..0d12cefc18 100644 --- a/src/corehost/common/utils.h +++ b/src/corehost/common/utils.h @@ -65,6 +65,7 @@ bool try_stou(const pal::string_t& str, unsigned* num); pal::string_t get_dotnet_root_env_var_name(); pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal::string_t& app); void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg); +pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path); // Helper class to make it easy to propagate error writer to the hostpolicy class propagate_error_writer_t From 72b24f7a4067f886a54012f43698474349be62bc Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 29 Apr 2019 15:22:33 -0700 Subject: [PATCH 18/24] Add struct for corehost_initialize (instead of full host interface) --- src/corehost/cli/corehost_context_contract.h | 13 ++++- src/corehost/cli/fxr/corehost_init.cpp | 6 --- src/corehost/cli/fxr/corehost_init.h | 2 - src/corehost/cli/fxr/fx_muxer.cpp | 24 ++++----- src/corehost/cli/fxr/host_context.cpp | 34 ++++++++++--- src/corehost/cli/fxr/host_context.h | 2 +- src/corehost/cli/fxr/hostpolicy_resolver.h | 2 +- src/corehost/cli/hostpolicy/hostpolicy.cpp | 51 +++++++++++++++----- 8 files changed, 87 insertions(+), 47 deletions(-) diff --git a/src/corehost/cli/corehost_context_contract.h b/src/corehost/cli/corehost_context_contract.h index 0b770adc55..2adf61cfc4 100644 --- a/src/corehost/cli/corehost_context_contract.h +++ b/src/corehost/cli/corehost_context_contract.h @@ -5,9 +5,20 @@ #ifndef __COREHOST_CONTEXT_CONTRACT_H__ #define __COREHOST_CONTEXT_CONTRACT_H__ +#include "host_interface.h" #include -typedef void* context_handle; +#pragma pack(push, _HOST_INTERFACE_PACK) +struct corehost_initialize_request_t +{ + size_t version; + strarr_t config_keys; + strarr_t config_values; +}; +#pragma pack(pop) +static_assert(offsetof(corehost_initialize_request_t, version) == 0 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(corehost_initialize_request_t, config_keys) == 1 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(corehost_initialize_request_t, config_values) == 3 * sizeof(size_t), "Struct offset breaks backwards compatibility"); enum intialization_options_t : int32_t { diff --git a/src/corehost/cli/fxr/corehost_init.cpp b/src/corehost/cli/fxr/corehost_init.cpp index 1e2ff2efdd..077f01f983 100644 --- a/src/corehost/cli/fxr/corehost_init.cpp +++ b/src/corehost/cli/fxr/corehost_init.cpp @@ -128,10 +128,4 @@ const host_interface_t& corehost_init_t::get_host_init_data() hi.host_info_app_path = m_host_info_app_path.c_str(); return hi; -} - -void corehost_init_t::get_runtime_properties(std::unordered_map &out_properties) -{ - for (int i = 0; i < m_clr_keys.size(); ++i) - out_properties[m_clr_keys[i]] = m_clr_values[i]; } \ No newline at end of file diff --git a/src/corehost/cli/fxr/corehost_init.h b/src/corehost/cli/fxr/corehost_init.h index 92117a0206..d4eb972625 100644 --- a/src/corehost/cli/fxr/corehost_init.h +++ b/src/corehost/cli/fxr/corehost_init.h @@ -47,8 +47,6 @@ class corehost_init_t const fx_definition_vector_t& fx_definitions); const host_interface_t& get_host_init_data(); - - void get_runtime_properties(std::unordered_map &out_properties); }; #endif // __COREHOST_INIT_H__ \ No newline at end of file diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index 00768dbe3e..bb0c6c8f66 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -814,19 +814,16 @@ namespace host_mode_t mode, pal::string_t &runtime_config_path, const host_context_t *existing_context, - /*out*/ std::unique_ptr &init) + /*out*/ std::unordered_map &config_properties) { // Read config - fx_definition_vector_t fx_definitions; - auto app = new fx_definition_t(); - fx_definitions.push_back(std::unique_ptr(app)); - + fx_definition_t app; const runtime_config_t::settings_t override_settings; - int rc = read_config(*app, host_info.app_path, runtime_config_path, override_settings); + int rc = read_config(app, host_info.app_path, runtime_config_path, override_settings); if (rc != StatusCode::Success) return rc; - const runtime_config_t app_config = app->get_runtime_config(); + const runtime_config_t app_config = app.get_runtime_config(); bool is_framework_dependent = app_config.get_is_framework_dependent(); if (!app_config.get_is_framework_dependent()) { @@ -836,11 +833,7 @@ namespace // [TODO] Validate the current context is acceptable for this request (frameworks) - const pal::string_t deps_file; - const pal::string_t additional_deps_serialized; - const std::vector probe_realpaths; - init.reset(new corehost_init_t(pal::string_t{}, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions)); - + app_config.combine_properties(config_properties); return StatusCode::Success; } @@ -956,19 +949,20 @@ int fx_muxer_t::initialize_for_runtime_config( int rc; host_mode_t mode = host_mode_t::libhost; pal::string_t runtime_config = runtime_config_path; - std::unique_ptr init; std::unique_ptr context; if (already_initialized) { - rc = get_init_info_for_secondary_component(host_info, mode, runtime_config, existing_context, init); + std::unordered_map config_properties; + rc = get_init_info_for_secondary_component(host_info, mode, runtime_config, existing_context, config_properties); if (rc != StatusCode::Success) return rc; - rc = host_context_t::create_secondary(existing_context->hostpolicy_contract, *init, initialization_options, context); + rc = host_context_t::create_secondary(existing_context->hostpolicy_contract, config_properties, initialization_options, context); } else { pal::string_t hostpolicy_dir; + std::unique_ptr init; rc = get_init_info_for_component(host_info, mode, runtime_config, hostpolicy_dir, init); if (rc != StatusCode::Success) { diff --git a/src/corehost/cli/fxr/host_context.cpp b/src/corehost/cli/fxr/host_context.cpp index c962574a41..8cf6319644 100644 --- a/src/corehost/cli/fxr/host_context.cpp +++ b/src/corehost/cli/fxr/host_context.cpp @@ -12,7 +12,8 @@ namespace int create_context_common( const hostpolicy_contract_t &hostpolicy_contract, - const host_interface_t &host_interface, + const host_interface_t *host_interface, + const corehost_initialize_request_t *init_request, int32_t initialization_options, bool already_loaded, /*out*/ corehost_context_contract *hostpolicy_context_contract) @@ -27,11 +28,14 @@ namespace { propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer); if (!already_loaded) - rc = hostpolicy_contract.load(&host_interface); + { + assert (host_interface != nullptr); + rc = hostpolicy_contract.load(host_interface); + } if (rc == StatusCode::Success) { - rc = hostpolicy_contract.initialize(&host_interface, initialization_options, hostpolicy_context_contract); + rc = hostpolicy_contract.initialize(init_request, initialization_options, hostpolicy_context_contract); } } @@ -47,7 +51,7 @@ int host_context_t::create( { const host_interface_t &host_interface = init.get_host_init_data(); corehost_context_contract hostpolicy_context_contract; - int rc = create_context_common(hostpolicy_contract, host_interface, initialization_options, /*already_loaded*/ false, &hostpolicy_context_contract); + int rc = create_context_common(hostpolicy_contract, &host_interface, nullptr, initialization_options, /*already_loaded*/ false, &hostpolicy_context_contract); if (rc == StatusCode::Success) { std::unique_ptr context_local(new host_context_t(host_context_type::initialized, hostpolicy_contract, hostpolicy_context_contract)); @@ -59,17 +63,31 @@ int host_context_t::create( int host_context_t::create_secondary( const hostpolicy_contract_t &hostpolicy_contract, - corehost_init_t &init, + std::unordered_map &config_properties, int32_t initialization_options, /*out*/ std::unique_ptr &context) { - const host_interface_t &host_interface = init.get_host_init_data(); + std::vector config_keys; + std::vector config_values; + for (auto &kv : config_properties) + { + config_keys.push_back(kv.first.c_str()); + config_values.push_back(kv.second.c_str()); + } + + corehost_initialize_request_t init_request; + init_request.version = sizeof(corehost_initialize_request_t); + init_request.config_keys.len = config_keys.size(); + init_request.config_keys.arr = config_keys.data(); + init_request.config_values.len = config_values.size(); + init_request.config_values.arr = config_values.data(); + corehost_context_contract hostpolicy_context_contract; - int rc = create_context_common(hostpolicy_contract, host_interface, initialization_options, /*already_loaded*/ true, &hostpolicy_context_contract); + int rc = create_context_common(hostpolicy_contract, nullptr, &init_request, initialization_options, /*already_loaded*/ true, &hostpolicy_context_contract); if (rc == StatusCode::CoreHostAlreadyInitialized) { std::unique_ptr context_local(new host_context_t(host_context_type::secondary, hostpolicy_contract, hostpolicy_context_contract)); - init.get_runtime_properties(context_local->config_properties); + context_local->config_properties = config_properties; context = std::move(context_local); } diff --git a/src/corehost/cli/fxr/host_context.h b/src/corehost/cli/fxr/host_context.h index 2fd3e8f930..749e44b82a 100644 --- a/src/corehost/cli/fxr/host_context.h +++ b/src/corehost/cli/fxr/host_context.h @@ -31,7 +31,7 @@ struct host_context_t /*out*/ std::unique_ptr &context); static int create_secondary( const hostpolicy_contract_t &hostpolicy_contract, - corehost_init_t &init, + std::unordered_map &config_properties, int32_t initialization_options, /*out*/ std::unique_ptr &context); static host_context_t* from_handle(const hostfxr_handle handle, bool allow_invalid_type = false); diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.h b/src/corehost/cli/fxr/hostpolicy_resolver.h index d6e141cc7f..74383e298c 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.h +++ b/src/corehost/cli/fxr/hostpolicy_resolver.h @@ -14,7 +14,7 @@ using corehost_load_fn = int(*) (const host_interface_t* init); using corehost_unload_fn = int(*) (); using corehost_error_writer_fn = void(*) (const pal::char_t* message); using corehost_set_error_writer_fn = corehost_error_writer_fn(*) (corehost_error_writer_fn error_writer); -using corehost_initialize_fn = int(*)(const host_interface_t* init, int32_t options, corehost_context_contract* handle); +using corehost_initialize_fn = int(*)(const corehost_initialize_request_t* init_request, int32_t options, corehost_context_contract* handle); struct hostpolicy_contract_t { diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index 749306b16e..582091e048 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -537,10 +537,11 @@ namespace // that information // // Parameters: -// init -// struct containing information about the initialization request. If hostpolicy is not yet initialized -// this is ignored. If hostpolicy is already initialized, this function will check this struct for -// compatibility with the way in which hostpolicy was previously initialized. +// init_request +// struct containing information about the initialization request. If hostpolicy is not yet initialized, +// this is expected to be nullptr. If hostpolicy is already initialized, this should not be nullptr and +// this function will use the struct to check for compatibility with the way in which hostpolicy was +// previously initialized. // options // initialization options // context_contract @@ -560,23 +561,47 @@ namespace // This function assumes corehost_load has already been called. It uses the init information set through that // call - not the struct passed into this function - to create a context. // -SHARED_API int __cdecl corehost_initialize(const host_interface_t *init, int32_t options, /*out*/ corehost_context_contract *context_contract) +SHARED_API int __cdecl corehost_initialize(const corehost_initialize_request_t *init_request, int32_t options, /*out*/ corehost_context_contract *context_contract) { - if (init == nullptr || context_contract == nullptr) + if (context_contract == nullptr) return StatusCode::InvalidArgFailure; bool wait_for_initialized = (options & intialization_options_t::wait_for_initialized) != 0; - if (wait_for_initialized) + { - trace::verbose(_X("Initialization option to wait for initialize request is set")); - std::unique_lock lock{ g_context_lock }; + std::unique_lock lock { g_context_lock }; bool already_initializing = g_context_initializing.load(); + bool already_initialized = g_context.get() != nullptr; + + if (wait_for_initialized) + { + trace::verbose(_X("Initialization option to wait for initialize request is set")); + if (init_request == nullptr) + { + trace::error(_X("Initialization request is expected to be non-null when waiting for initialize request option is set")); + return StatusCode::InvalidArgFailure; + } - // If we are not already initializing or done initializing, wait until another context initialization has started - if (g_context == nullptr && !already_initializing) + // If we are not already initializing or done initializing, wait until another context initialization has started + if (!already_initialized && !already_initializing) + { + trace::info(_X("Waiting for another request to initialize hostpolicy")); + g_context_cv.wait(lock, [&] { return g_context_initializing.load(); }); + } + } + else { - trace::info(_X("Waiting for another request to initialize hostpolicy")); - g_context_cv.wait(lock, [&] { return g_context_initializing.load(); }); + if (init_request != nullptr && !already_initialized && !already_initializing) + { + trace::error(_X("Initialization request is expected to be null for the first initialization request")); + return StatusCode::InvalidArgFailure; + } + + if (init_request == nullptr && (already_initializing || already_initialized)) + { + trace::error(_X("Initialization request is expected to be non-null for requests other than the first one")); + return StatusCode::InvalidArgFailure; + } } } From 14bd34f479baed777b474e22800d9f64fc63cf77 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 29 Apr 2019 21:43:12 -0700 Subject: [PATCH 19/24] Add basic test using context-based entry points and mock coreclr --- .../cli/test/mockcoreclr/mockcoreclr.cpp | 11 + .../cli/test/nativehost/nativehost.cpp | 7 +- src/corehost/common/pal.unix.cpp | 7 +- .../NativeHosting/HostContext.cs | 447 ++++++++++++++++++ .../HostContextResultExtensions.cs | 87 ++++ .../NativeHosting/Nethost.cs | 6 - .../NativeHosting/SharedTestStateBase.cs | 9 + .../Assertions/CommandResultAssertions.cs | 2 +- 8 files changed, 566 insertions(+), 10 deletions(-) create mode 100644 src/test/HostActivationTests/NativeHosting/HostContext.cs create mode 100644 src/test/HostActivationTests/NativeHosting/HostContextResultExtensions.cs diff --git a/src/corehost/cli/test/mockcoreclr/mockcoreclr.cpp b/src/corehost/cli/test/mockcoreclr/mockcoreclr.cpp index 6566fc7474..039ccdd84e 100644 --- a/src/corehost/cli/test/mockcoreclr/mockcoreclr.cpp +++ b/src/corehost/cli/test/mockcoreclr/mockcoreclr.cpp @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information. #include "mockcoreclr.h" +#include #include +#include #include "trace.h" #define MockLog(string) std::cout << "mock " << string << std::endl; @@ -81,6 +83,15 @@ SHARED_API pal::hresult_t STDMETHODCALLTYPE coreclr_execute_assembly( MockLogEntry("argv", i, argv[i]); } + pal::string_t path; + if (pal::getenv(_X("TEST_BLOCK_MOCK_EXECUTE_ASSEMBLY"), &path)) + { + while (pal::file_exists(path)) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + if (exitCode != nullptr) { *exitCode = 0; diff --git a/src/corehost/cli/test/nativehost/nativehost.cpp b/src/corehost/cli/test/nativehost/nativehost.cpp index ee2c4e552e..575938d74a 100644 --- a/src/corehost/cli/test/nativehost/nativehost.cpp +++ b/src/corehost/cli/test/nativehost/nativehost.cpp @@ -84,7 +84,7 @@ int main(const int argc, const pal::char_t *argv[]) } else if (pal::strcmp(command, _X("host_context")) == 0) { - // args: ... + // args: ... [] const int min_argc = 6; if (argc < min_argc) { @@ -96,7 +96,8 @@ int main(const int argc, const pal::char_t *argv[]) const pal::char_t *check_properties_str = argv[3]; const pal::string_t hostfxr_path = argv[4]; const pal::char_t *app_or_config_path = argv[5]; - + + // Remaining args used as property names to get/set as well as arguments for the app int remaining_argc = argc - min_argc; const pal::char_t **remaining_argv = nullptr; if (argc > min_argc) @@ -115,6 +116,7 @@ int main(const int argc, const pal::char_t *argv[]) } else if (pal::strcmp(scenario, _X("config_multiple")) == 0) { + // args: ... if (argc < min_argc + 1) { std::cerr << "Invalid arguments" << std::endl; @@ -129,6 +131,7 @@ int main(const int argc, const pal::char_t *argv[]) } else if (pal::strcmp(scenario, _X("mixed")) == 0) { + // args: ... if (argc < min_argc + 1) { std::cerr << "Invalid arguments" << std::endl; diff --git a/src/corehost/common/pal.unix.cpp b/src/corehost/common/pal.unix.cpp index de26da101f..1f31539c1f 100644 --- a/src/corehost/common/pal.unix.cpp +++ b/src/corehost/common/pal.unix.cpp @@ -614,7 +614,12 @@ bool pal::get_own_executable_path(pal::string_t* recv) bool pal::get_own_module_path(string_t* recv) { - return false; + Dl_info info; + if (dladdr((void *)&pal::get_own_module_path, &info) == 0) + return false; + + recv->assign(info.dli_fname); + return true; } bool pal::get_module_path(dll_t module, string_t* recv) diff --git a/src/test/HostActivationTests/NativeHosting/HostContext.cs b/src/test/HostActivationTests/NativeHosting/HostContext.cs new file mode 100644 index 0000000000..f8a5c240b8 --- /dev/null +++ b/src/test/HostActivationTests/NativeHosting/HostContext.cs @@ -0,0 +1,447 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.DotNet.Cli.Build.Framework; +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting +{ + public class HostContext : IClassFixture + { + public class Scenario + { + public const string App = "app"; + public const string Config = "config"; + public const string ConfigMultiple = "config_multiple"; + public const string Mixed = "mixed"; + } + + public class CheckProperties + { + public const string None = "none"; + public const string Get = "get"; + public const string Set = "set"; + public const string Remove = "remove"; + public const string GetAll = "get_all"; + public const string GetActive = "get_active"; + public const string GetAllActive = "get_all_active"; + } + + public class LogPrefix + { + public const string App = "[APP] "; + public const string Config = "[CONFIG] "; + public const string Secondary = "[SECONDARY] "; + } + + private const string HostContextArg = "host_context"; + private const string PropertyValueFromHost = "VALUE_FROM_HOST"; + + private const int InvalidArgFailure = unchecked((int)0x80008081); + private const int HostInvalidState = unchecked((int)0x800080a3); + private const int HostPropertyNotFound = unchecked((int)0x800080a4); + + private readonly SharedTestState sharedState; + + public HostContext(SharedTestState sharedTestState) + { + sharedState = sharedTestState; + } + + [Theory] + [InlineData(CheckProperties.None)] + [InlineData(CheckProperties.Get)] + [InlineData(CheckProperties.Set)] + [InlineData(CheckProperties.Remove)] + [InlineData(CheckProperties.GetAll)] + public void RunApp(string checkProperties) + { + string newPropertyName = "HOST_TEST_PROPERTY"; + string[] args = + { + HostContextArg, + Scenario.App, + checkProperties, + sharedState.HostFxrPath, + sharedState.AppPath, + sharedState.AppPropertyName, + newPropertyName + }; + CommandResult result = Command.Create(sharedState.NativeHostPath, args) + .CaptureStdErr() + .CaptureStdOut() + .EnvironmentVariable("COREHOST_TRACE", "1") + .EnvironmentVariable("DOTNET_ROOT", sharedState.DotNetRoot) + .EnvironmentVariable("DOTNET_ROOT(x86)", sharedState.DotNetRoot) + .Execute(); + + result.Should().Pass() + .And.InitializeContextForApp(sharedState.AppPath) + .And.ExecuteAssemblyMock(sharedState.AppPath); + + switch (checkProperties) + { + case CheckProperties.None: + int appArgIndex = 5; + result.Should() + .HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.HaveStdOutContaining($"mock argc:{args.Length - appArgIndex}"); + for (int i = appArgIndex; i < args.Length; ++i) + { + result.Should().HaveStdOutContaining($"mock argv[{i - appArgIndex}] = {args[i]}"); + } + break; + case CheckProperties.Get: + result.Should() + .GetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.FailToGetRuntimePropertyValue(LogPrefix.App, newPropertyName, HostPropertyNotFound) + .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.NotHavePropertyMock(newPropertyName); + break; + case CheckProperties.Set: + result.Should() + .SetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName) + .And.SetRuntimePropertyValue(LogPrefix.App, newPropertyName) + .And.HavePropertyMock(sharedState.AppPropertyName, PropertyValueFromHost) + .And.HavePropertyMock(newPropertyName, PropertyValueFromHost); + break; + case CheckProperties.Remove: + result.Should() + .SetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName) + .And.SetRuntimePropertyValue(LogPrefix.App, newPropertyName) + .And.NotHavePropertyMock(sharedState.AppPropertyName) + .And.NotHavePropertyMock(newPropertyName); + break; + case CheckProperties.GetAll: + result.Should() + .GetRuntimePropertiesIncludes(LogPrefix.App, sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue); + break; + default: + throw new Exception($"Unknown option: {checkProperties}"); + } + } + + [Theory] + [InlineData(CheckProperties.None)] + [InlineData(CheckProperties.Get)] + [InlineData(CheckProperties.Set)] + [InlineData(CheckProperties.Remove)] + [InlineData(CheckProperties.GetAll)] + public void GetDelegate(string checkProperties) + { + string newPropertyName = "HOST_TEST_PROPERTY"; + string[] args = + { + HostContextArg, + Scenario.Config, + checkProperties, + sharedState.HostFxrPath, + sharedState.RuntimeConfigPath, + sharedState.ConfigPropertyName, + newPropertyName + }; + CommandResult result = Command.Create(sharedState.NativeHostPath, args) + .CaptureStdErr() + .CaptureStdOut() + .EnvironmentVariable("COREHOST_TRACE", "1") + .EnvironmentVariable("DOTNET_ROOT", sharedState.DotNetRoot) + .EnvironmentVariable("DOTNET_ROOT(x86)", sharedState.DotNetRoot) + .Execute(); + + result.Should().Pass() + .And.InitializeContextForConfig(sharedState.RuntimeConfigPath) + .And.CreateDelegateMock(); + + switch (checkProperties) + { + case CheckProperties.None: + result.Should() + .HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue); + break; + case CheckProperties.Get: + result.Should() + .GetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue) + .And.FailToGetRuntimePropertyValue(LogPrefix.Config, newPropertyName, HostPropertyNotFound) + .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue); + break; + case CheckProperties.Set: + result.Should() + .SetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName) + .And.SetRuntimePropertyValue(LogPrefix.Config, newPropertyName) + .And.HavePropertyMock(sharedState.ConfigPropertyName, PropertyValueFromHost) + .And.HavePropertyMock(newPropertyName, PropertyValueFromHost); + break; + case CheckProperties.Remove: + result.Should() + .SetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName) + .And.SetRuntimePropertyValue(LogPrefix.Config, newPropertyName) + .And.NotHavePropertyMock(sharedState.ConfigPropertyName) + .And.NotHavePropertyMock(newPropertyName); + break; + case CheckProperties.GetAll: + result.Should() + .GetRuntimePropertiesIncludes(LogPrefix.Config, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue) + .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue); + break; + default: + throw new Exception($"Unknown option: {checkProperties}"); + } + } + + [Theory] + [InlineData(CheckProperties.None)] + [InlineData(CheckProperties.Get)] + [InlineData(CheckProperties.Set)] + [InlineData(CheckProperties.Remove)] + [InlineData(CheckProperties.GetAll)] + [InlineData(CheckProperties.GetActive)] + [InlineData(CheckProperties.GetAllActive)] + public void GetDelegate_Multiple(string checkProperties) + { + string[] args = + { + HostContextArg, + Scenario.ConfigMultiple, + checkProperties, + sharedState.HostFxrPath, + sharedState.RuntimeConfigPath, + sharedState.SecondaryRuntimeConfigPath, + sharedState.ConfigPropertyName, + sharedState.SecondaryConfigPropertyName + }; + CommandResult result = Command.Create(sharedState.NativeHostPath, args) + .CaptureStdErr() + .CaptureStdOut() + .EnvironmentVariable("COREHOST_TRACE", "1") + .EnvironmentVariable("DOTNET_ROOT", sharedState.DotNetRoot) + .EnvironmentVariable("DOTNET_ROOT(x86)", sharedState.DotNetRoot) + .Execute(); + + result.Should().Pass() + .And.InitializeContextForConfig(sharedState.RuntimeConfigPath) + .And.InitializeSecondaryContext(sharedState.SecondaryRuntimeConfigPath) + .And.CreateDelegateMock(); + + switch (checkProperties) + { + case CheckProperties.None: + result.Should() + .HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue); + break; + case CheckProperties.Get: + result.Should() + .GetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue) + .And.FailToGetRuntimePropertyValue(LogPrefix.Config, sharedState.SecondaryConfigPropertyName, HostPropertyNotFound) + .And.FailToGetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, HostPropertyNotFound) + .And.GetRuntimePropertyValue(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName, sharedState.SecondaryConfigPropertyValue) + .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue) + .And.NotHavePropertyMock(sharedState.SecondaryConfigPropertyName); + break; + case CheckProperties.Set: + result.Should() + .SetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName) + .And.SetRuntimePropertyValue(LogPrefix.Config, sharedState.SecondaryConfigPropertyName) + .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, InvalidArgFailure) + .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName, InvalidArgFailure) + .And.HavePropertyMock(sharedState.ConfigPropertyName, PropertyValueFromHost) + .And.HavePropertyMock(sharedState.SecondaryConfigPropertyName, PropertyValueFromHost); + break; + case CheckProperties.Remove: + result.Should() + .SetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName) + .And.SetRuntimePropertyValue(LogPrefix.Config, sharedState.SecondaryConfigPropertyName) + .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, InvalidArgFailure) + .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName, InvalidArgFailure) + .And.NotHavePropertyMock(sharedState.ConfigPropertyName) + .And.NotHavePropertyMock(sharedState.SecondaryConfigPropertyName); + break; + case CheckProperties.GetAll: + result.Should() + .GetRuntimePropertiesIncludes(LogPrefix.Config, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue) + .And.GetRuntimePropertiesIncludes(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName, sharedState.SecondaryConfigPropertyValue) + .And.GetRuntimePropertiesExcludes(LogPrefix.Secondary, sharedState.ConfigPropertyName) + .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue); + break; + case CheckProperties.GetActive: + result.Should() + .FailToGetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName, HostInvalidState) + .And.FailToGetRuntimePropertyValue(LogPrefix.Config, sharedState.SecondaryConfigPropertyName, HostInvalidState) + .And.GetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue) + .And.FailToGetRuntimePropertyValue(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName, HostPropertyNotFound) + .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue) + .And.NotHavePropertyMock(sharedState.SecondaryConfigPropertyName); + break; + case CheckProperties.GetAllActive: + result.Should() + .FailToGetRuntimeProperties(LogPrefix.Config, HostInvalidState) + .And.GetRuntimePropertiesIncludes(LogPrefix.Secondary, sharedState.ConfigPropertyName,sharedState.ConfigPropertyValue) + .And.GetRuntimePropertiesExcludes(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName) + .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue); + break; + default: + throw new Exception($"Unknown option: {checkProperties}"); + } + } + + [Theory] + [InlineData(CheckProperties.None)] + [InlineData(CheckProperties.Get)] + [InlineData(CheckProperties.Set)] + [InlineData(CheckProperties.Remove)] + [InlineData(CheckProperties.GetAll)] + [InlineData(CheckProperties.GetActive)] + [InlineData(CheckProperties.GetAllActive)] + public void RunApp_GetDelegate(string checkProperties) + { + string[] args = + { + HostContextArg, + Scenario.Mixed, + checkProperties, + sharedState.HostFxrPath, + sharedState.AppPath, + sharedState.RuntimeConfigPath, + sharedState.AppPropertyName, + sharedState.ConfigPropertyName + }; + CommandResult result = Command.Create(sharedState.NativeHostPath, args) + .CaptureStdErr() + .CaptureStdOut() + .EnvironmentVariable("COREHOST_TRACE", "1") + .EnvironmentVariable("DOTNET_ROOT", sharedState.DotNetRoot) + .EnvironmentVariable("DOTNET_ROOT(x86)", sharedState.DotNetRoot) + .EnvironmentVariable("TEST_BLOCK_MOCK_EXECUTE_ASSEMBLY", $"{sharedState.AppPath}.block") + .Execute(); + + result.Should().Pass() + .And.InitializeContextForApp(sharedState.AppPath) + .And.ExecuteAssemblyMock(sharedState.AppPath) + .And.InitializeSecondaryContext(sharedState.RuntimeConfigPath) + .And.CreateDelegateMock(); + + switch(checkProperties) + { + case CheckProperties.None: + result.Should() + .HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue); + break; + case CheckProperties.Get: + result.Should() + .GetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.FailToGetRuntimePropertyValue(LogPrefix.App, sharedState.ConfigPropertyName, HostPropertyNotFound) + .And.FailToGetRuntimePropertyValue(LogPrefix.Secondary, sharedState.AppPropertyName, HostPropertyNotFound) + .And.GetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue) + .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.NotHavePropertyMock(sharedState.ConfigPropertyName); + break; + case CheckProperties.Set: + result.Should() + .SetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName) + .And.SetRuntimePropertyValue(LogPrefix.App, sharedState.ConfigPropertyName) + .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.AppPropertyName, InvalidArgFailure) + .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, InvalidArgFailure) + .And.HavePropertyMock(sharedState.AppPropertyName, PropertyValueFromHost) + .And.HavePropertyMock(sharedState.ConfigPropertyName, PropertyValueFromHost); + break; + case CheckProperties.Remove: + result.Should() + .SetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName) + .And.SetRuntimePropertyValue(LogPrefix.App, sharedState.ConfigPropertyName) + .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.AppPropertyName, InvalidArgFailure) + .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, InvalidArgFailure) + .And.NotHavePropertyMock(sharedState.AppPropertyName) + .And.NotHavePropertyMock(sharedState.ConfigPropertyName); + break; + case CheckProperties.GetAll: + result.Should() + .GetRuntimePropertiesIncludes(LogPrefix.App, sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.GetRuntimePropertiesIncludes(LogPrefix.Secondary, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue) + .And.GetRuntimePropertiesExcludes(LogPrefix.Secondary, sharedState.AppPropertyName) + .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.NotHavePropertyMock(sharedState.ConfigPropertyName); + break; + case CheckProperties.GetActive: + result.Should() + .FailToGetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName, HostInvalidState) + .And.FailToGetRuntimePropertyValue(LogPrefix.App, sharedState.ConfigPropertyName, HostInvalidState) + .And.GetRuntimePropertyValue(LogPrefix.Secondary, sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.FailToGetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, HostPropertyNotFound) + .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.NotHavePropertyMock(sharedState.ConfigPropertyName); + break; + case CheckProperties.GetAllActive: + result.Should() + .FailToGetRuntimeProperties(LogPrefix.App, HostInvalidState) + .And.GetRuntimePropertiesIncludes(LogPrefix.Secondary, sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.GetRuntimePropertiesExcludes(LogPrefix.Secondary, sharedState.ConfigPropertyName) + .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue) + .And.NotHavePropertyMock(sharedState.ConfigPropertyName); + break; + default: + throw new Exception($"Unknown option: {checkProperties}"); + } + } + + public class SharedTestState : SharedTestStateBase + { + public string HostFxrPath { get; } + public string DotNetRoot { get; } + + public string AppPath { get; } + public string RuntimeConfigPath { get; } + public string SecondaryRuntimeConfigPath { get; } + + public string AppPropertyName => "APP_TEST_PROPERTY"; + public string AppPropertyValue => "VALUE_FROM_APP"; + + public string ConfigPropertyName => "CONFIG_TEST_PROPERTY"; + public string ConfigPropertyValue => "VALUE_FROM_CONFIG"; + + public string SecondaryConfigPropertyName => "SECONDARY_CONFIG_TEST_PROPERTY"; + public string SecondaryConfigPropertyValue => "VALUE_FROM_SECONDARY_CONFIG"; + + public SharedTestState() + { + var dotNet = new DotNetBuilder(BaseDirectory, Path.Combine(TestArtifact.TestArtifactsPath, "sharedFrameworkPublish"), "mockRuntime") + .AddMicrosoftNETCoreAppFrameworkMockCoreClr(RepoDirectories.MicrosoftNETCoreAppVersion) + .Build(); + DotNetRoot = dotNet.BinPath; + + HostFxrPath = Path.Combine( + dotNet.GreatestVersionHostFxrPath, + RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr")); + + string appDir = Path.Combine(BaseDirectory, "app"); + Directory.CreateDirectory(appDir); + AppPath = Path.Combine(appDir, "App.dll"); + File.WriteAllText(AppPath, string.Empty); + + RuntimeConfig.FromFile(Path.Combine(appDir, "App.runtimeconfig.json")) + .WithFramework(new RuntimeConfig.Framework("Microsoft.NETCore.App", RepoDirectories.MicrosoftNETCoreAppVersion)) + .WithProperty(AppPropertyName, AppPropertyValue) + .Save(); + + string configDir = Path.Combine(BaseDirectory, "config"); + Directory.CreateDirectory(configDir); + RuntimeConfigPath = Path.Combine(configDir, "Component.runtimeconfig.json"); + RuntimeConfig.FromFile(RuntimeConfigPath) + .WithFramework(new RuntimeConfig.Framework("Microsoft.NETCore.App", RepoDirectories.MicrosoftNETCoreAppVersion)) + .WithProperty(ConfigPropertyName, ConfigPropertyValue) + .Save(); + + string secondaryDir = Path.Combine(BaseDirectory, "secondary"); + Directory.CreateDirectory(secondaryDir); + SecondaryRuntimeConfigPath = Path.Combine(secondaryDir, "Secondary.runtimeconfig.json"); + RuntimeConfig.FromFile(SecondaryRuntimeConfigPath) + .WithFramework(new RuntimeConfig.Framework("Microsoft.NETCore.App", RepoDirectories.MicrosoftNETCoreAppVersion)) + .WithProperty(SecondaryConfigPropertyName, SecondaryConfigPropertyValue) + .Save(); + } + } + } +} diff --git a/src/test/HostActivationTests/NativeHosting/HostContextResultExtensions.cs b/src/test/HostActivationTests/NativeHosting/HostContextResultExtensions.cs new file mode 100644 index 0000000000..de3c0c5dd9 --- /dev/null +++ b/src/test/HostActivationTests/NativeHosting/HostContextResultExtensions.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using FluentAssertions; + +namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting +{ + internal static class HostContextResultExtensions + { + public static AndConstraint ExecuteAssemblyMock(this CommandResultAssertions assertion, string appPath) + { + return assertion.HaveStdOutContaining("mock coreclr_initialize() called") + .And.HaveStdOutContaining("mock coreclr_execute_assembly() called") + .And.HaveStdOutContaining($"mock managedAssemblyPath:{appPath}") + .And.HaveStdOutContaining("mock coreclr_shutdown_2() called"); + } + + public static AndConstraint CreateDelegateMock(this CommandResultAssertions assertion) + { + return assertion.HaveStdOutContaining("mock coreclr_initialize() called") + .And.HaveStdOutContaining("mock coreclr_create_delegate() called"); + } + + public static AndConstraint HavePropertyMock(this CommandResultAssertions assertion, string name, string value) + { + return assertion.HaveStdOutContaining($"mock property[{name}] = {value}"); + } + + public static AndConstraint NotHavePropertyMock(this CommandResultAssertions assertion, string name) + { + return assertion.NotHaveStdOutContaining($"mock property[{name}]"); + } + + public static AndConstraint InitializeContextForApp(this CommandResultAssertions assertion, string path) + { + return assertion.HaveStdErrContaining($"Initialized context for app: {path}"); + } + + public static AndConstraint InitializeContextForConfig(this CommandResultAssertions assertion, string path) + { + return assertion.HaveStdErrContaining($"Initialized context for config: {path}"); + } + + public static AndConstraint InitializeSecondaryContext(this CommandResultAssertions assertion, string path) + { + return assertion.HaveStdErrContaining($"Initialized secondary context for config: {path}"); + } + + public static AndConstraint GetRuntimePropertyValue(this CommandResultAssertions assertion, string prefix, string name, string value) + { + return assertion.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_property_value succeeded for property: {name}={value}"); + } + + public static AndConstraint FailToGetRuntimePropertyValue(this CommandResultAssertions assertion, string prefix, string name, int errorCode) + { + return assertion.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_property_value failed for property: {name} - 0x{errorCode.ToString("x")}"); + } + + public static AndConstraint SetRuntimePropertyValue(this CommandResultAssertions assertion, string prefix, string name) + { + return assertion.HaveStdOutContaining($"{prefix}hostfxr_set_runtime_property_value succeeded for property: {name}"); + } + + public static AndConstraint FailToSetRuntimePropertyValue(this CommandResultAssertions assertion, string prefix, string name, int errorCode) + { + return assertion.HaveStdOutContaining($"{prefix}hostfxr_set_runtime_property_value failed for property: {name} - 0x{errorCode.ToString("x")}"); + } + + public static AndConstraint GetRuntimePropertiesIncludes(this CommandResultAssertions assertion, string prefix, string name, string value) + { + return assertion.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_properties succeeded") + .And.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_properties: {name}={value}"); + } + + public static AndConstraint GetRuntimePropertiesExcludes(this CommandResultAssertions assertion, string prefix, string name) + { + return assertion.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_properties succeeded") + .And.NotHaveStdOutContaining($"{prefix}hostfxr_get_runtime_properties: {name}"); + } + + public static AndConstraint FailToGetRuntimeProperties(this CommandResultAssertions assertion, string prefix, int errorCode) + { + return assertion.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_properties failed - 0x{errorCode.ToString("x")}"); + } + } +} diff --git a/src/test/HostActivationTests/NativeHosting/Nethost.cs b/src/test/HostActivationTests/NativeHosting/Nethost.cs index 1a27e31e82..dfc86117bd 100644 --- a/src/test/HostActivationTests/NativeHosting/Nethost.cs +++ b/src/test/HostActivationTests/NativeHosting/Nethost.cs @@ -154,12 +154,6 @@ public class SharedTestState : SharedTestStateBase public SharedTestState() { - // Copy nethost next to native host - string nethostName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("nethost"); - File.Copy( - Path.Combine(RepoDirectories.CorehostPackages, nethostName), - Path.Combine(Path.GetDirectoryName(NativeHostPath), nethostName)); - InvalidInstallRoot = Path.Combine(BaseDirectory, "invalid"); Directory.CreateDirectory(InvalidInstallRoot); diff --git a/src/test/HostActivationTests/NativeHosting/SharedTestStateBase.cs b/src/test/HostActivationTests/NativeHosting/SharedTestStateBase.cs index 4c55b89d9c..ad788ac9dc 100644 --- a/src/test/HostActivationTests/NativeHosting/SharedTestStateBase.cs +++ b/src/test/HostActivationTests/NativeHosting/SharedTestStateBase.cs @@ -24,6 +24,15 @@ public SharedTestStateBase() // Copy over native host RepoDirectories = new RepoDirectoriesProvider(); File.Copy(Path.Combine(RepoDirectories.Artifacts, "corehost_test", nativeHostName), NativeHostPath); + + // Copy nethost next to native host + // This is done even for tests not directly using nethost because nativehost consumes nethost in the more + // user-friendly way of linking against nethost (instead of dlopen/LoadLibrary and dlsym/GetProcAddress). + // On Windows, we can delay load through a linker option, but on other platforms load is required on start. + string nethostName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("nethost"); + File.Copy( + Path.Combine(RepoDirectories.CorehostPackages, nethostName), + Path.Combine(Path.GetDirectoryName(NativeHostPath), nethostName)); } public void Dispose() diff --git a/src/test/TestUtils/Assertions/CommandResultAssertions.cs b/src/test/TestUtils/Assertions/CommandResultAssertions.cs index 3a6f58ae6e..f87cf6d123 100644 --- a/src/test/TestUtils/Assertions/CommandResultAssertions.cs +++ b/src/test/TestUtils/Assertions/CommandResultAssertions.cs @@ -63,7 +63,7 @@ public AndConstraint HaveStdOutContaining(string patter public AndConstraint NotHaveStdOutContaining(string pattern) { - Execute.Assertion.ForCondition(!_commandResult.StdErr.Contains(pattern)) + Execute.Assertion.ForCondition(!_commandResult.StdOut.Contains(pattern)) .FailWith("The command output contained a result it should not have contained: {0}{1}", pattern, GetDiagnosticsInfo()); return new AndConstraint(this); } From a9cacdf88655c14e1045157b5560316f95d24da1 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 30 Apr 2019 12:15:30 -0700 Subject: [PATCH 20/24] Fix unix build --- src/corehost/cli/fxr/fx_muxer.cpp | 3 --- src/corehost/cli/fxr_resolver.h | 6 +++++- src/corehost/cli/test/nativehost/host_context_test.cpp | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index 139fd84052..b3d18970c5 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -824,7 +824,6 @@ namespace return rc; const runtime_config_t app_config = app.get_runtime_config(); - bool is_framework_dependent = app_config.get_is_framework_dependent(); if (!app_config.get_is_framework_dependent()) { trace::error(_X("Initialization for self-contained components is not supported")); @@ -852,8 +851,6 @@ namespace } else { - const host_interface_t &host_interface = init.get_host_init_data(); - corehost_context_contract hostpolicy_context_contract; rc = host_context_t::create(hostpolicy_contract, init, initialize_options, context); } diff --git a/src/corehost/cli/fxr_resolver.h b/src/corehost/cli/fxr_resolver.h index 676e69bd65..38b35223d7 100644 --- a/src/corehost/cli/fxr_resolver.h +++ b/src/corehost/cli/fxr_resolver.h @@ -83,7 +83,11 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallb rc = hostfxr_get_runtime_delegate(context, type, reinterpret_cast(delegate)); int rcClose = hostfxr_close(context); - assert(rcClose == StatusCode::Success); + if (rcClose != StatusCode::Success) + { + assert(false && "Failed to close host context"); + trace::verbose(_X("Failed to close host context: 0x%x"), rcClose); + } return rc; } diff --git a/src/corehost/cli/test/nativehost/host_context_test.cpp b/src/corehost/cli/test/nativehost/host_context_test.cpp index e5c7c31967..36dd4c2d9b 100644 --- a/src/corehost/cli/test/nativehost/host_context_test.cpp +++ b/src/corehost/cli/test/nativehost/host_context_test.cpp @@ -136,7 +136,7 @@ namespace std::vector keys; std::vector values; int rc = hostfxr.get_properties(handle, &count, nullptr, nullptr); - if (rc == StatusCode::HostApiBufferTooSmall) + if (static_cast(rc) == StatusCode::HostApiBufferTooSmall) { keys.resize(count); values.resize(count); @@ -151,7 +151,7 @@ namespace } std::cout << log_prefix << "hostfxr_get_runtime_properties succeeded." << std::endl; - for (int i = 0; i < keys.size(); ++i) + for (size_t i = 0; i < keys.size(); ++i) { std::cout << log_prefix << "hostfxr_get_runtime_properties: " << to_str(keys[i]).data() << "=" << to_str(values[i]).data() << std::endl; From a431efa059b56cb95063f5df1346f6f2886f98a9 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 30 Apr 2019 16:08:32 -0700 Subject: [PATCH 21/24] PR feedback --- .../design-docs/hosting-layer-apis.md | 24 ++++++++++--- src/corehost/cli/corehost_context_contract.h | 33 +++++++++++------- src/corehost/cli/fxr/fx_muxer.cpp | 34 +++++++++++-------- src/corehost/cli/fxr/fx_muxer.h | 4 +-- src/corehost/cli/fxr/hostfxr.cpp | 17 +++++----- src/corehost/cli/fxr/hostpolicy_resolver.cpp | 12 +++++-- src/corehost/cli/fxr/hostpolicy_resolver.h | 2 +- 7 files changed, 81 insertions(+), 45 deletions(-) diff --git a/Documentation/design-docs/hosting-layer-apis.md b/Documentation/design-docs/hosting-layer-apis.md index 94fdd372a6..5ef43ab0a2 100644 --- a/Documentation/design-docs/hosting-layer-apis.md +++ b/Documentation/design-docs/hosting-layer-apis.md @@ -20,7 +20,9 @@ int hostfxr_main(const int argc, const char_t *argv[]) Run an application. * `argc` / `argv` - command-line arguments -This function does not return until the application completes execution. +This function does not return until the application completes execution. It will shutdown CoreCLR after the application executes. + +If the application is successfully executed, this value will return the exit code of the application. Otherwise, it will return an error code indicating the failure. ### .NET Core 2.0+ @@ -51,7 +53,9 @@ Run an application. * `dotnet_root` - path to the .NET Core installation root * `app_path` - path to the application to run -This function does not return until the application completes execution. +This function does not return until the application completes execution. It will shutdown CoreCLR after the application executes. + +If the application is successfully executed, this value will return the exit code of the application. Otherwise, it will return an error code indicating the failure. ``` C enum hostfxr_resolve_sdk2_flags_t @@ -222,6 +226,8 @@ Run the application specified by `hostfxr_initialize_for_app`. This function does not return until the application completes execution. It will shutdown CoreCLR after the application executes. +If the application is successfully executed, this value will return the exit code of the application. Otherwise, it will return an error code indicating the failure. + See [Native hosting](native-hosting.md#runtime-properties) ``` C @@ -266,6 +272,8 @@ Run an application. This function does not return until the application completes execution. It will shutdown CoreCLR after the application executes. +If the application is successfully executed, this value will return the exit code of the application. Otherwise, it will return an error code indicating the failure. + ``` C int corehost_unload() ``` @@ -372,9 +380,17 @@ Contract for performing operations on an initialized hostpolicy. * `delegate` - function pointer to the requested runtime functionality ``` C -int corehost_initialize(const host_interface_t *init, corehost_context_contract *context_contract) +enum intialization_options_t +{ + none = 0x0, + wait_for_initialized = 0x1, +}; + +int corehost_initialize(const corehost_initialize_request_t *init_request, int32_t options, corehost_context_contract *context_contract) ``` Initializes the host context. This calculates everything required to start CoreCLR (but does not actually do so). -* `init` - struct defining how the host context should be initialized. If the host context is already initialized, this function will check if `init` is compatible with the active context. +* `init_request` - struct containing information about the initialization request. If hostpolicy is not yet initialized, this is expected to be nullptr. If hostpolicy is already initialized, this should not be nullptr and this function will use the struct to check for compatibility with the way in which hostpolicy was previously initialized. +* `options` - initialization options + * `wait_for_initialized` - wait until initialization through a different request is completed * `context_contract` - if initialization is successful, this is populated with the contract for operating on the initialized host context. diff --git a/src/corehost/cli/corehost_context_contract.h b/src/corehost/cli/corehost_context_contract.h index 2adf61cfc4..789c2905f5 100644 --- a/src/corehost/cli/corehost_context_contract.h +++ b/src/corehost/cli/corehost_context_contract.h @@ -8,22 +8,10 @@ #include "host_interface.h" #include -#pragma pack(push, _HOST_INTERFACE_PACK) -struct corehost_initialize_request_t -{ - size_t version; - strarr_t config_keys; - strarr_t config_values; -}; -#pragma pack(pop) -static_assert(offsetof(corehost_initialize_request_t, version) == 0 * sizeof(size_t), "Struct offset breaks backwards compatibility"); -static_assert(offsetof(corehost_initialize_request_t, config_keys) == 1 * sizeof(size_t), "Struct offset breaks backwards compatibility"); -static_assert(offsetof(corehost_initialize_request_t, config_values) == 3 * sizeof(size_t), "Struct offset breaks backwards compatibility"); - enum intialization_options_t : int32_t { none = 0x0, - wait_for_initialized = 0x1, // Wait until initialization through a differnt request is completed + wait_for_initialized = 0x1, // Wait until initialization through a different request is completed }; enum class coreclr_delegate_type @@ -34,6 +22,17 @@ enum class coreclr_delegate_type winrt_activation }; +#pragma pack(push, _HOST_INTERFACE_PACK) +struct corehost_initialize_request_t +{ + size_t version; + strarr_t config_keys; + strarr_t config_values; +}; +static_assert(offsetof(corehost_initialize_request_t, version) == 0 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(corehost_initialize_request_t, config_keys) == 1 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(corehost_initialize_request_t, config_values) == 3 * sizeof(size_t), "Struct offset breaks backwards compatibility"); + struct corehost_context_contract { size_t version; @@ -55,5 +54,13 @@ struct corehost_context_contract coreclr_delegate_type type, /*out*/ void** delegate); }; +static_assert(offsetof(corehost_context_contract, version) == 0 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(corehost_context_contract, get_property_value) == 1 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(corehost_context_contract, set_property_value) == 2 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(corehost_context_contract, get_properties) == 3 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(corehost_context_contract, load_runtime) == 4 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(corehost_context_contract, run_app) == 5 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(corehost_context_contract, get_runtime_delegate) == 6 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +#pragma pack(pop) #endif // __COREHOST_CONTEXT_CONTRACT_H__ \ No newline at end of file diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index b3d18970c5..eaf27149b0 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -42,25 +42,29 @@ namespace // Tracks the active host context. This is the context that was used to load and initialize hostpolicy and coreclr. // It will only be set once both hostpolicy and coreclr are loaded and initialized. Once set, it should not be changed. + // This will remain set even if the context is closed through hostfxr_close. Since the context represents the active + // CoreCLR runtime and the active runtime cannot be unloaded, the active context is never unset. std::unique_ptr g_active_host_context; - // Tracks whether the host context is initializing (from creation of the first context to loading the runtime). + // Tracks whether the first host context is initializing (from creation of the first context to loading the runtime). // Initialization of other contexts should block if the first context is initializing (i.e. this is true). // The condition variable is used to block on and signal changes to this state. std::atomic g_context_initializing(false); - std::condition_variable g_context_cv; + std::condition_variable g_context_initializing_cv; void handle_initialize_failure_or_abort(const hostpolicy_contract_t *hostpolicy_contract = nullptr) { { std::lock_guard lock{ g_context_lock }; + assert(g_context_initializing.load()); + assert(g_active_host_context == nullptr); g_context_initializing.store(false); } if (hostpolicy_contract != nullptr && hostpolicy_contract->unload != nullptr) hostpolicy_contract->unload(); - g_context_cv.notify_all(); + g_context_initializing_cv.notify_all(); } } @@ -97,7 +101,7 @@ static int execute_app( { { std::unique_lock lock{ g_context_lock }; - g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); + g_context_initializing_cv.wait(lock, [] { return !g_context_initializing.load(); }); if (g_active_host_context != nullptr) { @@ -108,11 +112,11 @@ static int execute_app( g_context_initializing.store(true); } - pal::dll_t corehost; + pal::dll_t hostpolicy_dll; hostpolicy_contract_t hostpolicy_contract{}; corehost_main_fn host_main = nullptr; - int code = load_hostpolicy(impl_dll_dir, &corehost, hostpolicy_contract, "corehost_main", &host_main); + int code = load_hostpolicy(impl_dll_dir, &hostpolicy_dll, hostpolicy_contract, "corehost_main", &host_main); if (code != StatusCode::Success) { handle_initialize_failure_or_abort(); @@ -130,7 +134,7 @@ static int execute_app( g_context_initializing.store(false); } - g_context_cv.notify_all(); + g_context_initializing_cv.notify_all(); { propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer); @@ -862,14 +866,14 @@ namespace } int fx_muxer_t::initialize_for_app( - const host_startup_info_t& host_info, + const host_startup_info_t &host_info, int argc, const pal::char_t* argv[], - void** host_context_handle) + hostfxr_handle *host_context_handle) { { std::unique_lock lock{ g_context_lock }; - g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); + g_context_initializing_cv.wait(lock, [] { return !g_context_initializing.load(); }); if (g_active_host_context != nullptr) { @@ -916,15 +920,15 @@ int fx_muxer_t::initialize_for_app( } int fx_muxer_t::initialize_for_runtime_config( - const host_startup_info_t& host_info, - const pal::char_t * runtime_config_path, - void** host_context_handle) + const host_startup_info_t &host_info, + const pal::char_t *runtime_config_path, + hostfxr_handle *host_context_handle) { int32_t initialization_options = intialization_options_t::none; const host_context_t *existing_context; { std::unique_lock lock{ g_context_lock }; - g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); + g_context_initializing_cv.wait(lock, [] { return !g_context_initializing.load(); }); existing_context = g_active_host_context.get(); if (existing_context == nullptr) @@ -1004,7 +1008,7 @@ namespace g_context_initializing.store(false); } - g_context_cv.notify_all(); + g_context_initializing_cv.notify_all(); return rc; } } diff --git a/src/corehost/cli/fxr/fx_muxer.h b/src/corehost/cli/fxr/fx_muxer.h index bce6c38230..381a616f2b 100644 --- a/src/corehost/cli/fxr/fx_muxer.h +++ b/src/corehost/cli/fxr/fx_muxer.h @@ -30,11 +30,11 @@ class fx_muxer_t const host_startup_info_t& host_info, int argc, const pal::char_t* argv[], - void** host_context_handle); + hostfxr_handle *host_context_handle); static int initialize_for_runtime_config( const host_startup_info_t& host_info, const pal::char_t * runtime_config_path, - void** host_context_handle); + hostfxr_handle *host_context_handle); static int run_app(host_context_t *context); static int get_runtime_delegate( host_context_t *context, diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index 9e1e47bd57..a42ed43b23 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -560,7 +560,7 @@ SHARED_API int32_t __cdecl hostfxr_initialize_for_runtime_config( // Handle to the initialized host context // // Return value: -// The error code result. +// If the app was successfully run, the exit code of the application. Otherwise, the error code result. // // The host_context_handle must have been initialized using hostfxr_initialize_for_app. // @@ -620,7 +620,9 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_delegate( if (delegate == nullptr) return StatusCode::InvalidArgFailure; - host_context_t *context = host_context_t::from_handle(host_context_handle); + *delegate = nullptr; + + host_context_t *context = host_context_t::from_handle(host_context_handle); if (context == nullptr) return StatusCode::InvalidArgFailure; @@ -688,7 +690,7 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value( return StatusCode::HostPropertyNotFound; *value = (*iter).second.c_str(); - return S_OK; + return StatusCode::Success; } assert(context->type == host_context_type::initialized || context->type == host_context_type::active); @@ -725,7 +727,7 @@ SHARED_API int32_t __cdecl hostfxr_set_runtime_property_value( if (name == nullptr) return StatusCode::InvalidArgFailure; - host_context_t *context = host_context_t::from_handle(host_context_handle); + host_context_t *context = host_context_t::from_handle(host_context_handle); if (context == nullptr) return StatusCode::InvalidArgFailure; @@ -800,11 +802,10 @@ SHARED_API int32_t __cdecl hostfxr_get_runtime_properties( { const std::unordered_map &properties = context->config_properties; size_t actualCount = properties.size(); - if (*count < actualCount || keys == nullptr || values == nullptr) - { - *count = actualCount; + size_t input_count = *count; + *count = actualCount; + if (input_count < actualCount || keys == nullptr || values == nullptr) return StatusCode::HostApiBufferTooSmall; - } int i = 0; for (const auto& kv : properties) diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.cpp b/src/corehost/cli/fxr/hostpolicy_resolver.cpp index 2912cd6c9c..a96c00dcb3 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.cpp +++ b/src/corehost/cli/fxr/hostpolicy_resolver.cpp @@ -16,6 +16,7 @@ namespace std::mutex g_hostpolicy_lock; pal::dll_t g_hostpolicy; hostpolicy_contract_t g_hostpolicy_contract; + pal::string_t g_hostpolicy_dir; /** * Resolve the hostpolicy version from deps. @@ -189,7 +190,7 @@ namespace int hostpolicy_resolver::load( const pal::string_t& lib_dir, - pal::dll_t* h_host, + pal::dll_t* dll, hostpolicy_contract_t &hostpolicy_contract) { std::lock_guard lock{ g_hostpolicy_lock }; @@ -221,10 +222,17 @@ int hostpolicy_resolver::load( // introduced in 3.0, so 2.0 hostpolicy would not have the exports. In this case, we will // not propagate the error writer and errors will still be reported to stderr. Callers are // responsible for checking that the function pointers are not null before using them. + + g_hostpolicy_dir = lib_dir; + } + else + { + if (!pal::are_paths_equal_with_normalized_casing(g_hostpolicy_dir, lib_dir)) + trace::warning(_X("The library %s was already loaded from [%s]. Reusing the existing library for the request to load from [%s]"), LIBHOSTPOLICY_NAME, g_hostpolicy_dir.c_str(), lib_dir.c_str()); } // Return global values - *h_host = g_hostpolicy; + *dll = g_hostpolicy; hostpolicy_contract = g_hostpolicy_contract; return StatusCode::Success; diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.h b/src/corehost/cli/fxr/hostpolicy_resolver.h index 74383e298c..e290ad5fa9 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.h +++ b/src/corehost/cli/fxr/hostpolicy_resolver.h @@ -31,7 +31,7 @@ namespace hostpolicy_resolver { int load( const pal::string_t& lib_dir, - pal::dll_t* h_host, + pal::dll_t* dll, hostpolicy_contract_t &hostpolicy_contract); bool try_get_dir( host_mode_t mode, From bf32916ce94faf0b10839adfd515dfdc39e2714f Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 30 Apr 2019 19:23:17 -0700 Subject: [PATCH 22/24] Fix test output messages --- .../cli/test/mockcoreclr/mockcoreclr.cpp | 21 +++- .../cli/test/nativehost/host_context_test.cpp | 108 +++++++++--------- .../cli/test/nativehost/host_context_test.h | 12 +- .../cli/test/nativehost/nativehost.cpp | 10 +- 4 files changed, 88 insertions(+), 63 deletions(-) diff --git a/src/corehost/cli/test/mockcoreclr/mockcoreclr.cpp b/src/corehost/cli/test/mockcoreclr/mockcoreclr.cpp index 58635ec1cd..dc8b56785c 100644 --- a/src/corehost/cli/test/mockcoreclr/mockcoreclr.cpp +++ b/src/corehost/cli/test/mockcoreclr/mockcoreclr.cpp @@ -8,9 +8,24 @@ #include #include "trace.h" -#define MockLog(string) std::cout << "mock " << string << std::endl; -#define MockLogArg(arg) std::cout << "mock " << #arg << ":" << arg << std::endl; -#define MockLogEntry(dict, key, value) std::cout << "mock " << dict << "[" << key << "] = " << value << std::endl; +#define MockLog(string)\ +{\ + std::stringstream ss;\ + ss << "mock " << string << std::endl;\ + std::cout << ss.str();\ +} +#define MockLogArg(arg)\ +{\ + std::stringstream ss;\ + ss << "mock " << #arg << ":" << arg << std::endl;\ + std::cout << ss.str();\ +} +#define MockLogEntry(dict, key, value)\ +{\ + std::stringstream ss;\ + ss << "mock " << dict << "[" << key << "] = " << value << std::endl;\ + std::cout << ss.str();\ +} SHARED_API pal::hresult_t STDMETHODCALLTYPE coreclr_initialize( const char* exePath, diff --git a/src/corehost/cli/test/nativehost/host_context_test.cpp b/src/corehost/cli/test/nativehost/host_context_test.cpp index 36dd4c2d9b..093eac73c9 100644 --- a/src/corehost/cli/test/nativehost/host_context_test.cpp +++ b/src/corehost/cli/test/nativehost/host_context_test.cpp @@ -11,16 +11,9 @@ namespace { - const char *app_log_prefix = "[APP] "; - const char *config_log_prefix = "[CONFIG] "; - const char *secondary_log_prefix = "[SECONDARY] "; - - std::vector to_str(const pal::string_t &value) - { - std::vector vect; - pal::pal_utf8string(value, &vect); - return vect; - } + const pal::char_t *app_log_prefix = _X("[APP] "); + const pal::char_t *config_log_prefix = _X("[CONFIG] "); + const pal::char_t *secondary_log_prefix = _X("[SECONDARY] "); class hostfxr_exports { @@ -82,7 +75,8 @@ namespace hostfxr_handle handle, int property_count, const pal::char_t *property_keys[], - const char *log_prefix) + const pal::char_t *log_prefix, + pal::stringstream_t &test_output) { for (int i = 0; i < property_count; ++i) { @@ -91,13 +85,13 @@ namespace int rc = hostfxr.get_prop_value(handle, key, &value); if (rc == StatusCode::Success) { - std::cout << log_prefix << "hostfxr_get_runtime_property_value succeeded for property: " - << to_str(key).data() << "=" << to_str(value).data() << std::endl; + test_output << log_prefix << _X("hostfxr_get_runtime_property_value succeeded for property: ") + << key << _X("=") << value << std::endl; } else { - std::cout << log_prefix << "hostfxr_get_runtime_property_value failed for property: " << to_str(key).data() - << " - " << std::hex << std::showbase << rc << std::endl; + test_output << log_prefix << _X("hostfxr_get_runtime_property_value failed for property: ") << key + << _X(" - ") << std::hex << std::showbase << rc << std::endl; } } } @@ -108,7 +102,8 @@ namespace int property_count, const pal::char_t *property_keys[], bool remove, - const char *log_prefix) + const pal::char_t *log_prefix, + pal::stringstream_t &test_output) { for (int i = 0; i < property_count; ++i) { @@ -117,12 +112,12 @@ namespace int rc = hostfxr.set_prop_value(handle, key, value); if (rc == StatusCode::Success) { - std::cout << log_prefix << "hostfxr_set_runtime_property_value succeeded for property: " << to_str(key).data() << std::endl; + test_output << log_prefix << _X("hostfxr_set_runtime_property_value succeeded for property: ") << key << std::endl; } else { - std::cout << log_prefix << "hostfxr_set_runtime_property_value failed for property: " << to_str(key).data() - << " - " << std::hex << std::showbase << rc << std::endl; + test_output << log_prefix << _X("hostfxr_set_runtime_property_value failed for property: ") << key + << _X(" - ") << std::hex << std::showbase << rc << std::endl; } } } @@ -130,7 +125,8 @@ namespace void get_properties( const hostfxr_exports &hostfxr, hostfxr_handle handle, - const char *log_prefix) + const pal::char_t *log_prefix, + pal::stringstream_t &test_output) { size_t count = 0; std::vector keys; @@ -145,16 +141,16 @@ namespace if (rc != StatusCode::Success) { - std::cout << log_prefix << "hostfxr_get_runtime_properties failed - " + test_output << log_prefix << _X("hostfxr_get_runtime_properties failed - ") << std::hex << std::showbase << rc << std::endl; return; } - std::cout << log_prefix << "hostfxr_get_runtime_properties succeeded." << std::endl; + test_output << log_prefix << _X("hostfxr_get_runtime_properties succeeded.") << std::endl; for (size_t i = 0; i < keys.size(); ++i) { - std::cout << log_prefix << "hostfxr_get_runtime_properties: " - << to_str(keys[i]).data() << "=" << to_str(values[i]).data() << std::endl; + test_output << log_prefix << _X("hostfxr_get_runtime_properties: ") + << keys[i] << _X("=") << values[i] << std::endl; } } @@ -164,27 +160,28 @@ namespace hostfxr_handle handle, int key_count, const pal::char_t *keys[], - const char *log_prefix) + const pal::char_t *log_prefix, + pal::stringstream_t &test_output) { switch (scenario) { case host_context_test::check_properties::get: - get_property_value(hostfxr, handle, key_count, keys, log_prefix); + get_property_value(hostfxr, handle, key_count, keys, log_prefix, test_output); break; case host_context_test::check_properties::set: - set_property_value(hostfxr, handle, key_count, keys, false /*remove*/, log_prefix); + set_property_value(hostfxr, handle, key_count, keys, false /*remove*/, log_prefix, test_output); break; case host_context_test::check_properties::remove: - set_property_value(hostfxr, handle, key_count, keys, true /*remove*/, log_prefix); + set_property_value(hostfxr, handle, key_count, keys, true /*remove*/, log_prefix, test_output); break; case host_context_test::check_properties::get_all: - get_properties(hostfxr, handle, log_prefix); + get_properties(hostfxr, handle, log_prefix, test_output); break; case host_context_test::check_properties::get_active: - get_property_value(hostfxr, nullptr, key_count, keys, log_prefix); + get_property_value(hostfxr, nullptr, key_count, keys, log_prefix, test_output); break; case host_context_test::check_properties::get_all_active: - get_properties(hostfxr, nullptr, log_prefix); + get_properties(hostfxr, nullptr, log_prefix, test_output); break; case host_context_test::check_properties::none: default: @@ -198,26 +195,27 @@ namespace const pal::char_t *config_path, int argc, const pal::char_t *argv[], - const char *log_prefix) + const pal::char_t *log_prefix, + pal::stringstream_t &test_output) { hostfxr_handle handle; int rc = hostfxr.init_config(config_path, nullptr, &handle); if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized) { - std::cout << log_prefix << "hostfxr_initialize_for_runtime_config failed: " << std::hex << std::showbase << rc << std::endl; + test_output << log_prefix << _X("hostfxr_initialize_for_runtime_config failed: ") << std::hex << std::showbase << rc << std::endl; return false; } - inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, log_prefix); + inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, log_prefix, test_output); void *delegate; rc = hostfxr.get_delegate(handle, hostfxr_delegate_type::com_activation, &delegate); if (rc != StatusCode::Success) - std::cout << log_prefix << "hostfxr_get_runtime_delegate failed: " << std::hex << std::showbase << rc << std::endl; + test_output << log_prefix << _X("hostfxr_get_runtime_delegate failed: ") << std::hex << std::showbase << rc << std::endl; int rcClose = hostfxr.close(handle); if (rcClose != StatusCode::Success) - std::cout << log_prefix << "hostfxr_close failed: " << std::hex << std::showbase << rc << std::endl; + test_output << log_prefix << _X("hostfxr_close failed: ") << std::hex << std::showbase << rc << std::endl; return rc == StatusCode::Success && rcClose == StatusCode::Success; } @@ -258,7 +256,8 @@ bool host_context_test::app( const pal::string_t &hostfxr_path, const pal::char_t *app_path, int argc, - const pal::char_t *argv[]) + const pal::char_t *argv[], + pal::stringstream_t &test_output) { hostfxr_exports hostfxr { hostfxr_path }; @@ -266,19 +265,19 @@ bool host_context_test::app( int rc = hostfxr.init_app(argc, argv, app_path, nullptr, &handle); if (rc != StatusCode::Success) { - std::cout << "hostfxr_initialize_for_app failed: " << std::hex << std::showbase << rc << std::endl; + test_output << _X("hostfxr_initialize_for_app failed: ") << std::hex << std::showbase << rc << std::endl; return false; } - inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, app_log_prefix); + inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, app_log_prefix, test_output); rc = hostfxr.run_app(handle); if (rc != StatusCode::Success) - std::cout << "hostfxr_run_app failed: " << std::hex << std::showbase << rc << std::endl; + test_output << _X("hostfxr_run_app failed: ") << std::hex << std::showbase << rc << std::endl; int rcClose = hostfxr.close(handle); if (rcClose != StatusCode::Success) - std::cout << "hostfxr_close failed: " << std::hex << std::showbase << rc << std::endl; + test_output << _X("hostfxr_close failed: ") << std::hex << std::showbase << rc << std::endl; return rc == StatusCode::Success && rcClose == StatusCode::Success; } @@ -288,11 +287,12 @@ bool host_context_test::config( const pal::string_t &hostfxr_path, const pal::char_t *config_path, int argc, - const pal::char_t *argv[]) + const pal::char_t *argv[], + pal::stringstream_t &test_output) { hostfxr_exports hostfxr { hostfxr_path }; - return config_test(hostfxr, check_properties, config_path, argc, argv, config_log_prefix); + return config_test(hostfxr, check_properties, config_path, argc, argv, config_log_prefix, test_output); } bool host_context_test::config_multiple( @@ -301,14 +301,15 @@ bool host_context_test::config_multiple( const pal::char_t *config_path, const pal::char_t *secondary_config_path, int argc, - const pal::char_t *argv[]) + const pal::char_t *argv[], + pal::stringstream_t &test_output) { hostfxr_exports hostfxr { hostfxr_path }; - if (!config_test(hostfxr, check_properties, config_path, argc, argv, config_log_prefix)) + if (!config_test(hostfxr, check_properties, config_path, argc, argv, config_log_prefix, test_output)) return false; - return config_test(hostfxr, check_properties, secondary_config_path, argc, argv, secondary_log_prefix); + return config_test(hostfxr, check_properties, secondary_config_path, argc, argv, secondary_log_prefix, test_output); } namespace @@ -347,7 +348,8 @@ bool host_context_test::mixed( const pal::char_t *app_path, const pal::char_t *config_path, int argc, - const pal::char_t *argv[]) + const pal::char_t *argv[], + pal::stringstream_t &test_output) { hostfxr_exports hostfxr { hostfxr_path }; @@ -355,27 +357,29 @@ bool host_context_test::mixed( int rc = hostfxr.init_app(argc, argv, app_path, nullptr, &handle); if (rc != StatusCode::Success) { - std::cout << "hostfxr_initialize_for_app failed: " << std::hex << std::showbase << rc << std::endl; + test_output << _X("hostfxr_initialize_for_app failed: ") << std::hex << std::showbase << rc << std::endl; return false; } - inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, app_log_prefix); + inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, app_log_prefix, test_output); block_mock_execute_assembly block_mock; + pal::stringstream_t run_app_output; auto run_app = [&]{ int rc = hostfxr.run_app(handle); if (rc != StatusCode::Success) - std::cout << "hostfxr_run_app failed: " << std::hex << std::showbase << rc << std::endl; + run_app_output << _X("hostfxr_run_app failed: ") << std::hex << std::showbase << rc << std::endl; int rcClose = hostfxr.close(handle); if (rcClose != StatusCode::Success) - std::cout << "hostfxr_close failed: " << std::hex << std::showbase << rc << std::endl; + run_app_output << _X("hostfxr_close failed: ") << std::hex << std::showbase << rc << std::endl; }; std::thread app_start = std::thread(run_app); - bool success = config_test(hostfxr, check_properties, config_path, argc, argv, secondary_log_prefix); + bool success = config_test(hostfxr, check_properties, config_path, argc, argv, secondary_log_prefix, test_output); block_mock.unblock(); app_start.join(); + test_output << run_app_output.str(); return success; } \ No newline at end of file diff --git a/src/corehost/cli/test/nativehost/host_context_test.h b/src/corehost/cli/test/nativehost/host_context_test.h index 4f61e11f21..070cb57ff2 100644 --- a/src/corehost/cli/test/nativehost/host_context_test.h +++ b/src/corehost/cli/test/nativehost/host_context_test.h @@ -27,25 +27,29 @@ namespace host_context_test const pal::string_t &hostfxr_path, const pal::char_t *app_path, int argc, - const pal::char_t *argv[]); + const pal::char_t *argv[], + pal::stringstream_t &test_output); bool config( check_properties scenario, const pal::string_t &hostfxr_path, const pal::char_t *config_path, int argc, - const pal::char_t *argv[]); + const pal::char_t *argv[], + pal::stringstream_t &test_output); bool config_multiple( check_properties scenario, const pal::string_t &hostfxr_path, const pal::char_t *config_path, const pal::char_t *secondary_config_path, int argc, - const pal::char_t *argv[]); + const pal::char_t *argv[], + pal::stringstream_t &test_output); bool mixed( check_properties scenario, const pal::string_t &hostfxr_path, const pal::char_t *app_path, const pal::char_t *config_path, int argc, - const pal::char_t *argv[]); + const pal::char_t *argv[], + pal::stringstream_t &test_output); } \ No newline at end of file diff --git a/src/corehost/cli/test/nativehost/nativehost.cpp b/src/corehost/cli/test/nativehost/nativehost.cpp index dc98d98bce..bb21fcbd8e 100644 --- a/src/corehost/cli/test/nativehost/nativehost.cpp +++ b/src/corehost/cli/test/nativehost/nativehost.cpp @@ -105,14 +105,15 @@ int main(const int argc, const pal::char_t *argv[]) auto check_properties = host_context_test::check_properties_from_string(check_properties_str); + pal::stringstream_t test_output; bool success = false; if (pal::strcmp(scenario, _X("app")) == 0) { - success = host_context_test::app(check_properties, hostfxr_path, app_or_config_path, remaining_argc, remaining_argv); + success = host_context_test::app(check_properties, hostfxr_path, app_or_config_path, remaining_argc, remaining_argv, test_output); } else if (pal::strcmp(scenario, _X("config")) == 0) { - success = host_context_test::config(check_properties, hostfxr_path, app_or_config_path, remaining_argc, remaining_argv); + success = host_context_test::config(check_properties, hostfxr_path, app_or_config_path, remaining_argc, remaining_argv, test_output); } else if (pal::strcmp(scenario, _X("config_multiple")) == 0) { @@ -127,7 +128,7 @@ int main(const int argc, const pal::char_t *argv[]) --remaining_argc; ++remaining_argv; - success = host_context_test::config_multiple(check_properties, hostfxr_path, app_or_config_path, secondary_config_path, remaining_argc, remaining_argv); + success = host_context_test::config_multiple(check_properties, hostfxr_path, app_or_config_path, secondary_config_path, remaining_argc, remaining_argv, test_output); } else if (pal::strcmp(scenario, _X("mixed")) == 0) { @@ -142,9 +143,10 @@ int main(const int argc, const pal::char_t *argv[]) --remaining_argc; ++remaining_argv; - success = host_context_test::mixed(check_properties, hostfxr_path, app_or_config_path, config_path, remaining_argc, remaining_argv); + success = host_context_test::mixed(check_properties, hostfxr_path, app_or_config_path, config_path, remaining_argc, remaining_argv, test_output); } + std::cout << tostr(test_output.str()).data() << std::endl; return success ? EXIT_SUCCESS : EXIT_FAILURE; } #if defined(_WIN32) From cbdf212386e94bee45dc475237ddf0fe7ee62edb Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 1 May 2019 10:14:51 -0700 Subject: [PATCH 23/24] Rename g_context_cv -> g_context_initializing_cv Rename corehost -> hostpolicy_dll --- src/corehost/cli/fxr/fx_muxer.cpp | 14 ++++++++++---- src/corehost/cli/fxr/hostpolicy_resolver.cpp | 1 + src/corehost/cli/hostpolicy/hostpolicy.cpp | 18 +++++++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index eaf27149b0..f056d0ae2b 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -123,6 +123,8 @@ static int execute_app( return code; } + // Leak hostpolicy - just as we do not unload coreclr, we do not unload hostpolicy + { // Track an empty 'active' context so that host context-based APIs can work properly when // the runtime is loaded through non-host context-based APIs. Once set, the context is never @@ -159,14 +161,16 @@ static int execute_host_command( int32_t buffer_size, int32_t* required_buffer_size) { - pal::dll_t corehost; + pal::dll_t hostpolicy_dll; hostpolicy_contract_t hostpolicy_contract{}; corehost_main_with_output_buffer_fn host_main = nullptr; - int code = load_hostpolicy(impl_dll_dir, &corehost, hostpolicy_contract, "corehost_main_with_output_buffer", &host_main); + int code = load_hostpolicy(impl_dll_dir, &hostpolicy_dll, hostpolicy_contract, "corehost_main_with_output_buffer", &host_main); if (code != StatusCode::Success) return code; + // Leak hostpolicy - just as we do not unload coreclr, we do not unload hostpolicy + { propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer); @@ -846,9 +850,9 @@ namespace int32_t initialize_options, /*out*/ std::unique_ptr &context) { - pal::dll_t corehost; + pal::dll_t hostpolicy_dll; hostpolicy_contract_t hostpolicy_contract{}; - int rc = hostpolicy_resolver::load(hostpolicy_dir, &corehost, hostpolicy_contract); + int rc = hostpolicy_resolver::load(hostpolicy_dir, &hostpolicy_dll, hostpolicy_contract); if (rc != StatusCode::Success) { trace::error(_X("An error occurred while loading required library %s from [%s]"), LIBHOSTPOLICY_NAME, hostpolicy_dir.c_str()); @@ -858,6 +862,8 @@ namespace rc = host_context_t::create(hostpolicy_contract, init, initialize_options, context); } + // Leak hostpolicy - just as we do not unload coreclr, we do not unload hostpolicy + if (rc != StatusCode::Success) handle_initialize_failure_or_abort(&hostpolicy_contract); diff --git a/src/corehost/cli/fxr/hostpolicy_resolver.cpp b/src/corehost/cli/fxr/hostpolicy_resolver.cpp index a96c00dcb3..1feb3cbd5c 100644 --- a/src/corehost/cli/fxr/hostpolicy_resolver.cpp +++ b/src/corehost/cli/fxr/hostpolicy_resolver.cpp @@ -203,6 +203,7 @@ int hostpolicy_resolver::load( } // Load library + // We expect to leak hostpolicy - just as we do not unload coreclr, we do not unload hostpolicy if (!pal::load_library(&host_path, &g_hostpolicy)) { trace::info(_X("Load library of %s failed"), host_path.c_str()); diff --git a/src/corehost/cli/hostpolicy/hostpolicy.cpp b/src/corehost/cli/hostpolicy/hostpolicy.cpp index 56e9f4d9f7..42f272b548 100644 --- a/src/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy/hostpolicy.cpp @@ -34,7 +34,7 @@ namespace // Tracks the hostpolicy context. This is the one and only hostpolicy context. It represents the information // that hostpolicy will use or has already used to load and initialize coreclr. It will be set once a context - //is initialized and updated to hold coreclr once the runtime is loaded. + // is initialized and updated to hold coreclr once the runtime is loaded. std::unique_ptr g_context; // Tracks whether the hostpolicy context is initializing (from start of creation of the first context @@ -42,7 +42,7 @@ namespace // Attempts to get/create a context should block if the first context is initializing (i.e. this is true). // The condition variable is used to block on and signal changes to this state. std::atomic g_context_initializing(false); - std::condition_variable g_context_cv; + std::condition_variable g_context_initializing_cv; int create_coreclr() { @@ -91,7 +91,7 @@ namespace g_context_initializing.store(false); } - g_context_cv.notify_all(); + g_context_initializing_cv.notify_all(); return rc; } @@ -102,7 +102,7 @@ namespace { { std::unique_lock lock{ g_context_lock }; - g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); + g_context_initializing_cv.wait(lock, [] { return !g_context_initializing.load(); }); const hostpolicy_context_t *existing_context = g_context.get(); if (existing_context != nullptr) @@ -115,7 +115,7 @@ namespace g_context_initializing.store(true); } - g_context_cv.notify_all(); + g_context_initializing_cv.notify_all(); std::unique_ptr context_local(new hostpolicy_context_t()); int rc = context_local->initialize(hostpolicy_init, args, breadcrumbs_enabled); @@ -126,7 +126,7 @@ namespace g_context_initializing.store(false); } - g_context_cv.notify_all(); + g_context_initializing_cv.notify_all(); return rc; } @@ -586,7 +586,7 @@ SHARED_API int __cdecl corehost_initialize(const corehost_initialize_request_t * if (!already_initialized && !already_initializing) { trace::info(_X("Waiting for another request to initialize hostpolicy")); - g_context_cv.wait(lock, [&] { return g_context_initializing.load(); }); + g_context_initializing_cv.wait(lock, [&] { return g_context_initializing.load(); }); } } else @@ -616,7 +616,7 @@ SHARED_API int __cdecl corehost_initialize(const corehost_initialize_request_t * { // Wait for context initialization to complete std::unique_lock lock{ g_context_lock }; - g_context_cv.wait(lock, [] { return !g_context_initializing.load(); }); + g_context_initializing_cv.wait(lock, [] { return !g_context_initializing.load(); }); const hostpolicy_context_t *existing_context = g_context.get(); if (existing_context == nullptr || existing_context->coreclr == nullptr) @@ -662,7 +662,7 @@ SHARED_API int corehost_unload() g_context_initializing.store(false); } - g_context_cv.notify_all(); + g_context_initializing_cv.notify_all(); std::lock_guard init_lock{ g_init_lock }; g_init_done = false; From 47589895053907b75533d626948c3eda6fa818f1 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 2 May 2019 10:29:23 -0700 Subject: [PATCH 24/24] Block initialization for self-contained components --- src/corehost/cli/fxr/fx_muxer.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index f056d0ae2b..2428388c9b 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -792,18 +792,19 @@ namespace return rc; const runtime_config_t app_config = app->get_runtime_config(); - bool is_framework_dependent = app_config.get_is_framework_dependent(); - if (is_framework_dependent) + if (!app_config.get_is_framework_dependent()) { - rc = fx_resolver_t::resolve_frameworks_for_app(host_info, override_settings, app_config, fx_definitions); - if (rc != StatusCode::Success) - return rc; + trace::error(_X("Initialization for self-contained components is not supported")); + return StatusCode::InvalidConfigFile; } + rc = fx_resolver_t::resolve_frameworks_for_app(host_info, override_settings, app_config, fx_definitions); + if (rc != StatusCode::Success) + return rc; + const std::vector probe_realpaths = get_probe_realpaths(fx_definitions, std::vector() /* specified_probing_paths */); - trace::verbose(_X("Libhost loading occurring as a %s app as per config file [%s]"), - (is_framework_dependent ? _X("framework-dependent") : _X("self-contained")), app_config.get_path().c_str()); + trace::verbose(_X("Libhost loading occurring for a framework-dependent component per config file [%s]"), app_config.get_path().c_str()); const pal::string_t deps_file; if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, host_info.app_path, deps_file, probe_realpaths, &hostpolicy_dir))