Skip to content

Make AtomValenceTool initialization thread-safe - #1311

Open
kmansouri wants to merge 3 commits into
cdk:mainfrom
kmansouri:fix-atom-valence-thread-safety
Open

kmansouri wants to merge 3 commits into
cdk:mainfrom
kmansouri:fix-atom-valence-thread-safety

Conversation

@kmansouri

@kmansouri kmansouri commented Oct 2, 2026 •

Copy link
Copy Markdown

Summary

Replace the lazily initialized mutable valence map in AtomValenceTool with a
safely published immutable map initialized during class loading.

The previous implementation assigned the shared map before populating it. A
concurrent first call could therefore observe a non-null but incomplete table
and fail while unboxing a missing value.

Changes

  • construct the complete valence table during class initialization;
  • expose the table internally as an immutable map;
  • preserve the existing behavior for element symbols not present in the table;
  • add a focused unit test that verifies all currently supported symbols return
    a nonzero valence.

This issue was found while porting PaDEL-Descriptor from CDK 1.x to CDK 2.13,
but the change and test are independent of PaDEL.

Fixes #1308.

Testing

  • The original standalone concurrent-first-use reproducer was run 20 times
    against the patched class with no failure.
  • The supported-element test verifies the public functionality without
    duplicating the implementation's valence values.
  • The repository CI should run the complete Maven and code-quality checks.

@kmansouri kmansouri closed this Oct 2, 2026
@kmansouri kmansouri reopened this Oct 2, 2026
@johnmay

johnmay commented Oct 2, 2026

Copy link
Copy Markdown
Member

Can you remove the test please - not needed or useful.

@johnmay

johnmay commented Oct 2, 2026

Copy link
Copy Markdown
Member

This one is okay:

@Test
    void unsupportedElement() {
        Assertions.assertThrows(IllegalArgumentException.class,
                () -> AtomValenceTool.getValence(new Atom("Fe")));
    }

But the one which just mirrors all the data is slop.

@johnmay

johnmay commented Oct 2, 2026

Copy link
Copy Markdown
Member

@kmansouri Note it did not build because of the new exception

@egonw
egonw self-requested a review October 4, 2026 06:04

@egonw egonw left a comment •

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.

First, I think the move to a static, immutable collection is a good improvement, thanks!

A few requests, see comments on the code below.

The failing test is interesting, as the original code does not seem to define a valence for Pt. The problem is that the patch changes the API, by introducing a new exception. API changes should not happen in a stable series. I now think this is exactly why we now have a failing test. Let me think a bit more about the right "fix" here: 1. restore the previous API, or 2. fix the downstream code and/or test.

Maybe the fix is just to add the defined valence for Pt.

@@ -0,0 +1,46 @@
/* Copyright (C) 2026 Kamel Mansouri

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.

Please add some further contact information. Traditionally, your email address. This is important, allowing people to contact you later on. Format:

Copyright (C) 2026  Kamel Mansouri <your@email.org>

5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 1, 2, 2, 2, 2};

for (int i = 0; i < symbols.length; i++) {
Assertions.assertEquals(valences[i], AtomValenceTool.getValence(new Atom(symbols[i])), symbols[i]);

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.

If not mistaken, the third parameter is an error message. But just listing the symbol is not very informative. I would suggest something like

"Unexpected valence for atom with symbol " + symbols[i]

@Test
void unsupportedElement() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> AtomValenceTool.getValence(new Atom("Fe")));

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 guess this is technically right. Should Fe not have a valence defined? Like Pt?

@kmansouri

Copy link
Copy Markdown
Author

Thanks @johnmay and @egonw. I addressed the review comments in the latest commit:

  • retained the static, immutable, safely initialized valence table;
  • removed the new explicit IllegalArgumentException, restoring the previous
    behavior for symbols not present in the table and avoiding an API change;
  • removed the duplicated array of expected valence values;
  • simplified the test to verify that all currently supported symbols return a
    nonzero valence;
  • added a descriptive assertion message;
  • added my contact email to the test-file copyright header.

I left Fe/Pt coverage unchanged so this PR remains focused on thread-safe
initialization. Missing element definitions can be considered separately.

This branch has not been deployed

No deployments
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.

AtomValenceTool is not thread-safe: concurrent first use can observe a partially initialized valence table

3 participants