Skip to content

[browser] Support AppContext switches on runtime #97449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
maraf opened this issue Jan 24, 2024 · 9 comments · May be fixed by #115113
Open

[browser] Support AppContext switches on runtime #97449

maraf opened this issue Jan 24, 2024 · 9 comments · May be fixed by #115113
Assignees
Labels
arch-wasm WebAssembly architecture area-Build-mono os-browser Browser variant of arch-wasm Priority:1 Work that is critical for the release, but we could probably ship without
Milestone

Comments

@maraf
Copy link
Member

maraf commented Jan 24, 2024

AppContext switches defined in csproj are not available on runtime

<ItemGroup>
    ...
    <RuntimeHostConfigurationOption Include="System.Runtime.OptionalFeatureBehavior"
                                    Condition="'$(UnoptimizedUserFacingBehavior)' != ''"
                                    Value="$(UnoptimizedUserFacingBehavior)"
                                    Link="false" />
    ...
</ItemGroup>

On other platforms, they end up in runtime.config.json. We should put them into blazor.boot.json as that's the only config required (for both wasmbrowser and blazor experience)

@maraf maraf added arch-wasm WebAssembly architecture area-Build-mono os-browser Browser variant of arch-wasm labels Jan 24, 2024
@maraf maraf added this to the 9.0.0 milestone Jan 24, 2024
@ghost
Copy link

ghost commented Jan 24, 2024

Tagging subscribers to 'arch-wasm': @lewing
See info in area-owners.md if you want to be subscribed.

Issue Details

AppContext switches defined in csproj are not available on runtime

<ItemGroup>
    ...
    <RuntimeHostConfigurationOption Include="System.Runtime.OptionalFeatureBehavior"
                                    Condition="'$(UnoptimizedUserFacingBehavior)' != ''"
                                    Value="$(UnoptimizedUserFacingBehavior)"
                                    Link="false" />
    ...
</ItemGroup>

On other platforms, they end up in runtime.config.json. We should put them into blazor.boot.json as that's the only config required (for both wasmbrowser and blazor experience)

Author: maraf
Assignees: -
Labels:

arch-wasm, area-Build-mono, os-browser

Milestone: 9.0.0

@lewing
Copy link
Member

lewing commented Jan 24, 2024

AppContext switches defined in csproj are not available on runtime

<ItemGroup>
    ...
    <RuntimeHostConfigurationOption Include="System.Runtime.OptionalFeatureBehavior"
                                    Condition="'$(UnoptimizedUserFacingBehavior)' != ''"
                                    Value="$(UnoptimizedUserFacingBehavior)"
                                    Link="false" />
    ...
</ItemGroup>

On other platforms, they end up in runtime.config.json. We should put them into blazor.boot.json as that's the only config required (for both wasmbrowser and blazor experience)

That works for browser but we will have the same issue in wasi where there is no json parser.

@maraf
Copy link
Member Author

maraf commented Jan 25, 2024

That works for browser but we will have the same issue in wasi where there is no json parser.

That's true. We can use the binary format for WASI and we bundle it into wasm file even for browser, but I would like to avoid distributing it as a separate file for browser scenario

@maraf maraf self-assigned this Jan 25, 2024
@lambdageek
Copy link
Member

Mono has a task that converts rutnimeconfig.json files into a binary blob at build time so you don't need to do JSON parsing at execution time
https://github.com/dotnet/runtime/blob/main/src/tasks/MonoTargetsTasks/RuntimeConfigParser/RuntimeConfigParser.cs

the binary blob can be passed by the driver to monovm_runtimeconfig_initialize:

MONO_API_FUNCTION(int, monovm_runtimeconfig_initialize, (MonovmRuntimeConfigArguments *arg, MonovmRuntimeConfigArgumentsCleanup cleanup_fn, void *user_data))

typedef struct {
uint32_t kind; // 0 = Path of runtimeconfig.blob, 1 = pointer to image data, >= 2 undefined
union {
struct {
const char *path;
} name;
struct {
const char *data;
uint32_t data_len;
} data;
} runtimeconfig;
} MonovmRuntimeConfigArguments;
typedef void (*MonovmRuntimeConfigArgumentsCleanup) (MonovmRuntimeConfigArguments *args, void *user_data);

This is used by xamarin-android, for example:

https://github.com/xamarin/xamarin-android/blob/e67afc7d7ae848da6b33026b80973d30713b98ad/src/monodroid/jni/monodroid-glue.cc#L846-L848

@pavelsavara
Copy link
Member

pavelsavara commented Mar 11, 2025

I need this to work in order to implement #111680 and it's P1 goal

After discussion with @maraf I learned that

  • There is RuntimeConfigParser which translates the JSON to .bin file on multiple Mono platforms. In order to avoid managed JSON deserialization during app startup.
  • we are able to load runtimeconfig.bin from VFS and parse it, also in "demo" mode
  • but the file is only being produced with workload installed
  • and with now obsolete tooling _WasmGenerateRuntimeConfig which is only triggered for WasmGenerateAppBundle
  • Blazor prevents downloading runtimeconfig.bin anyway.
  • .bin doesn't have great MIME type configured in most places
  • I dislike using VFS for anything
  • Blazor is full of managed reflection based JSON deserialization anyway

