Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 19, 2025

  • Explore repository structure and understand the issue
  • Identify root cause: Event listener management in standalone service causes promises to never resolve when multiple concurrent calls occur
  • Fix event listener management to support concurrent requests
  • Build and verify the fix compiles correctly
  • Run existing tests to ensure no regressions
  • Run code review
  • Run security scan

Root Cause Analysis:
The issue occurs in /packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts. Methods like convert() (used by getSmiles()) and generateImageAsBase64() used an anti-pattern where they:

  1. Removed any existing event listener for that event type
  2. Added a new listener for the current request
  3. Posted the message to the worker

When multiple concurrent requests occurred (e.g., calling getSmiles() twice in quick succession), the second call would remove the first call's listener before it could receive its response, causing the first promise to never resolve.

Fix Applied:
Two different patterns based on whether the method checks inputData:

Pattern 1 - For methods with inputData checks (convert, generateImageAsBase64):

  • Add listeners WITHOUT removing existing ones first
  • Each listener checks if the response's inputData matches its request
  • Remove the listener AFTER the promise resolves/rejects for that specific request
  • This allows multiple listeners for the same event type to coexist

Pattern 2 - For methods without inputData checks (all others):

  • Use EventEmitter.once() instead of removeListener + addListener
  • Each request gets its own one-time listener that auto-removes after firing
  • No risk of interference between concurrent requests

Methods Fixed:

  • convert() - Used by getSmiles(), getRxn(), etc.
  • generateImageAsBase64() - Used for image generation
  • layout(), clean(), aromatize(), dearomatize(), calculateCip(), automap(), check(), calculate(), explicitHydrogens(), calculateMacromoleculeProperties(), getInChIKey(), info()

Verification:

  • ✅ All existing tests pass
  • ✅ Build succeeds for both core and standalone packages
  • ✅ Code review completed (1 comment addressed - pattern is actually consistent)
  • ✅ Security scan passed with no issues
Original prompt

This section details on the original issue you should resolve

<issue_title>getSmiles() promise never resolves in standalone mode</issue_title>
<issue_description>Steps to Reproduce

  1. Try to invoke getSmiles().then() or await getSmiles()
  2. Observe that the promise never resolves
  3. Observe that no error is thrown

Actual behavior
The promise never resolves, nor throws an error.

Expected behavior
The promise should resolve around a SMILES string (or throw an error).

Screenshots

Desktop (please complete the following information):

  • Browser: brave, firefox
  • Version 3.2.0

System Information:

Operating System:

Display Server:

  • Session Type: Wayland
  • Window Manager: Sway 1.10.1

GPU Information:

  • GPU: AMD Radeon 890M (Strix)
  • Driver: Mesa 25.0.5-arch1.1 (radeonsi, gfx1150, LLVM 19.1.7, DRM 3.61)
  • Mesa Package: mesa 1:25.0.5-1
  • Linux Firmware: linux-firmware 20250508.788aadc8-1

Hardware:

  • Formfactor: Laptop
  • Manufacturer: Framework
  • Model: AMD 13
  • CPU: AMD Ryzen AI 9 HX 370 w/ Radeon 890M
  • Cores: 12 cores, 24 threads
  • Memory: 30GB RAM (16GB available)
  • Swap: 33GB

Ketcher version [e.g. v2.4.2].

3.2.0

Test case

Additional context

Please see fully working example of error: https://github.com/ReactWise/frontend-interview/tree/ketcher

npm install
npm run dev

Try drawing a molecule, then observe terminal, or click one of the buttons to try other methods. Relevant file: https://github.com/ReactWise/frontend-interview/blob/ketcher/src/moduleone.tsx (sorry about the noise, I just needed a quick vite skeleton so our interview test repo worked best)
</issue_description>

Comments on the Issue (you are @copilot in this section)

@AlexeyGirin Hi @komali2 Try ketcher.getSmiles() from browser console - does it work for you? @mobilisf @komali2 Also if the standard `getSmiles()` method is unavailable, consider using alternative approaches:

1. Apply Alternative Approaches to Obtain the Result

Extract the Structure Using Another API:

Instead of getSmiles(), try using other APIs such as getMolfile() and then convert the result into SMILES using external libraries or tools.

Example:

const molfile = await ketcher.getMolfile();
console.log("Molfile generated:", molfile);

// Convert MOL to SMILES using an external library
const smiles = externalConverter.convertMolToSmiles(molfile);
console.log("Converted SMILES:", smiles);

To perform the conversion, you can use libraries such as Open Babel or RDKit.js.


2. Handle Promise Hanging Situations

Technically, you can force the method to complete by wrapping it with a timer and manually canceling the wait if it takes too long. While this won't resolve the core issue, it allows you to manage the Promise hanging situation:

Example:

function timeoutPromise(promise, ms) {
    const timeout = new Promise((_, reject) =>
        setTimeout(() => reject(new Error(`Promise did not resolve within ${ms}ms`)), ms)
    );
    return Promise.race([promise, timeout]);
}

try {
    const smiles = await timeoutPromise(ketcher.getSmiles(), 5000);
    console.log("SMILES result:", smiles);
} catch (err) {
    console.error("SMILES generation failed or timed out:", err);
}

This code will terminate execution after 5 seconds if the Promise doesn't resolve.

Fixes #7232


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

…rent requests

Co-authored-by: AlexeyGirin <26869421+AlexeyGirin@users.noreply.github.com>
Copilot AI requested a review from AlexeyGirin October 19, 2025 18:45
@AlexeyGirin AlexeyGirin marked this pull request as ready for review October 20, 2025 09:11
@AlexeyGirin AlexeyGirin requested review from svvald and removed request for NataliaLoginova October 20, 2025 09:11
@AlexeyGirin AlexeyGirin changed the title [WIP] Fix getSmiles() promise never resolves in standalone mode #7232 - getSmiles() promise never resolves in standalone mode Nov 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

getSmiles() promise never resolves in standalone mode

2 participants