Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to spawn child processes and send signals to them. #482

Closed
amano-kenji opened this issue Aug 6, 2022 · 17 comments
Closed

Ability to spawn child processes and send signals to them. #482

amano-kenji opened this issue Aug 6, 2022 · 17 comments

Comments

@amano-kenji
Copy link

I want to spawn child processes and kill them later. Or, do child processes get killed when the parent dies?

@candid82
Copy link
Owner

candid82 commented Aug 8, 2022

joker.os/sh, joker.os/sh-from and joker.os/exec are the only functions that spawn new processes. They are blocking and do not allow for fine-grained control over child processes. You can use go macro to run child processes in non-blocking way. However, there is no API to send signals to child processes. You may have to resort to using pgrep and kill for that, e.g.

(go
  (pprint (joker.os/sh "joker" "sleep.joke"))) ;; start a child process
(joker.time/sleep 1000000000) ;; simulate i/o to let the goroutine a chance to start
(let [pgrep-out (:out (joker.os/sh "pgrep" "-P" (str (joker.os/pid)))) ;; find child pid by parent pid
      child-pid-str (subs pgrep-out 0 (dec (count pgrep-out))) ;; delete trailing \n
      ]
  (joker.os/sh "kill" child-pid-str))

@amano-kenji
Copy link
Author

amano-kenji commented Aug 8, 2022

I wanted to emulate something like this in joker.

#!/bin/sh

cmd1 arg1 arg2 ... &
pid=$!

cmd2 arg1 arg2 ...

kill $pid

@candid82
Copy link
Owner

I added two functions in start-kill-process branch: joker.os/start-process and joker.os/kill-process. Usage example:

(let [pid1 (joker.os/start-process "/bin/sleep" "1000")
      pid2 (joker.os/start-process "/bin/sleep" "1000")]
  (joker.os/kill-process pid1)
  (println "done!"))

@amano-kenji Do you think this will work? Would you be willing to build that branch locally and try it for your use case?

@amano-kenji
Copy link
Author

amano-kenji commented Aug 10, 2022

I compiled and tested it. That seems to do the job, but what about control over stdout, stderr, stdin, and current working directory?

@jcburley
Copy link
Contributor

I wonder if it'd make sense to enhance joker.os/exec, or provide an alternate, that doesn't Wait for the process to finish, but instead returns the underlying os.Process object (perhaps among other things) returned by os.exec/Start()?

That way, the features provided by joker.os/exec to prepare a process would be available, with the flexibility of being able to manipulate the returned os.Process object such as your proposed joker.os/kill-process provides.

I think an alternate to joker.os/exec, like joker.os/exec-nowait, makes more sense at least to simplify the returned value, which wouldn't have to contain things that make sense only due to the current implementation calling os.Wait.

@candid82
Copy link
Owner

Yes, I think it makes sense. I was not sure how useful it would be to be able to set up stdin/out/err for processes that are started asynchronously and how this would behave when the process is killed by the parent program. I'll experiment with this on the weekend.
I also went for a simpler (perhaps too simple?) approach of returning a PID rather than a os.Process wrapper since os.Process can always be obtained by PID with os.FindProcess.

@amano-kenji
Copy link
Author

I may want to redirect stdout and stderr to /dev/null and feed some input to stdin.

@candid82
Copy link
Owner

@amano-kenji , @jcburley I made the change so that now joker.os/start-process accepts the same arguments as exec (and shares most of its implementation). Example:

(let [pid1 (joker.os/start-process "joker" {:args ["/Users/candid/sleep.joke"]
                                            :stdout *out*})
      pid2 (joker.os/start-process "/bin/sleep" {:args ["1000"]})]
  (joker.time/sleep (* 5 joker.time/second))
  (joker.os/kill-process pid1)
  (println "done!"))

Please let me know what you think.

@amano-kenji
Copy link
Author

That should do the job although being able to send other signals than TERM to a process is going to be useful.

@candid82
Copy link
Owner

@amano-kenji Fair enough. I added signal function (takes pid and signal as arguments). I also renamed start-process to start and kill-process to kill. Please give it a try and let me know what you think.

@amano-kenji
Copy link
Author

amano-kenji commented Aug 15, 2022

I just tested. If I start a process and kill its PID in joker REPL, the PID becomes defunct until I quit REPL. After I quit REPL, the PID actually dies. Is this normal?

Also, it would be cool to have predefined SIGNALs for signal function.

@candid82
Copy link
Owner

@amano-kenji Zombie processes are sort of normal: https://www.baeldung.com/linux/clean-zombie-process
They appear if the parent process doesn't wait on them after they finish execution. My initial implementation of joker.os/kill did not wait on the process being killed. I changed that, so now joker.os/kill waits after killing the process: baefcf5 This solves the issue with zombie processes (although I am not 100% it was a real issue).

As for predefined signals, they are platform specific. We would have to expose different set of constants on different platforms (at least on three officially supported platformes: Linux, macOS and Windows). I don't believe we do this anywhere else in Joker's codebase. @jcburley do you have any input on this (perhaps you've done this in your fork?)

@jcburley
Copy link
Contributor

As for predefined signals, they are platform specific. We would have to expose different set of constants on different platforms (at least on three officially supported platformes: Linux, macOS and Windows). I don't believe we do this anywhere else in Joker's codebase. @jcburley do you have any input on this (perhaps you've done this in your fork?)

My fork's gostd tool, which "wraps" much of the Go standard library for direct access via Joker code, is run as part of the build process for a specific target.

So it doesn't really handle this in any particularly sophisticated way, in that the constants and variables that get wrapped, for a specific Joker executable, are just whatever happen to apply to that particular target.

I.e. the Joker namespaces that get exposed, along with their contents, in a particular executable, depend on what makes sense for that executable's target. Things that are Mac-OS-specific will appear, as namespaces and/or entities within them, in a Joker executable built for the Mac OS platform, but not on one built for a Linux nor Windows platform.

Early on, I thought it might be possible to someday make gostd smart enough to generate wrappers for all namespaces (packages in the Go standard library) and their contents, and use build tags and such to parallel how the Go standard library itself does.

But that seems very difficult, with little (if any) benefit, other than that my "fork" could then just be a source distribution without any need to run gostd for a particular build, at least in the vanilla case of wrapping the Go standard library.

So I've punted that capability, which is Issue jcburley#10.

@candid82
Copy link
Owner

@jcburley thank you for the input! Here is my attempt to add support for platform-dependent declarations (non-functions) in std library: 2db1f41

This means that, e.g. joker.os/SIGBUS will be 0xa on macOS and 0x7 on Linux and Windows. joker.os/SIGCHLD will be 0x14 on macOS, 0x11 on Linux and won't exist on Windows.

This complicates build process a bit since now you have to generate std library for specific OS. Not sure how I feel about it... I guess it's useful to be able to write (joker.os/signal 1234 joker.os/SIGBUS) and the right signal will be sent on macOS and Linux... @jcburley any thoughts on this?

@candid82
Copy link
Owner

I guess the alternative to this would be to only define signals that have the same value across all platforms. Now that I look at it, most common/useful signals fall into that category (e.g. SIGKILL, SIGTERM, SIGABRT). So perhaps it makes sense to punt on platform-dependent declaration for now until there is a more compelling use case for it?

@amano-kenji
Copy link
Author

It's sufficient to add only platform-independent signals.

@candid82
Copy link
Owner

candid82 commented Sep 4, 2022

OK, I've removed OS-dependent signals for now and merged everything to master. Closing.

@candid82 candid82 closed this as completed Sep 4, 2022
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

No branches or pull requests

3 participants