private static void ConvertDictionaryToBlob(Dictionary<string, string> properties, BlobBuilder builder)
{
int count = properties.Count;
builder.WriteCompressedInteger(count);
foreach (var kvp in properties)
{
builder.WriteSerializedString(kvp.Key);
builder.WriteSerializedString(kvp.Value);
}
}

char *file_name = RUNTIMECONFIG_BIN_FILE;
int str_len = strlen (file_name) + 1; // +1 is for the "/"
char *file_path = (char *)malloc (sizeof (char) * (str_len +1)); // +1 is for the terminating null character
int num_char = snprintf (file_path, (str_len + 1), "/%s", file_name);
struct stat buffer;
assert (num_char > 0 && num_char == str_len);
if (stat (file_path, &buffer) == 0) {
MonovmRuntimeConfigArguments *arg = (MonovmRuntimeConfigArguments *)malloc (sizeof (MonovmRuntimeConfigArguments));
arg->kind = 0;
arg->runtimeconfig.name.path = file_path;
monovm_runtimeconfig_initialize (arg, cleanup_runtime_config, file_path);
} else {
free (file_path);
}

internal static unsafe void Setup(char** pNames, uint* pNameLengths, char** pValues, uint* pValueLengths, int count)
{
Debug.Assert(s_dataStore == null, "s_dataStore is not expected to be inited before Setup is called");
s_dataStore = new Dictionary<string, object?>(count);
for (int i = 0; i < count; i++)
{
s_dataStore.Add(new string(pNames[i], 0, (int)pNameLengths[i]), new string(pValues[i], 0, (int)pValueLengths[i]));
}
}

That gives us following implementation options
A) add any runtimeconfig.json as subset of dotnet.boot.json via GenerateWasmBootJson.
Only process the string->string dictionary, same as current AppContext.Setup
But do the JSON parsing in JavaScript for free.
Remove support for .bin file from wasm build
This will also work without workload installed.
This would NOT work for WASI

B) For consistency with other Mono platforms, fix runtimeconfig.bin
Lift RuntimeConfigParser into Microsoft.NET.Sdk.WebAssembly.Pack.Tasks, so that we could use it outside of workload.
Include it in VFS also for Blazor.
Have one extra file to download.
Deal with MIME type.

I prefer A) at this time

@am11
Copy link
Member

am11 commented Mar 26, 2025

FWIW, Native AOT passes RuntimeHostConfigurationOption to ilc during the publish time

<IlcArg Include="@(RuntimeHostConfigurationOption->'--runtimeknob:%(Identity)=%(Value)')" />
which materializes the key/value list as g_compilerEmbeddedKnobsBlob in the binary. At run-time, it populates a dictionary with those during init:
uint count = RuntimeImports.RhGetKnobValues(out byte** keys, out byte** values);

called here
private static Dictionary<string, object?>? s_dataStore
#if NATIVEAOT
= InitializeDataStore()

We could use the same approach in wasm/wasi, i.e. write them as C#, typescript or C file under obj/ dir in pre-publish step and let it compile as part of the output object. The idea is the initial values (RuntimeHostConfigurationOption) are immutable/frozen between publish and run. During run-time AppContext.SetData etc. allows us to overwrite the value because it's just a regular str->obj dictionary. So if the use-case is to be able to manually change the values between publish and run, we can drop it because NAOT (and probably singlefile apps) also doesn't allow that type of thing; modification of value require republishing the app.

@pavelsavara
Copy link
Member

it's just a regular str->obj dictionary.

I wonder why this is not str->str in AppContext.
Maybe you can add objects when the app is running ?
But the Setup only deals with string values.

We could use the same approach in wasm/wasi, i.e. write them as C file

Yes, this is probably the right approach for WASI. (which is out of scope for this issue)

In browser case, we also need this to work without LLVM and so the easiest way is to add it to dotnet.boot.js (formerly known as blazor.boot.json) have some exported AddKeyValue(string k, string v) and call it from TS.

@am11
Copy link
Member

am11 commented Mar 27, 2025

I wonder why this is not str->str in AppContext.

AppContext.SetSwitch and AppContext.TryGetSwitch expect bools.
AppContext.SetData and AppContext.GetData expect objects.

I think this shape makes more sense in the legacy context when multiple AppDomains were supported and it was there to share the in-memory data (SetData/GetData) in domain context during the process lifetime. The relationship with configs wasn't evolved in .NET Framework as it is today. In .NET Core, since there is only one AppDomain, the in-memory use-case isn't used much. Now its role is limited to be a low-level configuration system (mechanics are a bit involved 😅) https://learn.microsoft.com/dotnet/fundamentals/runtime-libraries/system-appcontext.

@radekdoulik
Copy link
Member

We can also put the binary blob to a data section with a new wa-info based task. Then it can be used to initialize a range of memory. This would work for both browser and wasi. (or we can use emcc's --embed-file option on browser and find similar solution for wasi)

Optionally it can be also added as a custom section, which can be accessed from JS on browser. On wasi it would not work though I guess.

@pavelsavara pavelsavara linked a pull request Apr 28, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch-wasm WebAssembly architecture area-Build-mono os-browser Browser variant of arch-wasm Priority:1 Work that is critical for the release, but we could probably ship without
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants