1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#![cfg_attr(not(feature = "std"), no_std)]
//! A thin abstraction over OS process scheduling APIs.
//!
//! By signalling the priority of our processes to the operating system, we
//! gain more control over our program's resource usage, and which tasks get
//! completed first.
//!
//! For example, we can configure our UI to preempt background tasks by giving
//! it a higher priority:
//!
//! ```rust
//! # use scrummage::{Process, Priority};
//! # let mut busy_child_process = std::process::Command::new("echo").spawn().unwrap();
//! let me = Process::current().priority().unwrap();
//! let boring_work = me.lower().next().expect("no lower priority available");
//! // It's fine if the `busy_child_process` has already finished
//! let _ = Process::from(&mut busy_child_process)
//!     .set_priority(boring_work);
//! ```
//!
//! This will tell the OS to make sure `me` is always given all the resources
//! it needs, making it snappier. 

macro_rules! doctest {
    ($x:expr) => {
        #[doc = $x]
        extern {}
    };
}
doctest!(include_str!("../README.md"));

#[cfg_attr(unix, path = "./unix.rs")]
mod imp;

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
/// A prioritisation level
///
/// The priority of a [`Process`] controls how much CPU time it gets
/// compared to other processes. Most programs don't need to be handled
/// especially, and should be given a [normal](Priority::normal) priority
/// to allow the OS to handle scheduling
pub struct Priority(imp::Priority);

impl Priority {
    // TODO: consider declaring these as `const fn`
    /// The priority level given to normal processes; The default priority
    /// level.
    ///
    /// ```rust
    /// # use scrummage::{Process, Priority};
    /// assert_eq!(Process::current().priority().unwrap(), Priority::normal(),
    ///            "I'm normal! Normal I tell you!");
    /// ```
    pub fn normal() -> Self {
        Self(imp::Priority::normal())
    }
    /// Raise the priority level.
    ///
    /// Be particularly careful with giving processes higher priority levels:
    /// Any process with a lower level will be halted until it pauses.
    /// Therefore, make sure any work it does is breif, and it uses OS APIs for
    /// delays ([`std::thread::sleep`] instead of `loop {}`)
    pub fn higher(&self) -> impl Iterator<Item = Self> {
        self.0.higher().map(Self)
    }
    /// Lower the priority level.
    ///
    /// Processes with lower priority levels will pause if other processes need
    /// to do work. They can be used for screen-savers e.t.c.
    pub fn lower(&self) -> impl Iterator<Item = Self> {
        self.0.lower().map(Self)
    }
}

#[derive(Debug)]
/// A process running on this machine.
///
/// Because the OS owns the process this "refers" to, we can't know it's valid:
/// someone could've killed it. Therefore, the methods return [`NotFound`] if
/// they are ever called on a dead process.
pub struct Process<'a>(imp::Process<'a>);

impl Process<'_> {
    /// Get the currently running process
    ///
    /// Note that this is will last for `'static`, since the OS process it
    /// refers to contains this very struct, and if it died, then this struct
    /// must have died with it.
    pub fn current() -> Process<'static> {
        Process(imp::Process::current())
    }
    /// Update the priority of this process

    pub fn set_priority(&mut self, priority: Priority) -> Result<(), Unchanged> {
        self.0.set_priority(priority.0)
    }
    /// Fetch the priority of this process
    pub fn priority(&self) -> Result<Priority, NotFound> {
        self.0.priority().map(Priority)
    }
}

// TODO: This API sorta sucks. Would Process::of_child(&Child) be better?
// The name's less than obvious
#[cfg(feature = "std")]
impl<'a> From<&'a mut std::process::Child> for Process<'a> {
    fn from(child: &'a mut std::process::Child) -> Self {
        Self(child.into())
    }
}

/// The process couldn't be found.
///
/// See [`Process`] for details.
#[derive(Debug)]
pub struct NotFound;

/// The reason the priority of a process couldn't be set.
#[derive(Debug)]
pub enum Unchanged {
    // This could be much cleaner with [enum variant types], which would
    // let `Process::priority` return `Result<Priority, Error::NotFound>`
    //
    // [enum variant types]: https://github.com/rust-lang/rfcs/pull/2593
    NotFound(NotFound),
    /// The [`Process`] handle didn't have the suitable permissions to
    /// set priority.
    /// 
    /// Each platform has a set of rules around who can set whose priority,
    /// and you should check the documentation for your platform to make sure
    /// you are setting up the right permissions. If the details of this error
    /// would be useful for you, do file an issue about your use case! 😁 
    PermissionDenied,
}

impl From<NotFound> for Unchanged {
    fn from(n: NotFound) -> Self {
        Self::NotFound(n)
    }
}

impl core::fmt::Display for NotFound {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        f.write_str("couldn't set priority of missing process")
    }
}

impl core::fmt::Display for Unchanged {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        match self {
            Self::NotFound(n) => core::fmt::Display::fmt(n, f),
            Self::PermissionDenied => f.write_str("missing permissions to set priority"),
        }
    }
}

#[cfg(feature = "std")]
impl std::error::Error for NotFound {}

#[cfg(feature = "std")]
impl std::error::Error for Unchanged {}