1use serde::de::DeserializeOwned;
2use std::cell::RefCell;
3use std::io::BufRead;
4use std::io::ErrorKind;
5use std::io::Read;
6use std::io::Write;
7use std::path::Path;
8use std::path::PathBuf;
9use std::process::Child;
10use std::process::ChildStderr;
11use std::process::Command;
12use std::process::Stdio;
13use std::rc::Rc;
14use std::sync::Arc;
15use std::time::Duration;
16use tokio::sync::oneshot;
17use tokio_util::sync::CancellationToken;
18
19use super::PLUGIN_SCHEMA_VERSION;
20use super::messages::CheckConfigUpdatesMessageBody;
21use super::messages::CheckConfigUpdatesResponseBody;
22use super::messages::FormatMessageBody;
23use super::messages::HostFormatMessageBody;
24use super::messages::MessageBody;
25use super::messages::ProcessPluginMessage;
26use super::messages::RegisterConfigMessageBody;
27use super::messages::ResponseBody;
28use crate::async_runtime::DropGuardAction;
29use crate::async_runtime::LocalBoxFuture;
30use crate::communication::AtomicFlag;
31use crate::communication::IdGenerator;
32use crate::communication::MessageReader;
33use crate::communication::MessageWriter;
34use crate::communication::RcIdStore;
35use crate::communication::SingleThreadMessageWriter;
36use crate::configuration::ConfigKeyMap;
37use crate::configuration::ConfigurationDiagnostic;
38use crate::configuration::GlobalConfiguration;
39use crate::plugins::ConfigChange;
40use crate::plugins::CriticalFormatError;
41use crate::plugins::FILE_MATCHING_INFO_ERROR_MESSAGE;
42use crate::plugins::FileMatchingInfo;
43use crate::plugins::FormatConfigId;
44use crate::plugins::FormatError;
45use crate::plugins::FormatRange;
46use crate::plugins::FormatResult;
47use crate::plugins::HostFormatRequest;
48use crate::plugins::NullCancellationToken;
49use crate::plugins::PluginInfo;
50use crate::plugins::error_to_string;
51
52type Result<T> = std::result::Result<T, FormatError>;
53
54#[cfg(unix)]
55use std::os::unix::process::CommandExt;
56
57type DprintCancellationToken = Arc<dyn super::super::CancellationToken>;
58
59#[derive(Debug, thiserror::Error)]
61enum SchemaVersionError {
62 #[error("Failed asking for schema version: {0}")]
63 Ask(std::io::Error),
64 #[error("Failed flushing schema version request: {0}")]
65 Flush(std::io::Error),
66 #[error("Could not read success response: {0}")]
67 ReadAcknowledgement(std::io::Error),
68 #[error("Plugin response was unexpected ({0}).")]
69 UnexpectedAcknowledgement(u32),
70 #[error("Could not read schema version: {0}")]
71 ReadVersion(std::io::Error),
72}
73
74#[derive(Debug, thiserror::Error)]
76enum CommunicatorError {
77 #[error("Error starting {executable} with args [{args}]. {error}")]
78 StartProcess { executable: String, args: String, error: std::io::Error },
79 #[error("Failed plugin schema verification. This may indicate you are using an old version of the dprint CLI or plugin and should upgrade")]
80 SchemaVerification(#[from] SchemaVersionError),
81 #[error(
82 "This plugin is too old to run in the dprint CLI and you will need to manually upgrade it (version was {actual}, but expected {expected}).\n\nUpgrade instructions: https://github.com/dprint/dprint/issues/731"
83 )]
84 PluginTooOld { actual: u32, expected: u32 },
85 #[error("Your dprint CLI is too old to run this plugin (version was {actual}, but expected {expected}). Try running: dprint upgrade")]
86 CliTooOld { actual: u32, expected: u32 },
87 #[error("Error waiting on message ({message_id}). {error}")]
88 WaitOnMessage {
89 message_id: u32,
90 error: tokio::sync::oneshot::error::RecvError,
91 },
92 #[error("Unexpected data channel for success response: {0}")]
93 UnexpectedDataForSuccess(u32),
94 #[error("Unexpected format channel for success response: {0}")]
95 UnexpectedFormatForSuccess(u32),
96 #[error("Unexpected success channel for data response: {0}")]
97 UnexpectedSuccessForData(u32),
98 #[error("Unexpected format channel for data response: {0}")]
99 UnexpectedFormatForData(u32),
100 #[error("Unexpected success channel for format response: {0}")]
101 UnexpectedSuccessForFormat(u32),
102 #[error("Unexpected data channel for format response: {0}")]
103 UnexpectedDataForFormat(u32),
104 #[error("Unknown message kind: {0}")]
105 UnknownMessageKind(u32),
106 #[error("Could not find host format callback for message id: {0}")]
107 HostFormatCallbackNotFound(u32),
108}
109
110impl From<CommunicatorError> for FormatError {
111 fn from(err: CommunicatorError) -> Self {
112 FormatError::new(err)
113 }
114}
115
116pub type HostFormatCallback = Rc<dyn Fn(HostFormatRequest) -> LocalBoxFuture<'static, FormatResult>>;
117
118pub struct ProcessPluginCommunicatorFormatRequest {
119 pub file_path: PathBuf,
120 pub file_bytes: Arc<Vec<u8>>,
121 pub range: FormatRange,
122 pub config_id: FormatConfigId,
123 pub override_config: ConfigKeyMap,
124 pub on_host_format: HostFormatCallback,
125 pub token: DprintCancellationToken,
126}
127
128enum MessageResponseChannel {
129 Acknowledgement(oneshot::Sender<Result<()>>),
130 Data(oneshot::Sender<Result<Vec<u8>>>),
131 Format(oneshot::Sender<Result<Option<Vec<u8>>>>),
132}
133
134struct Context {
135 stdin_writer: SingleThreadMessageWriter<ProcessPluginMessage>,
136 shutdown_flag: Arc<AtomicFlag>,
137 id_generator: IdGenerator,
138 messages: RcIdStore<MessageResponseChannel>,
139 format_request_tokens: RcIdStore<Arc<CancellationToken>>,
140 host_format_callbacks: RcIdStore<HostFormatCallback>,
141}
142
143pub struct ProcessPluginCommunicator {
145 child: RefCell<Option<Child>>,
146 #[cfg(windows)]
147 job: Option<ProcessJob>,
148 context: Rc<Context>,
149}
150
151impl Drop for ProcessPluginCommunicator {
152 fn drop(&mut self) {
153 self.kill();
154 }
155}
156
157impl ProcessPluginCommunicator {
158 pub async fn new(executable_file_path: &Path, on_std_err: impl Fn(String) + Clone + Send + Sync + 'static) -> Result<Self> {
159 ProcessPluginCommunicator::new_internal(executable_file_path, false, on_std_err).await
160 }
161
162 pub async fn new_with_init(executable_file_path: &Path, on_std_err: impl Fn(String) + Clone + Send + Sync + 'static) -> Result<Self> {
164 ProcessPluginCommunicator::new_internal(executable_file_path, true, on_std_err).await
165 }
166
167 async fn new_internal(executable_file_path: &Path, is_init: bool, on_std_err: impl Fn(String) + Clone + Send + Sync + 'static) -> Result<Self> {
168 let mut args = vec!["--parent-pid".to_string(), std::process::id().to_string()];
169 if is_init {
170 args.push("--init".to_string());
171 }
172
173 let shutdown_flag = Arc::new(AtomicFlag::default());
174 let mut command = Command::new(executable_file_path);
175 command.args(&args).stdin(Stdio::piped()).stderr(Stdio::piped()).stdout(Stdio::piped());
176 #[cfg(unix)]
177 command.process_group(0);
178 let mut child = command.spawn().map_err(|err| CommunicatorError::StartProcess {
179 executable: executable_file_path.display().to_string(),
180 args: args.join(" "),
181 error: err,
182 })?;
183 #[cfg(windows)]
185 let job = ProcessJob::for_child(&child);
186
187 let stderr = child.stderr.take().unwrap();
189 crate::async_runtime::spawn_blocking({
190 let shutdown_flag = shutdown_flag.clone();
191 let on_std_err = on_std_err.clone();
192 move || {
193 std_err_redirect(shutdown_flag, stderr, on_std_err);
194 }
195 });
196
197 let mut stdout_reader = MessageReader::new(child.stdout.take().unwrap());
199 let mut stdin_writer = MessageWriter::new(child.stdin.take().unwrap());
200
201 let (mut stdout_reader, stdin_writer, schema_version) = crate::async_runtime::spawn_blocking(move || {
202 let schema_version = get_plugin_schema_version(&mut stdout_reader, &mut stdin_writer).map_err(CommunicatorError::SchemaVerification)?;
203 Ok::<_, FormatError>((stdout_reader, stdin_writer, schema_version))
204 })
205 .await??;
206
207 if schema_version != PLUGIN_SCHEMA_VERSION {
208 let _ = child.kill();
210 let err = if schema_version < PLUGIN_SCHEMA_VERSION {
211 CommunicatorError::PluginTooOld {
212 actual: schema_version,
213 expected: PLUGIN_SCHEMA_VERSION,
214 }
215 } else {
216 CommunicatorError::CliTooOld {
217 actual: schema_version,
218 expected: PLUGIN_SCHEMA_VERSION,
219 }
220 };
221 return Err(err.into());
222 }
223
224 let stdin_writer = SingleThreadMessageWriter::for_stdin(stdin_writer);
225 let context = Rc::new(Context {
226 id_generator: Default::default(),
227 shutdown_flag,
228 stdin_writer,
229 messages: Default::default(),
230 format_request_tokens: Default::default(),
231 host_format_callbacks: Default::default(),
232 });
233
234 let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
236 crate::async_runtime::spawn_blocking({
237 let shutdown_flag = context.shutdown_flag.clone();
238 let on_std_err = on_std_err.clone();
239 move || {
240 loop {
241 match ProcessPluginMessage::read(&mut stdout_reader) {
242 Ok(message) => {
243 if tx.send(message).is_err() {
244 break; }
246 }
247 Err(err) if err.kind() == ErrorKind::BrokenPipe => {
248 break;
249 }
250 Err(err) => {
251 if !shutdown_flag.is_raised() {
252 on_std_err(format!("Error reading stdout message: {:#}", err));
253 }
254 break;
255 }
256 }
257 }
258 }
259 });
260 crate::async_runtime::spawn({
261 let context = context.clone();
262 async move {
263 while let Some(message) = rx.recv().await {
264 if let Err(err) = handle_stdout_message(message, &context) {
265 if !context.shutdown_flag.is_raised() {
266 on_std_err(format!("Error reading stdout message: {:#}", err));
267 }
268 break;
269 }
270 }
271 context.messages.take_all();
273 }
274 });
275
276 Ok(Self {
277 #[cfg(windows)]
278 job,
279 child: RefCell::new(Some(child)),
280 context,
281 })
282 }
283
284 pub async fn shutdown(&self) {
286 if self.context.shutdown_flag.raise() {
287 tokio::select! {
289 _ = self.send_with_acknowledgement(MessageBody::Close) => {}
293 _ = tokio::time::sleep(Duration::from_millis(250)) => {
294 self.kill();
295 }
296 }
297 } else {
298 self.kill();
299 }
300 }
301
302 pub fn kill(&self) {
303 self.context.shutdown_flag.raise();
304 if let Some(mut child) = self.child.borrow_mut().take() {
305 #[cfg(unix)]
306 unsafe {
307 libc::kill(-(child.id() as i32), libc::SIGKILL);
308 }
309 #[cfg(windows)]
310 if let Some(job) = &self.job {
311 job.terminate();
312 }
313 let _ignore = child.kill();
314 }
315 }
316
317 pub async fn register_config(&self, config_id: FormatConfigId, global_config: &GlobalConfiguration, plugin_config: &ConfigKeyMap) -> Result<()> {
318 let global_config = serde_json::to_vec(global_config)?;
319 let plugin_config = serde_json::to_vec(plugin_config)?;
320 self
321 .send_with_acknowledgement(MessageBody::RegisterConfig(RegisterConfigMessageBody {
322 config_id,
323 global_config,
324 plugin_config,
325 }))
326 .await?;
327 Ok(())
328 }
329
330 pub async fn release_config(&self, config_id: FormatConfigId) -> Result<()> {
331 self.send_with_acknowledgement(MessageBody::ReleaseConfig(config_id)).await?;
332 Ok(())
333 }
334
335 pub async fn ask_is_alive(&self) -> bool {
336 self.send_with_acknowledgement(MessageBody::IsAlive).await.is_ok()
337 }
338
339 pub async fn plugin_info(&self) -> Result<PluginInfo> {
340 self.send_receiving_data(MessageBody::GetPluginInfo).await
341 }
342
343 pub async fn license_text(&self) -> Result<String> {
344 self.send_receiving_string(MessageBody::GetLicenseText).await
345 }
346
347 pub async fn resolved_config(&self, config_id: FormatConfigId) -> Result<String> {
348 self.send_receiving_string(MessageBody::GetResolvedConfig(config_id)).await
349 }
350
351 pub async fn file_matching_info(&self, config_id: FormatConfigId) -> Result<FileMatchingInfo> {
352 let data = self.send_receiving_bytes(MessageBody::GetFileMatchingInfo(config_id)).await??;
353 serde_json::from_slice(&data).map_err(|err| FormatError::new(format!("{FILE_MATCHING_INFO_ERROR_MESSAGE}\n\n{err:#}")))
354 }
355
356 pub async fn config_diagnostics(&self, config_id: FormatConfigId) -> Result<Vec<ConfigurationDiagnostic>> {
357 self.send_receiving_data(MessageBody::GetConfigDiagnostics(config_id)).await
358 }
359
360 pub async fn check_config_updates(&self, message: &CheckConfigUpdatesMessageBody) -> Result<Vec<ConfigChange>> {
361 let bytes = serde_json::to_vec(&message)?;
362 let response: CheckConfigUpdatesResponseBody = self.send_receiving_data(MessageBody::CheckConfigUpdates(bytes)).await?;
363 Ok(response.changes)
364 }
365
366 pub async fn format_text(&self, request: ProcessPluginCommunicatorFormatRequest) -> FormatResult {
367 let (tx, rx) = oneshot::channel::<Result<Option<Vec<u8>>>>();
368
369 let message_id = self.context.id_generator.next();
370 let store_guard = self.context.host_format_callbacks.store_with_guard(message_id, request.on_host_format);
371 let maybe_result = self
372 .send_message_with_id(
373 message_id,
374 MessageBody::FormatShared(FormatMessageBody {
375 file_path: request.file_path,
376 file_bytes: request.file_bytes,
377 range: request.range,
378 config_id: request.config_id,
379 override_config: serde_json::to_vec(&request.override_config).unwrap(),
380 }),
381 MessageResponseChannel::Format(tx),
382 rx,
383 request.token.clone(),
384 )
385 .await;
386
387 drop(store_guard); if request.token.is_cancelled() {
390 Ok(None)
391 } else {
392 match maybe_result {
393 Ok(result) => result,
394 Err(err) => Err(CriticalFormatError(err).into()),
395 }
396 }
397 }
398
399 pub async fn is_process_alive(&self) -> bool {
401 if self.context.shutdown_flag.is_raised() {
402 false
403 } else {
404 self.ask_is_alive().await
405 }
406 }
407
408 async fn send_with_acknowledgement(&self, body: MessageBody) -> Result<()> {
409 let (tx, rx) = oneshot::channel::<Result<()>>();
410 self
411 .send_message(body, MessageResponseChannel::Acknowledgement(tx), rx, Arc::new(NullCancellationToken))
412 .await?
413 }
414
415 async fn send_receiving_string(&self, body: MessageBody) -> Result<String> {
416 let data = self.send_receiving_bytes(body).await??;
417 Ok(String::from_utf8(data)?)
418 }
419
420 async fn send_receiving_data<T: DeserializeOwned>(&self, body: MessageBody) -> Result<T> {
421 let data = self.send_receiving_bytes(body).await??;
422 Ok(serde_json::from_slice(&data)?)
423 }
424
425 async fn send_receiving_bytes(&self, body: MessageBody) -> Result<Result<Vec<u8>>> {
426 let (tx, rx) = oneshot::channel::<Result<Vec<u8>>>();
427 self
428 .send_message(body, MessageResponseChannel::Data(tx), rx, Arc::new(NullCancellationToken))
429 .await
430 }
431
432 async fn send_message<T: Default>(
433 &self,
434 body: MessageBody,
435 response_channel: MessageResponseChannel,
436 receiver: oneshot::Receiver<Result<T>>,
437 token: Arc<dyn super::super::CancellationToken>,
438 ) -> Result<Result<T>> {
439 let message_id = self.context.id_generator.next();
440 self.send_message_with_id(message_id, body, response_channel, receiver, token).await
441 }
442
443 async fn send_message_with_id<T: Default>(
444 &self,
445 message_id: u32,
446 body: MessageBody,
447 response_channel: MessageResponseChannel,
448 receiver: oneshot::Receiver<Result<T>>,
449 token: Arc<dyn super::super::CancellationToken>,
450 ) -> Result<Result<T>> {
451 let mut drop_guard = DropGuardAction::new(|| {
452 self.context.messages.take(message_id);
454 let _ = self.context.stdin_writer.send(ProcessPluginMessage {
456 id: self.context.id_generator.next(),
457 body: MessageBody::CancelFormat(message_id),
458 });
459 });
460
461 self.context.messages.store(message_id, response_channel);
462 self.context.stdin_writer.send(ProcessPluginMessage { id: message_id, body })?;
463 tokio::select! {
464 _ = token.wait_cancellation() => {
465 drop(drop_guard); Ok(Ok(Default::default()))
467 }
468 response = receiver => {
469 drop_guard.forget(); match response {
471 Ok(data) => Ok(data),
472 Err(err) => Err(CommunicatorError::WaitOnMessage { message_id, error: err }.into()),
473 }
474 }
475 }
476 }
477}
478
479fn get_plugin_schema_version<TRead: Read + Unpin, TWrite: Write + Unpin>(
480 reader: &mut MessageReader<TRead>,
481 writer: &mut MessageWriter<TWrite>,
482) -> std::result::Result<u32, SchemaVersionError> {
483 writer.send_u32(0).map_err(SchemaVersionError::Ask)?; writer.flush().map_err(SchemaVersionError::Flush)?;
486 let acknowledgement_response = reader.read_u32().map_err(SchemaVersionError::ReadAcknowledgement)?;
487 if acknowledgement_response != 0 {
488 return Err(SchemaVersionError::UnexpectedAcknowledgement(acknowledgement_response));
489 }
490 reader.read_u32().map_err(SchemaVersionError::ReadVersion)
491}
492
493fn std_err_redirect(shutdown_flag: Arc<AtomicFlag>, stderr: ChildStderr, on_std_err: impl Fn(String) + Send + Sync + 'static) {
494 let reader = std::io::BufReader::new(stderr);
495 for line in reader.lines() {
496 match line {
497 Ok(line) => on_std_err(line),
498 Err(err) => {
499 if shutdown_flag.is_raised() || err.kind() == ErrorKind::BrokenPipe {
500 return;
501 } else {
502 on_std_err(format!("Error reading line from process plugin stderr. {:#}", err));
503 }
504 }
505 }
506 }
507}
508
509fn handle_stdout_message(message: ProcessPluginMessage, context: &Rc<Context>) -> Result<()> {
510 match message.body {
511 MessageBody::Success(message_id) => match context.messages.take(message_id) {
512 Some(MessageResponseChannel::Acknowledgement(channel)) => {
513 let _ignore = channel.send(Ok(()));
514 }
515 Some(MessageResponseChannel::Data(channel)) => {
516 let _ignore = channel.send(Err(CommunicatorError::UnexpectedDataForSuccess(message_id).into()));
517 }
518 Some(MessageResponseChannel::Format(channel)) => {
519 let _ignore = channel.send(Err(CommunicatorError::UnexpectedFormatForSuccess(message_id).into()));
520 }
521 None => {}
522 },
523 MessageBody::DataResponse(response) => match context.messages.take(response.message_id) {
524 Some(MessageResponseChannel::Acknowledgement(channel)) => {
525 let _ignore = channel.send(Err(CommunicatorError::UnexpectedSuccessForData(response.message_id).into()));
526 }
527 Some(MessageResponseChannel::Data(channel)) => {
528 let _ignore = channel.send(Ok(response.data));
529 }
530 Some(MessageResponseChannel::Format(channel)) => {
531 let _ignore = channel.send(Err(CommunicatorError::UnexpectedFormatForData(response.message_id).into()));
532 }
533 None => {}
534 },
535 MessageBody::Error(response) => {
536 let err: FormatError = String::from_utf8_lossy(&response.data).into_owned().into();
537 match context.messages.take(response.message_id) {
538 Some(MessageResponseChannel::Acknowledgement(channel)) => {
539 let _ignore = channel.send(Err(err));
540 }
541 Some(MessageResponseChannel::Data(channel)) => {
542 let _ignore = channel.send(Err(err));
543 }
544 Some(MessageResponseChannel::Format(channel)) => {
545 let _ignore = channel.send(Err(err));
546 }
547 None => {}
548 }
549 }
550 MessageBody::FormatResponse(response) => match context.messages.take(response.message_id) {
551 Some(MessageResponseChannel::Acknowledgement(channel)) => {
552 let _ignore = channel.send(Err(CommunicatorError::UnexpectedSuccessForFormat(response.message_id).into()));
553 }
554 Some(MessageResponseChannel::Data(channel)) => {
555 let _ignore = channel.send(Err(CommunicatorError::UnexpectedDataForFormat(response.message_id).into()));
556 }
557 Some(MessageResponseChannel::Format(channel)) => {
558 let _ignore = channel.send(Ok(response.data));
559 }
560 None => {}
561 },
562 MessageBody::CancelFormat(message_id) => {
563 if let Some(token) = context.format_request_tokens.take(message_id) {
564 token.cancel();
565 }
566 context.host_format_callbacks.take(message_id);
567 }
569 MessageBody::HostFormat(body) => {
570 let context = context.clone();
573 crate::async_runtime::spawn(async move {
574 let result = host_format(context.clone(), message.id, body).await;
575
576 let _ignore = context.stdin_writer.send(ProcessPluginMessage {
579 id: context.id_generator.next(),
580 body: match result {
581 Ok(result) => MessageBody::FormatResponse(ResponseBody {
582 message_id: message.id,
583 data: result,
584 }),
585 Err(err) => MessageBody::Error(ResponseBody {
586 message_id: message.id,
587 data: error_to_string(&err).into_bytes(),
588 }),
589 },
590 });
591 });
592 }
593 MessageBody::IsAlive => {
594 let _ = context.stdin_writer.send(ProcessPluginMessage {
596 id: context.id_generator.next(),
597 body: MessageBody::Success(message.id),
598 });
599 }
600 MessageBody::Format(_)
601 | MessageBody::FormatShared(_)
602 | MessageBody::Close
603 | MessageBody::GetPluginInfo
604 | MessageBody::GetLicenseText
605 | MessageBody::RegisterConfig(_)
606 | MessageBody::ReleaseConfig(_)
607 | MessageBody::GetConfigDiagnostics(_)
608 | MessageBody::GetFileMatchingInfo(_)
609 | MessageBody::GetResolvedConfig(_)
610 | MessageBody::CheckConfigUpdates(_) => {
611 let _ = context.stdin_writer.send(ProcessPluginMessage {
612 id: context.id_generator.next(),
613 body: MessageBody::Error(ResponseBody {
614 message_id: message.id,
615 data: "Unsupported plugin to CLI message.".as_bytes().to_vec(),
616 }),
617 });
618 }
619 MessageBody::Unknown(message_kind) => {
622 return Err(CommunicatorError::UnknownMessageKind(message_kind).into());
623 }
624 }
625
626 Ok(())
627}
628
629async fn host_format(context: Rc<Context>, message_id: u32, body: HostFormatMessageBody) -> FormatResult {
630 let Some(callback) = context.host_format_callbacks.get_cloned(body.original_message_id) else {
631 return FormatResult::Err(CommunicatorError::HostFormatCallbackNotFound(body.original_message_id).into());
632 };
633
634 let token = Arc::new(CancellationToken::new());
635 let store_guard = context.format_request_tokens.store_with_guard(message_id, token.clone());
636 let result = callback(HostFormatRequest {
637 file_path: body.file_path,
638 file_bytes: body.file_text,
639 range: body.range,
640 override_config: serde_json::from_slice(&body.override_config).unwrap(),
641 token,
642 })
643 .await;
644 drop(store_guard); result
646}
647
648#[cfg(windows)]
651struct ProcessJob(winapi::um::winnt::HANDLE);
652
653#[cfg(windows)]
654impl ProcessJob {
655 fn for_child(child: &Child) -> Option<Self> {
658 use std::os::windows::io::AsRawHandle;
659 use winapi::um::jobapi2::AssignProcessToJobObject;
660 use winapi::um::jobapi2::CreateJobObjectW;
661
662 unsafe {
663 let handle = CreateJobObjectW(std::ptr::null_mut(), std::ptr::null());
664 if handle.is_null() {
665 return None;
666 }
667 let job = Self(handle);
668 if AssignProcessToJobObject(job.0, child.as_raw_handle() as _) == 0 {
669 return None;
670 }
671 Some(job)
672 }
673 }
674
675 fn terminate(&self) {
677 unsafe {
678 winapi::um::jobapi2::TerminateJobObject(self.0, 1);
679 }
680 }
681}
682
683#[cfg(windows)]
684impl Drop for ProcessJob {
685 fn drop(&mut self) {
686 unsafe {
687 winapi::um::handleapi::CloseHandle(self.0);
688 }
689 }
690}