Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

on exit, freetype/__init__.py", line 1233, in __del__ TypeError: 'NoneType' object is not callable #169

Open
HinTak opened this issue May 12, 2023 · 10 comments

Comments

@HinTak
Copy link
Collaborator

HinTak commented May 12, 2023

This seems to be a race condition on exit - I often get:

Exception ignored in: <function Face.__del__ at 0x7f4e89903a60>
Traceback (most recent call last):
  File "/usr/lib/python3.11/site-packages/freetype/__init__.py", line 1233, in __del__
TypeError: 'NoneType' object is not callable

The cure is, curious enough, explicitly doing del face at the end of the program. This seems to suggest that, maybe there is a bug somewhere about destructor's calling order, such as the library or other objects are destructed first (and destroying the face handle).

@HinTak
Copy link
Collaborator Author

HinTak commented May 12, 2023

Argh, I thought this has been discussed before, and it has:
#44

@HinTak
Copy link
Collaborator Author

HinTak commented May 12, 2023

This can be a good task to learn about freetype. This can be investigated by building a debug version of freetype, set FT_DEBUG / FT_TRACE to see when and what freetype receives from freetype-py from the other side. This could fix some of the other issues with other __del__ too.

At the moment we don't do anything in a few of __del__ , since they don't work correctly, but it leaks a bit after repeat usages.

@carandraug
Copy link
Contributor

I've been seeing reports for this error for a couple of years now microscope-cockpit/cockpit#684 (it's always when the program is exiting so we never paid much attention).

Effectively, the error is at https://github.com/rougier/freetype-py/blob/ccd4f5fca0fb1dd69e40b2966db60139ec1c49da/freetype/__init__.py#LL1231C1-L1231C42 , i.e.:

        FT_Done_Face( self._FT_Face )

And what is None is FT_Done_Face. I guess this means that __del__ is being called after ctypes having dlclosed the shared library.

@carandraug
Copy link
Contributor

I tried to look deeper into this and even freetype (the Python module) is already None at this point. It appears that it all boils down to this warning in Python's documentation:

Warning: Due to the precarious circumstances under which del() methods are invoked, exceptions that occur during their execution are ignored, and a warning is printed to sys.stderr instead. In particular:

  • del() can be invoked when arbitrary code is being executed, including from any arbitrary thread. If del() needs to take a lock or invoke any other blocking resource, it may deadlock as the resource may already be taken by the code that gets interrupted to execute del().

  • del() can be executed during interpreter shutdown. As a consequence, the global variables it needs to access (including other modules) may already have been deleted or set to None. Python guarantees that globals whose name begins with a single underscore are deleted from their module before other globals are deleted; if no other references to such globals exist, this may help in assuring that imported modules are still available at the time when the del() method is called.

There's stackoverflow questions and python bug reports about this (here's a similar Python bug report). Recommendation from Python itself seems to get a strong reference to the needed machinery before trying to use. I will open a pull request.

@HinTak
Copy link
Collaborator Author

HinTak commented May 18, 2023

That is nonsense. FT_Done_Face() is a binding of the shared library.

What is quite likely is that FT_Done_Library() (which also calls FT_Done_Face() on the library side) is occasionally called automatically first before FT_Done_Face() by ctypes. The del face method probably should check the library handle and that FT_Done_Library() has not been called yet.

Edit: hmm, on 2nd thought. I think it is really best to investigate with a debug build of freetype.

@HinTak
Copy link
Collaborator Author

HinTak commented May 18, 2023

Sorry, on another thought, since freetype itself hasn't segfaulted, the problem is entirely on the python side. There is the ctype object which holds the reference to the shares library, and the library pointer returned from FT_Init_Library(), both of these could go before the face object.

carandraug added a commit to carandraug/freetype-py that referenced this issue May 18, 2023
…ne (rougier#169)

When we're called during the interpreter shutdown, the functions we
need may already be set to None.  So check for that before trying to
call them which fixes the "'NoneType' object is not callable" errors
described in issues rougier#44 and rougier#169.
@carandraug
Copy link
Contributor

This is the testing I did that shows the issue. This is the diff:

diff --git a/freetype/__init__.py b/freetype/__init__.py
index 4b65985..0b749ee 100644
--- a/freetype/__init__.py
+++ b/freetype/__init__.py
@@ -21,6 +21,7 @@ from ctypes import *
 import ctypes.util
 import struct
 
+import freetype.raw
 from freetype.raw import *
 
 # Hack to get unicode class in python3
@@ -1227,6 +1228,9 @@ class Face( object ):
         '''
         Discard  face object, as well as all of its child slots and sizes.
         '''
+        print("is FT_Done_Face None", FT_Done_Face is None)
+        print("is freetype None", freetype is None)
+        print("does freetype.raw exist", hasattr(freetype, "raw"))
         if self._FT_Face is not None:
             FT_Done_Face( self._FT_Face )
 

and this is the output:

is FT_Done_Face None True
is freetype None True
does freetype.raw exist False
Exception ignored in: <function Face.__del__ at 0x00000167919035B0>
Traceback (most recent call last):
  File "C:\Users\vgg\AppData\Roaming\Python\Python310\site-packages\freetype\__init__.py", line 1235, in __del__
TypeError: 'NoneType' object is not callable

The error message is also clear. What is being called is FT_Done_Face and the issue is that None is not callable.

This shows that the problem is that FT_Done_Face has been set to None and I guess that this is happening because, as described in Python docs, the interpreting is shutting down and "the global variables [__del__] needs to access (including other modules) may already have been deleted or set to None."

@HinTak
Copy link
Collaborator Author

HinTak commented May 19, 2023

We probably need the case where FT_Done_Face is called. Otherwise it isn't better than simply deleting those lines.

@carandraug
Copy link
Contributor

We probably need the case where FT_Done_Face is called. Otherwise it isn't better than simply deleting those lines.

In the case where FT_Done_Face is called, with the patch above, this is the output (basically, there is no issue because FT_Done_Face has not yet been set to None):

is FT_Done_Face None False
is freetype None False
does freetype.raw exist True

rougier added a commit that referenced this issue Jun 1, 2023
freetype.Face.__del__: check first if FT_Face_Done has been set to None (#169)
@carandraug
Copy link
Contributor

With the pull request #171 this issue is fixed.

There may be other issues with cleanup during runtime but the specific issue described here, i.e., "on exit, freetype/init.py", line 1233, in del TypeError: 'NoneType' object is not callable", is specific to "faces" during shutdown.

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

No branches or pull requests

2 participants