Skip to content
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

Add Emscripten target #6263

Merged
merged 30 commits into from
May 9, 2023
Merged

Add Emscripten target #6263

merged 30 commits into from
May 9, 2023

Conversation

fealebenpae
Copy link
Member

@fealebenpae fealebenpae commented Feb 1, 2023

What, How & Why?

☑️ ToDos

  • 📝 Changelog update
  • 🚦 Tests (or not relevant)
  • C-API, if public C++ API changed.

@fealebenpae fealebenpae self-assigned this Feb 1, 2023
@cla-bot cla-bot bot added the cla: yes label Feb 1, 2023
@fealebenpae fealebenpae force-pushed the yg/emscripten branch 9 times, most recently from 8fc60f6 to 1425c93 Compare February 2, 2023 23:51
@fealebenpae fealebenpae force-pushed the yg/emscripten branch 2 times, most recently from 916fd7b to 3419c01 Compare February 3, 2023 00:15
.gitmodules Outdated
@@ -1,3 +1,9 @@
[submodule "external/catch"]
path = external/catch
url = https://github.com/catchorg/Catch2.git
[submodule "external/sha-2"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These submodules should be placed in src/external.

# Conflicts:
#	.gitmodules
#	src/realm/CMakeLists.txt
#	src/realm/util/encrypted_file_mapping.cpp
#	src/realm/util/sha_crypto.cpp
#	test/object-store/sync/app.cpp
@fealebenpae fealebenpae marked this pull request as ready for review March 27, 2023 09:25
Comment on lines +93 to +99
std::vector<const char*> request_headers_buf;
for (const auto& header : request.headers) {
request_headers_buf.push_back(header.first.c_str());
request_headers_buf.push_back(header.second.c_str());
}
request_headers_buf.push_back(nullptr);
attr.requestHeaders = request_headers_buf.data();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any risk of request_headers_buf going out of scope before emscripten_fetch() has had a chance to use it? Or will it generate and send the request before that function returns?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emscripten_fetch copies all the request data into JavaScript memory (it's implemented in JavaScript) before returning so we don't need those buffers to last longer than the stack frame.

Comment on lines +239 to +243
#ifdef __EMSCRIPTEN__
if (!m_config.transport) {
m_config.transport = std::make_shared<_impl::EmscriptenNetworkTransport>();
}
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is emscripten only going to be used with the WASM SDK? If so, what would you think about moving the transport and socketprovider classes to the SDK?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many of our SDKs can have wasm builds - realm-js is the priority now, but realm-dotnet is sure to follow, based on requests. Dart and Kotlin can also benefit. Since the networking code is browser-specific, but multiple SDKs could target the browser I opted to have it live in Core and be the default unless an SDKs chooses to do otherwise. However, since we have little control over the browser's network stack and our customization opportunities are limited I don't know if SDKs would ever need to override the defaults, expect maybe in tests where they mock the server endpoints.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK - we don't have to decide this now. If we decide to keep it in core since it is needed by multiple SDKs (fortunately it's a fairly lightweight set of classes), we should make sure we add tests to verify its operation. I did see that you added the emscripten Jenkins build test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - ideally I'd like to run Core's existing test suite, but right now we can only build them, not run them. The big blocker there is that virtually all of our tests expect filesystem persistence, and a lot of them rely on being able to spawn threads, both of which are unsupported right now. But as the project matures the expectation is that we'll be running Core tests on the target.

@@ -132,6 +132,13 @@ if(ANDROID)
add_compile_options(-fdata-sections -ffunction-sections -fomit-frame-pointer -fsigned-char -fstrict-aliasing -funwind-tables -no-canonical-prefixes $<$<CONFIG:Release>:-Oz>)
endif()

if(EMSCRIPTEN)
add_compile_options(-fwasm-exceptions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to use target_compiler_options(PUBLIC) on the realm target so that it automatically applies to all targets, including those of the SDKs that link against it. That said, you may need to use add_compile_options for any third_party code that doesn't depend on realm.

ditto for link_options.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be cumbersome because we'll need these on Storage, Bid, and a few other targets. Rather I can only set these if stand-alone Core builds (for tests) and let SDKs control the exception handling and memory growth before they include the Core submodule.

attr.protocols = protocols.c_str();
attr.url = url.c_str();
int result = emscripten_websocket_new(&attr);
REALM_ASSERT(result > 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably have some common util function for working with EMSCRIPTEN_RESULTs so we get more info on failure, but I'm fine leaving that as a future TODO though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is special - if the return value is >0 then it's a socket descriptor, if it's <0 then it's EMSCRIPTEN_RESULT. And this function only errors out if websockets are not supported, or attr is null. I'm not sure what we could do with this information, so I didn't think to record the actual error code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well we should at least log it and throw an exception or abort, rather than soldiering on as if nothing happened. Note that REALM_ASSERT is probably not sufficient for that because they are removed in release builds. It should only be used for things that we believe to be both impossible and within our control, and I don't think this qualifies.

Although again, I guess this code isn't 100% production ready anyway, so if you want to just file a ticket to revisit error handling of emscripten calls, and leave as-is for this PR, I'm fine with that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is 100% not production ready 😅
But in this particular case I'd argue the error conditions are entirely (Realm) programmer error - we either shipped on a browser target so old it doesn't support websockets, or we didn't initialize the function arguments correctly.
Right now release builds for WebAssembly omit any useful debug information so we lose stack traces and exception types and all of that, so even an REALM_ASSERT_RELEASE doesn't give us very actionable information. That is something we need to explore more when we tune file size. That's why I'd prefer to have just enough validation for Debug builds for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not disagreeing with your overall point, however

we either shipped on a browser target so old it doesn't support websockets

Isn't it the end user who chooses the browser they run on? Or do we have checks to ensure that we are using a new enough browser with sufficient support for websockets?

even an REALM_ASSERT_RELEASE doesn't give us very actionable information

Don't we still get logging of the file+line that failed? That seems like it would give us very actionable info in cases like that because we could tell the exact call that failed. More so if we used one of the macros that also printed the value of the result, eg REALM_ASSERT_3(result, >, 0) (which unfortunately doesn't have a RELEASE variant 😢)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do we have checks to ensure that we are using a new enough browser with sufficient support for websockets?

In our particular case the JS SDK itself won't run on a browser that old. But if I think about it some more, this will error out on Node.js as well, since that doesn't come with websockets out of the box.

Don't we still get logging of the file+line that failed?

If we put the assert inside a convenience function that checks EMSCRIPTEN_RESULT wouldn't we only get the line number of that function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we put the assert inside a convenience function that checks EMSCRIPTEN_RESULT wouldn't we only get the line number of that function?

There are solutions to that, including using a macro rather than a normal function. With C++20's source_location, we could use a plain function, although that requires a new compiler (only finished for clang/libc++ 16 which was released a few days ago ☹️).

@fealebenpae fealebenpae force-pushed the yg/emscripten branch 3 times, most recently from 614ab50 to fc9da3e Compare March 31, 2023 21:11
using namespace realm::app;

namespace {
Copy link
Contributor

@RedBeard0531 RedBeard0531 Apr 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you stop using the anonymous namespace and switch to static? One of the downsides is that you can't use static with types. I don't think that really matters in this case, but that is why namespace{} is usually preferred over static, and it may start to matter if methods are ever added to FetchState.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, I misinterpreted a previous comment about using a namespace. I will revert this change.

attr.protocols = protocols.c_str();
attr.url = url.c_str();
int result = emscripten_websocket_new(&attr);
REALM_ASSERT(result > 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we put the assert inside a convenience function that checks EMSCRIPTEN_RESULT wouldn't we only get the line number of that function?

There are solutions to that, including using a macro rather than a normal function. With C++20's source_location, we could use a plain function, although that requires a new compiler (only finished for clang/libc++ 16 which was released a few days ago ☹️).

Copy link
Contributor

@RedBeard0531 RedBeard0531 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few minor nits

@fealebenpae fealebenpae merged commit 6a40e0e into master May 9, 2023
@fealebenpae fealebenpae deleted the yg/emscripten branch May 9, 2023 10:29
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants