Skip to content

Use PyModule_AddObjectRef over PyModule_AddObject - #1059

Merged
provinzkraut merged 2 commits into
msgspec:mainfrom
provinzkraut:use-addobjectref
Jun 16, 2026
Merged

provinzkraut merged 2 commits into
msgspec:mainfrom
provinzkraut:use-addobjectref

Conversation

@provinzkraut

Copy link
Copy Markdown
Member

Use PyModule_AddObjectRef instead of PyModule_AddObject, to make these calls more robust and terse.

This also fixes quite a few leaks in error cases. We often used the pattern

Py_INCREF(&Some_Type);
if (PyModule_AddObject(m, "<name>", (PyObject *)&Some_Type) < 0)
    return NULL;

where we don't Py_DECREF in the error case.

@provinzkraut
provinzkraut requested a review from sobolevn June 12, 2026 18:18

@sobolevn sobolevn 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.

This is a nice first step! What would I ideally recommend?

  1. Create ADD_MODULE_REF macro that will abstract away this check + goto error
  2. In error: we can clean up all temporary references
  3. We should define these references at the top set as NULL
  4. Py_XDECREF them on error :)

This would also require to modify existing macros as well.

What do you think?

Comment thread src/msgspec/_core.c Resolved
@provinzkraut

Copy link
Copy Markdown
Member Author
  1. Create ADD_MODULE_REF macro that will abstract away this check + goto error
  2. In error: we can clean up all temporary references
  3. We should define these references at the top set as NULL
  4. Py_XDECREF them on error :)

Can you explain 4? Do you mean on the error check we're doing on the PyModule_AddObjectRef call, or on errors later on?

@sobolevn

Copy link
Copy Markdown
Member

@provinzkraut

pseudo-code :)

PyObject *temp_module = NULL;
PyObject *temp_obj = NULL;

#define SET_MODULE_REF(ATTR, VALUE) \
  do { \
      if (PyModule_AddObjectRef(m, (ATTR), (VALUE)) < 0) \
          goto error; \
  } while (0)

#define IMPORT_TEMP_MODULE(MOD_NAME) \
  do { \
     Py_XDECREF(temp_module); // clean other existing temp module \
     temp_module = PyImport_ImportModule((MOD_NAME));
     if (temp_module == NULL) goto error;
  } while (0)

SET_MODULE_REF("JSONDecoder", (PyObject *)&JSONDecoder_Type);
// ...
IMPORT_TEMP_MODULE("typing");
SET_REF(typing_union, "Union");

error:
    Py_XDECREF(temp_module);
    Py_XDECREF(temp_obj);

@Siyet

Siyet commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Reviewed and tested this (built the branch, full unit suite passes). The swap is refcount-correct everywhere, and on failure it is actually safer than before (the old Py_INCREF + PyModule_AddObject leaked on error). It also fixes a small real bug: Field was added without a Py_INCREF, so it under-counted Field_Type by one on import (harmless only because it is a static type). AddObjectRef fixes that.

On @sobolevn's macro idea: I like it, but I would do it in a separate PR. The goto error + temp cleanup is a different job (it also fixes the temp_module leaks on the import paths), and those leaks only trigger on init failure, which is practically unreachable. No need to hold this clean change for it.

LGTM to merge as a first step.

@provinzkraut

Copy link
Copy Markdown
Member Author

@sobolevn I like the macro idea, but I agree with @Siyet that I'd prefer doing that in a separate PR

@sobolevn sobolevn 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.

Thanks a lot!

@provinzkraut
provinzkraut added this pull request to the merge queue Jun 16, 2026
Merged via the queue into msgspec:main with commit a701460 Jun 16, 2026
2 checks passed
@provinzkraut
provinzkraut deleted the use-addobjectref branch June 16, 2026 12:04
digit50 pushed a commit to digit50/msgspec that referenced this pull request Oct 17, 2026
Clean up handling of module imports in `PyInit__core`:

- Unify error handling; Introduce and always use `error` label to ensure
temporary objects are cleaned up (previously we often did `return NULL`
early, missing `Py_XDECREF` in a few places)
- Introduce a new `SET_MODULE_REF` macro to uniformly handle the "call
`PyModule_AddObjectRef`, check for errors" pattern
- Introduce a new `IMPORT_TEMP_MODULE` to uniformly handle the "import
module as `temp_module`, get attribute from `temp_module` as `temp_obj`,
and assign it to the module state"


Removes 2 leaks found with `LSan` on (checked locally, as it's turned
off in CI)

<hr>

Based on @sobolevn's suggestion here:
msgspec#1059 (review)

---------

Co-authored-by: sobolevn <[email protected]>
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