Skip to content

[file_packager.py] Fix errback call in fetchRemotePackage #24871

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

Closed
wants to merge 12 commits into from

Conversation

lkwinta
Copy link
Contributor

@lkwinta lkwinta commented Aug 6, 2025

errback argument was ignored in fetchRemotePackage, Promise.reject was called instead

@lkwinta
Copy link
Contributor Author

lkwinta commented Aug 6, 2025

@sbc100

@sbc100
Copy link
Collaborator

sbc100 commented Aug 6, 2025

By the way it looks like this code was added in #22016, but the code in replaced also did throw new Error rather than calling errback.

Also, it looks like errback is just doing console.error('package error:', error); .. not sure thats any better.

This change does seem perfectly good, but I'm wondoring how it solves your issue if the result is just to call console.error?

@lkwinta
Copy link
Contributor Author

lkwinta commented Aug 6, 2025

By the way it looks like this code was added in #22016, but the code in replaced also did throw new Error rather than calling errback.

Also, it looks like errback is just doing console.error('package error:', error); .. not sure thats any better.

This change does seem perfectly good, but I'm wondoring how it solves your issue if the result is just to call console.error?

I will now have one place where I can reject the promise returned by function.

Other thing that comes to my mind is changing console.errors in whole script to Module.printErr to enable filtering or guard the logs with some flag

@lkwinta
Copy link
Contributor Author

lkwinta commented Aug 6, 2025

@sbc100 I think that I have more proposal as part of this mr

