Skip to content

Create the event loop after daemonizing, not before - #15

Open
stupalov wants to merge 1 commit into
lextudio:masterfrom
observium:upstream-daemonize-loop
Open

Create the event loop after daemonizing, not before#15
stupalov wants to merge 1 commit into
lextudio:masterfrom
observium:upstream-daemonize-loop

Conversation

@stupalov

Copy link
Copy Markdown

Problem

--daemonize is broken: the responder binds its socket, logs Listening at UDP/IPv4 endpoint …, and then vanishes a fraction of a second later, leaving no PID file, no traceback and no log entry beyond a normal Process terminated.

The cause is the order of two things in main(). The Python 3.14 compatibility fix (1.2.1) creates the default event loop at the very top of main(); daemon.daemonize() then forks twice. An asyncio event loop does not survive fork() — its selector (kqueue/epoll) and self-pipe descriptors belong to the parent — so the daemon ends up running a loop whose descriptors are invalid:

File "pysnmp/carrier/asyncio/dispatch.py", line 78, in run_dispatcher
  self.loop.run_forever()
…
OSError: [Errno 9] Bad file descriptor

daemonize() has already pointed stderr at /dev/null by then, so the traceback is lost and the process simply disappears — which is what makes this hard to diagnose in the field. Before 1.2.1 the loop was created lazily by AsyncioDispatcher, i.e. after the fork, so daemon mode worked.

Fix

Move the loop creation below the --daemonize block, so the daemon creates its own loop after the fork. Nothing between the two points touches asyncio. Non-daemon startup is unaffected: the loop is still created before the dispatcher is built.

The same reordering is applied to responder_lite.py, which has the identical pattern.

Verification

Before: --daemonize exits within a second, no PID file. After: the daemon stays up, writes its PID file, answers SNMP, and SIGTERM shuts it down cleanly and removes the PID file.

$ snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1:1651 \
    --data-dir=tests/data/short-oid --daemonize --pid-file=/tmp/d.pid
$ snmpget -v2c -c public udp:127.0.0.1:1651 .1.3.6.1.2.1.47.1.1.1.1.3.2
SNMPv2-SMI::mib-2.47.1.1.1.1.3.2 = OID: SNMPv2-SMI::zeroDotZero

Found while setting up a daemonized start script for a public simulator, where the responder kept dying seconds after startup. It is now running daemonized in production.

--daemonize has been broken since the Python 3.14 compatibility fix: main()
creates the default event loop before daemon.daemonize() forks, and an event
loop does not survive fork() - its selector and self-pipe descriptors belong
to the parent. The daemon binds its socket, logs "Listening", then dies in
loop.run_forever() with "OSError: [Errno 9] Bad file descriptor". Because
daemonize() redirects stderr to /dev/null, the traceback goes nowhere and the
process just disappears moments after startup.

Create the loop after the daemonize block instead, so the daemon builds its
own. Nothing between the two points needs a loop.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant