Kategória: Python

Zmenené: 8. august 2018

uWSGI Emperor a xterm

Režim Emperor aplikačného servera uwsgi je šikovný nástroj, ktorý môže ušetriť kopu detailov. Stačí jednoduchý konfiguračný súbor, nastaviť adresár jeho podriadených inštancií (vazalov), a potom stačí do tohoto adresár pridať konfiguračný súbor vazala a aplikácia beží. Problém je, že v prípade jeho spustenia v xterm po odhlásení neskončí…

Obsah článku

Jeho použitie je potom naozaj prosté, neriešim režim démona, jednoducho mám tlačidlo na paneli, ktoré spúšťa uwsgi v xterm (plus nejaké to formátovanie, ako geometria apod):

xterm -e uwsgi ~/Python/flask/emperor.ini

Popis problému

Všetko funguje ako má a v strome procesov možno vidieť niečo takéto (nesúvisiace procesy som vynechal):

init───xfce4-panel───xterm───uwsgi─┬─dallasrrd.py
                                   └─uwsgi─┬─uwsgi
                                           ├─uwsgi
                                           ├─uwsgi
                                           └─uwsgi

Problém nastane po odhlásení. Doma sa neodhlasujem často, ale občas to je nutné (napr. kvôli novej verzia ovládača grafickej karty). Môj predpoklad je, že po odhlásení bude oi. skončený xfce4-panel, ktorý ukončí xterm, ktorý zase ukončí uwsgi Emperor, a ten ukončí všetkých svojich vazalov. Lenže výsledok je iný a Emperor (ani jeho vazaly) ukončené nie sú (môžete si to vyskúšať pomocou xkill na okno xterm):

init───uwsgi─┬─dallasrrd.py
             └─uwsgi─┬─uwsgi
                     ├─uwsgi
                     ├─uwsgi
                     └─uwsgi

Ako vidno, Emperor neskončil, len jeho rodičom sa stal hlavný proces (init). Výsledkom je, že po opätovnom prihlásení sú hlásené problémy – môj skript dallasrrd.py sa samozrejme nedokáže pripojiť k sériovému portu, a vazaly sa nedokážu inicializovať, pretože ich porty sú použité. Potom mi neostáva nič iné, ako nájsť PID Emperor a pustiť na neho kill

Trvalo mi hodnú chvíľu, kým som pochopil v čom je problém a ako to býva, zistil som, že vo mne. Presnejšie, v spôsobe akým Emperor používam. Dokumentácia spomína, že uwsgi v režime Emperor má byť spúšťaný pri štarte systému ako démon a má byť ukončený až pri ukončení behu systému (zvyčajne pri vypnutí).

Keďže v mojom prípade nebeží ako démon, ale je spustený v xterm a keď xterm končí, pošle (všetkým) podriadeným procesom signál HUP. Lenže, SIGHUP je pri v uwsgi interpretovaný ako signál na znova načítanie (Emperor ukončí všetky vazaly a znova ich spustí) – môžete si to vyskúšať pokusom o klasické zatvorenie okna xterm (krížik). Samozrejme, použitie HUP na opätovné načítanie démona je bežné správanie, diskutabilné to je len ohľadom toho, že môj Emperor nie je spustený ako démon, ale s tým asi nič neurobím.

Riešenie

Samozrejme, môžem spúšťať Emperor ako démona, prídem o jeho výstup a musel by som nastavovať zaznamenávanie do súboru, no tak zase prídem o farby hlásení z vazalov, a tak som hľadal a našiel iné riešenie, ktoré spočíva v jednoduchom skripte.

Tento skript jednoducho používa obsluhu signálu HUP, ktorá pošle do uwsgi signál INT, čiže signál ukončenia. Skript spúšťa uwsgi na pozadí, pretože obsluha signálu je spustená až po skončení externého príkazu a potom čaká na jeho ukončenie pomocou wait, čo je interný príkaz BASH:

#!/bin/sh

EMPINI=~/Python/flask/emperor.ini

# SIGHUP handler
sighup() { kill -TERM $1; }

uwsgi $EMPINI &
# record uWSGI's PID
SPID=$!

# setup handler and wait for uWSGI
trap "sighup $SPID" HUP
wait $SPID

Skript som uložil s menom wrapemperror.sh a xterm teraz stačí spúšťať:

xterm -e ~/Python/flask/wrapemperror.sh

Pri skončení xterm teraz skončí aj uwsgi, ibaže tým, že beží na pozadí skriptu, je okno xterm zatvorené okamžite a nečaká na skončenie uwsgi, ale to je vlastne dobre. Ak si chcete funkčnosť overiť, stále môžete príkazu xterm pridať voľbu -hold (okno sa potom zavrie až na druhý krát).

Pokiaľ je počkanie na ukončenie uwsgi žiadúce (tzn. nutné), možno v obsluhe udalosti na jeho ukončenie počkať (vrátane Ctrl + C):

EMPINI=~/Python/flask/emperor.ini

# SIGHUP handler
sighup() {
    kill -TERM $1
    wait $SPID
}

sigint() {
    wait $SPID
}

uwsgi $EMPINI &
# record uWSGI's PID
SPID=$!

# setup handler and wait for uWSGI
trap "sighup $SPID" HUP
trap "sigint $SPID" INT
wait $SPID