Skip to content

Commit 347262a

Browse files
authored
Use HandleAllocator for fetch API. NFC (#19337)
1 parent 5fb5fe7 commit 347262a

File tree

5 files changed

+55
-74
lines changed

5 files changed

+55
-74
lines changed

src/Fetch.js

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
*/
66

77
var Fetch = {
8-
// Map of integer fetch id to XHR request object
9-
xhrs: {},
8+
// HandleAllocator for XHR request object
9+
// xhrs: undefined,
1010

11-
// The web worker that runs proxied file I/O requests. (this field is populated on demand, start as undefined to save code size)
11+
// The web worker that runs proxied file I/O requests. (this field is
12+
// populated on demand, start as undefined to save code size)
1213
// worker: undefined,
1314

1415
// Specifies an instance to the IndexedDB database. The database is opened
15-
// as a preload step before the Emscripten application starts. (this field is populated on demand, start as undefined to save code size)
16+
// as a preload step before the Emscripten application starts. (this field is
17+
// populated on demand, start as undefined to save code size)
1618
// dbInstance: undefined,
1719

1820
#if FETCH_SUPPORT_INDEXEDDB
@@ -40,8 +42,12 @@ var Fetch = {
4042
},
4143
#endif
4244

43-
staticInit: function() {
45+
init: function() {
46+
Fetch.xhrs = new HandleAllocator();
4447
#if FETCH_SUPPORT_INDEXEDDB
48+
#if PTHREADS
49+
if (ENVIRONMENT_IS_PTHREAD) return;
50+
#endif
4551
var onsuccess = (db) => {
4652
#if FETCH_DEBUG
4753
dbg('fetch: IndexedDB successfully opened.');
@@ -293,8 +299,12 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) {
293299
xhr.setRequestHeader(keyStr, valueStr);
294300
}
295301
}
296-
var id = {{{ makeGetValue('fetch', C_STRUCTS.emscripten_fetch_t.id, 'u32') }}};
297-
Fetch.xhrs[id] = xhr;
302+
303+
var id = Fetch.xhrs.allocate(xhr);
304+
#if FETCH_DEBUG
305+
dbg('fetch: id=' + id);
306+
#endif
307+
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.id, 'id', 'u32') }}};
298308
var data = (dataPtr && dataLength) ? HEAPU8.slice(dataPtr, dataPtr + dataLength) : null;
299309
// TODO: Support specifying custom headers to the request.
300310

