The PyPy plugin

Introduction

Idea/Design: Maciej Fijalkowski

Contributors: Alex Gaynor, Armin Rigo

A new PyPy plugin based on cffi is available since uWSGI 1.9.11. The old slow cpyext-based one has been removed from the tree.

The plugin is currently supported only on Linux systems. Following releases will support other platforms as well.

The plugin loads libpypy-s.so on startup, sets the home of the PyPy installation and executes a special Python module implementing the plugin logic. So yes, most of the plugin is implemented in Python, and theoretically this approach will allow writing uWSGI plugins directly in Python in addition to C, C++ and Objective-C.

As of August 2013 all of the required patches to PyPy have been merged, so you can get an official source tarball release. Unfortunately you still need to build/translate libpypy-c by yourself, or download the following file (built for ubuntu trusty 64bit):

In addition to the library you need to obviously download an official binary tarball too.

UPDATE:

As of August 2013, libpypy-c is not automatically build in pypy release tarballs or nightly builds. If you do not want to translate the whole tree every time, you can try the “alternative approach” (described below)

Building libpypy-c (if needed)

Get PyPy sources and translate it. This should require no more than 50 minutes, but be sure to have at least 2 gigabytes of free memory on 32-bit systems and 4 gigabytes for 64-bit systems.

./rpython/bin/rpython -Ojit --shared --gcrootfinder=shadowstack pypy/goal/targetpypystandalone

Install uWSGI with PyPy support

As always with uWSGI, you have different ways to install uWSGI based on your needs.

If you have installed pip in your PyPy home, you can run

pip install uwsgi

The uwsgi setup.py file will recognize the PyPy environment and will build a PyPy-only uWSGI binary.

You can compile manually:

UWSGI_PROFILE=pypy make

Or you can use the network installer:

curl http://uwsgi.it/install | bash -s pypy /tmp/uwsgi

This will build a uWSGI + PyPy binary in /tmp/uwsgi.

Or you can build PyPy support as a plugin.

python uwsgiconfig.py --plugin plugins/pypy

The PyPy home

The uWSGI Python plugin (more exactly the CPython plugin) works by linking in libpython. That means you need to rebuild the plugin for every different version of Python. The PyPy plugin is different, as libpypy-c is loaded on startup and its symbols are resolved at runtime. This allows you to migrate to a different PyPy version on the fly.

The downside of this approach is that you need to inform uWSGI where your PyPy installation is at runtime.

Supposing your PyPy is in /opt/pypy you can start uWSGI with:

uwsgi --http-socket :9090 --pypy-home /opt/pypy

With this command line uWSGI will search for /opt/pypy/libpypy-c.so and if found, it will set that path as the PyPy home.

If your libpypy-c.so is outside of the PyPy home (and in a directory not reachable by the dynamic linker), you can use the ``–pypy-lib``option.

uwsgi --http-socket :9090 --pypy-home /opt/pypy --pypy-lib /opt/libs/libpypy-c.so

With this approach you are able to use the library from a specific PyPy build and the home from another one.

Note

Remember to prefix –pypy-lib with ./ if you want to point to a .so file in your current directory!

The PyPy setup file

As said before, most of the uWSGI PyPy plugin is written in Python. This code is loaded at runtime, and you can also customize it.

Yes, this does mean you can change the way the plugin works without rebuilding uWSGI.

A default version of the pypy_setup.py file is embedded in the uWSGI binary, and it is automatically loaded on startup.

If you want to change it, just pass another filename via the --pypy-setup option.

uwsgi --http-socket :9090 --pypy-home /opt/pypy --pypy-lib /opt/libs/libpypy-c.so --pypy-setup /home/foobar/foo.py

This Python module implements uWSGI hooks and the virtual uwsgi python module for accessing the uWSGI API from your apps.

If you want to retrieve the contents of the embedded pypy_setup.py file you can read it from the binary symbols with the print-sym convenience option.

uwsgi --print-sym uwsgi_pypy_setup

WSGI support

The plugin implements PEP 333 and PEP 3333. You can load both WSGI modules and mod_wsgi style .wsgi files.

To load a WSGI module (it must be in your Python path):

uwsgi --http-socket :9090 --pypy-home /opt/pypy --pypy-wsgi myapp

