diff --git a/Cython/Utility/ModuleSetupCode.c b/Cython/Utility/ModuleSetupCode.c
index 174e4c125b87d831a7d7d5ccc1a3e55569fa2baa..9d8962b0bb06d897dfb7cef0028fea409d9d5a7d 100644
--- a/Cython/Utility/ModuleSetupCode.c
+++ b/Cython/Utility/ModuleSetupCode.c
@@ -580,8 +580,12 @@ static PyObject* ${cleanup_cname}(CYTHON_UNUSED PyObject *self, CYTHON_UNUSED Py
 //@substitute: naming
 
 static int __Pyx_RegisterCleanup(void) {
-    // Don't use Py_AtExit because that has a 32-call limit
-    // and is called after python finalization.
+    // Don't use Py_AtExit because that has a 32-call limit and is called
+    // after python finalization.
+    // Also, we try to prepend the cleanup function to "atexit._exithandlers"
+    // because CPython runs them last-to-first. Being run last allows
+    // user exit code to run before us that may depend on the globals
+    // and cached objects that we are about to clean up.
 
     static PyMethodDef cleanup_def = {__Pyx_NAMESTR("__cleanup"), (PyCFunction)${cleanup_cname}, METH_NOARGS, 0};
 
@@ -593,22 +597,43 @@ static int __Pyx_RegisterCleanup(void) {
     int ret = -1;
 
     cleanup_func = PyCFunction_New(&cleanup_def, 0);
-    args = PyTuple_New(1);
-    if (!cleanup_func || !args)
+    if (!cleanup_func)
         goto bad;
-    PyTuple_SET_ITEM(args, 0, cleanup_func);
-    cleanup_func = 0;
 
     atexit = __Pyx_ImportModule("atexit");
     if (!atexit)
         goto bad;
-    reg = __Pyx_GetAttrString(atexit, "register");
-    if (!reg)
-        goto bad;
-    res = PyObject_CallObject(reg, args);
-    if (!res)
-        goto bad;
-    ret = 0;
+    reg = __Pyx_GetAttrString(atexit, "_exithandlers");
+    if (reg && PyList_Check(reg)) {
+        PyObject *a, *kw;
+        a = PyTuple_New(0);
+        kw = PyDict_New();
+        if (!a || !kw) {
+            Py_XDECREF(a);
+            Py_XDECREF(kw);
+            goto bad;
+        }
+        args = PyTuple_Pack(3, cleanup_func, a, kw);
+        Py_DECREF(a);
+        Py_DECREF(kw);
+        if (!args)
+            goto bad;
+        ret = PyList_Insert(reg, 0, args);
+    } else {
+        if (!reg)
+            PyErr_Clear();
+        Py_XDECREF(reg);
+        reg = __Pyx_GetAttrString(atexit, "register");
+        if (!reg)
+            goto bad;
+        args = PyTuple_Pack(1, cleanup_func);
+        if (!args)
+            goto bad;
+        res = PyObject_CallObject(reg, args);
+        if (!res)
+            goto bad;
+        ret = 0;
+    }
 bad:
     Py_XDECREF(cleanup_func);
     Py_XDECREF(atexit);