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 resolve plugin prompt promise until modal has closed, to avoid a race condition #3970

Merged

Conversation

develohpanda
Copy link
Contributor

@develohpanda develohpanda commented Aug 30, 2021

The background

All modals in this codebase are callback based. That is, to process the result of a modal, you need to send an onComplete or onCancel callback into the modal.show() method.

The plugin API which exposes the prompt modal (context.app.prompt) is not callback based, it is promise based. That is, if the prompt was cancelled, context.app.prompt should reject with an error, and if the prompt was submitted, context.app.prompt should resolve with the prompt value.

The callback-based prompt is converted to a promise-based helper in packages/insomnia-app/app/plugins/context/app.tsx.

The alert modal "attempts" to make itself be both callback-based and promise-based, which is additional confusion, so this (and modals in general) do need a cleanup which is not done in this PR.

The change

The onComplete callback for a prompt was previously synchronous, however an update in #3474 introduced asynchronous handlers, adding a loading state and not closing the modal until the onComplete handler was finished.

The race condition

This change introduced a race condition with the plugin API implementation translation. On clicking submit and triggering the onComplete callback, the plugin API (which resolves the wrapped promise - see previous implementation) would resolve the promise and tell a plugin that "the prompt is complete", while at the same time the application would be in the process of closing the modal. If the plugin immediately opened another prompt (while the application was still trying to close the first, because of a race condition), the second prompt would close.

The fix

We already know that the modal APIs need to be improved, and ideally an attempt made to remove the difference in API (callback-based vs promise-based) to avoid mistakes in translation such as this one. This PR only makes a minimal fix for context.app.prompt. One approach to make this plugin stable would be to decouple the PromptModal used within the app from the plugin API and have a minimal PromptModal specifically for plugins with a restricted implementation matching the API available the plugin developers.

In this PR, when translating from the callback-based prompt modal to a promise-based helper for plugins, we only resolve the promise if:

  1. the user pressed submit
  2. after the modal has hidden

The demo

This demo uses the example plugin in #3959 and also https://github.com/Vyoam/insomnia-plugin-postman-export, both of which were impacted by this race condition.

2021-08-30 17 27 56

Closes #3959

@develohpanda develohpanda self-assigned this Aug 30, 2021
@develohpanda develohpanda changed the title Don't resolve prompt modal promise until modal has closed to avoid race condition Don't resolve plugin prompt promise until modal has closed, to avoid a race condition Aug 30, 2021
@develohpanda develohpanda force-pushed the feature/ins-911-fails-to-launch-2nd-prompt-window-in branch from 33c1a26 to 68965cf Compare August 30, 2021 05:43
@develohpanda develohpanda force-pushed the feature/ins-911-fails-to-launch-2nd-prompt-window-in branch from 68965cf to 5120233 Compare August 30, 2021 07:06
Copy link
Contributor

@gatzjames gatzjames left a comment

Choose a reason for hiding this comment

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

Nicely done and very well explained 🙏

packages/insomnia-app/app/plugins/context/app.tsx Outdated Show resolved Hide resolved
@@ -69,6 +71,7 @@ class PromptModal extends PureComponent<{}, State> {
onComplete: undefined,
onCancel: undefined,
onDeleteHint: undefined,
onHide: undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just not define it (to match the type)? when we have https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/#exact-optional-property-types on we'll need to come back and fix this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because it shouldn't be initialized to anything; we should just leave it off the initial state here, but since the other handlers initialize as undefined I followed the same pattern here. I vote, in this instance, on coming back to fix it once that's enabled, because this isn't the only instance that will need fixing in this file

Comment on lines 85 to +88
onComplete(value: string) {
resolve(value);
shouldResolve = true;
resolveWith = value;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm guessing you probably agree, actually, but I'm worried about how confusing it is that calling an onComplete callback doesn't actually... well... haha... complete. The promise doesn't actually resolve until the onHide function is called, and that's only called from within the a custom callback on a custom component on a custom wrapper...

        showModal(WrapperModal, {
          title,
          body: <HtmlElementWrapper el={body} onUnmount={options.onHide} />,
          tall: options.tall,
          skinny: options.skinny,
          wide: options.wide,
        });

I totally understand how in this moment it can make sense to do it this way, but I don't think this is an acceptable long-term solution because it adds even more complexity and cognitive-overhead to something that's already quite complex (hence, us introducing the original bug in the first place).

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 the snippet above is from something else, but fundamentally I understand and agree with your observation 🙌 All of this arises from the fact that we have single instance modals shared across all calls; we fix that, we fix many of these issues.

Copy link
Contributor

@dimitropoulos dimitropoulos left a comment

Choose a reason for hiding this comment

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

Overall, I want to see this fixed in production as soon as we can, so I don't think we should blockade this solution for now, but I definitely want to (if we merge this approach) see us fix the problem of global modals soon, which will (in itself) completely solve this issue. I can give examples you want of codebases that accomplish non-global modals.

Copy link
Contributor

@dimitropoulos dimitropoulos left a comment

Choose a reason for hiding this comment

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

This needs to be fixed immediately. The fix here works. Therefore I approve.

However, I think the fact that we're having a patch release due to a gotcha in the overall architecture of modals and then we had to fix it by introducing what I consider to be a new gotcha (that onComplete only defers the actual completion logic to onHide) should cause us to consider improving how modals work in the app. A necessary gotcha - I agree - given the current state of things. But we can change the state of things and incrementally improve. I think we should do so soon.

@develohpanda develohpanda force-pushed the feature/ins-911-fails-to-launch-2nd-prompt-window-in branch from 32ffd07 to 7e4fdb8 Compare August 31, 2021 03:51
@develohpanda develohpanda merged commit c8926d7 into develop Aug 31, 2021
@develohpanda develohpanda deleted the feature/ins-911-fails-to-launch-2nd-prompt-window-in branch August 31, 2021 04:52
develohpanda added a commit that referenced this pull request Aug 31, 2021
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.

Fails to launch 2nd prompt window in templateTag since release 2021.5.0
3 participants