emscripten-core / emscripten-core/emscripten

async fetch in wasm+threads

Open
#11,476 2 comments 0 reactions 0 assignees View on GitHub
wontfix
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.