-
Notifications
You must be signed in to change notification settings - Fork 173
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
Add Emscripten target #6263
Conversation
f69c299
to
1684cfd
Compare
8fc60f6
to
1425c93
Compare
916fd7b
to
3419c01
Compare
3419c01
to
9d8f848
Compare
3275999
to
492f71c
Compare
442285a
to
f35c949
Compare
.gitmodules
Outdated
@@ -1,3 +1,9 @@ | |||
[submodule "external/catch"] | |||
path = external/catch | |||
url = https://github.com/catchorg/Catch2.git | |||
[submodule "external/sha-2"] |
There was a problem hiding this comment.
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
ae14e6e
to
615ec45
Compare
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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
#ifdef __EMSCRIPTEN__ | ||
if (!m_config.transport) { | ||
m_config.transport = std::make_shared<_impl::EmscriptenNetworkTransport>(); | ||
} | ||
#endif |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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_RESULT
s so we get more info on failure, but I'm fine leaving that as a future TODO though.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 😢)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
src/realm/object-store/sync/impl/emscripten/socket_provider.hpp
Outdated
Show resolved
Hide resolved
614ab50
to
fc9da3e
Compare
fc9da3e
to
0f6398e
Compare
using namespace realm::app; | ||
|
||
namespace { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/realm/object-store/sync/impl/emscripten/socket_provider.cpp
Outdated
Show resolved
Hide resolved
src/realm/object-store/sync/impl/emscripten/socket_provider.cpp
Outdated
Show resolved
Hide resolved
src/realm/object-store/sync/impl/emscripten/socket_provider.cpp
Outdated
Show resolved
Hide resolved
attr.protocols = protocols.c_str(); | ||
attr.url = url.c_str(); | ||
int result = emscripten_websocket_new(&attr); | ||
REALM_ASSERT(result > 0); |
There was a problem hiding this comment.
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
There was a problem hiding this 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
What, How & Why?
☑️ ToDos