Skip to content

fix: prevent duplicate ID generation in fast multi-block renders#6621

Open
binary8421 wants to merge 3 commits intomermaid-js:developfrom
binary8421:fix/id-duplication-in-fast-render
Open

fix: prevent duplicate ID generation in fast multi-block renders#6621
binary8421 wants to merge 3 commits intomermaid-js:developfrom
binary8421:fix/id-duplication-in-fast-render

Conversation

@binary8421
Copy link
Copy Markdown

📑 Summary

Fix duplicate ID generation when rendering multiple Mermaid blocks quickly.

📏 Design Decisions

This fix changes the ID generation to use microsecond-level timestamps instead of milliseconds, greatly reducing the chance of duplicate IDs.
Additionally, if a duplicate ID is detected, an incremental counter is added to ensure uniqueness.

📋 Tasks

Make sure you

  • 📖 have read the contribution guidelines
  • 💻 have added necessary unit/e2e tests.
  • 📓 have added documentation. Make sure MERMAID_RELEASE_VERSION is used for all new features.
  • 🦋 If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented May 28, 2025

⚠️ No Changeset found

Latest commit: 131622e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@netlify
Copy link
Copy Markdown

netlify bot commented May 28, 2025

Deploy Preview for mermaid-js ready!

Name Link
🔨 Latest commit 131622e
🔍 Latest deploy log https://app.netlify.com/projects/mermaid-js/deploys/6839caac4f64eb00087a95d5
😎 Deploy Preview https://deploy-preview-6621--mermaid-js.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions bot added the Type: Bug / Error Something isn't working or is incorrect label May 28, 2025
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented May 28, 2025

Open in StackBlitz

mermaid

npm i https://pkg.pr.new/mermaid-js/mermaid@6621

@mermaid-js/layout-elk

npm i https://pkg.pr.new/mermaid-js/mermaid/@mermaid-js/layout-elk@6621

@mermaid-js/mermaid-zenuml

npm i https://pkg.pr.new/mermaid-js/mermaid/@mermaid-js/mermaid-zenuml@6621

@mermaid-js/parser

npm i https://pkg.pr.new/mermaid-js/mermaid/@mermaid-js/parser@6621

@mermaid-js/tiny

npm i https://pkg.pr.new/mermaid-js/mermaid/@mermaid-js/tiny@6621

commit: 131622e

@codecov
Copy link
Copy Markdown

codecov bot commented May 28, 2025

Codecov Report

❌ Patch coverage is 18.60465% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 3.89%. Comparing base (818699f) to head (131622e).
⚠️ Report is 1655 commits behind head on develop.

Files with missing lines Patch % Lines
packages/mermaid/src/utils.ts 18.60% 35 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           develop   #6621      +/-   ##
==========================================
+ Coverage     3.87%   3.89%   +0.01%     
==========================================
  Files          414     413       -1     
  Lines        43303   43334      +31     
  Branches       666     666              
==========================================
+ Hits          1679    1687       +8     
- Misses       41624   41647      +23     
Flag Coverage Δ
unit 3.89% <18.60%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/mermaid/src/utils.ts 18.15% <18.60%> (+0.06%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@argos-ci
Copy link
Copy Markdown

argos-ci bot commented May 28, 2025

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - May 30, 2025, 3:19 PM

Comment thread packages/mermaid/src/utils.ts Outdated
// v11: Use the actual value of seed string to generate an initial value for count.
this.count = seed ? seed.length : 0;
this.next = deterministic ? () => this.count++ : () => Date.now();
this.next = deterministic ? () => this.count++ : () => TimeProvider.getTimestamp();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Doing this should solve the problem of duplicate ID, and we won't have to add the complexity of TimeProvider.
We don't have a requirement that the ID should be the timestamp.

Suggested change
this.next = deterministic ? () => this.count++ : () => TimeProvider.getTimestamp();
this.next = deterministic
? () => this.count++
: () => Number.parseInt(`${Date.now()}${this.count++}`);

Or even simpler, we could concatenate it and return the string, we'd have to fix the tests to match the functionality though.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thank you for your input. From the perspective of long-term project maintenance, I’d like to explain why I believe TimeProvider is a suitable choice:

Timestamp Precision and Uniqueness:
By leveraging performance.now() for higher precision and ensuring monotonic increments, TimeProvider guarantees the generation of unique IDs.
Cross-Platform Compatibility:
TimeProvider is designed to work reliably across diverse environments, such as Node.js or older browsers, by handling differences in timing APIs.
Maintainability and Extensibility:
The modular design of TimeProvider encapsulates timestamp generation logic, making it easier to maintain and extend. If future requirements demand adjustments to timestamp precision or uniqueness strategies, changes can be made within TimeProvider without impacting the calling code.
Test Friendliness:
By building on the existing project structure, TimeProvider minimizes conceptual changes, allowing the current test cases to remain intact without requiring modifications.
I hope this clarifies the rationale behind using TimeProvider. If there are specific concerns or alternative approaches you’d like to explore, I’m happy to discuss further to find the best solution for the project!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are you using AI for this? 🤔

@sidharthv96 sidharthv96 self-assigned this May 28, 2025
By the way, since this class is created every time, the "+1" operation has no effect.

Co-authored-by: Sidharth Vinod <github@sidharth.dev>
Copy link
Copy Markdown
Author

@binary8421 binary8421 left a comment

Choose a reason for hiding this comment

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

I've tested your code, and ID conflicts still occur. Detailed comments have been added.

this.next = deterministic ? () => this.count++ : () => TimeProvider.getTimestamp();
this.next = deterministic
? () => this.count++
: () => Number.parseInt(`${Date.now()}${this.count++}`);
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

By the way, since this class is created every time, the "+1" operation has no effect.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the implementation has diverged somehow. My expectation was the IdGenerator was for individual nodes of the diagram, as the doc suggests. But currently it's only used for the ID of the diagram (which makes the deterministic flag pointless, as you said).

/**
* This option controls if the generated ids of nodes in the SVG are
* generated randomly or based on a seed.
* If set to `false`, the IDs are generated based on the current date and
* thus are not deterministic. This is the default behavior.
*
* This matters if your files are checked into source control e.g. git and
* should not change unless content is changed.
*
*/
deterministicIds?: boolean;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In this case, we'd be better off just using a cuid for diagram id. Which has timestamp and a random component.

Comment thread packages/mermaid/src/utils.ts Outdated
// TODO: Seed is only used for length?
// v11: Use the actual value of seed string to generate an initial value for count.
this.count = seed ? seed.length : 0;
this.next = deterministic ? () => this.count++ : () => TimeProvider.getTimestamp();
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Using a constant object to handle conflicts is a good approach.

@EnochGao
Copy link
Copy Markdown

EnochGao commented Jul 8, 2025

Are there any follow-up plans?Rendering multiple nodes simultaneously does indeed lead to duplicate ids

@sidharthv96
Copy link
Copy Markdown
Member

@binary8421 the IDGenerator is created once per invocation of run function, not for every diagram.
Are you calling run in parallel, what's the use case for that? run is expected to be used once during page load, to render all mermaid diagrams with the given selector.

@EnochGao This PR only handles IDs for diagrams, not nodes. Can you clarify your use case as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Bug / Error Something isn't working or is incorrect

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants