Keeping Julia alive while running a web server in the background

I’m using Mux.jl to run a web server. My code is in a module with the following structure:

module MyApp

using Mux

# all app functions here

function startserver()
    # setup routes here (using the functions above)
    @app webserver = (routes...)
    serve(webserver)
end
end

Everything works perfectly when I run it from the REPL with using MyApp; startserver(). The server runs in the background within that session. In other words, I can type commands and do other stuff in the REPL while the server is online responding to all requests I make in a browser. When I exit Julia, the server naturally goes down. So far so good.

Now I want to SSH into my computer and start the web server, and keep the server running after I exit the terminal window. I thought something like this at the Linux command line would work:

nohup julia -e 'using MyApp; startserver()' &

However, the process immediately terminates after starting the server (printing [1] Done) and the server goes down. Same result when I put those two commands in a script and call it. How can I keep the server alive?

1 Like

Linux answer here, not Julia specific. Use GNU Screen (or tmux), create a session, run the command you want, detach the screen. Involves learning how to use screen, but it’s useful for other things too - I used to have an IRC client running this way. There may be other answers.

3 Likes

Based on:

https://github.com/JuliaLang/IJulia.jl/blob/48f5bafa06d3efb919743b2a8567ae911b8fd629/src/IJulia.jl#L105-L127

You could try something like:

julia> p = spawn(Cmd(`julia -e 'using MyApp; startserver()'`, detach=true))
Process(`julia -e 'using MyApp; startserver()'`, ProcessRunning)

It appears that Mux.jl is choosing to run your server asynchronously by default, with no option to run synchronously (see here).

This seems like bad practice, in general, and at the very least should be something configurable. If your application is just a web server, the current best practice is to have the main server request/response loop run in the main thread, and then use an OS-level process manager (such as Upstart or systemd in Linux, or the classic rc system used by FreeBSD) to daemonize your server. The advantage to using such tools is that it becomes (relatively) trivial to start, stop, and restart the server, gather logs, ensure the server stays up, and have the server start on boot.

The “old-n-busted” way to write your own daemon involves the dreaded “double-fork” (see this Stack Overflow answer for a brief explanation as to why). Don’t do this. Really.

To solve your immediate problem, you have two choices:

  1. Pause the main thread after starting your server. Non-ideally, this could just be while(true); sleep(1); end (this solution is simple, but this unnecessarily consumes processor resources). Alternatively, see the documentation for wait for some other ideas. For example, I think creating a Condition that you wait on, and just never trigger, might be sufficient.
  2. Reproduce HttpServer.jl’s run command, dropping the @async qualifier. One caveat, having taken the briefest of looks at the underlying code in HttpServer.jl and Base, is that it seems like even dropping run’s @async might not be enough to run the server synchronously.

Three completely different responses, all useful. Thanks guys! Based on @jballanc’s last suggestion I came up with this simple hack:

nohup julia -e 'using MyApp; @sync startserver()' &

I believe that should be enough to force the server to run synchronously, despite all the @async calls in the underlying packages. It seems to be running as intended, with negligible CPU usage when not responding to a request.

Am I correct? Is this a very bad idea in some other way?

4 Likes

Heh…no, actually I didn’t realize that a top-level @sync macro would override any underlying @async calls in included packages, but if this works I see no problem with it. You’ve got a server running on a main thread and you didn’t even have to spin anything!

2 Likes

This solved another general problem for me

I was going open a thread asking how to make application loops

Say I have several tasks (via @async) running in my main method, the only way I saw so far to keep this alive while NOT running in the REPL was to add a while true;sleep(1);end loop, which seems such bad practise

Running @sync main() has solved this - the script keeps running (while all tasks run) without the while true...end

@sync did not work for my use case. However, adding timedwait(()->false, Inf; pollint=100.0) to the end of my script worked quite well without consuming any resources.

Much simpler and (IMO) more idiomatic: wait(Condition())

4 Likes

Pretty sure you can also run julia in Interactive move with the -i cmd line arg. This will leave a background REPL open though, so that could be a security risk.

Thanks. I was not aware that I could pass Condition() to wait without arguments.