Skip to content
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

Don't block other extensions from loading if one activation fails #4805

Merged
merged 6 commits into from
Feb 11, 2022

Conversation

panuhorsmalahti
Copy link
Contributor

@panuhorsmalahti panuhorsmalahti force-pushed the fix/extension-activate-error branch from 798f633 to 4f89f08 Compare February 3, 2022 09:13
@panuhorsmalahti panuhorsmalahti added the bug Something isn't working label Feb 3, 2022
src/extensions/extension-loader/extension-loader.ts Outdated Show resolved Hide resolved
// as this might register new catalog categories. Afterwards we can safely .enable the extension.
await Promise.all(extensions.map(extension => extension.activated));
await Promise.all(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Given that we have already started all the activations above, why not just use a for loop and try...catch, then we don't need another field, or the filter, or the map. Just an array to push to.

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 checked the original code, and it seems we want to return even extensions that fail to be activated from loadExtensions. I removed the filter and the field to simplify it.

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 we should really be mixing await and .catch here since we can easily avoid doing so.

I would suggest something like the following:

for (const extension of extensions) {
  try {
    await extension.activated;
  } catch (error) {
    logger.error(`${logModule}: activation extension error`, { ext: extension.installedExtension, error });
  }
}

Furthermore I think the use of Promise.all here is a case of using the wrong tool, since we don't actually care about the result. Only the errors (if there are any)

Copy link
Collaborator

Choose a reason for hiding this comment

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

So looking at the code around this, we could easily make activate call enable, have the logging within LensExtension and then pass those promises directly to the return extensions.map(...) bellow, because we don't actually need this awaits here.

Copy link
Contributor Author

@panuhorsmalahti panuhorsmalahti Feb 4, 2022

Choose a reason for hiding this comment

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

Promise.all is valid use here, because we want to wait until all the Promises have been resolved.

Your try / catch / await is okay.. but has a bit different semantics. It would wait until each extension has been activated, and only print the error in order (e.g. loading extensions A, B and C, if C fails, it would log error from C only after A and B are done). With Promise.all each error from activation will be logged immediately, which is slightly beneficial.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would do the following:

protected async loadExtensions(installedExtensions: Map<string, InstalledExtension>, register: (ext: LensExtension) => Promise<Disposer[]>) {
  const extensions: ExtensionLoading[] = [];

  for (const [extId, ext] of installedExtensions) {
    const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(ext.manifest.name);

    if (ext.isCompatible && ext.isEnabled && !alreadyInit) {
      try {
        const LensExtensionClass = this.requireExtension(ext);

        if (!LensExtensionClass) {
          this.nonInstancesByName.add(ext.manifest.name);

          continue;
        }

        const instance = this.dependencies.createExtensionInstance(
          LensExtensionClass,
          ext,
        );

        this.instances.set(extId, instance);

        extensions.push({
          isBundled: ext.isBundled,
          loaded: (async (): Promise<void> => {
            try {
              await instance.activate();
            } catch (error) {
              return void logger.error(`${logModule}: extension activation error`, { ext, error });
            }

            try {
              await instance.enable(register);
            } catch (error) {
              return void logger.error(`${logModule}: extension enabling error`, { ext, error });
            }
          })(),
        });
      } catch (error) {
        logger.error(`${logModule}: extension loading error`, { ext, error });
      }
    } else if (!ext.isEnabled && alreadyInit) {
      this.removeInstance(extId);
    }
  }

  return extensions;
}

Copy link
Contributor Author

@panuhorsmalahti panuhorsmalahti Feb 10, 2022

Choose a reason for hiding this comment

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

That doesn't work for this reason:

    // We first need to wait until each extension's `onActivate` is resolved,
    // as this might register new catalog categories. Afterwards we can safely .enable the extension.

Your code example might call .enable before another extension is activated fully, thus causing error.

See earlier PR for some discussion:
#4702

Copy link
Collaborator

Choose a reason for hiding this comment

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

If that parameter becomes IComputedValue<CatalogEntity | undefined> and you do an await when(() => Boolean(entity.get()); before that if, then the race condition would be fixed as well and it would also be fixed for non-bundled extensions.

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 propose we fix that in another PR as that's becoming not related to the issue at hand.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Okay sounds good

@panuhorsmalahti panuhorsmalahti force-pushed the fix/extension-activate-error branch from 4757bb6 to 16e8e6c Compare February 4, 2022 16:30
@panuhorsmalahti panuhorsmalahti merged commit e626cc9 into master Feb 11, 2022
@panuhorsmalahti panuhorsmalahti deleted the fix/extension-activate-error branch February 11, 2022 14:13
@Nokel81 Nokel81 added this to the 5.4.0 milestone May 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Extension activation error blocks other extensions
3 participants