-
Notifications
You must be signed in to change notification settings - Fork 468
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
fix(appbuilder): Prevent parallel build processes on the same template #6172
base: master
Are you sure you want to change the base?
Conversation
*/ | ||
export function throwIfTemplateIsBeingBuilt(templatePath: string) { | ||
if (isBuildInProgress(templatePath)) { | ||
throw new ToolkitError('Template is already being built', { code: 'BuildInProgress' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's say "This template is already being built" to make sure users know they are trying to build the same template again, and that they can build different templates simultaneously.
@@ -108,6 +108,35 @@ export async function updateRecentResponse( | |||
getLogger().warn(`sam: unable to save response at key "${key}": %s`, err) | |||
} | |||
} | |||
|
|||
const buildProcessMementoRootKey = 'samcli.build.processes' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's change this to something like
const BUILD_PROCESS_MEMENTO_ROOT_KEY = 'samcli.build.processes'; and maybe put it in the samutils if it makes sense to. We can also do the same with the 'global' as we should limit the amount of constant strings we have
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above format wouldn't work since eslint only allows camelCase or PascalCase formats. The constant is defined in the sam/utils file. Are you referring to a different utils file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, we'd put this in a constants.ts file, but if not we can disable eslint for this line. The convention is that constants should be in all caps.
} | ||
|
||
export async function registerTemplateBuild(templatePath: string) { | ||
await updateRecentResponse(buildProcessMementoRootKey, 'global', templatePath, 'true') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add error handling here to catch if something goes wrong in the process of registering a template build? I think currently, the error would just bubble up from the actual sam build command, but it could be the case that build command succeeds but the registering fails
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updateRecentResponse() already handles that by logging the error.
export async function updateRecentResponse(
mementoRootKey: string,
identifier: string,
key: string,
value: string | undefined
) {
try {
const root = globals.context.workspaceState.get(mementoRootKey, {} as Record<string, Record<string, string>>)
await globals.context.workspaceState.update(mementoRootKey, {
...root,
[identifier]: { ...root[identifier], [key]: value },
})
} catch (err) {
getLogger().warn(`sam: unable to save response at key "${key}": %s`, err)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we capture the error so that we can see it in our Kibana dashboard? I don't think we see logger errors in our dashboard iirc
*/ | ||
export function throwIfTemplateIsBeingBuilt(templatePath: string) { | ||
if (isBuildInProgress(templatePath)) { | ||
throw new ToolkitError('Template is already being built', { code: 'BuildInProgress' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make the error code an enum? I think we had a class with different error codes already right, let's add this one in with them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are specific SAM CLI error patterns. I don't think it makes sense to add this one to them since this error doesn't come directly from SAM CLI.
654bf52
to
3471429
Compare
3471429
to
86848f2
Compare
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
export const BUILD_PROCESS_MEMENTO_ROOT_KEY = 'samcli.build.processes' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the standard we have in the toolkit is to use camal case, even for constants
@@ -0,0 +1,12 @@ | |||
/* eslint-disable header/header */ | |||
/* eslint-disable @typescript-eslint/naming-convention */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you shouldn't need these es-lint disables
@@ -1,3 +1,5 @@ | |||
/* eslint-disable header/header */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you shouldn't need these es-lint disables
c46878d
to
0922083
Compare
* @Param templatePath The path to the template.yaml file | ||
*/ | ||
function isBuildInProgress(templatePath: string): boolean { | ||
return getRecentResponse(buildProcessMementoRootKey, globalIdentifier, templatePath) !== undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Future note: these functions (and probably more, I guess) are using shared mutable state. You may want to consider wrapping this in a dedicated file similar to the existing packages/core/src/shared/globalState.ts
, except for "workspace" state. That makes it clear when callers are using this global state. And it collects the pattern in one place, instead of many spread-out functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left few comments, thanks for implementing these changes!
*/ | ||
export function throwIfTemplateIsBeingBuilt(templatePath: string) { | ||
if (isBuildInProgress(templatePath)) { | ||
throw new ToolkitError('This template is already being built', { code: 'BuildInProgress' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say this check is too strict. There are possibility that the build is finished or exited but we also failed to unregisterTemplateBuild
or even toolkit maybe reload itself during the build process. In this case the user will never able to build the template again. One possible solution is to add a timeout to this cache. Like 2 minutes or 5 minutes. In this way, if the worst case happens ( build finished but we failed to update memeto) the user will still be able to build after time out is reached.
location: vscode.ProgressLocation.Notification, | ||
}, | ||
async (progress) => { | ||
progress.report({ message: `Building SAM template at ${params.template.uri.path}` }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we also add a cancel button to it? you can refer the implementation here:
aws-toolkit-vscode/packages/core/src/shared/utilities/cliUtils.ts
Lines 235 to 238 in 5bfb867
const progress = await showMessageWithCancel( | |
localize('AWS.cli.installProgress', 'Installing: {0} CLI', cliToInstall.name), | |
timeout | |
) |
@@ -551,3 +564,23 @@ describe('SAM runBuild', () => { | |||
}) | |||
}) | |||
}) | |||
|
|||
async function runInParallel(samLocation: SamAppLocation): Promise<[SamBuildResult, SamBuildResult]> { | |||
return Promise.all([runBuild(new AppNode(samLocation)), runBuild(new AppNode(samLocation))]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry this would cause some flakiness as the two build is run at same time. There might be a race condition that both Build run throwIfTemplateIsBeingBuilt
both passes and then both run registerTemplateBuild
and proceed to build. In this case the two build will be run together and cause test to fail. I would suggest we add a 0.05s wait time between them to avoid the unlikely but possible race condition.
Context
We currently allow multiple build processes to run at the same time for the same template.
Problem
When running a build, it’s possible to start a second build for the same project. This results in both builds failings
Solution
Prevent parallel builds on the same template
feature/x
branches will not be squash-merged at release time.License: I confirm that my contribution is made under the terms of the Apache 2.0 license.