1use std::collections::VecDeque;
8use std::io::{BufRead, BufReader, BufWriter, Write};
9use std::path::{Path, PathBuf};
10use std::process::{Child, ChildStdin, Command, ExitStatus, Stdio};
11use std::sync::mpsc::{Receiver, RecvTimeoutError, channel};
12use std::sync::{Arc, Mutex, OnceLock};
13use std::time::Duration;
14
15use arrow::array::RecordBatch;
16use arrow::datatypes::SchemaRef;
17use serde_json::{Map, Value};
18
19use crate::frame::{self, Frame, FrameError};
20use crate::msg::{ConnectionField, DeliveryFile, Request, Response, ResultSetMeta};
21use crate::options::OptionField;
22use crate::{Kind, MAX_VERSION, MIN_VERSION, PluginId, parse_executable_name};
23
24pub type LogSink = Arc<dyn Fn(&str, &str) + Send + Sync>;
26
27pub fn stderr_log() -> LogSink {
29 Arc::new(|plugin, line| eprintln!("[{plugin}] {line}"))
30}
31
32pub const DEFAULT_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(30);
33
34pub const INTERACTIVE_ENV: &str = "DRE_INTERACTIVE";
36const STDERR_TAIL: usize = 20;
37
38#[derive(Debug, Clone, PartialEq)]
39pub struct PluginInfo {
40 pub protocol_version: u32,
41 pub kind: Kind,
42 pub name: String,
43 pub version: String,
44 pub capabilities: Vec<String>,
45 pub provides: Vec<PluginId>,
47}
48
49#[derive(Debug)]
50pub enum HostError {
51 Spawn {
52 plugin: String,
53 error: std::io::Error,
54 },
55 Crashed {
56 plugin: String,
57 status: Option<ExitStatus>,
58 stderr: Vec<String>,
59 },
60 Malformed {
61 plugin: String,
62 message: String,
63 },
64 Incompatible {
65 plugin: String,
66 core: (u32, u32),
67 plugin_range: (u32, u32),
68 },
69 Timeout {
70 plugin: String,
71 waiting_for: &'static str,
72 },
73 Unexpected {
74 plugin: String,
75 expected: &'static str,
76 got: String,
77 },
78 Plugin {
80 plugin: String,
81 message: String,
82 },
83 Arrow {
84 plugin: String,
85 message: String,
86 },
87}
88
89impl std::fmt::Display for HostError {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 match self {
92 HostError::Spawn { plugin, error } => write!(f, "can't start plugin `{plugin}`: {error}"),
93 HostError::Crashed {
94 plugin,
95 status,
96 stderr,
97 } => {
98 match status {
99 Some(s) => write!(f, "plugin `{plugin}` exited unexpectedly ({s})")?,
100 None => write!(f, "plugin `{plugin}` closed its output unexpectedly")?,
101 }
102 if !stderr.is_empty() {
103 write!(f, "; its last log lines:\n {}", stderr.join("\n "))?;
104 }
105 Ok(())
106 }
107 HostError::Malformed { plugin, message } => {
108 write!(f, "plugin `{plugin}` broke the protocol: {message}")
109 }
110 HostError::Incompatible {
111 plugin,
112 core,
113 plugin_range,
114 } => write!(
115 f,
116 "plugin `{plugin}` speaks protocol versions {}..={}, but this DRE core speaks {}..={}; update {}",
117 plugin_range.0,
118 plugin_range.1,
119 core.0,
120 core.1,
121 if plugin_range.1 < core.0 {
122 "the plugin"
123 } else {
124 "DRE"
125 }
126 ),
127 HostError::Timeout { plugin, waiting_for } => {
128 write!(
129 f,
130 "plugin `{plugin}` didn't answer in time (waiting for {waiting_for})"
131 )
132 }
133 HostError::Unexpected {
134 plugin,
135 expected,
136 got,
137 } => {
138 write!(f, "plugin `{plugin}` sent {got} where {expected} was expected")
139 }
140 HostError::Plugin { message, .. } => f.write_str(message),
141 HostError::Arrow { plugin, message } => {
142 write!(f, "plugin `{plugin}` sent invalid Arrow data: {message}")
143 }
144 }
145 }
146}
147
148impl std::error::Error for HostError {}
149
150pub type Result<T> = std::result::Result<T, HostError>;
151
152pub enum Incoming {
154 Json(Response),
155 Arrow(Vec<u8>),
156}
157
158impl std::fmt::Debug for Incoming {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 match self {
161 Incoming::Json(r) => write!(f, "Json({r:?})"),
162 Incoming::Arrow(b) => write!(f, "Arrow frame ({} bytes)", b.len()),
163 }
164 }
165}
166
167#[derive(Debug)]
169pub enum Execution {
170 NoResult { rows_affected: Option<u64> },
171 Result { schema: SchemaRef, rows: u64 },
172}
173
174pub struct PluginProcess {
175 label: String,
176 path: PathBuf,
177 child: Child,
178 stdin: Option<BufWriter<ChildStdin>>,
179 rx: Receiver<std::result::Result<Frame, FrameError>>,
180 stderr: Arc<Mutex<VecDeque<String>>>,
181 stderr_thread: Option<std::thread::JoinHandle<()>>,
183 info: Option<PluginInfo>,
184 early: Option<std::result::Result<Frame, FrameError>>,
186 serve: Option<PluginId>,
189}
190
191impl PluginProcess {
192 pub fn start(path: &Path, log: LogSink) -> Result<PluginProcess> {
194 Self::start_in(path, log, None)
195 }
196
197 pub fn start_in(path: &Path, log: LogSink, cwd: Option<&Path>) -> Result<PluginProcess> {
199 Self::start_for(path, None, log, cwd)
200 }
201
202 pub fn start_for(
204 path: &Path,
205 plugin: Option<&PluginId>,
206 log: LogSink,
207 cwd: Option<&Path>,
208 ) -> Result<PluginProcess> {
209 let timeout = std::env::var("DRE_PLUGIN_HANDSHAKE_TIMEOUT_MS")
210 .ok()
211 .and_then(|v| v.parse().ok())
212 .map(Duration::from_millis)
213 .unwrap_or(DEFAULT_HANDSHAKE_TIMEOUT);
214 let mut p = Self::spawn_in(path, log, &[], cwd)?;
215 if let Some(id) = plugin {
216 p.serve = Some(id.clone());
217 }
218 p.handshake((MIN_VERSION, MAX_VERSION), timeout)?;
219 Ok(p)
220 }
221
222 pub fn start_with(
224 path: &Path,
225 log: LogSink,
226 versions: (u32, u32),
227 timeout: Duration,
228 ) -> Result<PluginProcess> {
229 let mut p = Self::spawn(path, log)?;
230 p.handshake(versions, timeout)?;
231 Ok(p)
232 }
233
234 pub fn spawn(path: &Path, log: LogSink) -> Result<PluginProcess> {
236 Self::spawn_env(path, log, &[])
237 }
238
239 pub fn spawn_env(path: &Path, log: LogSink, env: &[(&str, &str)]) -> Result<PluginProcess> {
241 Self::spawn_in(path, log, env, None)
242 }
243
244 pub fn spawn_in(
246 path: &Path,
247 log: LogSink,
248 env: &[(&str, &str)],
249 cwd: Option<&Path>,
250 ) -> Result<PluginProcess> {
251 let label = path
252 .file_name()
253 .map(|f| f.to_string_lossy().to_string())
254 .unwrap_or_default();
255 let mut cmd = Command::new(path);
256 if let Some(dir) = cwd {
257 cmd.current_dir(dir);
258 }
259 if std::env::var_os(INTERACTIVE_ENV).is_none() {
262 use std::io::IsTerminal;
263 let there = std::io::stderr().is_terminal() && std::io::stdin().is_terminal();
264 cmd.env(INTERACTIVE_ENV, if there { "1" } else { "0" });
265 }
266 let mut child = cmd
267 .envs(env.iter().copied())
268 .stdin(Stdio::piped())
269 .stdout(Stdio::piped())
270 .stderr(Stdio::piped())
271 .spawn()
272 .map_err(|error| HostError::Spawn {
273 plugin: label.clone(),
274 error,
275 })?;
276 let stdout = child.stdout.take().unwrap();
277 let stderr_pipe = child.stderr.take().unwrap();
278 let stdin = child.stdin.take().map(BufWriter::new);
279
280 let (tx, rx) = channel();
281 std::thread::spawn(move || {
282 let mut r = BufReader::new(stdout);
283 loop {
284 let f = frame::read_frame(&mut r);
285 let stop = f.is_err();
286 if tx.send(f).is_err() || stop {
287 break;
288 }
289 }
290 });
291 let stderr = Arc::new(Mutex::new(VecDeque::new()));
292 let tail = stderr.clone();
293 let who = label.clone();
294 let stderr_thread = std::thread::spawn(move || {
295 for line in BufReader::new(stderr_pipe).lines() {
296 let Ok(line) = line else { break };
297 log(&who, &line);
298 let mut t = tail.lock().unwrap();
299 if t.len() == STDERR_TAIL {
300 t.pop_front();
301 }
302 t.push_back(line);
303 }
304 });
305 let serve = parse_executable_name(&label).map(|(k, n)| PluginId::new(k, n));
306 Ok(PluginProcess {
307 label,
308 path: path.to_path_buf(),
309 child,
310 stdin,
311 rx,
312 stderr,
313 stderr_thread: Some(stderr_thread),
314 info: None,
315 early: None,
316 serve,
317 })
318 }
319
320 pub fn ask_for(&mut self, plugin: Option<PluginId>) {
322 self.serve = plugin;
323 }
324
325 pub fn handshake(&mut self, (min, max): (u32, u32), timeout: Duration) -> Result<()> {
327 self.send(&Request::Hello {
328 min_version: min,
329 max_version: max,
330 core_version: core_version(),
331 plugin: self.serve.clone(),
332 })?;
333 match self.recv(Some(timeout), "the hello reply")? {
334 Incoming::Json(Response::Hello {
335 protocol_version,
336 kind,
337 name,
338 version,
339 capabilities,
340 mut provides,
341 }) => {
342 if protocol_version < min || protocol_version > max {
343 return Err(HostError::Incompatible {
344 plugin: self.label.clone(),
345 core: (min, max),
346 plugin_range: (protocol_version, protocol_version),
347 });
348 }
349 if let Some(want) = &self.serve
350 && (want.kind != kind || want.name != name)
351 {
352 return Err(HostError::Plugin {
353 plugin: self.label.clone(),
354 message: format!(
355 "`{}` was asked for the {want} plugin but serves {kind}/{name}",
356 self.label
357 ),
358 });
359 }
360 if provides.is_empty() {
361 provides.push(PluginId::new(kind, name.clone()));
362 }
363 self.info = Some(PluginInfo {
364 protocol_version,
365 kind,
366 name,
367 version,
368 capabilities,
369 provides,
370 });
371 Ok(())
372 }
373 Incoming::Json(Response::VersionMismatch {
374 min_version,
375 max_version,
376 }) => Err(HostError::Incompatible {
377 plugin: self.label.clone(),
378 core: (min, max),
379 plugin_range: (min_version, max_version),
380 }),
381 Incoming::Json(Response::Error { message }) => Err(HostError::Plugin {
382 plugin: self.label.clone(),
383 message,
384 }),
385 other => Err(self.unexpected("a hello reply", &other)),
386 }
387 }
388
389 pub fn label(&self) -> &str {
390 &self.label
391 }
392
393 pub fn path(&self) -> &Path {
394 &self.path
395 }
396
397 pub fn info(&self) -> &PluginInfo {
398 self.info.as_ref().expect("handshake completed")
399 }
400
401 pub fn has(&self, capability: &str) -> bool {
402 self.info
403 .as_ref()
404 .is_some_and(|i| i.capabilities.iter().any(|c| c == capability))
405 }
406
407 fn unexpected(&self, expected: &'static str, got: &Incoming) -> HostError {
408 let got = match got {
409 Incoming::Json(r) => format!("{:?}", r)
410 .split([' ', '{'])
411 .next()
412 .unwrap_or("?")
413 .to_string(),
414 Incoming::Arrow(_) => "Arrow data".into(),
415 };
416 HostError::Unexpected {
417 plugin: self.label.clone(),
418 expected,
419 got: format!("`{got}`"),
420 }
421 }
422
423 fn crashed(&mut self) -> HostError {
424 let mut status = None;
426 for _ in 0..50 {
427 if let Ok(Some(s)) = self.child.try_wait() {
428 status = Some(s);
429 break;
430 }
431 std::thread::sleep(Duration::from_millis(20));
432 }
433 std::thread::sleep(Duration::from_millis(50));
434 let stderr = self.stderr.lock().unwrap().iter().cloned().collect();
435 HostError::Crashed {
436 plugin: self.label.clone(),
437 status,
438 stderr,
439 }
440 }
441
442 pub fn send(&mut self, req: &Request) -> Result<()> {
443 let ok = match self.stdin.as_mut() {
444 Some(w) => frame::write_json(w, req).is_ok(),
445 None => false,
446 };
447 if ok { Ok(()) } else { Err(self.write_failed()) }
448 }
449
450 pub fn send_batch(&mut self, batch: &RecordBatch) -> Result<()> {
451 if self.early.is_none()
454 && let Ok(f) = self.rx.try_recv()
455 {
456 self.early = Some(f);
457 }
458 if let Some(Ok(Frame::Json(v))) = &self.early
459 && let Ok(Response::Error { message }) = serde_json::from_value::<Response>(v.clone())
460 {
461 return Err(HostError::Plugin {
462 plugin: self.label.clone(),
463 message,
464 });
465 }
466 let ipc = frame::encode_batch(batch).map_err(|e| HostError::Arrow {
467 plugin: self.label.clone(),
468 message: e.to_string(),
469 })?;
470 let ok = match self.stdin.as_mut() {
471 Some(w) => frame::write_arrow(w, &ipc).is_ok(),
472 None => false,
473 };
474 if ok { Ok(()) } else { Err(self.write_failed()) }
475 }
476
477 fn write_failed(&mut self) -> HostError {
479 match self.rx.recv_timeout(Duration::from_secs(2)) {
480 Ok(Ok(Frame::Json(v))) => match serde_json::from_value::<Response>(v) {
481 Ok(Response::Error { message }) => HostError::Plugin {
482 plugin: self.label.clone(),
483 message,
484 },
485 _ => self.crashed(),
486 },
487 _ => self.crashed(),
488 }
489 }
490
491 pub fn recv(&mut self, timeout: Option<Duration>, waiting_for: &'static str) -> Result<Incoming> {
493 let got = match timeout {
494 _ if self.early.is_some() => self.early.take(),
495 Some(t) => match self.rx.recv_timeout(t) {
496 Ok(f) => Some(f),
497 Err(RecvTimeoutError::Timeout) => {
498 return Err(HostError::Timeout {
499 plugin: self.label.clone(),
500 waiting_for,
501 });
502 }
503 Err(RecvTimeoutError::Disconnected) => None,
504 },
505 None => self.rx.recv().ok(),
506 };
507 match got {
508 Some(Ok(Frame::Json(v))) => match serde_json::from_value::<Response>(v.clone()) {
509 Ok(r) => Ok(Incoming::Json(r)),
510 Err(e) => Err(HostError::Malformed {
511 plugin: self.label.clone(),
512 message: format!("unknown message {v}: {e}"),
513 }),
514 },
515 Some(Ok(Frame::Arrow(b))) => Ok(Incoming::Arrow(b)),
516 Some(Err(FrameError::Malformed(m))) => Err(HostError::Malformed {
517 plugin: self.label.clone(),
518 message: m,
519 }),
520 Some(Err(_)) | None => Err(self.crashed()),
521 }
522 }
523
524 pub fn recv_json(&mut self, waiting_for: &'static str) -> Result<Response> {
526 match self.recv(None, waiting_for)? {
527 Incoming::Json(Response::Error { message }) => Err(HostError::Plugin {
528 plugin: self.label.clone(),
529 message,
530 }),
531 Incoming::Json(r) => Ok(r),
532 other => Err(self.unexpected(waiting_for, &other)),
533 }
534 }
535
536 fn call_ok(&mut self, req: &Request, what: &'static str) -> Result<()> {
538 self.send(req)?;
539 match self.recv_json(what)? {
540 Response::Ok {} => Ok(()),
541 other => Err(self.unexpected(what, &Incoming::Json(other))),
542 }
543 }
544
545 pub fn describe(&mut self) -> Result<Vec<ConnectionField>> {
546 Ok(self.describe_all()?.0)
547 }
548
549 pub fn describe_all(&mut self) -> Result<(Vec<ConnectionField>, Vec<OptionField>)> {
551 self.send(&Request::Describe {})?;
552 match self.recv_json("a describe reply")? {
553 Response::Describe {
554 connection_fields,
555 option_fields,
556 } => Ok((connection_fields, option_fields)),
557 other => Err(self.unexpected("a describe reply", &Incoming::Json(other))),
558 }
559 }
560
561 pub fn validate(&mut self, options: Map<String, Value>) -> Result<Vec<String>> {
563 self.send(&Request::Validate { options })?;
564 match self.recv_json("a validate reply")? {
565 Response::Validated { errors } => Ok(errors),
566 other => Err(self.unexpected("a validate reply", &Incoming::Json(other))),
567 }
568 }
569
570 pub fn open(&mut self, connection: Map<String, Value>, read_only: bool) -> Result<()> {
571 self.call_ok(
572 &Request::Open {
573 connection,
574 read_only,
575 },
576 "an open reply",
577 )
578 }
579
580 pub fn check(&mut self, sql: &str) -> Result<()> {
581 self.call_ok(&Request::Check { sql: sql.to_string() }, "a check reply")
582 }
583
584 pub fn execute(
586 &mut self,
587 sql: &str,
588 row_limit: Option<u64>,
589 mut on_batch: impl FnMut(&SchemaRef, RecordBatch) -> std::result::Result<(), String>,
590 ) -> Result<Execution> {
591 self.send(&Request::Execute {
592 sql: sql.to_string(),
593 row_limit,
594 })?;
595 match self.recv_json("an execute reply")? {
596 Response::NoResult { rows_affected } => return Ok(Execution::NoResult { rows_affected }),
597 Response::Result { .. } => {}
598 other => return Err(self.unexpected("an execute reply", &Incoming::Json(other))),
599 }
600 let mut schema: Option<SchemaRef> = None;
601 let mut rows = 0u64;
602 let mut sink_error = None;
603 loop {
604 match self.recv(None, "result data")? {
605 Incoming::Arrow(ipc) => {
606 let (s, batches) = frame::decode_batches(&ipc).map_err(|e| HostError::Arrow {
607 plugin: self.label.clone(),
608 message: e.to_string(),
609 })?;
610 let schema = schema.get_or_insert(s);
611 for b in batches {
612 rows += b.num_rows() as u64;
613 if sink_error.is_none()
614 && let Err(e) = on_batch(schema, b)
615 {
616 sink_error = Some(e);
618 }
619 }
620 }
621 Incoming::Json(Response::ResultEnd { .. }) => break,
622 Incoming::Json(Response::Error { message }) => {
623 return Err(HostError::Plugin {
624 plugin: self.label.clone(),
625 message,
626 });
627 }
628 other => return Err(self.unexpected("result data", &other)),
629 }
630 }
631 if let Some(e) = sink_error {
632 return Err(HostError::Plugin {
633 plugin: self.label.clone(),
634 message: e,
635 });
636 }
637 let schema = schema.ok_or_else(|| HostError::Malformed {
638 plugin: self.label.clone(),
639 message: "a result ended without any Arrow frame carrying its schema".into(),
640 })?;
641 Ok(Execution::Result { schema, rows })
642 }
643
644 pub fn write_begin(
646 &mut self,
647 path: &str,
648 format: &str,
649 options: Map<String, Value>,
650 result_sets: Vec<ResultSetMeta>,
651 template: Option<Value>,
652 ) -> Result<()> {
653 self.send(&Request::Write {
654 path: path.to_string(),
655 format: format.to_string(),
656 options,
657 result_sets,
658 template,
659 })
660 }
661
662 pub fn write_result_set(
664 &mut self,
665 schema: &SchemaRef,
666 batches: impl IntoIterator<Item = RecordBatch>,
667 ) -> Result<()> {
668 let mut any = false;
669 for b in batches {
670 any = true;
671 self.send_batch(&b)?;
672 }
673 if !any {
674 self.send_batch(&RecordBatch::new_empty(schema.clone()))?;
675 }
676 self.send(&Request::ResultSetEnd {})
677 }
678
679 pub fn load(
682 &mut self,
683 name: &str,
684 schema: &SchemaRef,
685 batches: impl IntoIterator<Item = RecordBatch>,
686 ) -> Result<(String, Option<String>)> {
687 self.send(&Request::Load {
688 name: name.to_string(),
689 })?;
690 self.write_result_set(schema, batches)?;
691 match self.recv_json("a loaded reply")? {
692 Response::Loaded {
693 relation, warning, ..
694 } => Ok((relation, warning)),
695 other => Err(self.unexpected("a loaded reply", &Incoming::Json(other))),
696 }
697 }
698
699 pub fn write_finish(&mut self) -> Result<Vec<String>> {
700 Ok(self.write_finish_with_warnings()?.0)
701 }
702
703 pub fn write_finish_with_warnings(&mut self) -> Result<(Vec<String>, Vec<String>)> {
705 self.send(&Request::Finish {})?;
706 match self.recv_json("a written reply")? {
707 Response::Written { files, warnings } => Ok((files, warnings)),
708 other => Err(self.unexpected("a written reply", &Incoming::Json(other))),
709 }
710 }
711
712 pub fn deliver(
714 &mut self,
715 local_path: &str,
716 remote_path: Option<&str>,
717 connection: Map<String, Value>,
718 ) -> Result<String> {
719 let file = DeliveryFile {
720 local_path: local_path.to_string(),
721 remote_path: remote_path.map(str::to_string),
722 };
723 self.deliver_files(&[file], connection, Map::new())
724 }
725
726 pub fn deliver_files(
729 &mut self,
730 files: &[DeliveryFile],
731 connection: Map<String, Value>,
732 options: Map<String, Value>,
733 ) -> Result<String> {
734 let req = match files {
735 [] => {
736 return Err(HostError::Malformed {
737 plugin: self.label.clone(),
738 message: "nothing to deliver".into(),
739 });
740 }
741 [one] => Request::Deliver {
742 local_path: Some(one.local_path.clone()),
743 remote_path: one.remote_path.clone(),
744 files: Vec::new(),
745 connection,
746 options,
747 },
748 many => {
749 if !self.has(crate::CAP_MULTI_FILE) {
750 return Err(HostError::Plugin {
751 plugin: self.label.clone(),
752 message: format!(
753 "{} files in one delivery, but the plugin doesn't advertise `multi_file`",
754 many.len()
755 ),
756 });
757 }
758 Request::Deliver {
759 local_path: None,
760 remote_path: None,
761 files: many.to_vec(),
762 connection,
763 options,
764 }
765 }
766 };
767 self.send(&req)?;
768 match self.recv_json("a delivered reply")? {
769 Response::Delivered { location } => Ok(location),
770 other => Err(self.unexpected("a delivered reply", &Incoming::Json(other))),
771 }
772 }
773
774 pub fn close(mut self) -> Result<()> {
776 let _ = self.send(&Request::Close {});
777 let _ = self.recv(Some(Duration::from_secs(5)), "the close reply");
778 self.stdin.take();
779 for _ in 0..250 {
780 if let Ok(Some(_)) = self.child.try_wait() {
781 return Ok(());
782 }
783 std::thread::sleep(Duration::from_millis(20));
784 }
785 let _ = self.child.kill();
786 Ok(())
787 }
788
789 pub fn write_raw(&mut self, bytes: &[u8]) -> Result<()> {
791 let ok = match self.stdin.as_mut() {
792 Some(w) => w.write_all(bytes).and_then(|_| w.flush()).is_ok(),
793 None => false,
794 };
795 if ok { Ok(()) } else { Err(self.write_failed()) }
796 }
797
798 pub fn wait_exit(&mut self, timeout: Duration) -> Option<ExitStatus> {
800 self.stdin.take();
801 let deadline = std::time::Instant::now() + timeout;
802 while std::time::Instant::now() < deadline {
803 if let Ok(Some(s)) = self.child.try_wait() {
804 return Some(s);
805 }
806 std::thread::sleep(Duration::from_millis(20));
807 }
808 None
809 }
810}
811
812impl PluginProcess {
813 fn drain_stderr(&mut self) {
815 if let Some(t) = self.stderr_thread.take() {
816 let deadline = std::time::Instant::now() + Duration::from_secs(2);
817 while !t.is_finished() && std::time::Instant::now() < deadline {
818 std::thread::sleep(Duration::from_millis(5));
819 }
820 if t.is_finished() {
821 let _ = t.join();
822 }
823 }
824 }
825}
826
827impl Drop for PluginProcess {
828 fn drop(&mut self) {
829 self.stdin.take();
830 if let Ok(None) = self.child.try_wait() {
831 for _ in 0..25 {
833 if let Ok(Some(_)) = self.child.try_wait() {
834 break;
835 }
836 std::thread::sleep(Duration::from_millis(10));
837 }
838 let _ = self.child.kill();
839 let _ = self.child.wait();
840 }
841 self.drain_stderr();
842 }
843}
844
845static CORE_VERSION: OnceLock<String> = OnceLock::new();
846
847pub fn set_core_version(version: &str) {
850 let _ = CORE_VERSION.set(version.to_string());
851}
852
853fn core_version() -> String {
854 CORE_VERSION.get().cloned().unwrap_or_else(|| "unreleased".into())
855}