From m0serve to Mojo
One views module compiled into m0serve as a mount beside a Python application, and into a binary of its own.
A Django or Flask application served by m0serve can move one route at a time into compiled Mojo, in the same process and behind the same port. The unit that moves is a views module. The same module later builds into a binary with no Python in it.
One module, two hosts
apps/ramp/views.mojo in the repository is a views table,
its views and its state, and nothing that knows which server it is on. It
builds two ways.
Inside m0serve, as a mount.
m0serve --mount /=project.wsgi:application --mount /x=mojo
Requests under /x go to the compiled table on handler threads that never
take the GIL; everything else goes to the Python application. Each mount
has its own execution mode, so a slow Python view does not queue the Mojo
routes behind it. The Python application keeps authentication and every
page nobody has moved.
On the Mojo host, alone. serve[ViewsApp[Ramp]](config) is the whole
main (the host). No interpreter is in the process or the
image.
CI builds both from the one file on every pull request and sends each the
same requests under the prefix: the index, a route answered on the event
loop, a compute route, a 404 inside the prefix, a 405 with its Allow, an
OPTIONS. The answers are byte-identical apart from Date, and a link
rendered by one host resolves on the other (N20 in
Capabilities).
What makes a module portable
- The prefix is a value. The table is built as
Views[S](Mount("/x"))and every link goes through thatMount'surl_for. A request under a mount arrives with its whole path, so a link rendered as/nowwould land on the Python application. - The state is built from the prefix alone, so both adapters call one function.
- No body says where it ran. A thread index means a handler thread on one host and the loop on the other.
- A route that must not queue is
add_loop, on both hosts.
Each adapter is about a page: apps/ramp/mount/m0serve_mount.mojo for the
mount, apps/ramp/server.mojo for the host.
What each side needs
A host binary is built by m0, from a project m0 new wrote:
Quickstart (Mojo).
A mount is compiled into m0serve itself. The m0serve wheel on PyPI
carries a demonstration mount, not yours, so serving your own module under
m0serve means building m0serve from a checkout of the repository, with
M0SERVE_MOUNT_DIR naming the directory that holds your
m0serve_mount.mojo and M0SERVE_INCLUDE your module's root. m0 does
not do this build.
Holds work from a mount
Under --realtime, a Mojo view on a mount takes a Server-Sent Events hold
with the same two response headers a Django view uses, M0-Hold and
M0-Channel, and m0pub.publish() from Python reaches it. A streaming
response that is not a hold is refused from a mount.
--mount /rt=hold is a built-in mount for the opposite split: the Python
view signs a short-lived grant into the stream's URL, and the mount verifies
it and holds the connection without calling Python again
(After the quickstart).
The measurements behind the split, including what a full handler pool costs a loop route on each host, are in the ramp test.