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

fest(std/node): implement os.getPriority() and os.setPriority() #4202

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ clap = "2.33.0"
dirs = "2.0.2"
dlopen = "0.1.8"
dprint-plugin-typescript = "0.7.0"
errno = "0.1.8"
futures = { version = "0.3.1", features = [ "compat", "io-compat" ] }
glob = "0.3.0"
http = "0.2.0"
Expand Down
3 changes: 3 additions & 0 deletions cli/js/deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ export {
execPath,
hostname,
loadavg,
OsPriority,
getPriority,
setPriority,
osRelease
} from "./os.ts";
export {
Expand Down
32 changes: 32 additions & 0 deletions cli/js/lib.deno.ns.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,38 @@ declare namespace Deno {
/** Exit the Deno process with optional exit code. */
export function exit(code?: number): never;

/** **UNSTABLE**: Might not use all-caps. */
export enum OsPriority {
LOW = 19,
BELOW_NORMAL = 10,
NORMAL = 0,
ABOVE_NORMAL = -7,
HIGH = -14,
HIGHEST = -20
}

/**
* Returns the scheduling priority for the process specified by pid.
* If pid is not provided or is 0, the priority of the current process is returned.
*/
export function getPriority(pid?: number): number;

/**
ecyrbe marked this conversation as resolved.
Show resolved Hide resolved
* Attempts to set the scheduling priority for the process specified by pid.
* If pid is not provided or is 0, the process ID of the current process is used.
* The priority input must be an integer between -20 (high priority) and 19 (low priority).
* Due to differences between Unix priority levels and Windows priority classes,
* priority is mapped to one of six priority constants in Deno.OsPriority enum.
* When retrieving a process priority level, this range mapping may cause the return value to be slightly different on Windows.
* To avoid confusion, set priority to one of the priority constants.
* On Windows, setting priority to Deno.OsPriority.HIGHEST requires elevated user privileges.
* Otherwise the set priority will be silently reduced to Deno.OsPriority.HIGH.
*/
export function setPriority(
priority: number | OsPriority,
pid?: number
): void;

/** Returns a snapshot of the environment variables at invocation. Mutating a
* property in the object will set that variable in the environment for the
* process. The environment object will only accept `string`s as values.
Expand Down
33 changes: 33 additions & 0 deletions cli/js/os.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,39 @@ export function exit(code = 0): never {
return util.unreachable();
}

/** **UNSTABLE**: Might not use all-caps. */
export enum OsPriority {
LOW = 19,
BELOW_NORMAL = 10,
NORMAL = 0,
ABOVE_NORMAL = -7,
HIGH = -14,
HIGHEST = -20
}

/**
* Returns the scheduling priority for the process specified by pid.
* If pid is not provided or is 0, the priority of the current process is returned.
*/
export function getPriority(pid = 0): number {
return sendSync("op_get_priority", { pid });
}

/**
* Attempts to set the scheduling priority for the process specified by pid.
* If pid is not provided or is 0, the process ID of the current process is used.
* The priority input must be an integer between -20 (high priority) and 19 (low priority).
* Due to differences between Unix priority levels and Windows priority classes,
* priority is mapped to one of six priority constants in Deno.OsPriority enum.
* When retrieving a process priority level, this range mapping may cause the return value to be slightly different on Windows.
* To avoid confusion, set priority to one of the priority constants.
* On Windows, setting priority to Deno.OsPriority.HIGHEST requires elevated user privileges.
* Otherwise the set priority will be silently reduced to Deno.OsPriority.HIGH.
*/
export function setPriority(priority: number | OsPriority, pid = 0): void {
sendSync("op_set_priority", { pid, priority });
}

function setEnv(key: string, value: string): void {
sendSync("op_set_env", { key, value });
}
Expand Down
33 changes: 33 additions & 0 deletions cli/js/os_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,39 @@ test(function osPid(): void {
assert(Deno.pid > 0);
});

test(function testGetPriorityShouldBeBetweenBounds() {
const priority = Deno.getPriority();
assert(Deno.OsPriority.HIGHEST <= priority);
assert(priority <= Deno.OsPriority.LOW);
});

if (Deno.build.os === "win")
test(function testSetPriorityShouldBeSetToHigh() {
Deno.setPriority(Deno.OsPriority.HIGH);
assertEquals(Deno.getPriority(), Deno.OsPriority.HIGH);
});

if (Deno.build.os === "win")
test(function testSetPriorityShouldBeSetToAboveNormal() {
Deno.setPriority(Deno.OsPriority.ABOVE_NORMAL);
assertEquals(Deno.getPriority(), Deno.OsPriority.ABOVE_NORMAL);
});

test(function testSetPriorityShouldBeSetToNormal() {
Deno.setPriority(Deno.OsPriority.NORMAL);
assertEquals(Deno.getPriority(), Deno.OsPriority.NORMAL);
});

test(function testSetPriorityShouldBeSetToBelowNormal() {
Deno.setPriority(Deno.OsPriority.BELOW_NORMAL);
assertEquals(Deno.getPriority(), Deno.OsPriority.BELOW_NORMAL);
});

test(function testSetPriorityShouldBeSetToLow() {
Deno.setPriority(Deno.OsPriority.LOW);
assertEquals(Deno.getPriority(), Deno.OsPriority.LOW);
});

testPerm({ env: true }, function getDir(): void {
type supportOS = "mac" | "win" | "linux";

Expand Down
1 change: 1 addition & 0 deletions cli/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub mod msg;
pub mod op_error;
pub mod ops;
pub mod permissions;
pub mod priority;
mod repl;
pub mod resolve_addr;
pub mod signal;
Expand Down
34 changes: 34 additions & 0 deletions cli/ops/os.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use super::dispatch_json::{Deserialize, JsonOp, Value};
use crate::op_error::OpError;
use crate::priority::{get_priority, set_priority};
use crate::state::State;
use deno_core::*;
use std::collections::HashMap;
Expand All @@ -19,6 +20,8 @@ pub fn init(i: &mut Isolate, s: &State) {
i.register_op("op_hostname", s.stateful_json_op(op_hostname));
i.register_op("op_loadavg", s.stateful_json_op(op_loadavg));
i.register_op("op_os_release", s.stateful_json_op(op_os_release));
i.register_op("op_get_priority", s.stateful_json_op(op_get_priority));
i.register_op("op_set_priority", s.stateful_json_op(op_set_priority));
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -185,3 +188,34 @@ fn op_os_release(
let release = sys_info::os_release().unwrap_or_else(|_| "".to_string());
Ok(JsonOp::Sync(json!(release)))
}

#[derive(Deserialize)]
struct GetPriorityArgs {
pid: u32,
}

fn op_get_priority(
_state: &State,
args: Value,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<JsonOp, OpError> {
let args: GetPriorityArgs = serde_json::from_value(args)?;
let priority = get_priority(args.pid)?;
Ok(JsonOp::Sync(json!(priority)))
}

#[derive(Deserialize)]
struct SetPriorityArgs {
pid: u32,
priority: i32,
}

fn op_set_priority(
_state: &State,
args: Value,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<JsonOp, OpError> {
let args: SetPriorityArgs = serde_json::from_value(args)?;
set_priority(args.pid, args.priority)?;
Ok(JsonOp::Sync(json!({})))
}
159 changes: 159 additions & 0 deletions cli/priority.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::op_error::OpError;
ecyrbe marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(unix)]
use errno::{errno, set_errno, Errno};
#[cfg(unix)]
use libc::{id_t, PRIO_PROCESS};
#[cfg(windows)]
use winapi::shared::minwindef::{DWORD, FALSE};
#[cfg(windows)]
use winapi::shared::ntdef::NULL;
#[cfg(windows)]
use winapi::um::handleapi::CloseHandle;
#[cfg(windows)]
use winapi::um::processthreadsapi::{
GetCurrentProcess, GetPriorityClass, OpenProcess, SetPriorityClass,
};
#[cfg(windows)]
use winapi::um::winbase::{
ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS,
HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS,
REALTIME_PRIORITY_CLASS,
};
#[cfg(windows)]
use winapi::um::winnt::PROCESS_QUERY_LIMITED_INFORMATION;
#[cfg(target_os = "macos")]
#[allow(non_camel_case_types)]
type priority_t = i32;
#[cfg(target_os = "linux")]
#[allow(non_camel_case_types)]
type priority_t = u32;

pub const PRIORITY_LOW: i32 = 19;
pub const PRIORITY_BELOW_NORMAL: i32 = 10;
pub const PRIORITY_NORMAL: i32 = 0;
pub const PRIORITY_ABOVE_NORMAL: i32 = -7;
pub const PRIORITY_HIGH: i32 = -14;
pub const PRIORITY_HIGHEST: i32 = -20;

#[cfg(unix)]
pub fn get_priority(pid: u32) -> Result<i32, OpError> {
unsafe {
set_errno(Errno(0));
match (
libc::getpriority(PRIO_PROCESS as priority_t, pid as id_t),
errno(),
) {
(-1, Errno(0)) => Ok(PRIORITY_HIGH),
(-1, _) => Err(OpError::from(std::io::Error::last_os_error())),
(priority, _) => Ok(priority),
}
}
}

#[cfg(unix)]
pub fn set_priority(pid: u32, priority: i32) -> Result<(), OpError> {
unsafe {
match libc::setpriority(PRIO_PROCESS as priority_t, pid as id_t, priority) {
-1 => Err(OpError::from(std::io::Error::last_os_error())),
_ => Ok(()),
}
}
}

#[cfg(windows)]
pub fn get_priority(pid: u32) -> Result<i32, OpError> {
unsafe {
let handle = if pid == 0 {
GetCurrentProcess()
} else {
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid as DWORD)
};
if handle == NULL {
Err(OpError::from(std::io::Error::last_os_error()))
} else {
let result = match GetPriorityClass(handle) {
0 => Err(OpError::from(std::io::Error::last_os_error())),
REALTIME_PRIORITY_CLASS => Ok(PRIORITY_HIGHEST),
HIGH_PRIORITY_CLASS => Ok(PRIORITY_HIGH),
ABOVE_NORMAL_PRIORITY_CLASS => Ok(PRIORITY_ABOVE_NORMAL),
NORMAL_PRIORITY_CLASS => Ok(PRIORITY_NORMAL),
BELOW_NORMAL_PRIORITY_CLASS => Ok(PRIORITY_BELOW_NORMAL),
IDLE_PRIORITY_CLASS => Ok(PRIORITY_LOW),
_ => Ok(PRIORITY_LOW),
};
CloseHandle(handle);
result
}
}
}

#[cfg(windows)]
pub fn set_priority(pid: u32, priority: i32) -> Result<(), OpError> {
unsafe {
let handle = if pid == 0 {
GetCurrentProcess()
} else {
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid as DWORD)
};
if handle == NULL {
Err(OpError::from(std::io::Error::last_os_error()))
} else {
let prio_class = match priority {
p if p <= PRIORITY_HIGHEST => REALTIME_PRIORITY_CLASS,
p if PRIORITY_HIGHEST < p && p <= PRIORITY_HIGH => HIGH_PRIORITY_CLASS,
p if PRIORITY_HIGH < p && p <= PRIORITY_ABOVE_NORMAL => {
ABOVE_NORMAL_PRIORITY_CLASS
}
p if PRIORITY_ABOVE_NORMAL < p && p <= PRIORITY_NORMAL => {
NORMAL_PRIORITY_CLASS
}
p if PRIORITY_NORMAL < p && p <= PRIORITY_BELOW_NORMAL => {
BELOW_NORMAL_PRIORITY_CLASS
}
_ => IDLE_PRIORITY_CLASS,
};
let result = match SetPriorityClass(handle, prio_class) {
FALSE => Err(OpError::from(std::io::Error::last_os_error())),
_ => Ok(()),
};
CloseHandle(handle);
result
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_get_current_process_priority() {
get_priority(0).expect("Should get priority");
}

#[cfg(unix)]
#[test]
fn test_set_current_process_high_priority_should_fail() {
assert!(set_priority(0, PRIORITY_HIGH).is_err());
}

/// this test makes multiple tests at once
/// because we need to set them in order and rust
/// does not guarantee test order execution
#[test]
fn test_set_current_process_priority_from_normal_to_low() {
set_priority(0, PRIORITY_NORMAL).expect("Should set priority");
let priority = get_priority(0).expect("Should get priority");
assert_eq!(priority, PRIORITY_NORMAL);

set_priority(0, PRIORITY_BELOW_NORMAL).expect("Should set priority");
let priority = get_priority(0).expect("Should get priority");
assert_eq!(priority, PRIORITY_BELOW_NORMAL);

set_priority(0, PRIORITY_LOW).expect("Should set priority");
let priority = get_priority(0).expect("Should get priority");
assert_eq!(priority, PRIORITY_LOW);
}
}
ecyrbe marked this conversation as resolved.
Show resolved Hide resolved
Loading