@lkwinta lkwinta requested a review from sbc100 August 6, 2025 19:17
ret += '''
function handleError(error) {
console.error('package error:', error);
return error;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why add return error here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think think about it now again it is really unnecessary

@@ -1117,7 +1119,7 @@ def generate_js(data_target, data_files, metadata):
if (isNode) {
require('fs').readFile(metadataUrl, 'utf8', (err, contents) => {
if (err) {
return Promise.reject(err);
return handleError(err);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you can delete the return keyword here.

@lkwinta
Copy link
Contributor Author

lkwinta commented Aug 6, 2025

Should I merge when the pipeline passes now?

@lkwinta lkwinta requested a review from sbc100 August 6, 2025 21:05
@sbc100 sbc100 enabled auto-merge (squash) August 6, 2025 21:15
auto-merge was automatically disabled August 6, 2025 22:02

Head branch was pushed to by a user without write access

@lkwinta lkwinta force-pushed the file_packager_errback branch from d9001d8 to f566133 Compare August 7, 2025 07:41
@lkwinta
Copy link
Contributor Author

lkwinta commented Aug 7, 2025

@sbc100 I have fixed the pipeline, some returns where neccessary since errback had different meaning, I wonder if we should modify catch statement in fetchRemotePackage now

@@ -984,7 +989,7 @@ def generate_js(data_target, data_files, metadata):

const reader = response.body.getReader();
const iterate = () => reader.read().then(handleChunk).catch((cause) => {
return Promise.reject(new Error(`Unexpected error while handling : ${response.url} ${cause}`, {cause}));
return errback(new Error(`Unexpected error while handling : ${response.url} ${cause}`, {cause}));
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this return is needed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree,

@@ -1138,8 +1139,10 @@ def generate_js(data_target, data_files, metadata):
if (response.ok) {
return response.json();
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you still need to do something here when the response is not ok... you either need the throw or return a rejected promise object I think.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actaully I think just doing throwPackageError here on this line is probably what you want.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wonder now, if we take this code:

function preloadFallback(error) {
          console.error(error);
          console.error('falling back to default preload behavior');
          fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE, processPackageData, throwPackageError);
        };

        openDatabase(
          (db) => checkCachedPackage(db, PACKAGE_PATH + PACKAGE_NAME,
              (useCached, metadata) => {
                Module['preloadResults'][PACKAGE_NAME] = {fromCache: useCached};
                if (useCached) {
                  fetchCachedPackage(db, PACKAGE_PATH + PACKAGE_NAME, metadata, processPackageData, preloadFallback);
                } else {
                  fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE,
                    (packageData) => {
                      cacheRemotePackage(db, PACKAGE_PATH + PACKAGE_NAME, packageData, {uuid:PACKAGE_UUID}, processPackageData,
                        (error) => {
                          console.error(error);
                          processPackageData(packageData);
                        });
                    }
                  , preloadFallback);
                }
              }, preloadFallback)
        , preloadFallback);

then this catch in fetchRemotePackage will not fail

 Module['dataFileDownloads'] ??= {};
        fetch(packageName)
          .catch((cause) => errback(new Error(`Network Error: ${packageName}`, {cause}))) // If fetch fails, rewrite the error to include the failing URL & the cause.
          .then((response) => {

I think that just adding promise Rejsect here for security iss what we want

@lkwinta lkwinta requested a review from sbc100 August 7, 2025 21:06
errback(new Error(`Network Error: ${packageName}`, {cause}));
// We want to be sure to cancel promise chain because we sometimes call this function with method
// which doesn't throw error hance promise chain is not broken
return Promise.reject(new Error(`Network Error: ${packageName}`, {cause}));
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is starting to look pretty ugly because we mixing callbacks and promises .. I think converting to using async await is ultimately what we we probably want to do here.

Copy link
Contributor Author

@lkwinta lkwinta Aug 7, 2025

Choose a reason for hiding this comment

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

I have prototyped something, but maybe it deservse separate PR...

function fetchRemotePackage(packageName, packageSize) {
        return new Promise((fetchPackageResolve, fetchPackageReject) => {
          if (isNode) {
          require('fs').readFile(packageName, (err, contents) => {
            if (err) {
              fetchPackageReject(err);
            } else {
              fetchPackageResolve(contents.buffer);
            }
          });
          return;
        }
          Module['dataFileDownloads'] ??= {};
          fetch(packageName)
            .catch((cause) => {
              err = new Error(`Network Error: ${packageName}`, {cause});
              fetchPackageReject(err);
              return Promise.reject(err);
            }) // If fetch fails, rewrite the error to include the failing URL & the cause.
            .then((response) => {
              if (!response.ok) {
                fetchPackageReject(new Error(`${response.status}: ${response.url}`));
                return;
              }

              if (!response.body && response.arrayBuffer) { // If we're using the polyfill, readers won't be available...
                return response.arrayBuffer().then(fetchPackageResolve);
              }

              const reader = response.body.getReader();
              const iterate = () => reader.read().then(handleChunk).catch((cause) => {
                fetchPackageReject(new Error(`Unexpected error while handling : ${response.url} ${cause}`, {cause}));
                return;
              });

              const chunks = [];
              const headers = response.headers;
              const total = Number(headers.get('Content-Length') ?? packageSize);
              let loaded = 0;

              const handleChunk = ({done, value}) => {
                if (!done) {
                  chunks.push(value);
                  loaded += value.length;
                  Module['dataFileDownloads'][packageName] = {loaded, total};

                  let totalLoaded = 0;
                  let totalSize = 0;

                  for (const download of Object.values(Module['dataFileDownloads'])) {
                    totalLoaded += download.loaded;
                    totalSize += download.total;
                  }

                  Module['setStatus']?.(`Downloading data... (${totalLoaded}/${totalSize})`);
                  return iterate();
                } else {
                  const packageData = new Uint8Array(chunks.map((c) => c.length).reduce((a, b) => a + b, 0));
                  let offset = 0;
                  for (const chunk of chunks) {
                    packageData.set(chunk, offset);
                    offset += chunk.length;
                  }
                  fetchPackageResolve(packageData.buffer);
                }
              };

              Module['setStatus']?.('Downloading data...');
              return iterate();
            });
        });
      };
      ```

@sbc100
Copy link
Collaborator

sbc100 commented Aug 7, 2025

I took as stab as converting part of the file_packager output to async/await: #24871. Its not the relevant part .. but its a start.

@lkwinta
Copy link
Contributor Author

lkwinta commented Aug 7, 2025

I took as stab as converting part of the file_packager output to async/await: #24871. Its not the relevant part .. but its a start.

So should I fix that now ?

@sbc100
Copy link
Collaborator

sbc100 commented Aug 7, 2025

I took as stab as converting part of the file_packager output to async/await: #24871. Its not the relevant part .. but its a start.

So should I fix that now ?

I've made some progress on it, so can do this part if you like?

@sbc100
Copy link
Collaborator

sbc100 commented Aug 7, 2025

I took as stab as converting part of the file_packager output to async/await: #24871. Its not the relevant part .. but its a start.

So should I fix that now ?

I've made some progress on it, so can do this part if you like?

Ooops that should have been: #24882

@lkwinta
Copy link
Contributor Author

lkwinta commented Aug 7, 2025

I took as stab as converting part of the file_packager output to async/await: #24871. Its not the relevant part .. but its a start.

So should I fix that now ?

I've made some progress on it, so can do this part if you like?

Not right now for sure, but feel free to take and use my prototype, I have changed it now after some tests, at least happy path should pass with current version pasted in my comment.

@sbc100
Copy link
Collaborator

sbc100 commented Aug 7, 2025

I think I'm going to go for full conversion to async/await which should make your change much simpler (I hope)

@sbc100
Copy link
Collaborator

sbc100 commented Aug 7, 2025

Part 2: #24883

@lkwinta
Copy link
Contributor Author

lkwinta commented Aug 8, 2025

I think I'm going to go for full conversion to async/await which should make your change much simpler (I hope)

So we wait with this PR and the export_es6 one for the conversion to async?

@sbc100
Copy link
Collaborator

sbc100 commented Aug 8, 2025

Yes I think the async conversion work will supersede this PR

@lkwinta
Copy link
Contributor Author

lkwinta commented Aug 8, 2025

Yes I think the async conversion work will supersede this PR

@sbc100 to be honest I don't see the point of this PR anymore after your changes. I think it will be possible to Reject main functions promise from just one or two places without the need of handleError

@sbc100
Copy link
Collaborator

sbc100 commented Aug 8, 2025

Yes I think the async conversion work will supersede this PR

@sbc100 to be honest I don't see the point of this PR anymore after your changes. I think it will be possible to Reject main functions promise from just one or two places without the need of handleError

That sounds reasonable to me.

The non-es6 case I think the point of handleError is to show something on the console to the user so they know the package loading has failed.

For the es6 case i don't think that is necessary, so I think you could just do handleError = reject maybe?

@lkwinta
Copy link
Contributor Author

lkwinta commented Aug 8, 2025

Yes I think the async conversion work will supersede this PR

@sbc100 to be honest I don't see the point of this PR anymore after your changes. I think it will be possible to Reject main functions promise from just one or two places without the need of handleError

That sounds reasonable to me.

The non-es6 case I think the point of handleError is to show something on the console to the user so they know the package loading has failed.

For the es6 case i don't think that is necessary, so I think you could just do handleError = reject maybe?

So I think I can close that

@sbc100
Copy link
Collaborator

sbc100 commented Aug 8, 2025

Yes, I think this PR can be closed. Is that what you are asking? I think the conversion async removed the need for this fix.

@lkwinta lkwinta closed this Aug 8, 2025
@lkwinta lkwinta deleted the file_packager_errback branch August 8, 2025 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants