-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Extract POSIX implementation of Process and Signal to a portable API #8536
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great 👍
I made some stylistic suggestions to avoid self.
and Process::
when unnecessary.
I also propose to have exists_system?
and pgid_system
be class methods to avoid allocating a Process
object just to throw it away immediately in Process.exists?(pid)
and Process.pgid
methods.
I believe we can already wrap the fork
, chroot
and pgid
methods to not exist on Win32.
a986ebd
to
2006c6a
Compare
@ysbaddaden it's done, please look at it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one more tiny change (and Sija's stylistics suggestions are good too), but otherwise 👍 for me!
Oh, and thank you for spending so much time on making this super clean! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't have methods which exist on only some platforms. I've done some work on this before, so I'll dig out my old code and review it while looking at how I did this task before.
@RX14 Let's avoid holding things up and keep them hanging. IMO: the abstraction here is good. It doesn't remove any functionality and will be enough to implement |
246670d
to
4c61ae8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
What does |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Please try to keep
Process
's API the same, removingProcess.kill
must be another PR. - There's no need to attempt to abstract
Process.fork
, it will be removed anyway. Just abstractfork_internal
enough that it can be used to implement spawn in unix.
Also see my previous attempt at this 2 years ago for reference: RX14@ed14ad5#diff-4af37cd7133b9dffdefaf5fd05463245. Many of the refactors in this commit have made their way to master
after this, just not the actual move to Crystal::System
.
@RX14 Process.kill wasn't removed but renamed to Process.signal which is a better name. |
@ysbaddaden I agree - but it still should be in seperate PRs. |
45d6a0d
to
41f6e41
Compare
Ok @RX14, @straight-shoota i move kill method renaming to new PR #8642. But because of this and our intention with @ysbaddaden to have portable API to stopping processes i rename proposed kill method that immediately terminate process to interrupt. Otherwise it would be super unclear until we remove original kill methods. The more so that someone already suggested to make it only deprecated in #8642 (which I expected). |
@jan-zajic I'd rather just have no However, please no user-visible API changes in this PR. It'd make me far far happier to review this diff if all the methods and their signatures stayed in the same place and only their implementations changed. Or, at least if you do clean things up and rearrange, seaperate that out into into a different commit. |
d2f6b8b
to
d34295f
Compare
@RX14 i rearranged it as you wish. But i insist on to have some methods to stop Process (no matter how they are named, it can be discussed). We try to create portable API (at least @ysbaddaden and i), that can be implemented on every platform. If you can create Process, you must be able to stop it. Do not confuse it with that signal stuff. It's only implementation detail on unix. Stopping or killing most of the processes on Windows, for example, have nothing to do with signal API. When you mention go for example, it also has Kill() method on Process that causes the Process to exit immediately in platform specific way. But Go lang is not a good example of pretty apis at all, please let's not be influenced by previous compromises of other teams. |
@bcardiff builds now starts to fail on:
P.S. happy New Year to everyone involved in Crystal Language! |
That is true, but that does not mean that the API for killing a process on windows cannot be exposed as |
@jan-zajic You need to rebase on |
d34295f
to
224643f
Compare
@@ -186,14 +111,14 @@ class Process | |||
# | |||
# Returns the block's value. | |||
def self.run(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, | |||
input : Stdio = Redirect::Pipe, output : Stdio = Redirect::Pipe, error : Stdio = Redirect::Pipe, chdir : String? = nil) | |||
input : Stdio = Redirect::Pipe, output : Stdio = Redirect::Pipe, error : Stdio = Redirect::Pipe, chdir : String? = nil, &block : Process ->) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think &block
will eventually be changed to always capture, so & : Process ->
is the new syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This big one line (and the above one) can be split to one argument per line.
@@ -229,7 +154,7 @@ class Process | |||
end | |||
end | |||
|
|||
getter pid : Int32 | |||
getter pid : Int64 = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this default zero neccesary?
pids.each do |pid| | ||
ret = LibC.kill(pid, signal.value) | ||
raise Errno.new("kill") if ret < 0 | ||
Process.new(pid).kill(signal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't good on unix since you need to allocate a Process
and call waitpid
just to send a signal. I think there needs to be both .signal_system(signal, pid)
and #signal_system(signal)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
signal_system
is internal API, having a class method would be enough. It should be used for the (system specific) implementations of both class and instance methods kill
, terminate
and interrupt
, respectively.
Adding an instance method #signal_system(signal)
wouldn't be super useful when you can just call Process.signal_system(signal, @pid)
instead. It would only be internal API anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@straight-shoota that's fine on unix, but on windows you call TerminateProcess
on the handle.
Converting from the process handle to the PID just to convert to the process handle is inefficient, and that optimization is why there is both a class and an instance method: the Process
already has the handle open.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, you're right.
raise Errno.new("kill") if ret < 0 | ||
end | ||
|
||
protected def create_and_exec(command : String, args : (Array | Tuple)?, env : Env?, clear_env : Bool, fork_input : IO::FileDescriptor, fork_output : IO::FileDescriptor, fork_error : IO::FileDescriptor, chdir : String?) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps call this just spawn
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do t like it much, but I still prefer the explicit name than a confusing name (spawn fiber). Its internal API anyway.
def self.pgid : Int64 | ||
pgid(0) | ||
end | ||
{% if flag?(:unix) || flag?(:docs) %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All these methods must exist on windows, but raise at runtime. i.e. delete this entire commit and raise inside pgid_system
in src/crystal/system/win32/process.cr
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if one use this methods on Windows, this error will be raised only at runtime, despite being catchable a compile-time.
At least, maybe having a warning on this ones for Windows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@j8r This PR is mostly an internal refactor to extract the POSIX implementation. Not a place to discuss API changes.
I think we agreed to eventually remove fork and chroot, so they don't need to exist for win32. I'm not sure about the current discussion on pgid. @ysbaddaden and @jan-zajic voted to treat it like fork and chroot because it's a very POSIX-specific thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fork
is going, and will not be in 1.0 in any form. That's why it's special.
Every other API has to be identical at compile time on every platform. This was agreed in an issue, and is the status quo across the whole rest of the stdlib: see NotImplementedError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the question is: will process group id be in 1.0 or not? If I understood correctly, this is supposed to go as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@straight-shoota where's the discussion on that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To my knowledge there has not been a dedicated discussion on that specific feature. But it has been mentioned here and in #8515. There were no objections voiced, so it seems as if everyone is on board with this.
@@ -273,6 +273,16 @@ class Process | |||
@waitpid.closed? || !exists_system? | |||
end | |||
|
|||
# Terminate process gracefully |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this exist on windows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How? I can't see a way to terminate an arbitrary process gracefully. Go doesn't support it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of time you deal with GUI process, because Windows is about, surprise, windows ;). So you must find main window of target process and send WM_CLOSE message to it. If window not found, you can send WM_QUIT message to main thread. For console process, you should send crtl+c event to it using GenerateConsoleCtrlEvent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go doesn't seem to want to deal with that complexity for graceful process termination. I guess we'll see how much code there is :)
io << ' ' | ||
arg.inspect(io) | ||
end | ||
io << ")" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
io << ")" | |
io << ')' |
usage.ru_utime.tv_sec.to_f64 + usage.ru_utime.tv_usec.to_f64 / 1e6, | ||
usage.ru_stime.tv_sec.to_f64 + usage.ru_stime.tv_usec.to_f64 / 1e6, | ||
child.ru_utime.tv_sec.to_f64 + child.ru_utime.tv_usec.to_f64 / 1e6, | ||
child.ru_stime.tv_sec.to_f64 + child.ru_stime.tv_usec.to_f64 / 1e6, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The #to_f64
are not needed anymore since #8120
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. But I'd prefer to put such changes in a separate PR.
Prepare Process API for Windows implementation. After discussion in #8518 i extract the system implementation which provide
the following private interface (all methods are protected):
Public API design for Process
There need to be some changes to remove POSIXisms and make it portable.
kill method is renamed to signalmove to Rename Process kill methods to signal #8642Precursor PR: #8518