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

Fix dev server hanging on binary files #1017

Merged
merged 5 commits into from
Sep 8, 2020
Merged

Fix dev server hanging on binary files #1017

merged 5 commits into from
Sep 8, 2020

Conversation

drwpow
Copy link
Collaborator

@drwpow drwpow commented Sep 8, 2020

Discussion: #1016

Changes

We were silently processing binary files, trying to scan them for imports, when we shouldn’t.

This PR makes a change where extensions are no longer used to determine if something is a binary file or not. That was error-prone, and we likely get it wrong. We now use isbinaryfile which should be more reliable.

To be clear, our entire build pipeline & plugin workflow still operates by extensions, as it should remain. But us reading files is completely unrelated, and I think relying on extensions too much there have bitten us before. This change should simplify things and make it more automatic.

Testing

@drwpow drwpow requested a review from a team as a code owner September 8, 2020 18:50
@vercel
Copy link

vercel bot commented Sep 8, 2020

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/pikapkg/snowpack/haxmfz204
✅ Preview: Canceled

[update for 2cb3fbd canceled]

@vercel vercel bot temporarily deployed to Preview September 8, 2020 18:51 Inactive
@vercel vercel bot temporarily deployed to Preview September 8, 2020 20:01 Inactive
return parseJsForInstallTargets(extractJSFromHTML({contents, baseExt}));
}
default: {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was the source of the bug: we were trying to parse binary files for imports, but hanging on the error.

case '.jsx':
case '.mjs':
case '.ts':
case '.tsx': {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Now we’re more explicit in when we scan JS files for imports.

Copy link
Owner

Choose a reason for hiding this comment

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

Not opposed to this as a larger direction, but in the interest of not unintentionally breaking users could we instead use that "is binary data" check so that we don't introduce a huge change here? Considering that this will most likely go out in a bug fix, I'd like to minimize the change if possible.

Copy link
Owner

Choose a reason for hiding this comment

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

since we're doing the filter below as well, could we walk this switch statement change back out and still fix the breaking issue for 2.10.2, and then re-introduce the switch statement change in 2.11.0? I'd hate to release a patch release for this that then unnecessarily breaks someone else.

Copy link
Collaborator Author

@drwpow drwpow Sep 8, 2020

Choose a reason for hiding this comment

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

could we instead use that "is binary data" check so that we don't introduce a huge change here?

We actually can’t, because we load string files like .svg and .txt and are parsing them for imports. Before we were passing more things to parseJsForInstallTargets() than I think we realized, and it was just silently working.

I don’t think reverting is safe here; I think reverting is unworkable because we had errors with files before, just different types. +1 agree that patches should change as little as possible. But I think this patch unblocks the most people.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also to be clear, the loading of .svg and .txt files probably shouldn’t happen, but I think that trying to change that would be a riskier change.

return {
baseExt,
expandedExt,
locOnDisk: filePath,
contents: await fs.promises.readFile(filePath, 'utf-8'),
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The underlying issue: we were trying to enforce binary files be code, which led to breakages when we tried to parse that nonsense code

@vercel vercel bot temporarily deployed to Preview September 8, 2020 20:07 Inactive
@@ -1,2 +1 @@
[snowpack] ignoring unsupported file "src/c.abcdefg"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was the only test change: now we don’t throw a warning; we just silently skip it.

Reason is that we were accidentally scanning a lot more text files than intended .json, .txt, but do we surface to the user that we are skipping those? Instead, this PR moves this message to a debug message visible with --verbose.

@vercel vercel bot temporarily deployed to Preview September 8, 2020 20:16 Inactive
@@ -457,17 +458,23 @@ export async function getInstallTargets(
export async function command(commandOptions: CommandOptions) {
const {cwd, config} = commandOptions;

logger.debug('Starting install');
Copy link
Collaborator Author

@drwpow drwpow Sep 8, 2020

Choose a reason for hiding this comment

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

This PR added a few new debug statements for --verbose mode

@@ -15,11 +15,11 @@ export type SnowpackBuiltFile = {code: string | Buffer; map?: string};
export type SnowpackBuildMap = Record<string, SnowpackBuiltFile>;

/** Standard file interface */
export interface SnowpackSourceFile {
export interface SnowpackSourceFile<Type = string | Buffer> {
Copy link
Collaborator Author

@drwpow drwpow Sep 8, 2020

Choose a reason for hiding this comment

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

We need to start a discussion on whether or not we load binary files in Snowpack (sure, we need to copy them over, but should we read them?) However, simply ignoring binary files isn’t a quick change as it affects all of Snowpack.

I know this PR seems like it goes in the direction of reading buffer contents, but it doesn’t. This PR simply exposes the fact we were already doing this (that’s where the bug was), and we make it more transparent to the type system.

export function getEncodingType(ext: string): 'utf-8' | undefined {
return UTF8_FORMATS.includes(ext) ? 'utf-8' : undefined;
/** Read file from disk; return a string if it’s a code file */
export async function readFile(filepath: string): Promise<string | Buffer> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We replaced getEncodingType with a universal readFile function. The way isbinaryfile works, we actually need to read the file contents to determine if it’s a binary file or not, so it makes sense to combine them into one. The end result is this:

const contents = await readFile(filepath);
if (typeof contents === 'string') {
 // This is a UTF-8 file (.js, .css, etc.)
} else {
 // This is a binary file (.jpg, .png, etc.)
}

@@ -166,7 +166,7 @@ const sendFile = (
}

res.writeHead(200, headers);
res.write(body, getEncodingType(ext) as BufferEncoding);
res.write(body);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a benefit of this approach: by not caring about encoding types, we’re less likely to get it wrong.

@drwpow drwpow requested a review from gr2m September 8, 2020 20:32
@vercel vercel bot temporarily deployed to Preview September 8, 2020 21:04 Inactive
Copy link
Owner

@FredKSchott FredKSchott left a comment

Choose a reason for hiding this comment

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

awesome investigation and fix yall. Left some comments, but in the interest of getting this out consider none of them blocking.

@drwpwr curious which change introduced this between 2.10.1 and 2.10.2? Or, is this not actually a new issue?

}
if (finalResult.stats) {
logger.info(printStats(finalResult.stats));
logger.debug('Stats printed');
Copy link
Owner

Choose a reason for hiding this comment

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

nit: this one feels more unnecessary than the others since the stats printing seems to signify the same. Just a thought, don't feel too strongly

case '.jsx':
case '.mjs':
case '.ts':
case '.tsx': {
Copy link
Owner

Choose a reason for hiding this comment

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

Not opposed to this as a larger direction, but in the interest of not unintentionally breaking users could we instead use that "is binary data" check so that we don't introduce a huge change here? Considering that this will most likely go out in a bug fix, I'd like to minimize the change if possible.

@@ -273,7 +264,8 @@ export async function scanImportsFromFiles(
config: SnowpackConfig,
): Promise<InstallTarget[]> {
return loadedFiles
.map(parseFileForInstallTargets)
.filter((sourceFile) => !Buffer.isBuffer(sourceFile.contents)) // filter out binary files from import scanning
Copy link
Owner

Choose a reason for hiding this comment

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

if we already filter by .js, .ts, etc, above should we also filter here? Or, should this really be a warning of "hold up, why is there binary data in a JS file?"

Copy link
Collaborator Author

@drwpow drwpow Sep 8, 2020

Choose a reason for hiding this comment

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

That’s kind of the thing—this install step runs in both build and dev and the reading of those files happens outside this function sometimes. So in order to not have this, we’d have to make an even more sweeping change.

Example:

  if (scannedFiles) {
    // we scanned files in `install.ts` and pass it to `scanImportsFromFiles()`
    installTargets.push(...(await scanImportsFromFiles(scannedFiles, config)));
  } else {
    // we didn’t scan files, so we call scanImports() which scans its own files then calls `scanImportsFromFiles()` anyway
    installTargets.push(...(await scanImports(cwd, config)));
  }

We have to handle many different places within Snowpack reading files, which means multiple code paths where binary files could slip through. I could change all that, but that would have been a bigger PR.

Copy link
Owner

Choose a reason for hiding this comment

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

got it, thanks for explaining 👍

case '.jsx':
case '.mjs':
case '.ts':
case '.tsx': {
Copy link
Owner

Choose a reason for hiding this comment

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

since we're doing the filter below as well, could we walk this switch statement change back out and still fix the breaking issue for 2.10.2, and then re-introduce the switch statement change in 2.11.0? I'd hate to release a patch release for this that then unnecessarily breaks someone else.

@drwpow
Copy link
Collaborator Author

drwpow commented Sep 8, 2020

curious which change introduced this between 2.10.1 and 2.10.2? Or, is this not actually a new issue?

It was introduced by #882, which fixed some of our encoding errors, but not all (it fixed some issues but introduced others). Hoping this PR fully fixes it 🤞🏻

@ralphtheninja
Copy link
Contributor

ralphtheninja commented Sep 8, 2020

It was introduced by #882, which fixed some of our encoding errors, but not all (it fixed some issues but introduced others). Hoping this PR fully fixes it 🤞🏻

I believe it was introduced by #986, which was the first bad commit between 2.10.1 and 2.10.2. See https://github.com/pikapkg/snowpack/pull/986/files#r485214929

@vercel vercel bot temporarily deployed to Preview September 8, 2020 21:46 Inactive
This makes the string vs buffer handling less brittle by centralizing the logic
@vercel vercel bot temporarily deployed to Preview September 8, 2020 21:52 Inactive
@vercel vercel bot temporarily deployed to Preview September 8, 2020 21:54 Inactive
@drwpow drwpow merged commit 236ba63 into master Sep 8, 2020
@drwpow drwpow deleted the bugfix-dev-server branch September 8, 2020 22:13
drwpow added a commit that referenced this pull request Sep 8, 2020
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.

3 participants