Replies: 2 comments
-
Hi @vashek, thank you for the well-written details about your use case. I'll start with a couple of considerations:
Now, regarding everything else:
In conclusion: you are super-welcome to open PRs! I'm quite busy/AFK during this time of the year, but I'll make my best to comment/review them as soon as I can :) |
Beta Was this translation helpful? Give feedback.
-
I'm familiar with this requirement for (frozen) windows deployments. Out of curiosity: you didn't run or try to run multiple workers with a custom Ref: Lines 309 to 317 in ee9b0dd |
Beta Was this translation helpful? Give feedback.
-
Hi there. I'm making a program that will generally run as a Windows service (using cx-Freeze for the .exe bulding) and needs to serve a simple, low-traffic web app using an embedded web server for simplicity of deployment. I've stumbled upon Granian so I thought I'd give it a try this time around, not really having settled on a Python web server that I really like using.
I do have it working and don't see any obvious problems so far, but I did have to work around a few things and I wonder if I'm stretching the use case beyond what was intended, or if these are just things that haven't yet been thought of / implemented.
First off, the README only documents running Granian as a standalone command-line program, whereas I needed to embed it in mine. This was simple enough to solve, I just do
(web_server := Granian(something)).serve()
in a thread, and thenself.web_server.exit_event.set()
when stopping the service. No problem, just not documented.Next, because this runs in a non-main thread, I got a
ValueError: signal only works in main thread of the main interpreter
inGranian.startup()
, so I setGranian.SIGNALS = set()
.Then I had to figure out logging to 1) make sure it works properly with the multiple processes, and 2) avoid re-inventing the logging configuration that my service already has, and what I did was I:
configure_logging
with a no-op in the parent process (granian.server.configure_logging = granian.log.configure_logging = lambda *args, **kwargs: None
) so that it doesn't touch my existing logging configuration,log_dictconfig
that useslogging.handlers.QueueHandler(log_queue)
wherelog_queue
is amultiprocessing.Queue
, and then I have a separate thread in the parent process that reads the queue and does the actual logging (basically the code from https://docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes),log_level
, I'm passingLogLevels[logging.getLevelName(logging.root.level).lower()]
(to just preserve the already configured log level), which seems unnecessarily complicated.Finally and perhaps most importantly (and most hackishly), the spawned process needs to get some configuration data from the parent process, which on Windows doesn't work via a global variable (https://docs.python.org/3/library/multiprocessing.html#the-spawn-and-forkserver-start-methods). Unfortunately Granian doesn't seem to provide a way to pass a value into the spawned process that I could then access from my web app, so what I did was this:
And then in my web app, I call
ensure_config()
which is:(where
GLOBAL_CONFIG
is just a trivial object with some data attributes).For the sake of completeness, my main script also needs to include calling
multiprocessing.freeze_support()
like this:That's it, everything seems to work this way. But I'm wondering whether there was a better way (in particular considering I don't have experience with
multiprocessing
)?Would you consider adding supported solutions to these things to Granian? I'd be happy to offer some PRs.
Beta Was this translation helpful? Give feedback.
All reactions