Search code examples
pythontwistedmonkeypatching

Twisted server: monkey-patch file


I'm running Twisted (Python 2.7.x) on Alpine Linux 3.7 inside Docker.

I now wanted to use the twisted.internet.inotify module, but it fails loading.

It's triggering the following exception in twisted.python._inotify:

name = ctypes.util.find_library('c')
if not name:
    raise ImportError("Can't find C library.")
libc = ctypes.cdll.LoadLibrary(name)
initializeModule(libc)

The problem is that Alpine Linux 3.x has a bug which makes ctypes.util.find_library('c') return None.

I've compared the code with the inotify module, which I've successfully used in Alpine before, and that one deals with the issue in the following way:

_FILEPATH = ctypes.util.find_library('c')
if _FILEPATH is None:
    _FILEPATH = 'libc.so.6'

instance = ctypes.cdll.LoadLibrary(_FILEPATH)

So i've tried calling ctypes.util.find_library('libc.so.6') in the interpreter, and that call succeeds.

What I now want to do is to monkey-patch twisted.python._inotify so that it loads libc.so.6 instead of c, but I'm unaware of how I can do that, because I can't load the module at all.

I have one option, which is to sed the source code during docker build, or possibly even inside the server right after it starts, but that feels like a hack.

I've seen that Twisted contains a MonkeyPatch module, but I have no idea on how to use it, or if it is even suited for this task.

How can I solve this problem in the cleanest possible way?


Note: The server is running as non-root, so it has no write access to /usr/lib/python2.7/site-packages/twisted/python/_inotify.py.

This means that I either have to sed it in the Dockerfile, or patch in in-memory when the server starts, before it loads the module (if that's possible, I'd prefer that).


Solution

  • In addition to anything else, I hope that you contribute a patch to Twisted to either solve this problem outright or make it easier to solve from application code or at an operations level.

    That said, here's a monkey-patch that should do for you:

    import ctypes.util
    def fixed_find_library(name):
        if name == "c":
            result = original_find_library(name)
            if result is not None:
                return result
            else:
                return "libc.so.6"
        return original_find_library(name)
    original_find_library = ctypes.util.find_library
    ctypes.util.find_library = fixed_find_library
    
    # The rest of your application code...
    

    This works simply by codifying the logic from your question which you suggest works around the problem. As long as this code runs before _inotify.py is imported then when it does get imported it will end up using the "fixed" version instead of the original.