emscripten-core / emscripten-core/emscripten
async fetch in wasm+threads
- Dominant language
- C++
- Stars
- 27.6k
- Forks
- 3.6k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 105
Description
Hi,
In response to https://github.com/emscripten-core/emscripten/issues/7024#issuecomment-601386326 I am experimenting with asynchronous fetches in wasm+threads application.
I have tried several approaches and none of the asynchronous has worked so far.
When I try using callbacks, none of them get called after the fetch is initiated.
When I try polling on the fetch pointer, it returns EMSCRIPTEN_RESULT_INVALID_PARAM (probably because the proxyState is zero).
Polling with some positive timeout parameter makes no difference.
I also tried using EMSCRIPTEN_FETCH_REPLACE, as mentioned in https://github.com/emscripten-core/emscripten/issues/8749 .
Here is excerpt of the code I use:
```c++
// configure here:
//#define VTS_FETCH_POLLING
//#define VTS_FETCH_SYNCHRONOUS
#ifndef VTS_FETCH_POLLING
void onUpdate(emscripten_fetch_t *fetch);
void onStateChange(emscripten_fetch_t *fetch)
{
LOG(info4) << "fetch state change";
}
void onProgress(emscripten_fetch_t *fetch)
{
LOG(info4) << "fetch progress";
}
#endif
class WasmTask
{
public:
std::shared_ptr task;
emscripten_fetch_t *fetch = nullptr;
bool finished = false;
public:
WasmTask(const std::shared_ptr &task) : task(task)
{
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
attr.userData = this;
strcpy(attr.requestMethod, "GET");
// todo attr.timeoutMSecs
// todo attr.requestHeaders
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY
#ifndef VTS_FETCH_SYNCHRONOUS
| EMSCRIPTEN_FETCH_WAITABLE;
#else
| EMSCRIPTEN_FETCH_SYNCHRONOUS;
#endif
#ifndef VTS_FETCH_POLLING
attr.onerror = &onUpdate;
attr.onsuccess = &onUpdate;
attr.onreadystatechange = &onStateChange;
attr.onprogress = &onProgress;
#endif
fetch = emscripten_fetch(&attr, task->query.url.c_str());
}
~WasmTask()
{
emscripten_fetch_close(fetch);
task->fetchDone();
}
WasmTask(const WasmTask &) = delete;
WasmTask &operator = (const WasmTask &) = delete;
#ifdef VTS_FETCH_POLLING
bool update()
{
if (!fetch)
{
LOG(warn2) << "no fetch task";
return true;
}
#ifndef VTS_FETCH_SYNCHRONOUS
uint32_t proxyState = fetch->__proxyState;
LOG(info4) << "fetch proxy state: " << proxyState;
switch (emscripten_fetch_wait(fetch, 0))
{
case EMSCRIPTEN_RESULT_TIMED_OUT:
return false; // keep the task going
case EMSCRIPTEN_RESULT_SUCCESS:
#endif
task->reply.code = fetch->status;
task->reply.content.allocate(fetch->numBytes);
memcpy(task->reply.content.data(), fetch->data, fetch->numBytes);
return true;
#ifndef VTS_FETCH_SYNCHRONOUS
case EMSCRIPTEN_RESULT_INVALID_PARAM:
LOG(warn2) << "emscripten_fetch_wait returned "
"EMSCRIPTEN_RESULT_INVALID_PARAM";
return true;
case EMSCRIPTEN_RESULT_FAILED:
LOG(warn2) << "emscripten_fetch_wait returned "
"EMSCRIPTEN_RESULT_FAILED";
return true;
default:
LOG(err3) << "emscripten_fetch_wait returned "
"unknown error code";
return true;
};
#endif
}
#else
bool update()
{
return finished;
}
#endif
};
#ifndef VTS_FETCH_POLLING
void onUpdate(emscripten_fetch_t *fetch)
{
LOG(info4) << "status: " << fetch->status
<< ", bytes: " << fetch->numBytes;
WasmTask *w = (WasmTask*)fetch->userData;
FetchTask *t = w->task.get();
//assert(fetch == task->fetch); // wtf why not?!?!
t->reply.code = fetch->status;
t->reply.content.allocate(fetch->numBytes);
memcpy(t->reply.content.data(), fetch->data, fetch->numBytes);
w->finished = true;
}
#endif
class FetcherImpl : public Fetcher
{
std::list> tasks;
public:
FetcherImpl(const FetcherOptions &options)
{}
void finalize() override
{
tasks.clear();
}
void update() override
{
auto it = tasks.begin();
while (it != tasks.end())
{
if ((*it)->update())
it = tasks.erase(it);
else
it++;
}
}
void fetch(const std::shared_ptr &task) override
{
tasks.insert(tasks.end(), std::make_unique(task));
}
};
```
I build with these flags (among others):
```
-s FETCH_DEBUG=1 -s WASM=1 -s USE_PTHREADS=1 -s ALLOW_BLOCKING_ON_MAIN_THREAD=0 -s PROXY_TO_PTHREAD=1
```
The code is run in a thread dedicated to manage fetches.
-------
And here are the four cases:
-------
```c++
#define VTS_FETCH_POLLING
#define VTS_FETCH_SYNCHRONOUS
// working
```
---
```c++
//#define VTS_FETCH_POLLING
#define VTS_FETCH_SYNCHRONOUS
// working
```
---
```c++
#define VTS_FETCH_POLLING
//#define VTS_FETCH_SYNCHRONOUS
fetch: IndexedDB not available!
fetch: starting (uncached) XHR: IndexedDB not available!
fetch: xhr.timeout: 0, xhr.withCredentials: false
fetch: xhr.open(requestMethod="GET", url: "https://cdn.melown.com/mario/store/melown2015/map-config/melown/Melown-Earth-Intergeo-2017/mapConfig.json", userName: undefined, password: undefined);
fetch: xhr.send(data=null)
2020-06-23 13:53:48 I4 [42(fetcher)]: fetch proxy state: 0 {wasm.cpp:update():110}
2020-06-23 13:53:48 W2 [42(fetcher)]: emscripten_fetch_wait returned EMSCRIPTEN_RESULT_INVALID_PARAM {wasm.cpp:update():123}
fetch: Deleting id:0 of [object XMLHttpRequest]
```
And that repeats for all requests.
---
```c++
//#define VTS_FETCH_POLLING
//#define VTS_FETCH_SYNCHRONOUS
fetch: IndexedDB not available!
fetch: starting (uncached) XHR: IndexedDB not available!
fetch: xhr.timeout: 0, xhr.withCredentials: false
fetch: xhr.open(requestMethod="GET", url: "https://cdn.melown.com/mario/store/melown2015/map-config/melown/Melown-Earth-Intergeo-2017/mapConfig.json", userName: undefined, password: undefined);
fetch: xhr.send(data=null)
```
Afterwards the application keeps spinning but the fetch is stuck.
---
I also noticed that the pointer passed into the callbacks is different than the one returned from emscripten_fetch. Is that something to worry about? Is it correct to use the pointer returned by emscripten_fetch for polling?
Am I doing wrong something else?
Thanks for any help.
Contributor guide
Assessment
This issue has not been assessed yet.