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

A race condition issue in postcss-custom-properties #331

Closed
3 tasks done
malash opened this issue Apr 8, 2022 · 5 comments · Fixed by #332
Closed
3 tasks done

A race condition issue in postcss-custom-properties #331

malash opened this issue Apr 8, 2022 · 5 comments · Fixed by #332

Comments

@malash
Copy link
Contributor

malash commented Apr 8, 2022

Bug description

Recently I found a race condition issue of postcss-custom-properties while using it with other async PostCSS plugins.

I cost a lot of time to figure out the root issue and finally I can provide a easy way to reproduce this bug.

Full example repo: https://github.com/malash/postcss-plugins-issue-331

To reproduce this issue you need:

  1. Use postcss-custom-properties with another async plugin ( which means the listeners of plugin should return a promise ).

I implemented this fake plugin:

// this fake async plugin do nothing but await for next tick
const fakeAsyncPlugin = (opts = {}) => {
  // Plugin creator to check options or prepare caches
  return {
    postcssPlugin: 'A fake async plugin',
    Declaration: async () => {
      await new Promise(resolve => setTimeout(resolve, 0));
    },
  };
};
fakeAsyncPlugin.postcss = true;
  1. Create a PostCSS instance and make sure the async runs before postcss-custom-properties.
const postcssInstance = postcss([
  fakeAsyncPlugin(),
  postcssCustomProperties({
    preserve: false,
  }),
]);
  1. Call process method in parallel. Make sure one CSS contains custom properties and others doesn't.
const WITH_CUSTOM_PROPERTIES = `
:root {
  --color: red;
}

h1 {
  color: var(--color);
}
`;

const WITHOUT_CUSTOM_PROPERTIES = `
div {
  height: 10px;
}
`;

await Promise.all([
  postcssInstance.process(WITH_CUSTOM_PROPERTIES);
  postcssInstance.process(WITHOUT_CUSTOM_PROPERTIES);
  postcssInstance.process(WITHOUT_CUSTOM_PROPERTIES);
  postcssInstance.process(WITHOUT_CUSTOM_PROPERTIES);
  ...
]);
  1. Check the output of WITH_CUSTOM_PROPERTIES, the var(--color) is not replaced as expected.

In my case I use PostCSS with Webpack, the postcss-loader usually run in parallel, that's why I fall into this bug.

Root issue and solution

I believe the root cause is this line:

let customProperties: Map<string, valuesParser.ParsedValue> = new Map();

The plugin function is initialed only once in the postcss([pluginFn()]) call. But the prepare listener runs for each CSS input file. In this plugin, the customProperties should be placed `prepare function.

// let customProperties: Map<string, valuesParser.ParsedValue> = new Map();

return {
  postcssPlugin: 'postcss-custom-properties',
  prepare() {
    // move to here
    let customProperties: Map<string, valuesParser.ParsedValue> = new Map();
    return {
      Once:() => {},
      Declaration: () => {},
    };
  },
};

Source CSS

:root {
  --color: red;
}

h1 {
  color: var(--color);
}

Expected CSS

h1 {
  color: red;
}

Actual CSS

:root {
  --color: red;
}

h1 {
  color: var(--color);
}

Does it happen with npx @csstools/csstools-cli <plugin-name> minimal-example.css?

No

Debug output

No response

Extra config

No response

What plugin are you experiencing this issue on?

No response

Plugin version

12.1.6

What OS are you experiencing this on?

macOS

Node Version

16.13.0

Validations

  • Follow our Code of Conduct
  • Check that there isn't already an issue that request the same feature to avoid creating a duplicate.

Would you like to open a PR for this bug?

  • I'm willing to open a PR
@romainmenke
Copy link
Member

Thank you for reporting this!

I don't think you even need the second async plugin to trigger this.
The plugin isn't safe to use in this way :/

At the moment it only supports reuse of the same instance in series, not in parallel.
We will have to take a deep dive to see if this can be improved.

To use the plugin in parallel you can create multiple instances.

@malash
Copy link
Contributor Author

malash commented Apr 8, 2022

@romainmenke I added a Root issue and solution section in this issue's description. I tested it in my project and it works.

Could you take a look? Also if it is ok I'm glad to create a PR later.

@romainmenke
Copy link
Member

romainmenke commented Apr 8, 2022

My current gut feeling is that everything should be moved into prepare.
But I am unsure of what the side effects of this will be.

Any PR is always welcome!

@malash
Copy link
Contributor Author

malash commented Apr 8, 2022

@romainmenke #332 😄

@Antonio-Laguna
Copy link
Member

Antonio-Laguna commented Apr 8, 2022

@malash this has been released as 12.1.7. Thanks a lot for reporting and fixing this!

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 a pull request may close this issue.

3 participants