From: pius.lee Date: Mon, 25 Jul 2016 08:28:32 +0000 (+0900) Subject: Add Launcher and Waiter for dotnet-launcher X-Git-Tag: submit/tizen/20161214.063015~33 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c5df376cb893c2d0205ecab37b0f381cb67300ce;p=platform%2Fcore%2Fdotnet%2Flauncher.git Add Launcher and Waiter for dotnet-launcher --- diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b711b0d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT("dotnet-launcher") + +INCLUDE(FindPkgConfig) +PKG_CHECK_MODULES(${PROJECT_NAME} REQUIRED + aul + dlog + ecore + bundle + dlog + launchpad + ) + +FOREACH(flag ${${PROJECT_NAME}_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Wl,-zdefs" ) +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden") +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fPIE") +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fdata-sections -ffunction-sections -Wl,--gc-sections") +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -D_FILE_OFFSET_BITS=64") + +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g") +SET(CMAKE_C_FLAGS_RELEASE "-O2") +SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed") + +SET(${PROJECT_NAME}_SOURCE_FILES + src/launcher.cc + src/waiter.cc +) +ADD_EXECUTABLE(${PROJECT_NAME} ${${PROJECT_NAME}_SOURCE_FILES}) + +TARGET_LINK_LIBRARIES(${PROJECT_NAME} aul) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${${PROJECT_NAME}_LDFLAGS} "-pie") + +SET_TARGET_PROPERTIES(${PROJECT_NAME} + PROPERTIES SKIP_BUILD_RPATH TRUE + ) # remove rpath option that is automatically generated by cmake. + +CONFIGURE_FILE(${PACKAGE_NAME}.xml.in ${PACKAGE_NAME}.xml) + +INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin) +INSTALL(FILES ${PACKAGE_NAME}.xml DESTINATION ${MANIFESTDIR}) +INSTALL(FILES packaging/dotnet.loader DESTINATION ${LOADERDIR}) + diff --git a/packaging/dotnet-launcher.ini b/packaging/dotnet-launcher.ini new file mode 100644 index 0000000..700eb67 --- /dev/null +++ b/packaging/dotnet-launcher.ini @@ -0,0 +1,5 @@ +[dotnet] +libcoreclr = libcoreclr.so +coreclr_dir = /usr/share/dotnet +tpa_dirs = /usr/share/dotnet:/usr/share/assemblies +native_so_search_dirs = /usr/share/dotnet:/usr/share/assemblies diff --git a/packaging/dotnet-launcher.service b/packaging/dotnet-launcher.service new file mode 100644 index 0000000..be05477 --- /dev/null +++ b/packaging/dotnet-launcher.service @@ -0,0 +1,16 @@ +[Unit] +Description=Start dotnet launchpad daemon +After=dbus.service buxton2.service ac.service +Requires=csf-content-engine.service + +[Service] +User=owner +Group=users +EnvironmentFile=/run/tizen-mobile-ui +ExecStart=/usr/bin/launch_app dotnet-launcher + +TimeoutStopSec=3s +RestartSec=5 + +[Install] +WantedBy=starter.target diff --git a/packaging/dotnet-launcher.spec b/packaging/dotnet-launcher.spec new file mode 100644 index 0000000..c9dca6e --- /dev/null +++ b/packaging/dotnet-launcher.spec @@ -0,0 +1,58 @@ +Name: dotnet-launcher +Summary: Launchpad plugin for dotnet apps +Version: 0.1.0 +Release: 1 +Group: Application Framework/Daemons +License: Apache License, Version 2.0 +Source0: %{name}-%{version}.tar.gz + +BuildRequires: cmake +BuildRequires: pkgconfig(aul) +BuildRequires: pkgconfig(bundle) +BuildRequires: pkgconfig(dlog) +BuildRequires: pkgconfig(ecore) +BuildRequires: pkgconfig(launchpad) +BuildRequires: aul-devel +Requires: aul + +Requires(post): /sbin/ldconfig +Requires(post): /usr/bin/systemctl +Requires(postun): /sbin/ldconfig +Requires(postun): /usr/bin/systemctl +Requires(preun): /usr/bin/systemctl + +%define _sys_bin %{TZ_SYS_BIN} +%define _sys_sbin %{TZ_SYS_SBIN} +%define _sys_share %{TZ_SYS_RO_SHARE} +%define _manifestdir %{TZ_SYS_RO_PACKAGES} +%define _loaderdir %{TZ_SYS_RO_SHARE}/aul + +%description +Launchpad for dotnet apps + +%prep +%setup -q + +%build +cmake \ + -DCMAKE_INSTALL_PREFIX=%{_prefix} \ + -DMANIFESTDIR=%{_manifestdir} \ + -DPACKAGE_NAME=%{name} \ + -DBINDIR=%{_bindir} \ + -DLOADERDIR=%{_loaderdir} \ + -DVERSION=%{version} + +make %{?jobs:-j%jobs} + +%install +rm -rf %{buildroot} +%make_install + +%post + +%files +%manifest dotnet-launchpad.manifest +%{_manifestdir}/%{name}.xml +%{_loaderdir}/dotnet.loader +%attr(0755,root,root) %{_bindir}/dotnet-loader +%caps(cap_mac_admin,cap_mac_override,cap_setgid=ei) %{_bindir}/dotnet-loader diff --git a/packaging/dotnet.loader b/packaging/dotnet.loader new file mode 100644 index 0000000..43e2d21 --- /dev/null +++ b/packaging/dotnet.loader @@ -0,0 +1,6 @@ +[LOADER] +NAME dotnet-loader +EXE /usr/bin/dotnet-loader +APP_TYPE dotnet +DETECTION_METHOD TIMEOUT|DEMAND +TIMEOUT 5000 diff --git a/src/environment.h b/src/environment.h new file mode 100644 index 0000000..6e4e57b --- /dev/null +++ b/src/environment.h @@ -0,0 +1,12 @@ +#ifndef __ENVIRONMENT_H__ + +#ifndef TIZEN_CORECLR_PATH +#define TIZEN_CORECLR_PATH "/usr/share/coreclr/libcoreclr.so" +#endif + +#ifndef TIZEN_NETCORE_TPA_PATH +#define TIZEN_NETCORE_TPA_PATH "/usr/share/assemblies" +#endif + + +#endif // __ENVIRONMENT_H__ diff --git a/src/launcher.cc b/src/launcher.cc new file mode 100644 index 0000000..631f7e5 --- /dev/null +++ b/src/launcher.cc @@ -0,0 +1,287 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "environment.h" +#include "waiter.h" +#include "log.h" + +#include "tini.hpp" + +#ifndef LAUNCHER_CONFIG +#define LAUNCHER_CONFIG "/usr/share/dotnet/dotnet-launcher.ini" +#endif + +static std::string AbsolutePath(const std::string& path) +{ + std::string absPath; + + char realPath[PATH_MAX]; + if (realpath(path, realPath) != nullptr && realPath[0] != '\0') + { + absPath.assign(realPath); + } + + return absPath; +} + +static std::string Basename(const std::string& path) +{ + auto pos = path.find_last_of('/'); + if (pos != std::string::npos) + { + return path.substr(0, pos); + } + return path; +} + +static std::string AssembliesInDirectory(const char *directory) +{ + const char * const nativeImageExtension = ".ni"; + const char * const tpaExtensions[] = {".dll", ".exe"}; + int niExtensionSize = strlen(nativeImageExtension); + int directoryPathSize = strlen(directory); + + std::string tpaList; + + DIR* dir = opendir(directory); + + if (dir == nullptr) return std::string(); + + std::map, bool> addedAssemblies; + + struct dirent *entry; + while ((entry = readdir(dir)) != nullptr) + { + switch (entry->d_type) + { + case DT_REG: break; + case DT_LNK: + case DT_UNKNOWN: + { + std::string fullname; + fullname.append(directory); + fullname.append("/"); + fullname.append(entry->d_name); + + struct stat sb; + if (stat(fullname.c_str(), &sb) == -1) + continue; + + if (!S_ISREG(sb.st_mode)) + continue; + } + break; + default: + continue; + } + + // Check the extension. + std::string filename(entry->d_name); + int extPos = filename.find_last_of('.'); + if (extPos <= 0) continue; + for (auto ext : tpaExtensions) + { + int extLength = strlen(ext); + if (filename.compare(extPos-1, extLength, ext) != 0) + continue; + } + + + std::string filenameWithoutExt; + bool isNativeImage = extPos > niExtensionSize ? + filename.compare(extPos-niExtensionSize, niExtensionSize, nativeImageExtension) == 0 : false; + if (isNativeImage) + { + filenameWithoutExt = filename.substr(0, extPos-niExtensionSize); + } + else + { + filenameWithoutExt = filename.substr(0, extPos); + } + std::string ext = filename.substr(extPos); + + std::pair key(filenameWithoutExt, ext); + std::cout << " " << filenameWithoutExt << ext << " " << isNativeImage << std::endl; + if (addedAssemblies.count(key)) + { + isNativeImage = isNativeImage || addedAssemblies[key]; + } + addedAssemblies[key] = isNativeImage; + } + + for (auto pair : addedAssemblies) + { + tpaList.append(directory); + if (directory[directoryPathSize-1] != '/') + tpaList.append("/"); + tpaList.append(pair.first.first); + if (pair.second) + { + tpaList.append(nativeImageExtension); + } + tpaList.append(pair.first.second); + tpaList.append(":"); + } + + closedir(dir); + return tpaList; +} + +Launcher::Launcher() +{ +} + +Launcher::~Launcher() +{ +} + +void Launcher::Initialize() +{ + std::ifstream iniStream(LAUNCHER_CONFIG); + std::stringstream iniString; + iniString << iniStream.rdbuf(); + tini::ini launcherIni(iniString); + + auto &dotnet = ini["dotnet"]; + if (dotnet.find("libcoreclr") == dotnet.end()) + dotnet["libcoreclr"] = "libcoreclr.so"; + if (dotnet.find("coreclr_dir") == dotnet.end()) + dotnet["coreclr_dir"] = "/usr/share/dotnet"; + if (dotnet.find("tpa_dir") == dotnet.end()) + dotnet["tpa_dir"] = dotnet["coreclr_dir"] + ":" + "/usr/share/assemblies"; + if (dontet.find("native_so_search_dirs") == dotnet.end()) + dotnet["native_so_search_dirs"] = dotnet["tpa_dir"]; + + std::string libcoreclr = dotnet["coreclr_dir"] + "/" + dotnet["libcoreclr"]; + std::string tpaDirs = dotnet["tpa_dirs"]; + std::string nativeSoSearchDirs = dotnet["native_so_search_dirs"]; + + void* coreclrLib = dlopen(libcoreclr, RTLD_NOW | RTLD_LOCAL); + if (coreclrLib == nullptr) + { + char *err = dlerror(); + _ERR("dlopen failed to open libcoreclr.so with error %s", err); + goto CoreClrLibClose; + } + + initializeCoreCLR = (coreclr_initialize_ptr)dlsym(coreclrLib, "coreclr_initialize"); + executeAssembly = (coreclr_execute_assembly_ptr)dlsym(coreclrLib, "coreclr_execute_assembly"); + shutdownCoreCLR = (coreclr_shutdown_ptr)dlsym(coreclrLib, "coreclr_shutdown"); + + if (coreclr_initialize_ptr == nullptr) + { + _ERR("coreclr_initialize_ptr not found in the libcoreclr.so"); + } + else if (coreclr_execute_assembly_ptr == nullptr) + { + _ERR("coreclr_execute_assembly_ptr not found in the libcoreclr.so"); + } + else if (coreclr_shutdown_ptr == nullptr) + { + _ERR("coreclr_shutdown_ptr not found in the libcoreclr.so"); + } + else + { + std::istringstream f(tpaDirs); + std::string s; + while (std::getline(f, s, ':')) + { + TrustedPlatformAssemblies += AssembliesInDirectory(s); + } + NativeDllSearchDirectories = nativeSoSearchDirs; + AppDomainCompatSwitch = "UseLatestBehaviorWhenTFMNotSpecified"; + } + +CoreClrLibClose: + if (dlclose(coreclrLib) != 0) + { + _ERR("libcoreclr.so close failed"); + } +} + +void Launcher::Dispose() +{ +} + +void Launcher::Launch(const string& exe_path, int argc, char *argv[]) +{ + std::string bin_path = Basename(exe_path); + std::string app_home = Basename(app_path); + std::string lib_path = app_home + "/lib"; + std::string app_path = bin_path + ":" + lib_path + std::string app_ni_path = app_path; + std::string nativeDllSearchDirectories = NativeDllSearchDirectories + app_path; + + const char *propertyKeys[] = + { + "TRUSTED_PLATFORM_ASSEMBLIES", + "APP_PATHS", + "APP_NI_PATHS", + "NATIVE_DLL_SEARCH_DIRECTORIES", + "AppDomainCompatSwitch" + }; + + const char *propertyValues[] = + { + TrustedPlatformAssemblies.c_str(), + app_path.c_str(), + app_ni_path.c_str(), + nativeDllSearchDirectories.c_str(), + AppDomainCompatSwitch.c_str() + }; + + void* hostHandle; + unsigned int domainId; + + int st = initializeCoreCLR(exe_path, + "tizen_dotnet_launcher", + sizeof(propertyKeys) / sizeof(propertyKeys[0]), + propertyKeys, + propertyValues, + &hostHandle, + &domainId); + if (st < 0) + { + // initialize coreclr fail + exit(-1); + } + + unsigned int exitCode; + st = executeAssembly(hostHandle, domainId, argc, argv, exe_path, &exitCode); + if (st < 0) + { + // execute coreclr fail + exit(-1); + } + + st = shutdownCoreCLR(hostHandle, domainId); + if (st < 0) + { + // shutdown fail + exit(-1); + } +} + +int main(int argc, char *argv[]) +{ + std::unique_ptr launcher(new Launcher()); + auto on_prepare = [&launcher]() + { + launcher.Initialize(); + }; + auto on_requested = [&launcher]() + { + }; + auto on_executed = [&launcher](const std::string& path, int argc, char *argv[]) + { + launcher.launch(path, argc, argv); + } + std::unique_ptr waiter(new Waiter(on_prepare, on_requested, on_executed)); +} diff --git a/src/launcher.h b/src/launcher.h new file mode 100644 index 0000000..eb53100 --- /dev/null +++ b/src/launcher.h @@ -0,0 +1,51 @@ +#include +#include + +namespace dotnet { +namespace runtime { + +typedef int (*coreclr_initialize_ptr)( + const char* exePath, + const char* appDomainFriendlyName, + int propertyCount, + const char** propertyKeys, + const char** propertyValues, + void** hostHandle, + unsigned int* domainId); + +typedef int (*coreclr_execute_assembly_ptr)( + void* hostHandle, + unsigned int domainId, + int argc, + const char** argv, + const char* managedAssemblyPath, + unsigned int* exitCode); + +typedef int (*coreclr_shutdown_ptr)( + void* hostHandle, + unsigned int domainId); + +using std::string; +using std::vector; + +class Launcher +{ + public: + Launcher(); + ~Launcher(); + void Initialize(); + void Dispose(); + void Launch(const string& exe_path, int argc, char *argv[]); + + private: + coreclr_initialize_ptr initializeCoreCLR; + coreclr_execute_assembly_ptr executeAssembly; + coreclr_shutdown_ptr shutdownCoreCLR; + + string TrustedPlatformAssemblies; + string NativeDllSearchDirectories; + string AppDomainCompatSwitch; +}; + +} // namespace runtime +} // namespace dotnet diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..c5b6f9d --- /dev/null +++ b/src/log.h @@ -0,0 +1,23 @@ +#ifndef __LOG_H__ +#define __LOG_H__ + +#include + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "NETCORE_LAUNCHER" + +#ifndef _ERR +#define _ERR(fmt, args...) LOGE(fmt "\n", __func__, __LINE__, ##args) +#endif + +#ifndef _DBG +#define _DBG(fmt, args...) LOGD(fmt "\n", __func__, __LINE__, ##args) +#endif + +#ifndef _INFO +#define _INFO(fmt, args...) LOGI(fmt "\n", __func__, __LINE__, ##args) +#endif + +#endif /* __LOG_H__ */ diff --git a/src/tini.hpp b/src/tini.hpp new file mode 100644 index 0000000..fac3424 --- /dev/null +++ b/src/tini.hpp @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include +#include + +namespace tini +{ + +const std::string global = "___GLOBAL"; + +inline void ltrim(std::string& s) +{ + s.erase(s.begin(), std::find_if(s.begin(), s.end(), + std::not1(std::ptr_fun(std::isspace)))); +} + +inline void rtrim(std::string& s) +{ + s.erase(std::find_if(s.rbegin(), s.rend(), + std::not1(std::ptr_fun(std::isspace))).base(), s.end()); +} + +inline void trim(std::string& s) +{ + ltrim(s); + rtrim(s); +} + +inline int find_first_nonescaped(const std::string& str, char ch) +{ + if (!str.empty()) + { + if (str[0] == ch) return 0; + for (size_t i=1; i get_pair(const std::string& str) +{ + std::string key, value; + std::string::size_type eq_pos = str.find('='); + if (eq_pos == std::string::npos) + { + key = str; + } + else + { + key = str.substr(0, eq_pos); + value = str.substr(eq_pos+1); + trim(value); + } + trim(key); + return std::pair(key, value); +} + +class ini +{ + public: + using pairs = std::map; + ini() + { + } + ini(const std::string& str) + { + std::istringstream input(str); + init(input); + } + ini(std::istream &input) + { + init(input); + } + inline void init (std::istream &input) + { + std::string line; + std::string title(global); + while (std::getline(input, line)) + { + line = uncomment(line); + if (line.empty()) continue; + trim(line); + if (get_header(line, title) && groups.find(title) == groups.end()) + { + groups[title] = std::map(); + continue; + } + + auto pair = get_pair(line); + groups[title][pair.first] = pair.second; + } + } + std::string to_string() const + { + std::stringstream str_out; + auto gpair = groups.find(global); + if (gpair != groups.end()) + { + for (auto &pair : gpair->second) + { + str_out << pair.first << " = " << pair.second << std::endl; + } + } + for (auto &group : groups) + { + auto &title = group.first; + if (title == global) continue; + auto &pairs = group.second; + str_out << '[' << title << ']' << std::endl; + for (auto &pair : pairs) + { + str_out << pair.first << " = " << pair.second << std::endl; + } + } + + return str_out.str(); + } + + pairs& operator [](const std::string& group_name) + { + if (groups.find(group_name) == groups.end()) + { + groups[group_name] = pairs(); + } + return groups[group_name]; + } + + std::map groups; +}; + +std::ostream& operator << (std::ostream& os, const ini& rhs) +{ + os << rhs.to_string(); + return os; +} + +} // namespace tini diff --git a/src/waiter.cc b/src/waiter.cc new file mode 100644 index 0000000..0f622be --- /dev/null +++ b/src/waiter.cc @@ -0,0 +1,170 @@ +#include "waiter.h" + +#include +#include + +#include +#include +#include +#include + +namespace dotnet { +namespace runtime { + +struct Waiter::FdHandler; +{ + pollfd *info; + Receiver re; +}; + +static volatile bool Waiting_; +static std::vector Fdlist_; +static std::map Handlers_; +static Waiter::AppInfo AppInfo_; + +void Waiter::OnPrepare() +{ + // preload the libraries. +} + +void Waiter::OnLaunchRequested() +{ + // do some job on user id is still system +} + +void Waiter::OnWaiting() +{ + // Start the loop + Waiting_ = true; + + while (Waiting_) + { + if (poll(Fdlist_.data(), Fdlist_.size(), -1) < 0) + continue; + + for (auto &p : Fdlist_) + { + if ( (p.revents | POLLIN) != 0 ) + Handlers_[p.fd].receiver(); + } + } +} + +void Waiter::Stop() +{ + // Stop the loop + + Waiting_ = false; +} + + +void Waiter::RegisterFd(int fd, Receiver receiver) +{ + // register fd should be caught in event loop + + pollfd info; + info.fd = fd; + info.events = POLLIN; + info.revents = 0; + + FdHandler handler; + Fdlist_.push_back(info); + handler.info = &Fdlist_.back(); + handler.receiver = receiver; + + Handlers_[fd] = handler; +} + +void Waiter::DeregisterFd(int fd) +{ + // deregister fd should be caught in event loop + + pollfd *info = Handlers_[fd].info; + Fdlist_.erase(Fdlist_.begin() - (info - &Fdlist_.front())); + Handlers_.erase(fd); +} + + +Waiter::Waiter(Action prepare, Action requested, Executor executor) +{ + prepare_ = prepare; + requested_ = requested; + executor_ = executor; +} + +Waiter::~Waiter() +{ +} + +void Waiter::WaitToLaunching(int argc, char *argv[]) +{ + auto on_create = [](bundle *extra, int type, void *user_data) + { + Waiter* waiter = static_cast(user_data); + waiter->OnPrepare(); + }; + + auto on_launch = [](int argc, char **argv, const char *app_path, + const char *appid, const char *pkgid, + const char *pkg_type, void *user_data) + { + Waiter* waiter = static_cast(user_data); + + AppInfo info = { + AppPath : app_path, + AppId : appid, + PkgId : pkgid, + PkgType : pkg_type + }; + + waiter->OnLaunchRequested(info); + }; + + auto on_terminate = [](int argc, char **argv, void *user_data) + { + Waiter* waiter = static_cast(user_data); + waiter->executor_(argv[LOADER_ARG_PATH], argc, argv); + }; + + auto on_start_loop = [](void *user_data) + { + Waiter* waiter = static_cast(user_data); + waiter->OnWaiting(); + }; + + auto on_quit_loop = [](void *user_data) + { + Waiter* waiter = static_cast(user_data); + waiter->Stop(); + }; + + auto on_add_fd = [](void *user_data, int fd, loader_receiver_cb receiver) + { + Waiter* waiter = static_cast(user_data); + waiter->RegisterFd(fd, receiver); + }; + + auto on_remove_fd = [](void *user_data, int fd) + { + Waiter* waiter = static_cast(user_data); + waiter->DeregisterFd(fd); + }; + + loader_lifecycle_callback_s callbacks = { + .create = on_create, + .launch = on_launch, + .terminate = on_terminate + } + + loader_adapter_s adapter = { + .loop_begin = on_start_loop, + .loop_quit = on_quit_loop, + .add_fd = on_add_fd, + .remove_fd = on_remove_fd + }; + + return launchpad_loader_main(argc, argv, &callbacks, &adapter, this); +} + +} // namespace runtime +} // namespace dotnet diff --git a/src/waiter.h b/src/waiter.h new file mode 100644 index 0000000..4315d0c --- /dev/null +++ b/src/waiter.h @@ -0,0 +1,44 @@ +#include + +using std::string; + +namespace dotnet { +namespace runtime { + +class Waiter +{ + public: + struct AppInfo + { + string AppPath; + string AppId; + string PkgId; + string PkgType; + }; + using Action = std::function; + using Receiver = std::function; + using Executor = std::function; + + Waiter(Action prepare, Action requested, Executor executor); + ~Waiter(); + + int WaitToLaunching(int argc, char *argv[]); + void Stop(); + + void RegisterFd(int fd, Receiver receiver); + void DeregisterFd(int fd); + + protected: + void OnPrepare(); + void OnLaunchRequested(const AppInfo&); + void OnWaiting(); + + private: + + Action prepare_; + Action requested_; + Executor executor_; +}; + +} // namespace runtime +} // namespace dotnet