@@ -179,6 +179,8 @@ clear_owner_if_matches(PyThreadState *tstate, PyLazyImportObject *m,
179179 _PyImport_AcquireLock (interp );
180180 if (owner_matches (m , globals , name )) {
181181 m -> lz_owner_pending = 0 ;
182+ // Keep lz_owner_finalized set: this owner has been committed or
183+ // abandoned, so later stores of the same proxy are not canonical.
182184 Py_CLEAR (m -> lz_owner_globals );
183185 Py_CLEAR (m -> lz_owner_key );
184186 }
@@ -217,7 +219,10 @@ finalize_owner_if_matches(PyThreadState *tstate, PyLazyImportObject *m,
217219 int finalized = 0 ;
218220 PyInterpreterState * interp = tstate -> interp ;
219221 _PyImport_AcquireLock (interp );
220- if (!m -> lz_owner_finalized && owner_matches (m , globals , name )) {
222+ if (!m -> lz_owner_pending &&
223+ !m -> lz_owner_finalized &&
224+ owner_matches (m , globals , name ))
225+ {
221226 m -> lz_owner_finalized = 1 ;
222227 finalized = 1 ;
223228 }
@@ -237,21 +242,45 @@ restore_owner_if_matches(PyThreadState *tstate, PyLazyImportObject *m,
237242 _PyImport_ReleaseLock (interp );
238243}
239244
240- int
241- _PyLazyImport_CommitIfCurrent (PyThreadState * tstate , PyObject * op ,
242- PyObject * globals , PyObject * name ,
243- PyObject * value )
245+ static int
246+ owner_is_pending (PyThreadState * tstate , PyLazyImportObject * m ,
247+ PyObject * globals , PyObject * name )
244248{
245- if (! PyLazyImport_CheckExact ( op ) ||
246- ! PyDict_CheckExact ( globals ) ||
247- ! PyUnicode_CheckExact ( name ))
248- {
249- return 0 ;
249+ int pending = 0 ;
250+ PyInterpreterState * interp = tstate -> interp ;
251+ _PyImport_AcquireLock ( interp );
252+ if ( m -> lz_owner_pending && owner_matches ( m , globals , name )) {
253+ pending = 1 ;
250254 }
255+ _PyImport_ReleaseLock (interp );
256+ return pending ;
257+ }
251258
252- PyLazyImportObject * m = PyLazyImportObject_CAST (op );
253- int finalized_owner = finalize_owner_if_matches (tstate , m , globals , name );
259+ static int
260+ claim_stored_owner (PyThreadState * tstate , PyLazyImportObject * m ,
261+ PyObject * * globals , PyObject * * name )
262+ {
263+ int claimed = 0 ;
264+ PyInterpreterState * interp = tstate -> interp ;
265+ _PyImport_AcquireLock (interp );
266+ if (!m -> lz_owner_pending &&
267+ !m -> lz_owner_finalized &&
268+ m -> lz_owner_globals != NULL &&
269+ m -> lz_owner_key != NULL )
270+ {
271+ m -> lz_owner_finalized = 1 ;
272+ * globals = Py_NewRef (m -> lz_owner_globals );
273+ * name = Py_NewRef (m -> lz_owner_key );
274+ claimed = 1 ;
275+ }
276+ _PyImport_ReleaseLock (interp );
277+ return claimed ;
278+ }
254279
280+ static int
281+ commit_if_current (PyObject * op , PyObject * globals , PyObject * name ,
282+ PyObject * value )
283+ {
255284 PyObject * current = NULL ;
256285 int err = 0 ;
257286 Py_BEGIN_CRITICAL_SECTION (globals );
@@ -265,7 +294,28 @@ _PyLazyImport_CommitIfCurrent(PyThreadState *tstate, PyObject *op,
265294 }
266295 Py_END_CRITICAL_SECTION ();
267296 Py_XDECREF (current );
297+ return err ;
298+ }
268299
300+ int
301+ _PyLazyImport_CommitIfCurrent (PyThreadState * tstate , PyObject * op ,
302+ PyObject * globals , PyObject * name ,
303+ PyObject * value )
304+ {
305+ if (!PyLazyImport_CheckExact (op ) ||
306+ !PyDict_CheckExact (globals ) ||
307+ !PyUnicode_CheckExact (name ))
308+ {
309+ return 0 ;
310+ }
311+
312+ PyLazyImportObject * m = PyLazyImportObject_CAST (op );
313+ if (owner_is_pending (tstate , m , globals , name )) {
314+ return 0 ;
315+ }
316+
317+ int finalized_owner = finalize_owner_if_matches (tstate , m , globals , name );
318+ int err = commit_if_current (op , globals , name , value );
269319 if (finalized_owner ) {
270320 if (err < 0 ) {
271321 restore_owner_if_matches (tstate , m , globals , name );
@@ -289,27 +339,19 @@ _PyLazyImport_CommitStoredIfCurrent(PyThreadState *tstate, PyObject *op,
289339 PyObject * globals = NULL ;
290340 PyObject * name = NULL ;
291341
292- PyInterpreterState * interp = tstate -> interp ;
293- // Copy the owner under the import lock, then release it before entering a
294- // dict critical section in _PyLazyImport_CommitIfCurrent().
295- _PyImport_AcquireLock (interp );
296- if (!m -> lz_owner_pending &&
297- !m -> lz_owner_finalized &&
298- m -> lz_owner_globals != NULL &&
299- m -> lz_owner_key != NULL )
300- {
301- globals = Py_NewRef (m -> lz_owner_globals );
302- name = Py_NewRef (m -> lz_owner_key );
342+ if (!claim_stored_owner (tstate , m , & globals , & name )) {
343+ return 0 ;
303344 }
304- _PyImport_ReleaseLock (interp );
305345
306- int err = 0 ;
307- if (globals != NULL && name != NULL ) {
308- err = _PyLazyImport_CommitIfCurrent (
309- tstate , op , globals , name , value );
346+ int err = commit_if_current (op , globals , name , value );
347+ if (err < 0 ) {
348+ restore_owner_if_matches (tstate , m , globals , name );
310349 }
311- Py_XDECREF (globals );
312- Py_XDECREF (name );
350+ else {
351+ clear_owner_if_matches (tstate , m , globals , name );
352+ }
353+ Py_DECREF (globals );
354+ Py_DECREF (name );
313355 return err ;
314356}
315357
@@ -345,7 +387,8 @@ PyDoc_STRVAR(lazy_import_doc,
345387"\n"
346388"A successful resolution is cached. Instances of this object accessed\n"
347389"from the global scope will be automatically imported based upon their\n"
348- "name and then replaced with the imported value." );
390+ "name. The original global is replaced only if it still refers to this\n"
391+ "lazy import object." );
349392
350393PyTypeObject PyLazyImport_Type = {
351394 PyVarObject_HEAD_INIT (& PyType_Type , 0 )
0 commit comments