nim-lang / Nim

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
https://nim-lang.org
Other
16.6k stars 1.47k forks source link

addProcess on Linux does not register process exiting nor does it error out if registered to a PID that does not exist #22758

Open PhilippMDoerner opened 1 year ago

PhilippMDoerner commented 1 year ago

Description

addProcess seems to not trigger on Linux when the process exits that addProcess is supposed to listen to.

Step 1: Have a nim file with this code, compile and run it

import asyncdispatch

proc cb(fd: AsyncFD): bool =
  echo "GONE"

const firefoxProcessId = 2329 # Adjust to whatever processid you want

addProcess(firefoxProcessId, cb)

runForever()

Step 2: Execute killall firefox in a shell Step 3: Observe the shell in which you compiled and ran Step 1 - Nothing will have happened

Further, if you set firefoxProcessId to a processId that currently is not in use by any process (e.g. 2) then the program will just run as normal. Which might be erroneous behaviour, I'm not entirely sure, just thought it worth mentioning.

Nim Version

Nim Compiler Version 2.0.0 [Linux: amd64] Compiled at 2023-08-01 Copyright (c) 2006-2023 by Andreas Rumpf

git hash: a488067a4130f029000be4550a0fb1b39e0e9e7c active boot switches: -d:release

Current Output

- Nothing -

Expected Output

GONE

Possible Solution

No response

Additional Information

This also occurs on nim 1.6.12. Both issues (no triggering of the callback and no error for registering for an unused PID) are not a problem on windows. https://discord.com/channels/371759389889003530/371759389889003532/1156172175846408242

shirleyquirk commented 11 months ago

it does work on linux on a child process. i.e.

import std/[asyncdispatch,cmdline,strutils,osproc]
proc cb(fd:AsyncFD):bool = 
    echo "DONE"

let p = startProcess("echo 10",options={poEvalCommand,poParentStreams})
addProcess(p.processID,cb)
poll()
p.close()

prints

10
DONE

addProcess uses signalfd, which is never going to access signals directed at an unrelated process. you'd have to create the signalfd in that process, and then pass it over a socket or something to the awaiting process.

However, as a workaround you can leverage this stackoverflow answer

import std/[asyncdispatch,cmdline,strutils,strformat,osproc]
proc cb(fd:AsyncFD):bool = 
    echo "DONE"

proc addProcessExternal(pid:int; cb: Callback ) =
    let p = startProcess(&"tail --pid={pid} -f /dev/null",options={poEvalCommand,poParentStreams})
    addProcess(p.processId,proc(fd:AsyncFD):bool =
        result = cb(fd)
        p.close()
    )
addProcessExternal(parseInt(commandLineParams()[0]),cb)
runForever()

try with

$ sleep 10& ./process2 $!
❯ sleep 10& ./process2 $!
[1] 222998
[1]  + 222998 done       sleep 10
DONE
/tmp/process2.nim(12)    process2
/home/bwsq/.choosenim/toolchains/nim-#devel/lib/pure/asyncdispatch.nim(2017) runForever
/home/bwsq/.choosenim/toolchains/nim-#devel/lib/pure/asyncdispatch.nim(1711) poll
/home/bwsq/.choosenim/toolchains/nim-#devel/lib/pure/asyncdispatch.nim(1401) runOnce
Error: unhandled exception: No handles or timers registered in dispatcher. [ValueError]

from that answer it seems that tail works by calling kill(pid,SIG_0) repeatedly

shirleyquirk commented 11 months ago

hang on. ignore me. waitpid works with external processes. and it does so by

❯ sleep 10& strace waitpid $!
[1] 223810
...
pidfd_open(223810, 0)                   = 3
epoll_create(1)                         = 4
epoll_ctl(4, EPOLL_CTL_ADD, 3, {events=EPOLLIN, data={u32=0, u64=0}}) = 0
epoll_wait(4, [1]  + 223810 done       sleep 10
[{events=EPOLLIN, data={u32=0, u64=0}}], 1, -1) = 1
epoll_ctl(4, EPOLL_CTL_DEL, 3, NULL)    = 0
exit_group(0)                           = ?

so this could work if addProcess used pidfd instead of signalfd

ref: pidfd_open.2