@@ -334,7 +344,7 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) {
334344

335345
xhr.onload = (e) => {
336346
// check if xhr was aborted by user and don't try to call back
337-
if (!(id in Fetch.xhrs)) {
347+
if (!Fetch.xhrs.has(id)) {
338348
return;
339349
}
340350
saveResponseAndStatus();
@@ -352,7 +362,7 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) {
352362
};
353363
xhr.onerror = (e) => {
354364
// check if xhr was aborted by user and don't try to call back
355-
if (!(id in Fetch.xhrs)) {
365+
if (!Fetch.xhrs.has(id)) {
356366
return;
357367
}
358368
#if FETCH_DEBUG
@@ -363,7 +373,7 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) {
363373
};
364374
xhr.ontimeout = (e) => {
365375
// check if xhr was aborted by user and don't try to call back
366-
if (!(id in Fetch.xhrs)) {
376+
if (!Fetch.xhrs.has(id)) {
367377
return;
368378
}
369379
#if FETCH_DEBUG
@@ -373,7 +383,7 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) {
373383
};
374384
xhr.onprogress = (e) => {
375385
// check if xhr was aborted by user and don't try to call back
376-
if (!(id in Fetch.xhrs)) {
386+
if (!Fetch.xhrs.has(id)) {
377387
return;
378388
}
379389
var ptrLen = (fetchAttrLoadToMemory && fetchAttrStreamData && xhr.response) ? xhr.response.byteLength : 0;
@@ -405,7 +415,7 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) {
405415
};
406416
xhr.onreadystatechange = (e) => {
407417
// check if xhr was aborted by user and don't try to call back
408-
if (!(id in Fetch.xhrs)) {
418+
if (!Fetch.xhrs.has(id)) {
409419
{{{ runtimeKeepalivePop() }}}
410420
return;
411421
}
@@ -560,27 +570,27 @@ function startFetch(fetch, successcb, errorcb, progresscb, readystatechangecb) {
560570
}
561571

562572
function fetchGetResponseHeadersLength(id) {
563-
return lengthBytesUTF8(Fetch.xhrs[id].getAllResponseHeaders()) + 1;
573+
return lengthBytesUTF8(Fetch.xhrs.get(id).getAllResponseHeaders()) + 1;
564574
}
565575

566576
function fetchGetResponseHeaders(id, dst, dstSizeBytes) {
567-
var responseHeaders = Fetch.xhrs[id].getAllResponseHeaders();
568-
var lengthBytes = lengthBytesUTF8(responseHeaders) + 1;
569-
stringToUTF8(responseHeaders, dst, dstSizeBytes);
570-
return Math.min(lengthBytes, dstSizeBytes);
577+
var responseHeaders = Fetch.xhrs.get(id).getAllResponseHeaders();
578+
var lengthBytes = lengthBytesUTF8(responseHeaders) + 1;
579+
stringToUTF8(responseHeaders, dst, dstSizeBytes);
580+
return Math.min(lengthBytes, dstSizeBytes);
571581
}
572582

573583
//Delete the xhr JS object, allowing it to be garbage collected.
574584
function fetchFree(id) {
575585
#if FETCH_DEBUG
576-
dbg("fetch: Deleting id:" + id + " of " + Fetch.xhrs);
586+
dbg("fetch: fetchFree id:" + id);
577587
#endif
578-
var xhr = Fetch.xhrs[id];
579-
if (xhr) {
580-
delete Fetch.xhrs[id];
581-
// check if fetch is still in progress and should be aborted
582-
if (xhr.readyState > 0 && xhr.readyState < 4) {
583-
xhr.abort();
584-
}
588+
if (Fetch.xhrs.has(id)) {
589+
var xhr = Fetch.xhrs.get(id);
590+
Fetch.xhrs.free(id);
591+
// check if fetch is still in progress and should be aborted
592+
if (xhr.readyState > 0 && xhr.readyState < 4) {
593+
xhr.abort();
585594
}
595+
}
586596
}

src/library.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3667,6 +3667,9 @@ mergeInto(LibraryManager.library, {
36673667
#endif
36683668
return this.allocated[id];
36693669
};
3670+
this.has = function(id) {
3671+
return this.allocated[id] !== undefined;
3672+
};
36703673
this.allocate = function(handle) {
36713674
var id = this.freelist.pop() || this.allocated.length;
36723675
this.allocated[id] = handle;

src/library_fetch.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,8 @@
77
#include Fetch.js
88

99
var LibraryFetch = {
10-
#if PTHREADS
11-
$Fetch__postset: 'if (!ENVIRONMENT_IS_PTHREAD) Fetch.staticInit();',
12-
#else
13-
$Fetch__postset: 'Fetch.staticInit();',
14-
#endif
10+
$Fetch__postset: 'Fetch.init();',
11+
$Fetch__deps: ['$HandleAllocator'],
1512
$Fetch: Fetch,
1613
_emscripten_fetch_get_response_headers_length__deps: ['$lengthBytesUTF8'],
1714
_emscripten_fetch_get_response_headers_length: fetchGetResponseHeadersLength,

system/lib/fetch/emscripten_fetch.c

Lines changed: 14 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,10 @@ void emscripten_fetch_attr_init(emscripten_fetch_attr_t* fetch_attr) {
6767
memset(fetch_attr, 0, sizeof(emscripten_fetch_attr_t));
6868
}
6969

70-
static int globalFetchIdCounter = 1;
7170
emscripten_fetch_t* emscripten_fetch(emscripten_fetch_attr_t* fetch_attr, const char* url) {
72-
if (!fetch_attr)
73-
return 0;
74-
if (!url)
75-
return 0;
71+
if (!fetch_attr || !url) {
72+
return NULL;
73+
}
7674

7775
const bool synchronous = (fetch_attr->attributes & EMSCRIPTEN_FETCH_SYNCHRONOUS) != 0;
7876
const bool readFromIndexedDB =
@@ -85,14 +83,13 @@ emscripten_fetch_t* emscripten_fetch(emscripten_fetch_attr_t* fetch_attr, const
8583
#ifdef FETCH_DEBUG
8684
emscripten_console_errorf("emscripten_fetch('%s') failed! Synchronous blocking XHRs and IndexedDB operations are not supported on the main browser thread. Try dropping the EMSCRIPTEN_FETCH_SYNCHRONOUS flag, or run with the linker flag --proxy-to-worker to decouple main C runtime thread from the main browser thread.", url);
8785
#endif
88-
return 0;
86+
return NULL;
8987
}
9088

91-
emscripten_fetch_t* fetch = (emscripten_fetch_t*)malloc(sizeof(emscripten_fetch_t));
92-
if (!fetch)
93-
return 0;
94-
memset(fetch, 0, sizeof(emscripten_fetch_t));
95-
fetch->id = globalFetchIdCounter++; // TODO: make this thread-safe!
89+
emscripten_fetch_t* fetch = (emscripten_fetch_t*)calloc(1, sizeof(emscripten_fetch_t));
90+
if (!fetch) {
91+
return NULL;
92+
}
9693
fetch->userData = fetch_attr->userData;
9794
fetch->__attributes.timeoutMSecs = fetch_attr->timeoutMSecs;
9895
fetch->__attributes.attributes = fetch_attr->attributes;
@@ -117,61 +114,35 @@ emscripten_fetch_t* emscripten_fetch(emscripten_fetch_attr_t* fetch_attr, const
117114
STRDUP_OR_ABORT(fetch->__attributes.userName, fetch_attr->userName);
118115
STRDUP_OR_ABORT(fetch->__attributes.password, fetch_attr->password);
119116
STRDUP_OR_ABORT(fetch->__attributes.overriddenMimeType, fetch_attr->overriddenMimeType);
117+
#undef STRDUP_OR_ABORT
118+
120119
if (fetch_attr->requestHeaders) {
121120
size_t headersCount = 0;
122121
while (fetch_attr->requestHeaders[headersCount])
123122
++headersCount;
124123
const char** headers = (const char**)malloc((headersCount + 1) * sizeof(const char*));
125124
if (!headers) {
126125
fetch_free(fetch);
127-
return 0;
126+
return NULL;
128127
}
129128
memset((void*)headers, 0, (headersCount + 1) * sizeof(const char*));
130129

131130
for (size_t i = 0; i < headersCount; ++i) {
132131
headers[i] = strdup(fetch_attr->requestHeaders[i]);
133-
if (!headers[i])
134-
135-
{
132+
if (!headers[i]) {
136133
for (size_t j = 0; j < i; ++j) {
137134
free((void*)headers[j]);
138135
}
139136
free((void*)headers);
140137
fetch_free(fetch);
141-
return 0;
138+
return NULL;
142139
}
143140
}
144141
headers[headersCount] = 0;
145142
fetch->__attributes.requestHeaders = headers;
146143
}
147144

148-
#undef STRDUP_OR_ABORT
149-
150-
// In asm.js we can use a fetch worker, which is created from the main asm.js
151-
// code. That lets us do sync operations by blocking on the worker etc.
152-
// In the wasm backend we don't have a fetch worker implemented yet, however,
153-
// we can still do basic synchronous fetches in the same places: if we can
154-
// block on another thread then we aren't the main thread, and if we aren't
155-
// the main thread then synchronous xhrs are legitimate.
156-
#if __EMSCRIPTEN_PTHREADS__ && !defined(__wasm__)
157-
const bool waitable = (fetch_attr->attributes & EMSCRIPTEN_FETCH_WAITABLE) != 0;
158-
// Depending on the type of fetch, we can either perform it in the same Worker/thread than the
159-
// caller, or we might need to run it in a separate Worker. There is a dedicated fetch worker that
160-
// is available for the fetch, but in some scenarios it might be desirable to run in the same
161-
// Worker as the caller, so deduce here whether to run the fetch in this thread, or if we need to
162-
// use the fetch-worker instead.
163-
if (waitable // Waitable fetches can be synchronously waited on, so must always be proxied
164-
|| (synchronous &&
165-
(readFromIndexedDB || writeToIndexedDB))) // Synchronous IndexedDB access needs proxying
166-
{
167-
fetch->__proxyState = 1; // sent to proxy worker.
168-
emscripten_proxy_fetch(fetch);
169-
170-
if (synchronous)
171-
emscripten_fetch_wait(fetch, INFINITY);
172-
} else
173-
#endif
174-
emscripten_start_fetch(fetch);
145+
emscripten_start_fetch(fetch);
175146
return fetch;
176147
}
177148

test/fetch/xhr_abort.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ void handleError(emscripten_fetch_t *fetch) {
1818
bool isAbortedStatus = fetch->status == (unsigned short) -1;
1919
assert(isAbortedStatus); // should have aborted status
2020
EM_ASM({
21-
const xhr = Fetch.xhrs[$0];
21+
const xhr = Fetch.xhrs.get($0);
2222
const oldReadyStateChangeHandler = xhr.onreadystatechange;
2323
// Overriding xhr handlers to check if xhr.abort() was called
2424
xhr.onreadystatechange = (e) => {

0 commit comments

Comments
 (0)