mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
Add Deno.kill(pid, signo) and process.kill(signo) (Unix only) (#2177)
This commit is contained in:
parent
9dfebbc949
commit
1d4b92ac85
11 changed files with 247 additions and 3 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -225,6 +225,7 @@ dependencies = [
|
|||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -42,6 +42,9 @@ main_extern = [
|
|||
if (is_win) {
|
||||
main_extern += [ "$rust_build:winapi" ]
|
||||
}
|
||||
if (is_posix) {
|
||||
main_extern += [ "$rust_build:nix" ]
|
||||
}
|
||||
|
||||
ts_sources = [
|
||||
"../js/assets.ts",
|
||||
|
|
|
@ -50,3 +50,6 @@ url = "1.7.2"
|
|||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.3.7"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = "0.11.0"
|
||||
|
|
|
@ -4,6 +4,8 @@ pub use crate::msg::ErrorKind;
|
|||
use crate::resolve_addr::ResolveAddrError;
|
||||
use deno::JSError;
|
||||
use hyper;
|
||||
#[cfg(unix)]
|
||||
use nix::{errno::Errno, Error as UnixError};
|
||||
use std;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
@ -168,6 +170,32 @@ impl From<ResolveAddrError> for DenoError {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl From<UnixError> for DenoError {
|
||||
fn from(e: UnixError) -> Self {
|
||||
match e {
|
||||
UnixError::Sys(Errno::EPERM) => Self {
|
||||
repr: Repr::Simple(
|
||||
ErrorKind::PermissionDenied,
|
||||
Errno::EPERM.desc().to_owned(),
|
||||
),
|
||||
},
|
||||
UnixError::Sys(Errno::EINVAL) => Self {
|
||||
repr: Repr::Simple(
|
||||
ErrorKind::InvalidInput,
|
||||
Errno::EINVAL.desc().to_owned(),
|
||||
),
|
||||
},
|
||||
UnixError::Sys(err) => Self {
|
||||
repr: Repr::Simple(ErrorKind::UnixError, err.desc().to_owned()),
|
||||
},
|
||||
_ => Self {
|
||||
repr: Repr::Simple(ErrorKind::Other, format!("{}", e)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bad_resource() -> DenoError {
|
||||
new(ErrorKind::BadResource, String::from("bad resource id"))
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ extern crate futures;
|
|||
extern crate serde_json;
|
||||
extern crate clap;
|
||||
extern crate deno;
|
||||
#[cfg(unix)]
|
||||
extern crate nix;
|
||||
|
||||
mod ansi;
|
||||
pub mod compiler;
|
||||
|
@ -27,6 +29,7 @@ pub mod permissions;
|
|||
mod repl;
|
||||
pub mod resolve_addr;
|
||||
pub mod resources;
|
||||
mod signal;
|
||||
mod startup_data;
|
||||
pub mod state;
|
||||
mod tokio_util;
|
||||
|
|
|
@ -21,6 +21,7 @@ union Any {
|
|||
GlobalTimerStop,
|
||||
IsTTY,
|
||||
IsTTYRes,
|
||||
Kill,
|
||||
Link,
|
||||
Listen,
|
||||
ListenRes,
|
||||
|
@ -129,7 +130,8 @@ enum ErrorKind: byte {
|
|||
InvalidUri,
|
||||
InvalidSeekMode,
|
||||
OpNotAvaiable,
|
||||
WorkerInitFailed
|
||||
WorkerInitFailed,
|
||||
UnixError,
|
||||
}
|
||||
|
||||
table Cwd {}
|
||||
|
@ -453,6 +455,11 @@ table Close {
|
|||
rid: uint32;
|
||||
}
|
||||
|
||||
table Kill {
|
||||
pid: int32;
|
||||
signo: int32;
|
||||
}
|
||||
|
||||
table Shutdown {
|
||||
rid: uint32;
|
||||
how: uint;
|
||||
|
|
17
cli/ops.rs
17
cli/ops.rs
|
@ -14,6 +14,7 @@ use crate::resolve_addr::resolve_addr;
|
|||
use crate::resources;
|
||||
use crate::resources::table_entries;
|
||||
use crate::resources::Resource;
|
||||
use crate::signal::kill;
|
||||
use crate::startup_data;
|
||||
use crate::state::ThreadSafeState;
|
||||
use crate::tokio_util;
|
||||
|
@ -171,6 +172,7 @@ pub fn op_selector_std(inner_type: msg::Any) -> Option<OpCreator> {
|
|||
msg::Any::GlobalTimer => Some(op_global_timer),
|
||||
msg::Any::GlobalTimerStop => Some(op_global_timer_stop),
|
||||
msg::Any::IsTTY => Some(op_is_tty),
|
||||
msg::Any::Kill => Some(op_kill),
|
||||
msg::Any::Link => Some(op_link),
|
||||
msg::Any::Listen => Some(op_listen),
|
||||
msg::Any::MakeTempDir => Some(op_make_temp_dir),
|
||||
|
@ -906,6 +908,21 @@ fn op_close(
|
|||
}
|
||||
}
|
||||
|
||||
fn op_kill(
|
||||
_state: &ThreadSafeState,
|
||||
base: &msg::Base<'_>,
|
||||
data: deno_buf,
|
||||
) -> Box<OpWithError> {
|
||||
assert_eq!(data.len(), 0);
|
||||
let inner = base.inner_as_kill().unwrap();
|
||||
let pid = inner.pid();
|
||||
let signo = inner.signo();
|
||||
match kill(pid, signo) {
|
||||
Ok(_) => ok_future(empty_buf()),
|
||||
Err(e) => odd_future(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn op_shutdown(
|
||||
_state: &ThreadSafeState,
|
||||
base: &msg::Base<'_>,
|
||||
|
|
20
cli/signal.rs
Normal file
20
cli/signal.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
#[cfg(unix)]
|
||||
use nix::sys::signal::{kill as unix_kill, Signal};
|
||||
#[cfg(unix)]
|
||||
use nix::unistd::Pid;
|
||||
|
||||
use crate::errors::DenoResult;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn kill(pid: i32, signo: i32) -> DenoResult<()> {
|
||||
use crate::errors::DenoError;
|
||||
let sig = Signal::from_c_int(signo)?;
|
||||
unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(DenoError::from)
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn kill(_pid: i32, _signal: i32) -> DenoResult<()> {
|
||||
// NOOP
|
||||
// TODO: implement this for windows
|
||||
Ok(())
|
||||
}
|
|
@ -68,7 +68,14 @@ export { FileInfo } from "./file_info";
|
|||
export { connect, dial, listen, Listener, Conn } from "./net";
|
||||
export { metrics, Metrics } from "./metrics";
|
||||
export { resources } from "./resources";
|
||||
export { run, RunOptions, Process, ProcessStatus } from "./process";
|
||||
export {
|
||||
kill,
|
||||
run,
|
||||
RunOptions,
|
||||
Process,
|
||||
ProcessStatus,
|
||||
Signal
|
||||
} from "./process";
|
||||
export { inspect } from "./console";
|
||||
export { build, platform, OperatingSystem, Arch } from "./build";
|
||||
export { version } from "./version";
|
||||
|
|
|
@ -7,6 +7,7 @@ import { File, close } from "./files";
|
|||
import { ReadCloser, WriteCloser } from "./io";
|
||||
import { readAll } from "./buffer";
|
||||
import { assert, unreachable } from "./util";
|
||||
import { platform } from "./build";
|
||||
|
||||
/** How to handle subprocess stdio.
|
||||
*
|
||||
|
@ -51,6 +52,16 @@ async function runStatus(rid: number): Promise<ProcessStatus> {
|
|||
}
|
||||
}
|
||||
|
||||
/** Send a signal to process under given PID. Unix only at this moment.
|
||||
* If pid is negative, the signal will be sent to the process group identified
|
||||
* by -pid.
|
||||
*/
|
||||
export function kill(pid: number, signo: number): void {
|
||||
const builder = flatbuffers.createBuilder();
|
||||
const inner = msg.Kill.createKill(builder, pid, signo);
|
||||
dispatch.sendSync(builder, msg.Any.Kill, inner);
|
||||
}
|
||||
|
||||
export class Process {
|
||||
readonly rid: number;
|
||||
readonly pid: number;
|
||||
|
@ -113,6 +124,10 @@ export class Process {
|
|||
close(): void {
|
||||
close(this.rid);
|
||||
}
|
||||
|
||||
kill(signo: number): void {
|
||||
kill(this.pid, signo);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProcessStatus {
|
||||
|
@ -179,3 +194,77 @@ export function run(opt: RunOptions): Process {
|
|||
|
||||
return new Process(res);
|
||||
}
|
||||
|
||||
// From `kill -l`
|
||||
enum LinuxSignal {
|
||||
SIGHUP = 1,
|
||||
SIGINT = 2,
|
||||
SIGQUIT = 3,
|
||||
SIGILL = 4,
|
||||
SIGTRAP = 5,
|
||||
SIGABRT = 6,
|
||||
SIGBUS = 7,
|
||||
SIGFPE = 8,
|
||||
SIGKILL = 9,
|
||||
SIGUSR1 = 10,
|
||||
SIGSEGV = 11,
|
||||
SIGUSR2 = 12,
|
||||
SIGPIPE = 13,
|
||||
SIGALRM = 14,
|
||||
SIGTERM = 15,
|
||||
SIGSTKFLT = 16,
|
||||
SIGCHLD = 17,
|
||||
SIGCONT = 18,
|
||||
SIGSTOP = 19,
|
||||
SIGTSTP = 20,
|
||||
SIGTTIN = 21,
|
||||
SIGTTOU = 22,
|
||||
SIGURG = 23,
|
||||
SIGXCPU = 24,
|
||||
SIGXFSZ = 25,
|
||||
SIGVTALRM = 26,
|
||||
SIGPROF = 27,
|
||||
SIGWINCH = 28,
|
||||
SIGIO = 29,
|
||||
SIGPWR = 30,
|
||||
SIGSYS = 31
|
||||
}
|
||||
|
||||
// From `kill -l`
|
||||
enum MacOSSignal {
|
||||
SIGHUP = 1,
|
||||
SIGINT = 2,
|
||||
SIGQUIT = 3,
|
||||
SIGILL = 4,
|
||||
SIGTRAP = 5,
|
||||
SIGABRT = 6,
|
||||
SIGEMT = 7,
|
||||
SIGFPE = 8,
|
||||
SIGKILL = 9,
|
||||
SIGBUS = 10,
|
||||
SIGSEGV = 11,
|
||||
SIGSYS = 12,
|
||||
SIGPIPE = 13,
|
||||
SIGALRM = 14,
|
||||
SIGTERM = 15,
|
||||
SIGURG = 16,
|
||||
SIGSTOP = 17,
|
||||
SIGTSTP = 18,
|
||||
SIGCONT = 19,
|
||||
SIGCHLD = 20,
|
||||
SIGTTIN = 21,
|
||||
SIGTTOU = 22,
|
||||
SIGIO = 23,
|
||||
SIGXCPU = 24,
|
||||
SIGXFSZ = 25,
|
||||
SIGVTALRM = 26,
|
||||
SIGPROF = 27,
|
||||
SIGWINCH = 28,
|
||||
SIGINFO = 29,
|
||||
SIGUSR1 = 30,
|
||||
SIGUSR2 = 31
|
||||
}
|
||||
|
||||
/** Signals numbers. This is platform dependent.
|
||||
*/
|
||||
export const Signal = platform.os === "mac" ? MacOSSignal : LinuxSignal;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
import { test, testPerm, assert, assertEquals } from "./test_util.ts";
|
||||
const { run, DenoError, ErrorKind } = Deno;
|
||||
const { kill, run, DenoError, ErrorKind } = Deno;
|
||||
|
||||
test(function runPermissions(): void {
|
||||
let caughtError = false;
|
||||
|
@ -223,3 +223,69 @@ testPerm({ run: true }, async function runEnv(): Promise<void> {
|
|||
assertEquals(s, "01234567");
|
||||
p.close();
|
||||
});
|
||||
|
||||
testPerm({ run: true }, async function runClose(): Promise<void> {
|
||||
const p = run({
|
||||
args: [
|
||||
"python",
|
||||
"-c",
|
||||
"from time import sleep; import sys; sleep(10000); sys.stderr.write('error')"
|
||||
],
|
||||
stderr: "piped"
|
||||
});
|
||||
assert(!p.stdin);
|
||||
assert(!p.stdout);
|
||||
|
||||
p.close();
|
||||
|
||||
const data = new Uint8Array(10);
|
||||
let r = await p.stderr.read(data);
|
||||
assertEquals(r.nread, 0);
|
||||
assertEquals(r.eof, true);
|
||||
});
|
||||
|
||||
test(function signalNumbers(): void {
|
||||
if (Deno.platform.os === "mac") {
|
||||
assertEquals(Deno.Signal.SIGSTOP, 17);
|
||||
} else if (Deno.platform.os === "linux") {
|
||||
assertEquals(Deno.Signal.SIGSTOP, 19);
|
||||
}
|
||||
});
|
||||
|
||||
// Ignore signal tests on windows for now...
|
||||
if (Deno.platform.os !== "win") {
|
||||
testPerm({ run: true }, async function killSuccess(): Promise<void> {
|
||||
const p = run({
|
||||
args: ["python", "-c", "from time import sleep; sleep(10000)"]
|
||||
});
|
||||
|
||||
assertEquals(Deno.Signal.SIGINT, 2);
|
||||
kill(p.pid, Deno.Signal.SIGINT);
|
||||
const status = await p.status();
|
||||
|
||||
assertEquals(status.success, false);
|
||||
assertEquals(status.code, undefined);
|
||||
assertEquals(status.signal, Deno.Signal.SIGINT);
|
||||
});
|
||||
|
||||
testPerm({ run: true }, async function killFailed(): Promise<void> {
|
||||
const p = run({
|
||||
args: ["python", "-c", "from time import sleep; sleep(10000)"]
|
||||
});
|
||||
assert(!p.stdin);
|
||||
assert(!p.stdout);
|
||||
|
||||
let err;
|
||||
try {
|
||||
kill(p.pid, 12345);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
assert(!!err);
|
||||
assertEquals(err.kind, Deno.ErrorKind.InvalidInput);
|
||||
assertEquals(err.name, "InvalidInput");
|
||||
|
||||
p.close();
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue