Comments

I am playing around with web development in Rust again and it turns out that cargo-watch is actually a good first place to support auto reloading web servers during development in some circumstances. The idea is that once #25 lands one could add a flag to cargo-watch that would spawn a listening TCP socket and then pass it in an environment variable to the process.

Basically something like this:

$ cargo watch --pass-listen-socket=127.0.0.1:5000 run

When pass-listen-socket is specified a new listen socket is created like this:

This comment has been minimized.

@passcod if the server binds to the port the user gets connection reset between runs. Worse: you cannot bind to the same port after a process just stopped using the port which will give you a "address in use" error. There are some situations in which you can prevent that error through REUSEADDR/REUSEPORT but they need to be used with care.

This avoids the problem entirely. The cargo process sets up the socket and requests that come in will be buffered by the kernel. When the process starts up it starts picking up the clients which are hanging in the socket queue.

This is generally also used by things like systemd and in the Python world by things like uwsgi to allow an efficient reload even if things are slow.

This comment has been minimized.

What env var name do systemd and uwsgi use, if any? I'd rather use something standard (or at leadt defacto) if it exists, rather like PORT, than make cargo watch expose something only cargo-watch-compatible servers could use.

Also, I can see the interest for production, but much less for development, and cargo watch is very much a development tool, at least currently. Do you have examples of devtools that make use of this, for reference?

This comment has been minimized.

They all use different methods. uwsgi does not document it but uses it internally. systemd sets LISTEN_FDS which is the number of FDs that it sets up and it sets LISTEN_PID so you know if the FDs are for you.

This obviously does not make any sense for windows. In Windows there is no way I know about where you can pass the socket through environment variables. You would need to set up an IPC channel and then use WSADuplicateSocket from the parent to the child. This in any case would require a shim crate.

Also, I can see the interest for production, but much less for development, and cargo watch is very much a development tool, at least currently.

I am exclusively talking about development here, not about production which is why I did not even talk about systemd.

Do you have examples of devtools that make use of this, for reference?

The reason why in case of Rust it needs to be in a different tool than in the framework itself is because something needs to trigger the recompilation. Where Python just evaluates bytecode whenever it starts up we here need to invoke cargo run.

Here is how it works in Flask in Python:

Flask starts up

Starts reloader

Starts socket

Forks

Flask starts up again in the child, this time with the app

Picks up the parent socket

If the parent detects a reload it kills the child and restarts it

Nowhere a request is lost because the FD is owned by the parent.

In case of Rust we need to look at things differently. The reloader in our case is cargo-watch. My proposal is to let cargo watch open the socket as such. It's the perfect place to start and stop cargo run and the HTTP server started up by the app after running could just pick up on this socket and everything works as fluently as it does in Python (ignoring the fact that compilation takes longer).

This comment has been minimized.

Just to reiterate what I said before: without passing a socket to a child you cannot implement reloading reliably because the socket cannot immediately be reused (that's not entirely true but it depends on a few factors and is by no means portable).

This comment has been minimized.

edited

I'm not sure that I want to do it in cargo-watch itself. I'm experimenting in creating a standalone tool that would achieve this and be usable in combination with cargo-watch (or anything else). Tentatively called "catflap", it would look like:

$ catflap -p 5000 -- cargo watch -x run

"Catflap" would open a TCP socket at port 5000 on localhost (by default, but other addresses would be targetable using e.g. 127.1.2.3:5000 etc) and set an env variable to the socked FD index, then spawn or exec the command given.

Then cargo-watch wouldn't do anything special, the env would pass through (as it does now), and the server below would then detect e.g. std::env::var("LISTEN_FD") and bind to that.

It would mean that "catflap" could be used to add this functionality to any other reloader out there (or even to non-reloader things).

Also, cargo-watch wouldn't need to half-support it: I want cargo-watch to have at least good support on all three platforms major platforms, and this feature is not great on Windows. Catflap, on the other hand, could expressly specify it only works on Linux/Unix (and perhaps on the Windows 10 Linux subsystem? haven't tested).

So, that's the direction I'm going at the moment. If/when I implement "catflap", and if it works, I'll of course chime in on here and also add a note on the readme.