script com `read` no bash while loop causando alto uso da CPU quando executado como um serviço systemd

script com `read` no bash while loop causando alto uso da CPU quando executado como um serviço systemd

Eu escrevi um script para executar ações específicas condicionadas a eventos de entrada informados por um monitor de eventos, que se parece com

$ cat script.sh
-----------------------------------------------------------
#!/usr/bin/bash

stdbuf -oL /usr/bin/event_monitor | while IFS= read LINE
do
    something with $LINE
done

Quando executado como um script a partir de um bashterminal, o script consome uma quantidade normal de CPU e executa a ação somente quando uma nova linha é impressa. No entanto, quando executado como um serviço com a seguinte configuração

$ cat event.service
-----------------------------------------------------------
[Unit]
Description=Actions upon events

[Service]
Type=simple
ExecStart=/path/to/script.sh

[Install]
WantedBy=default.target

O event_monitorcomando agora assume todo um núcleo lógico e stracerevela que não readestá read()executando nada com a frequência que o processador permite:

$ strace -p $event_monitor_pid
-----------------------------------------------------------
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
................ad nauseum

enquanto o serviço ainda registra eventos e executa comandos condicionais quando eventos reais ocorrem. O que deu errado aqui?

ps isso acontece com cras_monitormas não com acpi_listen. Tentei garantir que o whileloop só iniciasse depois de garantir que o serviço subjacente fosse iniciado com êxito, mas sem sucesso.

Atualização: aqui estão alguns trechos potencialmente relevantes do event_monitorcódigo de:

...
#include <headers.h>
...
# Print to console function:
static void event_occurrence(void *context, int32_t attribute)
{
    printf("Some attribute has changed to %d.\n", attribute);
}
...
int main(int argc, char **argv)
{
    struct some_service_client *client # defined in headers
    int rc
...
# Some routine
...
    some_service_client_set_event_occurence_callback(client,event_occurence)
...
    rc = some_func(client)
...
    while (1) {
        int rc;
        char c;
        rc = read(STDIN_FILENO, &c, 1);
        if (rc < 0 || c == 'q')
            return 0;
    }
...
}


Responder1

É o seu event_monitorprograma que está em loop, usando toda a CPU, não o seu script bash.

Quando executado no systemd, STDIN tem /dev/null anexado (ou talvez até esteja fechado). Quando o loop do monitor de eventos em main executa um read(2), ele obtém EOF e percorre o loop novamente.

Quando executado de forma interativa, event_monitor tem o terminal anexado ao stdin, então ele read(2)bloqueia até que haja entrada.

event_monitor só deve fazer um loop na leitura de stdin se estiver aberto. Se receber EOF, deverá sair (provavelmente não desejável neste caso) ou apenas dormir por um longo tempo.

Se você não conseguir alterar event_monitor, poderá ter algum sucesso ao anexar um FIFO (pipe nomeado) ao stdin do serviço. systemd tem a StandardInputopção (documentada na página man systemd.exec(5)), onde você pode especificar StandardInput=file:/run/event_monitor_ctl. Então você só precisa criar o /run/event_monitor_ctlpipe nomeado. Para isso, você pode usar systemd-tmpfiles criando um arquivo de configuração (veja tmpfiles.d(5)) para criar esse pipe nomeado.

informação relacionada