1use std::{
2 collections::{BTreeMap, BTreeSet, HashMap, HashSet, hash_map::Entry},
3 sync::{
4 Arc, Mutex,
5 mpsc::{Receiver, RecvTimeoutError, Sender, TryRecvError},
6 },
7};
8
9#[cfg(not(target_family = "wasm"))]
10use nu_utils::time::Instant;
11#[cfg(not(target_family = "wasm"))]
12use std::time::Duration;
13
14use nu_system::{UnfreezeHandle, kill_by_pid};
15
16use crate::{PipelineData, Signals, shell_error};
17
18use crate::JobId;
19
20#[derive(Debug)]
21pub struct Jobs {
22 next_job_id: usize,
23
24 last_frozen_job_id: Option<JobId>,
28 jobs: HashMap<JobId, Job>,
29}
30
31impl Default for Jobs {
32 fn default() -> Self {
33 Self {
34 next_job_id: 1,
35 last_frozen_job_id: None,
36 jobs: HashMap::default(),
37 }
38 }
39}
40
41impl Jobs {
42 pub fn iter(&self) -> impl Iterator<Item = (JobId, &Job)> {
43 self.jobs.iter().map(|(k, v)| (*k, v))
44 }
45
46 pub fn is_empty(&self) -> bool {
48 self.jobs.is_empty()
49 }
50
51 pub fn lookup(&self, id: JobId) -> Option<&Job> {
52 self.jobs.get(&id)
53 }
54
55 pub fn lookup_mut(&mut self, id: JobId) -> Option<&mut Job> {
56 self.jobs.get_mut(&id)
57 }
58
59 pub fn remove_job(&mut self, id: JobId) -> Option<Job> {
60 if self.last_frozen_job_id.is_some_and(|last| id == last) {
61 self.last_frozen_job_id = None;
62 }
63
64 self.jobs.remove(&id)
65 }
66
67 fn assign_last_frozen_id_if_frozen(&mut self, id: JobId, job: &Job) {
68 if let Job::Frozen(_) = job {
69 self.last_frozen_job_id = Some(id);
70 }
71 }
72
73 pub fn add_job(&mut self, job: Job) -> JobId {
74 let this_id = JobId::new(self.next_job_id);
75
76 self.assign_last_frozen_id_if_frozen(this_id, &job);
77
78 self.jobs.insert(this_id, job);
79 self.next_job_id += 1;
80
81 this_id
82 }
83
84 pub fn most_recent_frozen_job_id(&mut self) -> Option<JobId> {
85 self.last_frozen_job_id
86 }
87
88 pub fn add_job_with_id(&mut self, id: JobId, job: Job) -> Result<(), &'static str> {
90 self.assign_last_frozen_id_if_frozen(id, &job);
91
92 if let Entry::Vacant(e) = self.jobs.entry(id) {
93 e.insert(job);
94 Ok(())
95 } else {
96 Err("job already exists")
97 }
98 }
99
100 pub fn kill_and_remove(&mut self, id: JobId) -> shell_error::io::Result<()> {
104 if let Some(job) = self.jobs.get(&id) {
105 let err = job.kill();
106
107 self.remove_job(id);
108
109 err?
110 }
111
112 Ok(())
113 }
114
115 pub fn kill_all(&mut self) -> shell_error::io::Result<()> {
121 self.last_frozen_job_id = None;
122
123 let first_err = self
124 .iter()
125 .map(|(_, job)| job.kill().err())
126 .fold(None, |acc, x| acc.or(x));
127
128 self.jobs.clear();
129
130 if let Some(err) = first_err {
131 Err(err)
132 } else {
133 Ok(())
134 }
135 }
136}
137
138#[derive(Debug)]
139pub enum Job {
140 Thread(ThreadJob),
141 Frozen(FrozenJob),
142}
143
144#[derive(Clone, Debug)]
153pub struct ThreadJob {
154 signals: Signals,
155 pids: Arc<Mutex<HashSet<u32>>>,
156 description: Option<String>,
157 pub sender: Sender<Mail>,
158}
159
160impl ThreadJob {
161 pub fn new(signals: Signals, description: Option<String>, sender: Sender<Mail>) -> Self {
162 ThreadJob {
163 signals,
164 pids: Arc::new(Mutex::new(HashSet::default())),
165 sender,
166 description,
167 }
168 }
169
170 pub fn try_add_pid(&self, pid: u32) -> bool {
175 let mut pids = self.pids.lock().expect("PIDs lock was poisoned");
176
177 if self.signals.interrupted() {
179 false
180 } else {
181 pids.insert(pid);
182 true
183 }
184 }
185
186 pub fn collect_pids(&self) -> Vec<u32> {
187 let lock = self.pids.lock().expect("PID lock was poisoned");
188
189 lock.iter().copied().collect()
190 }
191
192 pub fn kill(&self) -> shell_error::io::Result<()> {
193 self.signals.trigger();
197
198 let mut pids = self.pids.lock().expect("PIDs lock was poisoned");
199
200 for pid in pids.iter() {
201 kill_by_pid((*pid).into())?;
202 }
203
204 pids.clear();
205
206 Ok(())
207 }
208
209 pub fn remove_pid(&self, pid: u32) {
210 let mut pids = self.pids.lock().expect("PID lock was poisoned");
211
212 pids.remove(&pid);
213 }
214}
215
216impl Job {
217 pub fn kill(&self) -> shell_error::io::Result<()> {
218 match self {
219 Job::Thread(thread_job) => thread_job.kill(),
220 Job::Frozen(frozen_job) => frozen_job.kill(),
221 }
222 }
223
224 pub fn description(&self) -> Option<&String> {
225 match self {
226 Job::Thread(thread_job) => thread_job.description.as_ref(),
227 Job::Frozen(frozen_job) => frozen_job.description.as_ref(),
228 }
229 }
230
231 pub fn assign_description(&mut self, description: Option<String>) {
232 match self {
233 Job::Thread(thread_job) => thread_job.description = description,
234 Job::Frozen(frozen_job) => frozen_job.description = description,
235 }
236 }
237}
238
239#[derive(Debug)]
240pub struct FrozenJob {
241 pub unfreeze: UnfreezeHandle,
242 pub description: Option<String>,
243}
244
245impl FrozenJob {
246 pub fn kill(&self) -> shell_error::io::Result<()> {
247 #[cfg(unix)]
248 {
249 Ok(kill_by_pid(self.unfreeze.pid() as i64)?)
250 }
251
252 #[cfg(not(unix))]
254 {
255 Ok(())
256 }
257 }
258}
259
260#[derive(Clone, Debug)]
262pub struct CurrentJob {
263 pub id: JobId,
264
265 pub background_thread_job: Option<ThreadJob>,
268
269 pub mailbox: Arc<Mutex<Mailbox>>,
272}
273
274#[derive(Debug)]
280pub struct Mailbox {
281 receiver: Receiver<Mail>,
282 ignored_mail: IgnoredMail,
283}
284
285impl Mailbox {
286 pub fn new(receiver: Receiver<Mail>) -> Self {
287 Mailbox {
288 receiver,
289 ignored_mail: IgnoredMail::default(),
290 }
291 }
292
293 #[cfg(not(target_family = "wasm"))]
294 pub fn recv_timeout(
295 &mut self,
296 filter_tag: Option<FilterTag>,
297 timeout: Duration,
298 ) -> Result<PipelineData, RecvTimeoutError> {
299 if let Some(value) = self.ignored_mail.pop(filter_tag) {
300 Ok(value)
301 } else {
302 let mut waited_so_far = Duration::ZERO;
303 let mut before = Instant::now();
304
305 while waited_so_far < timeout {
306 let (tag, value) = self
307 .receiver
308 .recv_timeout(timeout.checked_sub(waited_so_far).unwrap_or(Duration::ZERO))?;
309
310 if filter_tag.is_none() || filter_tag == tag {
311 return Ok(value);
312 } else {
313 self.ignored_mail.add((tag, value));
314 let now = Instant::now();
315 waited_so_far += now - before;
316 before = now;
317 }
318 }
319
320 Err(RecvTimeoutError::Timeout)
321 }
322 }
323
324 #[cfg(not(target_family = "wasm"))]
325 pub fn try_recv(
326 &mut self,
327 filter_tag: Option<FilterTag>,
328 ) -> Result<PipelineData, TryRecvError> {
329 if let Some(value) = self.ignored_mail.pop(filter_tag) {
330 Ok(value)
331 } else {
332 loop {
333 let (tag, value) = self.receiver.try_recv()?;
334
335 if filter_tag.is_none() || filter_tag == tag {
336 return Ok(value);
337 } else {
338 self.ignored_mail.add((tag, value));
339 }
340 }
341 }
342 }
343
344 pub fn clear(&mut self) {
345 self.ignored_mail.clear();
346
347 while self.receiver.try_recv().is_ok() {}
348 }
349}
350
351#[derive(Default, Debug)]
354struct IgnoredMail {
355 next_id: usize,
356 messages: BTreeMap<usize, Mail>,
357 by_tag: HashMap<FilterTag, BTreeSet<usize>>,
358}
359
360pub type FilterTag = u64;
361pub type Mail = (Option<FilterTag>, PipelineData);
362
363impl IgnoredMail {
364 pub fn add(&mut self, (tag, value): Mail) {
365 let id = self.next_id;
366 self.next_id += 1;
367
368 self.messages.insert(id, (tag, value));
369
370 if let Some(tag) = tag {
371 self.by_tag.entry(tag).or_default().insert(id);
372 }
373 }
374
375 pub fn pop(&mut self, tag: Option<FilterTag>) -> Option<PipelineData> {
376 if let Some(tag) = tag {
377 self.pop_oldest_with_tag(tag)
378 } else {
379 self.pop_oldest()
380 }
381 }
382
383 pub fn clear(&mut self) {
384 self.messages.clear();
385 self.by_tag.clear();
386 }
387
388 fn pop_oldest(&mut self) -> Option<PipelineData> {
389 let (id, (tag, value)) = self.messages.pop_first()?;
390
391 if let Some(tag) = tag
392 && let Entry::Occupied(mut occupied_entry) = self.by_tag.entry(tag)
393 {
394 occupied_entry.get_mut().remove(&id);
395 if occupied_entry.get().is_empty() {
396 occupied_entry.remove();
397 }
398 }
399
400 Some(value)
401 }
402
403 fn pop_oldest_with_tag(&mut self, tag: FilterTag) -> Option<PipelineData> {
404 let ids = self.by_tag.get_mut(&tag)?;
405
406 let id = ids.pop_first()?;
407
408 if ids.is_empty() {
409 self.by_tag.remove(&tag);
410 }
411
412 Some(self.messages.remove(&id)?.1)
413 }
414}