To load a WSGI file:

uwsgi --http-socket :9090 --pypy-home /opt/pypy --pypy-wsgi-file /var/www/myapp/myapp.wsgi

RPC support

You can register RPC functions using the uwsgi.register_rpc() API function, like you would with the vanilla Python plugin.

import uwsgi

def hello():
    return "Hello World"

uwsgi.register_rpc('hello', hello)

To call RPC functions, both uwsgi.rpc() and uwsgi.call() are available.

import uwsgi

uwsgi.rpc('192.168.173.100:3031', 'myfunc', 'myarg')
uwsgi.call('myfunc', 'myarg')
uwsgi.call('myfunc@192.168.173.100:3031', 'myarg')

Integration (with local RPC) has been tested between PyPy and PyPy, PyPy and JVM, and PyPy and Lua. All of these worked flawlessly... so that means you can call Java functions from PyPy.

IPython trick

Having a runtime shell for making tests is very nice to have. You can use IPython for this.

uwsgi --socket :3031 --pypy-home /opt/pypy --pypy-eval "import IPython; IPython.embed()" --honour-stdin

Options

  • pypy-lib - load the specified libpypy-s.so
  • pypy-setup - load the specified pypy_setup script file
  • pypy-home - set the pypy home
  • pypy-wsgi - load a WSGI module
  • pypy-wsgi-file - load a mod_wsgi compatible .wsgi file
  • pypy-eval - execute the specified string before fork()
  • pypy-eval-post-fork - execute the specified string after each fork()
  • pypy-exec - execute the specified python script before fork()
  • pypy-exec-post-fork - execute the specified python script after each fork()
  • pypy-pp/pypy-python-path/pypy-pythonpath - add the specified item to the pythonpath
  • pypy-paste - load a paste.deploy .ini configuration
  • pypy-ini-paste - load a paste.deploy .ini configuration and use its [uwsgi] section

The alternative approach

The PyPy plugin in uWSGI 1.9.15 has been extended to automatically detect if uWSGI has been called as a shared library by the pypy binary itself (via ctypes for example).

This approach (albeit suboptimal) could be useful to test new pypy releases (‘til the PyPy guys start building libpypy-c).

To build a uWSGI shared library with the pypy plugin embedded run:

UWSGI_PROFILE=pypy UWSGI_AS_LIB=libuwsgi.so make

you will end with a libuwsgi.so shared library you can load with ctypes:

#/usr/bin/pypy
import sys
import ctypes
# load the uwsgi library in the global namespace
uwsgi = ctypes.CDLL('./libuwsgi.so',mode=ctypes.RTLD_GLOBAL)

# build command line args
argv = (ctypes.c_char_p * (len(sys.argv)+1))()
pos = 0
for arg in sys.argv:
    argv[pos] = arg
    pos+=1

# inform the uwsgi engine, the passed environ is not safe to overwrite
envs = (ctypes.c_char_p * 1)()

uwsgi.uwsgi_init(len(sys.argv), argv, envs)

You can now run the script as a standard uwsgi binary:

./fakeuwsgi.py --http-socket :9090 --master --processes 2 --threads 8 --pypy-wsgi myapp

as you can see there is no need to specify –pypy-home or –pypy-lib as the pypy environment is already available.

in library mode (under pypy) uWSGI cannot change its process name (under Linux and Solaris, as “environ” is no more valid) and a reload could need a bit of help for finding the right commandline. Generally this trick will be more than enough:

./fakeuwsgi.py --http-socket :9090 --master --processes 2 --threads 8 --pypy-wsgi myapp --binary-path ./fakeuwsgi.py

Notes

  • Mixing libpython with libpypy-c is explicitly forbidden. A check in the pypy plugin prevents you from doing such a hellish thing.
  • The PyPy plugin is generally somewhat more “orthodox” from a Python programmer point of view, while the CPython one may be a little blasphemous in many areas. We have been able to make that choice as we do not need backward compatibility with older uWSGI releases.
  • The uWSGI API is still incomplete.
  • The WSGI loader does not update the uWSGI internal application list, so things like --need-app will not work. The server will report “dynamic mode” on startup even if the app has been successfully loaded. This will be fixed soon.