From 92880f31da1eca98a42e0f61708b10d9d8d83955 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Sun, 19 Aug 2018 00:34:17 -0400 Subject: [PATCH] os: add os.{get,set}Priority() PR-URL: https://github.com/nodejs/node/pull/22407 Reviewed-By: James M Snell Reviewed-By: Refael Ackermann --- doc/api/os.md | 87 ++++++++++++++ lib/constants.js | 1 + lib/os.js | 37 +++++- src/node_constants.cc | 44 ++++++++ src/node_os.cc | 43 +++++++ test/parallel/test-binding-constants.js | 2 +- test/parallel/test-os-process-priority.js | 131 ++++++++++++++++++++++ 7 files changed, 343 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-os-process-priority.js diff --git a/doc/api/os.md b/doc/api/os.md index b7bd246f97dbfd..4557bb6b7d369a 100644 --- a/doc/api/os.md +++ b/doc/api/os.md @@ -192,6 +192,19 @@ added: v0.3.3 The `os.freemem()` method returns the amount of free system memory in bytes as an integer. +## os.getPriority([pid]) + + +* `pid` {integer} The process ID to retrieve scheduling priority for. + **Default** `0`. +* Returns: {integer} + +The `os.getPriority()` method 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. + ## os.homedir() + +* `pid` {integer} The process ID to set scheduling priority for. + **Default** `0`. +* `priority` {integer} The scheduling priority to assign to the process. + +The `os.setPriority()` method attempts to set the scheduling priority for the +process specified by `pid`. If `pid` is not provided, or is `0`, the priority +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 +`os.constants.priority`. When retrieving a process priority level, this range +mapping may cause the return value to be slightly different on Windows. To avoid +confusion, it is recommended to set `priority` to one of the priority constants. + ## os.tmpdir() + +The following process scheduling constants are exported by +`os.constants.priority`: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ConstantDescription
PRIORITY_LOWThe lowest process scheduling priority. This corresponds to + IDLE_PRIORITY_CLASS on Windows, and a nice value of + 19 on all other platforms.
PRIORITY_BELOW_NORMALThe process scheduling priority above PRIORITY_LOW and + below PRIORITY_NORMAL. This corresponds to + BELOW_NORMAL_PRIORITY_CLASS on Windows, and a nice value of + 10 on all other platforms.
PRIORITY_NORMALThe default process scheduling priority. This corresponds to + NORMAL_PRIORITY_CLASS on Windows, and a nice value of + 0 on all other platforms.
PRIORITY_ABOVE_NORMALThe process scheduling priority above PRIORITY_NORMAL and + below PRIORITY_HIGH. This corresponds to + ABOVE_NORMAL_PRIORITY_CLASS on Windows, and a nice value of + -7 on all other platforms.
PRIORITY_HIGHThe process scheduling priority above PRIORITY_ABOVE_NORMAL + and below PRIORITY_HIGHEST. This corresponds to + HIGH_PRIORITY_CLASS on Windows, and a nice value of + -14 on all other platforms.
PRIORITY_HIGHESTThe highest process scheduling priority. This corresponds to + REALTIME_PRIORITY_CLASS on Windows, and a nice value of + -20 on all other platforms.
+ ### libuv Constants diff --git a/lib/constants.js b/lib/constants.js index dee46126447e06..a4c2ca6fb38ba8 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -29,6 +29,7 @@ const constants = process.binding('constants'); Object.assign(exports, constants.os.dlopen, constants.os.errno, + constants.os.priority, constants.os.signals, constants.fs, constants.crypto); diff --git a/lib/os.js b/lib/os.js index 15d06f1576f2e5..09a70d2f7b19e5 100644 --- a/lib/os.js +++ b/lib/os.js @@ -27,6 +27,7 @@ const { deprecate } = require('internal/util'); const isWindows = process.platform === 'win32'; const { codes: { ERR_SYSTEM_ERROR } } = require('internal/errors'); +const { validateInt32 } = require('internal/validators'); const { getCPUs, @@ -37,10 +38,12 @@ const { getLoadAvg, getOSRelease: _getOSRelease, getOSType: _getOSType, + getPriority: _getPriority, getTotalMem, getUserInfo: _getUserInfo, getUptime, - isBigEndian + isBigEndian, + setPriority: _setPriority } = process.binding('os'); function getCheckedFunction(fn) { @@ -206,17 +209,49 @@ function networkInterfaces() { return interfaceAddresses; } +function setPriority(pid, priority) { + if (priority === undefined) { + priority = pid; + pid = 0; + } + + validateInt32(pid, 'pid'); + validateInt32(priority, 'priority', -20, 19); + + const ctx = {}; + + if (_setPriority(pid, priority, ctx) !== 0) + throw new ERR_SYSTEM_ERROR(ctx); +} + +function getPriority(pid) { + if (pid === undefined) + pid = 0; + else + validateInt32(pid, 'pid'); + + const ctx = {}; + const priority = _getPriority(pid, ctx); + + if (priority === undefined) + throw new ERR_SYSTEM_ERROR(ctx); + + return priority; +} + module.exports = { arch, cpus, endianness, freemem: getFreeMem, + getPriority, homedir: getHomeDirectory, hostname: getHostname, loadavg, networkInterfaces, platform, release: getOSRelease, + setPriority, tmpdir, totalmem: getTotalMem, type: getOSType, diff --git a/src/node_constants.cc b/src/node_constants.cc index 28d5a9ca4edd38..f1468ff7ca03f2 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -758,6 +758,44 @@ void DefineSignalConstants(Local target) { #endif } +void DefinePriorityConstants(Local target) { +#ifdef UV_PRIORITY_LOW +# define PRIORITY_LOW UV_PRIORITY_LOW + NODE_DEFINE_CONSTANT(target, PRIORITY_LOW); +# undef PRIORITY_LOW +#endif + +#ifdef UV_PRIORITY_BELOW_NORMAL +# define PRIORITY_BELOW_NORMAL UV_PRIORITY_BELOW_NORMAL + NODE_DEFINE_CONSTANT(target, PRIORITY_BELOW_NORMAL); +# undef PRIORITY_BELOW_NORMAL +#endif + +#ifdef UV_PRIORITY_NORMAL +# define PRIORITY_NORMAL UV_PRIORITY_NORMAL + NODE_DEFINE_CONSTANT(target, PRIORITY_NORMAL); +# undef PRIORITY_NORMAL +#endif + +#ifdef UV_PRIORITY_ABOVE_NORMAL +# define PRIORITY_ABOVE_NORMAL UV_PRIORITY_ABOVE_NORMAL + NODE_DEFINE_CONSTANT(target, PRIORITY_ABOVE_NORMAL); +# undef PRIORITY_ABOVE_NORMAL +#endif + +#ifdef UV_PRIORITY_HIGH +# define PRIORITY_HIGH UV_PRIORITY_HIGH + NODE_DEFINE_CONSTANT(target, PRIORITY_HIGH); +# undef PRIORITY_HIGH +#endif + +#ifdef UV_PRIORITY_HIGHEST +# define PRIORITY_HIGHEST UV_PRIORITY_HIGHEST + NODE_DEFINE_CONSTANT(target, PRIORITY_HIGHEST); +# undef PRIORITY_HIGHEST +#endif +} + void DefineOpenSSLConstants(Local target) { #ifdef OPENSSL_VERSION_NUMBER NODE_DEFINE_CONSTANT(target, OPENSSL_VERSION_NUMBER); @@ -1338,6 +1376,10 @@ void DefineConstants(v8::Isolate* isolate, Local target) { CHECK(sig_constants->SetPrototype(env->context(), Null(env->isolate())).FromJust()); + Local priority_constants = Object::New(isolate); + CHECK(priority_constants->SetPrototype(env->context(), + Null(env->isolate())).FromJust()); + Local fs_constants = Object::New(isolate); CHECK(fs_constants->SetPrototype(env->context(), Null(env->isolate())).FromJust()); @@ -1361,6 +1403,7 @@ void DefineConstants(v8::Isolate* isolate, Local target) { DefineErrnoConstants(err_constants); DefineWindowsErrorConstants(err_constants); DefineSignalConstants(sig_constants); + DefinePriorityConstants(priority_constants); DefineSystemConstants(fs_constants); DefineOpenSSLConstants(crypto_constants); DefineCryptoConstants(crypto_constants); @@ -1374,6 +1417,7 @@ void DefineConstants(v8::Isolate* isolate, Local target) { os_constants->Set(OneByteString(isolate, "dlopen"), dlopen_constants); os_constants->Set(OneByteString(isolate, "errno"), err_constants); os_constants->Set(OneByteString(isolate, "signals"), sig_constants); + os_constants->Set(OneByteString(isolate, "priority"), priority_constants); target->Set(OneByteString(isolate, "os"), os_constants); target->Set(OneByteString(isolate, "fs"), fs_constants); target->Set(OneByteString(isolate, "crypto"), crypto_constants); diff --git a/src/node_os.cc b/src/node_os.cc index c9eef808b0addf..d3e9460f473122 100644 --- a/src/node_os.cc +++ b/src/node_os.cc @@ -52,6 +52,7 @@ using v8::Context; using v8::Float64Array; using v8::Function; using v8::FunctionCallbackInfo; +using v8::Int32; using v8::Integer; using v8::Local; using v8::MaybeLocal; @@ -405,6 +406,46 @@ static void GetUserInfo(const FunctionCallbackInfo& args) { } +static void SetPriority(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 3); + CHECK(args[0]->IsInt32()); + CHECK(args[1]->IsInt32()); + + const int pid = args[0].As()->Value(); + const int priority = args[1].As()->Value(); + const int err = uv_os_setpriority(pid, priority); + + if (err) { + CHECK(args[2]->IsObject()); + env->CollectUVExceptionInfo(args[2], err, "uv_os_setpriority"); + } + + args.GetReturnValue().Set(err); +} + + +static void GetPriority(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 2); + CHECK(args[0]->IsInt32()); + + const int pid = args[0].As()->Value(); + int priority; + const int err = uv_os_getpriority(pid, &priority); + + if (err) { + CHECK(args[1]->IsObject()); + env->CollectUVExceptionInfo(args[1], err, "uv_os_getpriority"); + return; + } + + args.GetReturnValue().Set(priority); +} + + void Initialize(Local target, Local unused, Local context) { @@ -420,6 +461,8 @@ void Initialize(Local target, env->SetMethod(target, "getInterfaceAddresses", GetInterfaceAddresses); env->SetMethod(target, "getHomeDirectory", GetHomeDirectory); env->SetMethod(target, "getUserInfo", GetUserInfo); + env->SetMethod(target, "setPriority", SetPriority); + env->SetMethod(target, "getPriority", GetPriority); target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "isBigEndian"), Boolean::New(env->isolate(), IsBigEndian())); } diff --git a/test/parallel/test-binding-constants.js b/test/parallel/test-binding-constants.js index b8e852b3525ab8..9855af19422882 100644 --- a/test/parallel/test-binding-constants.js +++ b/test/parallel/test-binding-constants.js @@ -10,7 +10,7 @@ assert.deepStrictEqual( assert.deepStrictEqual( Object.keys(constants.os).sort(), ['UV_UDP_REUSEADDR', 'dlopen', 'errno', - 'signals'] + 'priority', 'signals'] ); // Make sure all the constants objects don't inherit from Object.prototype diff --git a/test/parallel/test-os-process-priority.js b/test/parallel/test-os-process-priority.js new file mode 100644 index 00000000000000..9d66cfc49bc27a --- /dev/null +++ b/test/parallel/test-os-process-priority.js @@ -0,0 +1,131 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const os = require('os'); +const { + PRIORITY_LOW, + PRIORITY_BELOW_NORMAL, + PRIORITY_NORMAL, + PRIORITY_ABOVE_NORMAL, + PRIORITY_HIGH, + PRIORITY_HIGHEST +} = os.constants.priority; + +// Validate priority constants. +assert.strictEqual(typeof PRIORITY_LOW, 'number'); +assert.strictEqual(typeof PRIORITY_BELOW_NORMAL, 'number'); +assert.strictEqual(typeof PRIORITY_NORMAL, 'number'); +assert.strictEqual(typeof PRIORITY_ABOVE_NORMAL, 'number'); +assert.strictEqual(typeof PRIORITY_HIGH, 'number'); +assert.strictEqual(typeof PRIORITY_HIGHEST, 'number'); + +// Test pid type validation. +[null, true, false, 'foo', {}, [], /x/].forEach((pid) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "pid" argument must be of type number\./ + }; + + common.expectsError(() => { + os.setPriority(pid, PRIORITY_NORMAL); + }, errObj); + + common.expectsError(() => { + os.getPriority(pid); + }, errObj); +}); + +// Test pid range validation. +[NaN, Infinity, -Infinity, 3.14, 2 ** 32].forEach((pid) => { + const errObj = { + code: 'ERR_OUT_OF_RANGE', + message: /The value of "pid" is out of range\./ + }; + + common.expectsError(() => { + os.setPriority(pid, PRIORITY_NORMAL); + }, errObj); + + common.expectsError(() => { + os.getPriority(pid); + }, errObj); +}); + +// Test priority type validation. +[null, true, false, 'foo', {}, [], /x/].forEach((priority) => { + common.expectsError(() => { + os.setPriority(0, priority); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "priority" argument must be of type number\./ + }); +}); + +// Test priority range validation. +[ + NaN, + Infinity, + -Infinity, + 3.14, + 2 ** 32, + PRIORITY_HIGHEST - 1, + PRIORITY_LOW + 1 +].forEach((priority) => { + common.expectsError(() => { + os.setPriority(0, priority); + }, { + code: 'ERR_OUT_OF_RANGE', + message: /The value of "priority" is out of range\./ + }); +}); + +// Verify that valid values work. +for (let i = PRIORITY_HIGHEST; i <= PRIORITY_LOW; i++) { + // A pid of 0 corresponds to the current process. + try { + os.setPriority(0, i); + } catch (err) { + // The current user might not have sufficient permissions to set this + // specific priority level. Skip this priority, but keep trying lower + // priorities. + if (err.info.code === 'EACCES') + continue; + + assert(err); + } + + checkPriority(0, i); + + // An undefined pid corresponds to the current process. + os.setPriority(i); + checkPriority(undefined, i); + + // Specifying the actual pid works. + os.setPriority(process.pid, i); + checkPriority(process.pid, i); +} + + +function checkPriority(pid, expected) { + const priority = os.getPriority(pid); + + // Verify that the priority values match on Unix, and are range mapped on + // Windows. + if (!common.isWindows) { + assert.strictEqual(priority, expected); + return; + } + + if (expected < PRIORITY_HIGH) + assert.strictEqual(priority, PRIORITY_HIGHEST); + else if (expected < PRIORITY_ABOVE_NORMAL) + assert.strictEqual(priority, PRIORITY_HIGH); + else if (expected < PRIORITY_NORMAL) + assert.strictEqual(priority, PRIORITY_ABOVE_NORMAL); + else if (expected < PRIORITY_BELOW_NORMAL) + assert.strictEqual(priority, PRIORITY_NORMAL); + else if (expected < PRIORITY_LOW) + assert.strictEqual(priority, PRIORITY_BELOW_NORMAL); + else + assert.strictEqual(priority, PRIORITY_LOW); +}